Skip to content

Commit

Permalink
Cherry pick ls updates (#6202)
Browse files Browse the repository at this point in the history
Co-authored-by: Gil Ben-Shachar <[email protected]>
  • Loading branch information
orizi and gilbens-starkware authored Aug 13, 2024
1 parent f24bbd6 commit 185fb24
Show file tree
Hide file tree
Showing 4 changed files with 325 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ use cairo_lang_defs::ids::{
use cairo_lang_filesystem::db::FilesGroup;
use cairo_lang_filesystem::ids::FileId;
use cairo_lang_filesystem::span::TextOffset;
use cairo_lang_semantic::corelib::{core_submodule, get_submodule};
use cairo_lang_semantic::db::SemanticGroup;
use cairo_lang_semantic::diagnostic::{NotFoundItemType, SemanticDiagnostics};
use cairo_lang_semantic::expr::inference::InferenceId;
use cairo_lang_semantic::items::function_with_body::SemanticExprLookup;
use cairo_lang_semantic::items::structure::SemanticStructEx;
use cairo_lang_semantic::items::us::SemanticUseEx;
use cairo_lang_semantic::lookup_item::{HasResolverData, LookupItemEx};
use cairo_lang_semantic::resolve::{ResolvedConcreteItem, ResolvedGenericItem, Resolver};
use cairo_lang_semantic::types::peel_snapshots;
Expand Down Expand Up @@ -271,11 +273,13 @@ pub fn completion_for_method(
let mut additional_text_edits = vec![];

// If the trait is not in scope, add a use statement.
if let Some(trait_path) = db.visible_traits_from_module(module_id).get(&trait_id) {
additional_text_edits.push(TextEdit {
range: Range::new(position, position),
new_text: format!("use {};\n", trait_path),
});
if !module_has_trait(db, module_id, trait_id)? {
if let Some(trait_path) = db.visible_traits_from_module(module_id).get(&trait_id) {
additional_text_edits.push(TextEdit {
range: Range::new(position, position),
new_text: format!("use {};\n", trait_path),
});
}
}

let completion = CompletionItem {
Expand All @@ -288,3 +292,36 @@ pub fn completion_for_method(
};
Some(completion)
}

/// Checks if a module has a trait in scope.
#[tracing::instrument(level = "trace", skip_all)]
fn module_has_trait(
db: &AnalysisDatabase,
module_id: ModuleId,
trait_id: cairo_lang_defs::ids::TraitId,
) -> Option<bool> {
if db.module_traits_ids(module_id).ok()?.contains(&trait_id) {
return Some(true);
}
let mut current_top_module = module_id;
while let ModuleId::Submodule(submodule_id) = current_top_module {
current_top_module = submodule_id.parent_module(db.upcast());
}
let crate_id = match current_top_module {
ModuleId::CrateRoot(crate_id) => crate_id,
ModuleId::Submodule(_) => unreachable!("current module is not a top-level module"),
};
let edition =
db.crate_config(crate_id).map(|config| config.settings.edition).unwrap_or_default();
let prelude_submodule_name = edition.prelude_submodule_name();
let core_prelude_submodule = core_submodule(db, "prelude");
let prelude_submodule = get_submodule(db, core_prelude_submodule, prelude_submodule_name)?;
for module_id in [prelude_submodule, module_id].iter().copied() {
for use_id in db.module_uses_ids(module_id).ok()?.iter().copied() {
if db.use_resolved_item(use_id) == Ok(ResolvedGenericItem::Trait(trait_id)) {
return Some(true);
}
}
}
Some(false)
}
76 changes: 76 additions & 0 deletions crates/cairo-lang-language-server/tests/e2e/completions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use cairo_lang_test_utils::parse_test_file::TestRunnerResult;
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use tower_lsp::lsp_types::{lsp_request, CompletionParams, TextDocumentPositionParams};

use crate::support::cursor::peek_caret;
use crate::support::{cursors, sandbox};

cairo_lang_test_utils::test_file_test!(
completions,
"tests/test_data/completions",
{
methods_text_edits: "methods_text_edits.txt",
},
test_completions_text_edits

);

/// Perform completions text edits test. Notice that the test shows many possible completions,
/// however in practice only those who have the same prefix as the existing code are shown.
///
/// This function spawns a sandbox language server with the given code in the `src/lib.cairo` file.
/// The Cairo source code is expected to contain caret markers.
/// The function then requests quick fixes at each caret position and compares the result with the
/// expected quick fixes from the snapshot file.
fn test_completions_text_edits(
inputs: &OrderedHashMap<String, String>,
_args: &OrderedHashMap<String, String>,
) -> TestRunnerResult {
let (cairo, cursors) = cursors(&inputs["cairo_code"]);

let mut ls = sandbox! {
files {
"cairo_project.toml" => inputs["cairo_project.toml"].clone(),
"src/lib.cairo" => cairo.clone(),
}
};

ls.open("src/lib.cairo");

let mut completions = OrderedHashMap::default();

for (n, position) in cursors.carets().into_iter().enumerate() {
let mut report = String::new();

report.push_str(&peek_caret(&cairo, position));
let completion_params = CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: ls.doc_id("src/lib.cairo"),
position,
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
context: None,
};
let caret_completions =
ls.send_request::<lsp_request!("textDocument/completion")>(completion_params);
if let Some(completions) = caret_completions {
let completion_items = match completions {
tower_lsp::lsp_types::CompletionResponse::Array(items) => items,
tower_lsp::lsp_types::CompletionResponse::List(list) => list.items,
};
for completion in completion_items {
if let Some(text_edit) = completion.additional_text_edits {
report.push_str("--------------------------\n");
report.push_str(format!("Completion: {}\n", completion.label).as_str());
for edit in text_edit {
report.push_str(format!("Text edit: {}", edit.new_text).as_str());
}
}
}
}
completions.insert(format!("Completions #{}", n), report);
}

TestRunnerResult::success(completions)
}
1 change: 1 addition & 0 deletions crates/cairo-lang-language-server/tests/e2e/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod code_actions;
mod completions;
mod hover;
mod semantic_tokens;
mod support;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
//! > Test adding simple trait.

//! > test_runner_name
test_completions_text_edits

//! > cairo_project.toml
[crate_roots]
hello = "src"

[config.global]
edition = "2024_07"

//! > cairo_code
mod hidden_trait {
pub trait ATrait1<T> {
fn some_method(self: @T);
}
impl Felt252ATraitImpl of ATrait1<felt252> {
fn some_method(self: @felt252) {}
}
}

use hidden_trait::ATrait1;

mod inner_mod {
fn main() {
let x = 5_felt252;
x.some_me<caret>
}
}

//! > Completions #0
x.some_me<caret>
--------------------------
Completion: add_eq()
Text edit: use core::traits::AddEq;
--------------------------
Completion: sub_eq()
Text edit: use core::traits::SubEq;
--------------------------
Completion: mul_eq()
Text edit: use core::traits::MulEq;
--------------------------
Completion: into()
--------------------------
Completion: try_into()
--------------------------
Completion: destruct()
--------------------------
Completion: panic_destruct()
--------------------------
Completion: new_inputs()
Text edit: use core::circuit::CircuitInputs;
--------------------------
Completion: get_descriptor()
--------------------------
Completion: clone()
--------------------------
Completion: is_zero()
Text edit: use core::num::traits::Zero;
--------------------------
Completion: is_non_zero()
Text edit: use core::num::traits::Zero;
--------------------------
Completion: is_one()
Text edit: use core::num::traits::One;
--------------------------
Completion: is_non_one()
Text edit: use core::num::traits::One;
--------------------------
Completion: add_assign()
Text edit: use core::ops::AddAssign;
--------------------------
Completion: sub_assign()
Text edit: use core::ops::SubAssign;
--------------------------
Completion: mul_assign()
Text edit: use core::ops::MulAssign;
--------------------------
Completion: serialize()
--------------------------
Completion: print()
--------------------------
Completion: fmt()
Text edit: use core::fmt::Display;
--------------------------
Completion: fmt()
Text edit: use core::fmt::Debug;
--------------------------
Completion: is_zero()
--------------------------
Completion: is_non_zero()
--------------------------
Completion: append_formatted_to_byte_array()
Text edit: use core::to_byte_array::AppendFormattedToByteArray;
--------------------------
Completion: format_as_byte_array()
Text edit: use core::to_byte_array::FormatAsByteArray;
--------------------------
Completion: some_method()
Text edit: use super::ATrait1;

//! > ==========================================================================

//! > Test adding non directly visible traits.

//! > test_runner_name
test_completions_text_edits

//! > cairo_project.toml
[crate_roots]
hello = "src"

[config.global]
edition = "2024_07"

//! > cairo_code
mod hidden_trait {

pub trait ATrait1<T> {
fn some_method(self: @T);
}
impl Felt252ATraitImpl of ATrait1<felt252> {
fn some_method(self: @felt252) {}
}
}

use hidden_trait::ATrait1;

mod inner_mod {
fn main() {
let x = 5_felt252;
x.some_me<caret>
}
}

//! > Completions #0
x.some_me<caret>
--------------------------
Completion: add_eq()
Text edit: use core::traits::AddEq;
--------------------------
Completion: sub_eq()
Text edit: use core::traits::SubEq;
--------------------------
Completion: mul_eq()
Text edit: use core::traits::MulEq;
--------------------------
Completion: into()
--------------------------
Completion: try_into()
--------------------------
Completion: destruct()
--------------------------
Completion: panic_destruct()
--------------------------
Completion: new_inputs()
Text edit: use core::circuit::CircuitInputs;
--------------------------
Completion: get_descriptor()
--------------------------
Completion: clone()
--------------------------
Completion: is_zero()
Text edit: use core::num::traits::Zero;
--------------------------
Completion: is_non_zero()
Text edit: use core::num::traits::Zero;
--------------------------
Completion: is_one()
Text edit: use core::num::traits::One;
--------------------------
Completion: is_non_one()
Text edit: use core::num::traits::One;
--------------------------
Completion: add_assign()
Text edit: use core::ops::AddAssign;
--------------------------
Completion: sub_assign()
Text edit: use core::ops::SubAssign;
--------------------------
Completion: mul_assign()
Text edit: use core::ops::MulAssign;
--------------------------
Completion: serialize()
--------------------------
Completion: print()
--------------------------
Completion: fmt()
Text edit: use core::fmt::Display;
--------------------------
Completion: fmt()
Text edit: use core::fmt::Debug;
--------------------------
Completion: is_zero()
--------------------------
Completion: is_non_zero()
--------------------------
Completion: append_formatted_to_byte_array()
Text edit: use core::to_byte_array::AppendFormattedToByteArray;
--------------------------
Completion: format_as_byte_array()
Text edit: use core::to_byte_array::FormatAsByteArray;
--------------------------
Completion: some_method()
Text edit: use super::ATrait1;

0 comments on commit 185fb24

Please sign in to comment.