Skip to content

Commit

Permalink
LS: refactor in preparation for lsp-server migration (#6357)
Browse files Browse the repository at this point in the history
  • Loading branch information
piotmag769 authored Sep 5, 2024
1 parent 3f92254 commit 1360ba2
Show file tree
Hide file tree
Showing 13 changed files with 551 additions and 512 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ use cairo_lang_utils::Upcast;
use tower_lsp::lsp_types::Hover;

use crate::ide::hover::markdown_contents;
use crate::ide::hover::render::markdown::{fenced_code_block, RULE};
use crate::lang::db::AnalysisDatabase;
use crate::lang::inspect::defs::{MemberDef, SymbolDef};
use crate::lang::lsp::ToLsp;
use crate::markdown::{fenced_code_block, RULE};

/// Get declaration and documentation "definition" of an item referred by the given identifier.
#[tracing::instrument(level = "trace", skip_all)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use cairo_lang_utils::Upcast;
use tower_lsp::lsp_types::Hover;

use crate::ide::hover::markdown_contents;
use crate::ide::hover::render::markdown::{fenced_code_block, RULE};
use crate::lang::db::{AnalysisDatabase, LsSemanticGroup};
use crate::markdown::{fenced_code_block, RULE};

/// Legacy hover rendering backported from Cairo 2.6.3 codebase.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pub use self::legacy::*;

mod definition;
mod legacy;
mod markdown;
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use cairo_lang_filesystem::db::get_originating_location;
use cairo_lang_filesystem::ids::FileId;
use cairo_lang_filesystem::span::{TextPosition, TextSpan};
use cairo_lang_utils::Upcast;
use tower_lsp::lsp_types::{GotoDefinitionParams, GotoDefinitionResponse, Location};

use crate::get_definition_location;
use crate::lang::db::AnalysisDatabase;
use crate::lang::db::{AnalysisDatabase, LsSemanticGroup, LsSyntaxGroup};
use crate::lang::inspect::defs::find_definition;
use crate::lang::lsp::{LsProtoGroup, ToCairo, ToLsp};

/// Get the definition location of a symbol at a given text document position.
Expand All @@ -23,3 +26,34 @@ pub fn goto_definition(
let range = span.position_in_file(db.upcast(), found_file)?.to_lsp();
Some(GotoDefinitionResponse::Scalar(Location { uri: found_uri, range }))
}

/// Returns the file id and span of the definition of an expression from its position.
///
/// # Arguments
///
/// * `db` - Preloaded compilation database
/// * `uri` - Uri of the expression position
/// * `position` - Position of the expression
///
/// # Returns
///
/// The [FileId] and [TextSpan] of the expression definition if found.
fn get_definition_location(
db: &AnalysisDatabase,
file: FileId,
position: TextPosition,
) -> Option<(FileId, TextSpan)> {
let identifier = db.find_identifier_at_position(file, position)?;

let syntax_db = db.upcast();
let node = db.find_syntax_node_at_position(file, position)?;
let lookup_items = db.collect_lookup_items_stack(&node)?;
let (_, stable_ptr) = find_definition(db, &identifier, &lookup_items)?;
let node = stable_ptr.lookup(syntax_db);
let found_file = stable_ptr.file_id(syntax_db);
let span = node.span_without_trivia(syntax_db);
let width = span.width();
let (file_id, mut span) = get_originating_location(db.upcast(), found_file, span.start_only());
span.end = span.end.add_width(width);
Some((file_id, span))
}
208 changes: 202 additions & 6 deletions crates/cairo-lang-language-server/src/lang/inspect/defs.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
use std::iter;

use cairo_lang_defs::db::DefsGroup;
use cairo_lang_defs::ids::{
LanguageElementId, LookupItemId, MemberId, ModuleItemId, TopLevelLanguageElementId, TraitItemId,
FunctionTitleId, LanguageElementId, LookupItemId, MemberId, ModuleId, ModuleItemId,
SubmoduleLongId, TopLevelLanguageElementId, TraitItemId,
};
use cairo_lang_diagnostics::ToOption;
use cairo_lang_doc::db::DocGroup;
use cairo_lang_parser::db::ParserGroup;
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::items::functions::GenericFunctionId;
use cairo_lang_semantic::items::imp::ImplLongId;
use cairo_lang_semantic::lookup_item::LookupItemEx;
use cairo_lang_semantic::resolve::{ResolvedConcreteItem, ResolvedGenericItem};
use cairo_lang_semantic::{Binding, Mutability};
use cairo_lang_semantic::{Binding, Expr, Mutability, TypeLongId};
use cairo_lang_syntax::node::ast::{Param, PatternIdentifier, PatternPtr, TerminalIdentifier};
use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
use cairo_lang_syntax::node::kind::SyntaxKind;
use cairo_lang_syntax::node::utils::is_grandparent_of_kind;
use cairo_lang_syntax::node::{SyntaxNode, Terminal, TypedSyntaxNode};
use cairo_lang_utils::Upcast;
use cairo_lang_syntax::node::{ast, SyntaxNode, Terminal, TypedStablePtr, TypedSyntaxNode};
use cairo_lang_utils::{Intern, LookupIntern, Upcast};
use itertools::Itertools;
use smol_str::SmolStr;
use tracing::error;

use crate::lang::db::{AnalysisDatabase, LsSemanticGroup};
use crate::lang::db::{AnalysisDatabase, LsSemanticGroup, LsSyntaxGroup};
use crate::lang::inspect::defs::SymbolDef::Member;
use crate::{find_definition, ResolvedItem};

/// Keeps information about the symbol that is being searched for/inspected.
///
Expand All @@ -40,6 +46,13 @@ pub struct MemberDef {
pub structure: ItemDef,
}

/// Either [`ResolvedGenericItem`], [`ResolvedConcreteItem`] or [`MemberId`].
pub enum ResolvedItem {
Generic(ResolvedGenericItem),
Concrete(ResolvedConcreteItem),
Member(MemberId),
}

impl SymbolDef {
/// Finds definition of the symbol referred by the given identifier.
#[tracing::instrument(name = "SymbolDef::find", level = "trace", skip_all)]
Expand Down Expand Up @@ -277,3 +290,186 @@ impl VariableDef {
format!("{prefix}{mutability}{name}: {ty}")
}
}

// TODO(mkaput): make private.
#[tracing::instrument(level = "trace", skip_all)]
pub fn find_definition(
db: &AnalysisDatabase,
identifier: &ast::TerminalIdentifier,
lookup_items: &[LookupItemId],
) -> Option<(ResolvedItem, SyntaxStablePtrId)> {
if let Some(parent) = identifier.as_syntax_node().parent() {
if parent.kind(db) == SyntaxKind::ItemModule {
let Some(containing_module_file_id) = db.find_module_file_containing_node(&parent)
else {
error!("`find_definition` failed: could not find module");
return None;
};

let submodule_id = SubmoduleLongId(
containing_module_file_id,
ast::ItemModule::from_syntax_node(db, parent).stable_ptr(),
)
.intern(db);
let item = ResolvedGenericItem::Module(ModuleId::Submodule(submodule_id));
return Some((
ResolvedItem::Generic(item.clone()),
resolved_generic_item_def(db, item)?,
));
}
}

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

for lookup_item_id in lookup_items.iter().copied() {
if let Some(item) =
db.lookup_resolved_generic_item_by_ptr(lookup_item_id, identifier.stable_ptr())
{
return Some((
ResolvedItem::Generic(item.clone()),
resolved_generic_item_def(db, item)?,
));
}

if let Some(item) =
db.lookup_resolved_concrete_item_by_ptr(lookup_item_id, identifier.stable_ptr())
{
let stable_ptr = resolved_concrete_item_def(db.upcast(), item.clone())?;
return Some((ResolvedItem::Concrete(item), stable_ptr));
}
}

// Skip variable definition, otherwise we would get parent ModuleItem for variable.
if db.first_ancestor_of_kind(identifier.as_syntax_node(), SyntaxKind::StatementLet).is_none() {
let item = match lookup_items.first().copied()? {
LookupItemId::ModuleItem(item) => {
ResolvedGenericItem::from_module_item(db, item).to_option()?
}
LookupItemId::TraitItem(trait_item) => {
if let TraitItemId::Function(trait_fn) = trait_item {
ResolvedGenericItem::TraitFunction(trait_fn)
} else {
ResolvedGenericItem::Trait(trait_item.trait_id(db))
}
}
LookupItemId::ImplItem(impl_item) => {
ResolvedGenericItem::Impl(impl_item.impl_def_id(db))
}
};

Some((ResolvedItem::Generic(item.clone()), resolved_generic_item_def(db, item)?))
} else {
None
}
}

/// 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(
db: &AnalysisDatabase,
identifier: &ast::TerminalIdentifier,
lookup_items: &[LookupItemId],
) -> Option<MemberId> {
let syntax_node = identifier.as_syntax_node();
let binary_expr_syntax_node =
db.first_ancestor_of_kind(syntax_node.clone(), SyntaxKind::ExprBinary)?;
let binary_expr = ast::ExprBinary::from_syntax_node(db, binary_expr_syntax_node);

let function_with_body = lookup_items.first()?.function_with_body()?;

let expr_id =
db.lookup_expr_by_ptr(function_with_body, binary_expr.stable_ptr().into()).ok()?;
let semantic_expr = db.expr_semantic(function_with_body, expr_id);

if let Expr::MemberAccess(expr_member_access) = semantic_expr {
let pointer_to_rhs = binary_expr.rhs(db).stable_ptr().untyped();

let mut current_node = syntax_node;
// Check if the terminal identifier points to a member, not a struct variable.
while pointer_to_rhs != current_node.stable_ptr() {
// If we found the node with the binary expression, then we are sure we won't find the
// node with the member.
if current_node.stable_ptr() == binary_expr.stable_ptr().untyped() {
return None;
}
current_node = current_node.parent().unwrap();
}

Some(expr_member_access.member)
} else {
None
}
}

#[tracing::instrument(level = "trace", skip_all)]
fn resolved_concrete_item_def(
db: &AnalysisDatabase,
item: ResolvedConcreteItem,
) -> Option<SyntaxStablePtrId> {
match item {
ResolvedConcreteItem::Type(ty) => {
if let TypeLongId::GenericParameter(param) = ty.lookup_intern(db) {
Some(param.untyped_stable_ptr(db.upcast()))
} else {
None
}
}
ResolvedConcreteItem::Impl(imp) => {
if let ImplLongId::GenericParameter(param) = imp.lookup_intern(db) {
Some(param.untyped_stable_ptr(db.upcast()))
} else {
None
}
}
_ => None,
}
}

#[tracing::instrument(level = "trace", skip_all)]
fn resolved_generic_item_def(
db: &AnalysisDatabase,
item: ResolvedGenericItem,
) -> Option<SyntaxStablePtrId> {
let defs_db = db.upcast();
Some(match item {
ResolvedGenericItem::GenericConstant(item) => item.untyped_stable_ptr(defs_db),
ResolvedGenericItem::Module(module_id) => {
// Check if the module is an inline submodule.
if let ModuleId::Submodule(submodule_id) = module_id {
if let ast::MaybeModuleBody::Some(submodule_id) =
submodule_id.stable_ptr(defs_db).lookup(db.upcast()).body(db.upcast())
{
// Inline module.
return Some(submodule_id.stable_ptr().untyped());
}
}
let module_file = db.module_main_file(module_id).ok()?;
let file_syntax = db.file_module_syntax(module_file).ok()?;
file_syntax.as_syntax_node().stable_ptr()
}
ResolvedGenericItem::GenericFunction(item) => {
let title = match item {
GenericFunctionId::Free(id) => FunctionTitleId::Free(id),
GenericFunctionId::Extern(id) => FunctionTitleId::Extern(id),
GenericFunctionId::Impl(id) => {
// Note: Only the trait title is returned.
FunctionTitleId::Trait(id.function)
}
GenericFunctionId::Trait(id) => FunctionTitleId::Trait(id.trait_function(db)),
};
title.untyped_stable_ptr(defs_db)
}
ResolvedGenericItem::GenericType(generic_type) => generic_type.untyped_stable_ptr(defs_db),
ResolvedGenericItem::GenericTypeAlias(type_alias) => type_alias.untyped_stable_ptr(defs_db),
ResolvedGenericItem::GenericImplAlias(impl_alias) => impl_alias.untyped_stable_ptr(defs_db),
ResolvedGenericItem::Variant(variant) => variant.id.stable_ptr(defs_db).untyped(),
ResolvedGenericItem::Trait(trt) => trt.stable_ptr(defs_db).untyped(),
ResolvedGenericItem::Impl(imp) => imp.stable_ptr(defs_db).untyped(),
ResolvedGenericItem::TraitFunction(trait_function) => {
trait_function.stable_ptr(defs_db).untyped()
}
ResolvedGenericItem::Variable(var) => var.untyped_stable_ptr(defs_db),
})
}
Loading

0 comments on commit 1360ba2

Please sign in to comment.