Skip to content

Commit

Permalink
Added discovery of struct members inside constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
integraledelebesgue committed Nov 13, 2024
1 parent 5e02697 commit 64a30c3
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 3 deletions.
44 changes: 43 additions & 1 deletion crates/cairo-lang-language-server/src/lang/inspect/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,9 @@ pub fn find_definition(
}
}

if let Some(member_id) = try_extract_member(db, identifier, lookup_items) {
if let Some(member_id) = try_extract_member(db, identifier, lookup_items)
.or_else(|| try_extract_member_from_constructor(db, identifier, lookup_items))
{
return Some((ResolvedItem::Member(member_id), member_id.untyped_stable_ptr(db)));
}

Expand Down Expand Up @@ -363,6 +365,46 @@ pub fn find_definition(
}
}

/// Extracts [`MemberId`] if the [`ast::TerminalIdentifier`] is used as a struct member
/// in [`ast::ExprStructCtorCall`].
fn try_extract_member_from_constructor(
db: &AnalysisDatabase,
identifier: &ast::TerminalIdentifier,
lookup_items: &[LookupItemId],
) -> Option<MemberId> {
let function_id = lookup_items.first()?.function_with_body()?;

let identifier_node = identifier.as_syntax_node();

let struct_member =
db.first_ancestor_of_kind(identifier_node.clone(), SyntaxKind::StructArgSingle)?;
let struct_member = ast::StructArgSingle::from_syntax_node(db, struct_member);

let ast::OptionStructArgExpr::StructArgExpr(struct_member_expr) = struct_member.arg_expr(db)
else {
return None;
};

let struct_member_expr_id =
db.lookup_expr_by_ptr(function_id, struct_member_expr.expr(db).stable_ptr()).ok()?;

let constructor = db.first_ancestor_of_kind(identifier_node, SyntaxKind::ExprStructCtorCall)?;
let constructor_expr = ast::ExprStructCtorCall::from_syntax_node(db, constructor);
let constructor_expr_id =
db.lookup_expr_by_ptr(function_id, constructor_expr.stable_ptr().into()).ok()?;

let Expr::StructCtor(constructor_expr_semantic) =
db.expr_semantic(function_id, constructor_expr_id)
else {
return None;
};

constructor_expr_semantic
.members
.iter()
.find_map(|(id, expr_id)| struct_member_expr_id.eq(expr_id).then_some(*id))
}

/// Extracts [`MemberId`] if the [`ast::TerminalIdentifier`] points to
/// right-hand side of access member expression e.g., to `xyz` in `self.xyz`.
fn try_extract_member(
Expand Down
1 change: 1 addition & 0 deletions crates/cairo-lang-language-server/tests/e2e/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ cairo_lang_test_utils::test_file_test!(
partial: "partial.txt",
starknet: "starknet.txt",
literals: "literals.txt",
structs: "structs.txt"
},
test_hover
);
Expand Down
13 changes: 11 additions & 2 deletions crates/cairo-lang-language-server/tests/test_data/hover/basic.txt
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,20 @@ Rectangle struct.
// = source context
let mut rect = Rectangle { wid<caret>th: 30, height: 50 };
// = highlight
No highlight information.
let mut rect = Rectangle { <sel>width</sel>: 30, height: 50 };
// = popover
```cairo
hello::Rectangle
hello
```
```cairo
#[derive(Copy, Drop)]
struct Rectangle {
width: u64,
height: u64,
}
```
---
Width of the rectangle.

//! > hover #12
// = source context
Expand Down
119 changes: 119 additions & 0 deletions crates/cairo-lang-language-server/tests/test_data/hover/structs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//! > Hover

//! > test_runner_name
test_hover

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

[config.global]
edition = "2023_11"

//! > cairo_code
/// Docstring of Struct.
struct Struct {
/// Docstring of member1.
member<caret>1: felt252,
member2: u256
}

mod happy_cases {
use super::Struct;

fn constructor() {
let _s = Struct { member1<caret>: 0, member2: 0 };
let _s = Struct { member1: 0, mem<caret>ber2: 0 };
}

fn member_access() {
let s = Struct { member1: 0, member2: 0 };
let _ = s.member1<caret>;
}
}

mod unhappy_cases {
fn non_existent_struct {
let _ = NonExistentStruct { mem<caret>ber: 0 };
}
}

//! > hover #0
// = source context
member<caret>1: felt252,
// = highlight
<sel>member1</sel>: felt252,
// = popover
```cairo
hello
```
```cairo
struct Struct {
member1: felt252,
member2: u256,
}
```
---
Docstring of Struct.

//! > hover #1
// = source context
let _s = Struct { member1<caret>: 0, member2: 0 };
// = highlight
let _s = Struct { <sel>member1</sel>: 0, member2: 0 };
// = popover
```cairo
hello
```
```cairo
struct Struct {
member1: felt252,
member2: u256,
}
```
---
Docstring of member1.

//! > hover #2
// = source context
let _s = Struct { member1: 0, mem<caret>ber2: 0 };
// = highlight
let _s = Struct { member1: 0, <sel>member2</sel>: 0 };
// = popover
```cairo
hello
```
```cairo
struct Struct {
member1: felt252,
member2: u256,
}
```

//! > hover #3
// = source context
let _ = s.member1<caret>;
// = highlight
let _ = s.<sel>member1</sel>;
// = popover
```cairo
hello
```
```cairo
struct Struct {
member1: felt252,
member2: u256,
}
```
---
Docstring of member1.

//! > hover #4
// = source context
let _ = NonExistentStruct { mem<caret>ber: 0 };
// = highlight
No highlight information.
// = popover
```cairo
<missing>
```

0 comments on commit 64a30c3

Please sign in to comment.