-
Notifications
You must be signed in to change notification settings - Fork 535
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
LS: Add "fill struct fields" code action
commit-id:0051578a
- Loading branch information
1 parent
920e8b0
commit efe63a2
Showing
6 changed files
with
286 additions
and
39 deletions.
There are no files selected for viewing
127 changes: 127 additions & 0 deletions
127
crates/cairo-lang-language-server/src/ide/code_actions/fill_struct_fields.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
use std::collections::HashMap; | ||
|
||
use cairo_lang_defs::ids::LanguageElementId; | ||
use cairo_lang_semantic::Expr; | ||
use cairo_lang_semantic::db::SemanticGroup; | ||
use cairo_lang_semantic::items::function_with_body::SemanticExprLookup; | ||
use cairo_lang_semantic::items::structure::concrete_struct_members; | ||
use cairo_lang_semantic::items::visibility::peek_visible_in; | ||
use cairo_lang_semantic::lookup_item::LookupItemEx; | ||
use cairo_lang_syntax::node::ast::{ExprStructCtorCall, StructArg}; | ||
use cairo_lang_syntax::node::kind::SyntaxKind; | ||
use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode}; | ||
use lsp_types::{CodeAction, CodeActionKind, CodeActionParams, Range, TextEdit, WorkspaceEdit}; | ||
use tracing::error; | ||
|
||
use crate::lang::db::{AnalysisDatabase, LsSemanticGroup, LsSyntaxGroup}; | ||
use crate::lang::lsp::ToLsp; | ||
|
||
/// Generates a completion adding all visible struct members that have not yet been specified | ||
/// to the constructor call, filling their values with a placeholder unit type. | ||
pub fn fill_struct_fields( | ||
db: &AnalysisDatabase, | ||
node: SyntaxNode, | ||
params: &CodeActionParams, | ||
) -> Option<CodeAction> { | ||
let module_file_id = db.find_module_file_containing_node(&node)?; | ||
let module_id = module_file_id.0; | ||
let file_id = module_file_id.file_id(db).ok()?; | ||
let function_id = db.find_lookup_item(&node)?.function_with_body()?; | ||
|
||
let constructor = db.first_ancestor_of_kind(node, SyntaxKind::ExprStructCtorCall)?; | ||
let constructor_expr = ExprStructCtorCall::from_syntax_node(db, constructor.clone()); | ||
|
||
let mut last_important_element = None; | ||
let mut has_trailing_comma = false; | ||
|
||
for node in constructor.descendants(db) { | ||
match node.kind(db) { | ||
SyntaxKind::TokenComma => { | ||
has_trailing_comma = true; | ||
last_important_element = Some(node) | ||
} | ||
SyntaxKind::StructArgSingle => { | ||
has_trailing_comma = false; | ||
last_important_element = Some(node) | ||
} | ||
// Don't complete any fields if initialization contains tail. | ||
SyntaxKind::StructArgTail => return None, | ||
_ => {} | ||
} | ||
} | ||
|
||
let code_prefix = String::from(if !has_trailing_comma && last_important_element.is_some() { | ||
", " | ||
} else { | ||
" " | ||
}); | ||
|
||
let struct_arguments = constructor_expr.arguments(db); | ||
let left_brace = struct_arguments.lbrace(db); | ||
let struct_arguments = struct_arguments.arguments(db).elements(db); | ||
|
||
let already_present_arguments = struct_arguments | ||
.iter() | ||
.map(|member| match member { | ||
StructArg::StructArgSingle(argument) => { | ||
argument.identifier(db).token(db).as_syntax_node().get_text_without_trivia(db) | ||
} | ||
StructArg::StructArgTail(_) => unreachable!(), | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
let constructor_expr_id = | ||
db.lookup_expr_by_ptr(function_id, constructor_expr.stable_ptr().into()).ok()?; | ||
|
||
let constructor_semantic = match db.expr_semantic(function_id, constructor_expr_id) { | ||
Expr::StructCtor(semantic) => semantic, | ||
_ => { | ||
error!( | ||
"Semantic expression obtained from StructCtorCall doesn't refer to constructor." | ||
); | ||
return None; | ||
} | ||
}; | ||
|
||
let concrete_struct_id = constructor_semantic.concrete_struct_id; | ||
let struct_parent_module_id = concrete_struct_id.struct_id(db).parent_module(db); | ||
|
||
let arguments_to_complete = concrete_struct_members(db, concrete_struct_id) | ||
.ok()? | ||
.iter() | ||
.filter_map(|(name, member)| { | ||
let name = name.to_string(); | ||
|
||
if already_present_arguments.contains(&name) { | ||
None | ||
} else if peek_visible_in(db, member.visibility, struct_parent_module_id, module_id) { | ||
Some(format!("{name}: ()")) | ||
} else { | ||
None | ||
} | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
let code_to_insert = code_prefix + &arguments_to_complete.join(", "); | ||
|
||
let edit_start = last_important_element | ||
.unwrap_or(left_brace.as_syntax_node()) | ||
.span_end_without_trivia(db) | ||
.position_in_file(db, file_id)? | ||
.to_lsp(); | ||
|
||
let mut changes = HashMap::new(); | ||
let url = params.text_document.uri.clone(); | ||
let change = TextEdit { range: Range::new(edit_start, edit_start), new_text: code_to_insert }; | ||
|
||
changes.insert(url, vec![change]); | ||
|
||
let edit = WorkspaceEdit::new(changes); | ||
|
||
Some(CodeAction { | ||
title: String::from("Fill struct fields"), | ||
kind: Some(CodeActionKind::QUICKFIX), | ||
edit: Some(edit), | ||
..Default::default() | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
crates/cairo-lang-language-server/tests/test_data/code_actions/fill_struct_fields.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
//! > Test filling missing members in struct constructor. | ||
|
||
//! > test_runner_name | ||
test_quick_fix | ||
|
||
//! > cairo_project.toml | ||
[crate_roots] | ||
hello = "src" | ||
|
||
[config.global] | ||
edition = "2024_07" | ||
|
||
//! > cairo_code | ||
mod some_module { | ||
pub struct Struct { | ||
x: u32, | ||
pub y: felt252, | ||
pub z: i16 | ||
} | ||
|
||
fn build_struct() { | ||
let s = Struct { | ||
x: 0x0, | ||
y: 0x0, | ||
z: 0x0 | ||
}; | ||
|
||
let _a = Struct { <caret> }; | ||
|
||
let _b = Struct { x: 0x0, <caret> }; | ||
|
||
let _c = Struct { <caret>..s }; | ||
} | ||
} | ||
|
||
mod happy_cases { | ||
use super::some_module::Struct; | ||
|
||
fn foo() { | ||
let _a = Struct { <caret> }; | ||
let _b = Struct { y: 0x0, <caret> }; | ||
let _c = Struct { y: 0x0, x: 0x0, <caret> } | ||
} | ||
} | ||
|
||
mod unhappy_cases { | ||
fn foo() { | ||
let _a = NonexsitentStruct { <caret> }; | ||
} | ||
} | ||
|
||
//! > Code action #0 | ||
let _a = Struct { <caret> }; | ||
Title: Fill struct fields | ||
Add new text: " x: (), y: (), z: ()" | ||
At: Range { start: Position { line: 14, character: 25 }, end: Position { line: 14, character: 25 } } | ||
|
||
//! > Code action #1 | ||
let _b = Struct { x: 0x0, <caret> }; | ||
Title: Fill struct fields | ||
Add new text: " y: (), z: ()" | ||
At: Range { start: Position { line: 16, character: 33 }, end: Position { line: 16, character: 33 } } | ||
|
||
//! > Code action #2 | ||
let _c = Struct { <caret>..s }; | ||
No code actions. | ||
|
||
//! > Code action #3 | ||
let _a = Struct { <caret> }; | ||
Title: Fill struct fields | ||
Add new text: " y: (), z: ()" | ||
At: Range { start: Position { line: 26, character: 25 }, end: Position { line: 26, character: 25 } } | ||
|
||
//! > Code action #4 | ||
let _b = Struct { y: 0x0, <caret> }; | ||
Title: Fill struct fields | ||
Add new text: " z: ()" | ||
At: Range { start: Position { line: 27, character: 33 }, end: Position { line: 27, character: 33 } } | ||
|
||
//! > Code action #5 | ||
let _c = Struct { y: 0x0, x: 0x0, <caret> } | ||
Title: Fill struct fields | ||
Add new text: " z: ()" | ||
At: Range { start: Position { line: 28, character: 41 }, end: Position { line: 28, character: 41 } } | ||
|
||
//! > Code action #6 | ||
let _a = NonexsitentStruct { <caret> }; | ||
No code actions. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters