Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LS: Implement hover definition for variables & params #5806

Merged
merged 1 commit into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/cairo-lang-language-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ salsa.workspace = true
scarb-metadata = "1.12"
serde = { workspace = true, default-features = true }
serde_json.workspace = true
smol_str.workspace = true
tokio.workspace = true
tower-lsp = "0.20.0"
tracing = "0.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub fn definition(
}
md
}

SymbolDef::Variable(var) => Markdown::fenced_code_block(&var.signature(db)),
};

Some(Hover {
Expand Down
149 changes: 140 additions & 9 deletions crates/cairo-lang-language-server/src/lang/inspect/defs.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
use cairo_lang_compiler::db::RootDatabase;
use cairo_lang_defs::db::DefsGroup;
use cairo_lang_defs::ids::LookupItemId;
use cairo_lang_semantic::db::SemanticGroup;
use cairo_lang_semantic::expr::pattern::QueryPatternVariablesFromDb;
use cairo_lang_semantic::items::function_with_body::SemanticExprLookup;
use cairo_lang_semantic::lookup_item::LookupItemEx;
use cairo_lang_semantic::resolve::{ResolvedConcreteItem, ResolvedGenericItem};
use cairo_lang_syntax::node::ast::TerminalIdentifier;
use cairo_lang_syntax::node::TypedSyntaxNode;
use cairo_lang_semantic::{Mutability, Variable};
use cairo_lang_syntax::node::ast::{Param, PatternIdentifier, PatternPtr, TerminalIdentifier};
use cairo_lang_syntax::node::kind::SyntaxKind;
use cairo_lang_syntax::node::{SyntaxNode, Terminal, TypedSyntaxNode};
use cairo_lang_utils::Upcast;
use smol_str::SmolStr;
use tracing::error;

use crate::lang::db::LsSemanticGroup;
use crate::markdown::Markdown;
Expand All @@ -16,10 +24,12 @@ use crate::{find_definition, ResolvedItem};
/// Do not store it in any kind of state.
pub enum SymbolDef {
Item(ItemDef),
Variable(VariableDef),
}

impl SymbolDef {
/// Finds definition of the symbol referred by the given identifier.
#[tracing::instrument(name = "SymbolDef::find", level = "trace", skip_all)]
pub fn find(db: &RootDatabase, identifier: &TerminalIdentifier) -> Option<Self> {
// Get the resolved item info and the syntax node of the definition.
let (definition_item, definition_node) = {
Expand Down Expand Up @@ -48,25 +58,31 @@ impl SymbolDef {
| ResolvedItem::Concrete(ResolvedConcreteItem::Variant(_))
| ResolvedItem::Concrete(ResolvedConcreteItem::Trait(_))
| ResolvedItem::Concrete(ResolvedConcreteItem::Impl(_)) => {
// Get the lookup item representing the defining item.
let lookup_item_id = db.find_lookup_item(&definition_node)?;

Some(Self::Item(ItemDef { lookup_item_id }))
ItemDef::new(db, &definition_node).map(Self::Item)
}

// TODO(mkaput): Implement hover for variables.
ResolvedItem::Generic(ResolvedGenericItem::Variable(_)) => None,
ResolvedItem::Generic(ResolvedGenericItem::Variable(_)) => {
VariableDef::new(db, definition_node).map(Self::Variable)
}
}
}
}

/// Information about definition of an item (function, trait, impl, module, etc.).
/// Information about the definition of an item (function, trait, impl, module, etc.).
pub struct ItemDef {
/// The [`LookupItemId`] associated with the item.
lookup_item_id: LookupItemId,
}

impl ItemDef {
/// Constructs new [`ItemDef`] instance.
fn new(db: &RootDatabase, definition_node: &SyntaxNode) -> Option<Self> {
// Get the lookup item representing the defining item.
let lookup_item_id = db.find_lookup_item(definition_node)?;

Some(Self { lookup_item_id })
}

/// Get item signature without its body.
pub fn signature(&self, db: &RootDatabase) -> String {
db.get_item_signature(self.lookup_item_id)
Expand All @@ -86,3 +102,118 @@ impl ItemDef {
})
}
}

/// Information about the definition of a variable (local, function parameter).
pub struct VariableDef {
name: SmolStr,
var: Variable,
}

impl VariableDef {
/// Constructs new [`VariableDef`] instance.
fn new(db: &RootDatabase, definition_node: SyntaxNode) -> Option<Self> {
match definition_node.kind(db.upcast()) {
SyntaxKind::TerminalIdentifier => {
let definition_node = definition_node.parent()?;
match definition_node.kind(db.upcast()) {
SyntaxKind::PatternIdentifier => {
let pattern_identifier =
PatternIdentifier::from_syntax_node(db.upcast(), definition_node);
Self::new_pattern_identifier(db, pattern_identifier)
}
kind => {
error!(
"variable definition node parent is not an pattern identifier: \
{kind:?}"
);
None
}
}
}

SyntaxKind::Param => {
let param = Param::from_syntax_node(db.upcast(), definition_node);
Self::new_param(db, param)
}

kind => {
error!("variable definition node is not an identifier nor param: {kind:?}");
None
}
}
}

/// Constructs new [`VariableDef`] instance for [`PatternIdentifier`].
fn new_pattern_identifier(
db: &RootDatabase,
pattern_identifier: PatternIdentifier,
) -> Option<Self> {
let name = pattern_identifier.name(db.upcast()).text(db.upcast());

// Get the function which contains the variable/parameter.
let function_id =
db.find_lookup_item(&pattern_identifier.as_syntax_node())?.function_with_body()?;

// Get semantic model for the pattern.
let pattern = {
let pattern_ptr = PatternPtr::from(pattern_identifier.stable_ptr());
let id = db.lookup_pattern_by_ptr(function_id, pattern_ptr).ok()?;
db.pattern_semantic(function_id, id)
};

// Extract variable semantic from the found pattern.
let var = pattern
.variables(&QueryPatternVariablesFromDb(db.upcast(), function_id))
.into_iter()
.find(|pv| pv.name == name)?
.var
.into();

Some(Self { name, var })
}

/// Constructs new [`VariableDef`] instance for [`Param`].
fn new_param(db: &RootDatabase, param: Param) -> Option<Self> {
let name = param.name(db.upcast()).text(db.upcast());

// Get the function which contains the variable/parameter.
let function_id = db.find_lookup_item(&param.as_syntax_node())?.function_with_body()?;

// Get function signature.
let signature = db.function_with_body_signature(function_id).ok()?;

// Extract parameter semantic from the found signature.
let var = signature.params.into_iter().find(|p| p.name == name)?.into();

Some(Self { name, var })
}

/// Gets variable signature, which tries to resemble the way how it is defined in code.
pub fn signature(&self, db: &RootDatabase) -> String {
let Self { name, var } = self;

let prefix = match var {
Variable::Local(_) => "let ",
Variable::Param(_) => "",
};

let mutability = match var {
Variable::Local(local) => {
if local.is_mut {
"mut "
} else {
""
}
}
Variable::Param(param) => match param.mutability {
Mutability::Immutable => "",
Mutability::Mutable => "mut ",
Mutability::Reference => "ref ",
},
};

let ty = var.ty().format(db.upcast());

format!("{prefix}{mutability}{name}: {ty}")
}
}
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 @@ -13,6 +13,7 @@ cairo_lang_test_utils::test_file_test!(
"tests/test_data/hover",
{
basic: "basic.txt",
partial: "partial.txt",
starknet: "starknet.txt",
},
test_hover
Expand Down
14 changes: 11 additions & 3 deletions crates/cairo-lang-language-server/tests/test_data/hover/basic.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ edition = "2023_11"

//! > src/lib.cairo
fn main() {
let mut x = 5;
let mut x<caret> = 5;
p<caret>rintln!("The value of x is: {}", x);
x<caret> = <caret>a<caret>dd_two<caret>(x);

Expand Down Expand Up @@ -72,6 +72,14 @@ pub mod front_of_house {
}
}

//! > hover 1:13
// = source context
let mut x<caret> = 5;
// = highlight
No highlight information.
// = popover
Type: `core::integer::u32`

//! > hover 2:5
// = source context
p<caret>rintln!("The value of x is: {}", x);
Expand All @@ -86,10 +94,10 @@ No highlight information.
// = source context
x<caret> = add_two(x);
// = highlight
No highlight information.
<sel>x</sel> = add_two(x);
// = popover
```cairo
core::integer::u32
let mut x: core::integer::u32
```

//! > hover 3:8
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//! > Hover

//! > test_runner_name
test_hover

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

[config.global]
edition = "2023_11"

//! > src/lib.cairo
fn main() {
let mut xy<caret>z = unkn<caret>own_function();
let y = xy<caret>z * 2;
}

fn f(ab<caret>c) -> felt252 {
2 * ab<caret>c
}

//! > hover 1:14
// = source context
let mut xy<caret>z = unknown_function();
// = highlight
No highlight information.
// = popover
Type: `<missing>`

//! > hover 1:22
// = source context
let mut xyz = unkn<caret>own_function();
// = highlight
No highlight information.
// = popover
```cairo
<missing>
```

//! > hover 2:14
// = source context
let y = xy<caret>z * 2;
// = highlight
let y = <sel>xyz</sel> * 2;
// = popover
```cairo
let mut xyz: <missing>
```

//! > hover 5:7
// = source context
fn f(ab<caret>c) -> felt252 {
// = highlight
No highlight information.
// = popover

//! > hover 6:10
// = source context
2 * ab<caret>c
// = highlight
2 * <sel>abc</sel>
// = popover
```cairo
abc: <missing>
```
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ hello::Balance::__member_module_value::ContractMemberState
// = source context
self.value.write(<caret>value_);
// = highlight
No highlight information.
self.value.write(<sel>value_</sel>);
// = popover
```cairo
core::integer::u128
value_: core::integer::u128
```

//! > hover 28:30
Expand Down Expand Up @@ -152,8 +152,8 @@ fn write(ref self: TMemberState, value: Self::Value);
// = source context
self.value.write( self.value.read() + <caret>a );
// = highlight
No highlight information.
self.value.write( self.value.read() + <sel>a</sel> );
// = popover
```cairo
core::integer::u128
a: core::integer::u128
```
Loading