diff --git a/kclvm/Cargo.lock b/kclvm/Cargo.lock index dc29b7958..5f67754b5 100644 --- a/kclvm/Cargo.lock +++ b/kclvm/Cargo.lock @@ -1375,6 +1375,7 @@ dependencies = [ "im-rc", "indexmap", "kclvm-ast", + "kclvm-compiler", "kclvm-config", "kclvm-driver", "kclvm-error", diff --git a/kclvm/ast/src/ast.rs b/kclvm/ast/src/ast.rs index ee7f0b4b9..72548f381 100644 --- a/kclvm/ast/src/ast.rs +++ b/kclvm/ast/src/ast.rs @@ -81,6 +81,8 @@ impl Into<(Position, Position)> for Pos { /// Node is the file, line and column number information /// that all AST nodes need to contain. +/// In fact, column and end_column are the counts of character, +/// For example, `\t` is counted as 1 character, so it is recorded as 1 here, but generally col is 4. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Node { pub node: T, diff --git a/kclvm/sema/src/resolver/import.rs b/kclvm/sema/src/resolver/import.rs index e3901fa1b..e4019ab81 100644 --- a/kclvm/sema/src/resolver/import.rs +++ b/kclvm/sema/src/resolver/import.rs @@ -5,7 +5,7 @@ use crate::{ builtin::system_module::STANDARD_SYSTEM_MODULES, ty::{Type, TypeKind}, }; -use indexmap::IndexMap; +use indexmap::{IndexMap, IndexSet}; use kclvm_ast::ast; use kclvm_error::*; use std::{cell::RefCell, path::Path, rc::Rc}; @@ -213,7 +213,7 @@ impl<'ctx> Resolver<'ctx> { elems: IndexMap::default(), start: Position::dummy_pos(), end: Position::dummy_pos(), - kind: ScopeKind::Package(vec![]), + kind: ScopeKind::Package(IndexSet::new()), })); self.scope_map .insert(pkgpath.to_string(), Rc::clone(&scope)); @@ -222,9 +222,6 @@ impl<'ctx> Resolver<'ctx> { self.ctx.pkgpath = pkgpath.to_string(); self.ctx.filename = filename.to_string(); let scope = self.scope_map.get(pkgpath).unwrap().clone(); - if let ScopeKind::Package(files) = &mut scope.borrow_mut().kind { - files.push(filename.to_string()) - } self.scope = scope; } } diff --git a/kclvm/sema/src/resolver/mod.rs b/kclvm/sema/src/resolver/mod.rs index bd5f4a7d8..5c89b1230 100644 --- a/kclvm/sema/src/resolver/mod.rs +++ b/kclvm/sema/src/resolver/mod.rs @@ -72,6 +72,9 @@ impl<'ctx> Resolver<'ctx> { Some(modules) => { for module in modules { self.ctx.filename = module.filename.to_string(); + if let scope::ScopeKind::Package(files) = &mut self.scope.borrow_mut().kind { + files.insert(module.filename.to_string()); + } for stmt in &module.body { self.stmt(&stmt); } diff --git a/kclvm/sema/src/resolver/scope.rs b/kclvm/sema/src/resolver/scope.rs index 045bd082f..76663cf2b 100644 --- a/kclvm/sema/src/resolver/scope.rs +++ b/kclvm/sema/src/resolver/scope.rs @@ -1,6 +1,6 @@ use anyhow::bail; use compiler_base_session::Session; -use indexmap::IndexMap; +use indexmap::{IndexMap, IndexSet}; use kclvm_ast::{ast, MAIN_PKG}; use kclvm_error::{Handler, Level}; use std::sync::Arc; @@ -113,17 +113,7 @@ impl ContainsPos for Scope { /// Check if current scope contains a position fn contains_pos(&self, pos: &Position) -> bool { match &self.kind { - ScopeKind::Package(files) => { - if files.contains(&pos.filename) { - self.children.iter().any(|s| s.borrow().contains_pos(pos)) - || self - .elems - .iter() - .any(|(_, child)| child.borrow().contains_pos(pos)) - } else { - false - } - } + ScopeKind::Package(files) => files.contains(&pos.filename), _ => self.start.less_equal(pos) && pos.less_equal(&self.end), } } @@ -132,7 +122,7 @@ impl ContainsPos for Scope { #[derive(Clone, Debug)] pub enum ScopeKind { /// Package scope. - Package(Vec), + Package(IndexSet), /// Builtin scope. Builtin, /// Schema name string. diff --git a/kclvm/sema/src/resolver/tests.rs b/kclvm/sema/src/resolver/tests.rs index 64dab670f..7cbeb8e08 100644 --- a/kclvm/sema/src/resolver/tests.rs +++ b/kclvm/sema/src/resolver/tests.rs @@ -408,13 +408,6 @@ fn test_pkg_scope() { assert!(main_scope.contains_pos(&pos)); - let pos = Position { - filename: filename.clone(), - line: 10, - column: Some(0), - }; - assert!(!main_scope.contains_pos(&pos)); - let filename = Path::new(&root.clone()) .join("pkg") .join("pkg.k") @@ -428,12 +421,4 @@ fn test_pkg_scope() { }; assert!(pkg_scope.contains_pos(&pos)); - - let pos = Position { - filename: filename.clone(), - line: 10, - column: Some(0), - }; - - assert!(!pkg_scope.contains_pos(&pos)); } diff --git a/kclvm/tools/src/LSP/Cargo.toml b/kclvm/tools/src/LSP/Cargo.toml index ef7e0924e..a6f1afe55 100644 --- a/kclvm/tools/src/LSP/Cargo.toml +++ b/kclvm/tools/src/LSP/Cargo.toml @@ -27,6 +27,7 @@ kclvm-parser = {path = "../../../parser"} kclvm-sema = {path = "../../../sema"} kclvm-ast = {path = "../../../ast"} kclvm-utils = {path = "../../../utils"} +kclvm-compiler = {path = "../../../compiler"} compiler_base_session = {path = "../../../../compiler_base/session"} lsp-server = { version = "0.6.0", default-features = false } diff --git a/kclvm/tools/src/LSP/src/completion.rs b/kclvm/tools/src/LSP/src/completion.rs index e40e592ed..0ae38cdfb 100644 --- a/kclvm/tools/src/LSP/src/completion.rs +++ b/kclvm/tools/src/LSP/src/completion.rs @@ -13,6 +13,7 @@ use std::{fs, path::Path}; use indexmap::IndexSet; use kclvm_ast::ast::{Expr, ImportStmt, Program, Stmt}; +use kclvm_compiler::pkgpath_without_prefix; use kclvm_config::modfile::KCL_FILE_EXTENSION; use kclvm_error::Position as KCLPos; @@ -22,8 +23,9 @@ use kclvm_sema::builtin::{ use kclvm_sema::resolver::scope::ProgramScope; use lsp_types::CompletionItem; -use crate::goto_def::get_identifier_last_name; -use crate::{goto_def::find_objs_in_program_scope, util::inner_most_expr_in_stmt}; +use crate::goto_def::{get_identifier_last_name, resolve_var}; +use crate::util::inner_most_expr_in_stmt; +use crate::util::{fix_missing_identifier, get_pkg_scope}; /// Computes completions at the given position. pub(crate) fn completion( @@ -115,7 +117,7 @@ fn get_completion_items(expr: &Expr, prog_scope: &ProgramScope) -> IndexSet { - let name = get_identifier_last_name(id); + let name = get_identifier_last_name(&id); if !id.pkgpath.is_empty() { // standard system module if STANDARD_SYSTEM_MODULES.contains(&name.as_str()) { @@ -126,7 +128,10 @@ fn get_completion_items(expr: &Expr, prog_scope: &ProgramScope) -> IndexSet IndexSet { - let binding = STRING_MEMBER_FUNCTIONS; - for k in binding.keys() { - items.insert(format!("{}{}", k, "()")); - } - } - // schema attrs - kclvm_sema::ty::TypeKind::Schema(schema) => { - for k in schema.attrs.keys() { - if k != "__settings__" { - items.insert(k.clone()); + let def = resolve_var( + &fix_missing_identifier(&id.names), + &get_pkg_scope(&id.pkgpath, &prog_scope.scope_map), + &prog_scope.scope_map, + ); + + if let Some(def) = def { + match def { + crate::goto_def::Definition::Object(obj) => { + match &obj.ty.kind { + // builtin (str) functions + kclvm_sema::ty::TypeKind::Str => { + let binding = STRING_MEMBER_FUNCTIONS; + for k in binding.keys() { + items.insert(format!("{}{}", k, "()")); + } + } + // schema attrs + kclvm_sema::ty::TypeKind::Schema(schema) => { + for k in schema.attrs.keys() { + if k != "__settings__" { + items.insert(k.clone()); + } + } } + _ => {} } } - _ => {} + crate::goto_def::Definition::Scope(_) => { + // todo + } } } } + Expr::Selector(select_expr) => { + let res = get_completion_items(&select_expr.value.node, prog_scope); + items.extend(res); + } Expr::StringLit(_) => { let binding = STRING_MEMBER_FUNCTIONS; for k in binding.keys() { items.insert(format!("{}{}", k, "()")); } } - Expr::Selector(select_expr) => { - let res = get_completion_items(&select_expr.value.node, prog_scope); - items.extend(res); - } _ => {} } items diff --git a/kclvm/tools/src/LSP/src/goto_def.rs b/kclvm/tools/src/LSP/src/goto_def.rs index 5225c362f..f2e7edde8 100644 --- a/kclvm/tools/src/LSP/src/goto_def.rs +++ b/kclvm/tools/src/LSP/src/goto_def.rs @@ -7,19 +7,25 @@ //! + schema attr //! + attr type -use indexmap::IndexSet; - -use kclvm_ast::ast::{Expr, Identifier, ImportStmt, Node, Program, SchemaExpr, Stmt}; +use indexmap::{IndexMap, IndexSet}; +use kclvm_ast::pos::{ContainsPos, GetPos}; +use kclvm_ast::ast::{Expr, Identifier, ImportStmt, Node, Program, Stmt}; +use kclvm_compiler::pkgpath_without_prefix; use kclvm_error::Position as KCLPos; -use kclvm_sema::resolver::scope::{ProgramScope, ScopeObject}; +use kclvm_sema::resolver::scope::{ProgramScope, Scope, ScopeObject}; +use kclvm_sema::ty::SchemaType; use lsp_types::{GotoDefinitionResponse, Url}; use lsp_types::{Location, Range}; +use std::cell::RefCell; use std::path::Path; +use std::rc::Rc; use crate::to_lsp::lsp_pos; -use crate::util::{get_pos_from_real_path, get_real_path_from_external, inner_most_expr_in_stmt}; +use crate::util::{ + get_pkg_scope, get_pos_from_real_path, get_real_path_from_external, inner_most_expr_in_stmt, +}; // Navigates to the definition of an identifier. pub(crate) fn goto_definition( @@ -30,59 +36,221 @@ pub(crate) fn goto_definition( match program.pos_to_stmt(kcl_pos) { Some(node) => match node.node { Stmt::Import(stmt) => goto_def_for_import(&stmt, kcl_pos, prog_scope, program), - _ => { - let objs = find_definition_objs(node, kcl_pos, prog_scope); - let positions = objs - .iter() - .map(|obj| (obj.start.clone(), obj.end.clone())) - .collect(); - positions_to_goto_def_resp(&positions) - } + _ => match find_def(node.clone(), kcl_pos, prog_scope) { + Some(def) => positions_to_goto_def_resp(&def.get_positions()), + None => None, + }, }, None => None, } } -pub(crate) fn find_definition_objs( + +// Todo: fix ConfigExpr +// ```kcl +// schema Person: +// name: str +// data: Data + +// schema Data: +// id: int + +// person = Person { +// data.id = 1 +// data: { +// id = 1 +// } +// data: Data { +// id = 3 +// } +// } + +pub enum Definition { + Object(ScopeObject), + Scope(Scope), +} + +impl Definition { + pub(crate) fn get_positions(&self) -> IndexSet<(KCLPos, KCLPos)> { + let mut positions = IndexSet::new(); + match self { + Definition::Object(obj) => { + positions.insert((obj.start.clone(), obj.end.clone())); + } + Definition::Scope(scope) => match &scope.kind { + kclvm_sema::resolver::scope::ScopeKind::Package(filenames) => { + for file in filenames { + let dummy_pos = KCLPos { + filename: file.clone(), + line: 1, + column: None, + }; + positions.insert((dummy_pos.clone(), dummy_pos)); + } + } + _ => { + positions.insert((scope.start.clone(), scope.end.clone())); + } + }, + } + positions + } +} + +pub(crate) fn find_def( node: Node, kcl_pos: &KCLPos, prog_scope: &ProgramScope, -) -> Vec { +) -> Option { + fn pre_process_identifier(id: Node, pos: &KCLPos) -> Identifier { + if !id.contains_pos(pos) && id.node.names.is_empty() { + return id.node.clone(); + } + + let mut id = id.node.clone(); + let mut names = vec![]; + for name in id.names { + names.push(name.clone()); + if name.contains_pos(pos) { + break; + } + } + id.names = names; + if !id.pkgpath.is_empty() { + id.names[0].node = pkgpath_without_prefix!(id.pkgpath); + } + id + } + let (inner_expr, parent) = inner_most_expr_in_stmt(&node.node, kcl_pos, None); if let Some(expr) = inner_expr { if let Expr::Identifier(id) = expr.node { - let name = get_identifier_last_name(&id); - let objs = if let Some(parent) = parent { - // find schema attr def - match parent.node { - Expr::Schema(schema_expr) => { - find_def_of_schema_attr(schema_expr, prog_scope, name) + let id_node = Node::node_with_pos( + id.clone(), + ( + expr.filename, + expr.line, + expr.column, + expr.end_line, + expr.end_column, + ), + ); + let id = pre_process_identifier(id_node, kcl_pos); + match parent { + Some(schema_expr) => { + if let Expr::Schema(schema_expr) = schema_expr.node { + let schema_def = + find_def(node, &schema_expr.name.get_end_pos(), prog_scope); + if let Some(schema) = schema_def { + match schema { + Definition::Object(obj) => { + let schema_type = obj.ty.into_schema_type(); + return find_attr_in_schema( + &schema_type, + &id.names, + &prog_scope.scope_map, + ); + } + Definition::Scope(_) => { + //todo + } + } + } } - _ => vec![], } - } else { - find_objs_in_program_scope(&name, prog_scope) - }; - return objs; + None => { + for (_, scope) in &prog_scope.scope_map { + match scope.borrow().inner_most(kcl_pos) { + Some(s) => return resolve_var(&id.names, &s, &prog_scope.scope_map), + None => continue, + } + } + } + } } } - vec![] + None } -// This function serves as the result of a global search, which may cause duplication. -// It needs to be pruned according to the situation. There are two actions todo: -// + AST Identifier provides location information for each name. -// + Scope provides a method similar to resolve_var of the resolver to replace this function. -pub(crate) fn find_objs_in_program_scope( - name: &str, - prog_scope: &ProgramScope, -) -> Vec { - let mut res = vec![]; - for s in prog_scope.scope_map.values() { - let mut objs = s.borrow().search_obj_by_name(name); - res.append(&mut objs); +/// Similar to vars.rs/resolver_var, find a ScopeObj corresponding to the definition of identifier +pub(crate) fn resolve_var( + node_names: &[Node], + current_scope: &Scope, + scope_map: &IndexMap>>, +) -> Option { + let names = node_names + .iter() + .map(|node| node.node.clone()) + .collect::>(); + match names.len() { + 0 => None, + 1 => { + let name = names[0].clone(); + match current_scope.lookup(&name) { + Some(obj) => match obj.borrow().kind { + kclvm_sema::resolver::scope::ScopeObjectKind::Module => { + match scope_map.get(&name) { + Some(scope) => Some(Definition::Scope(scope.borrow().clone())), + None => None, + } + } + _ => Some(Definition::Object(obj.borrow().clone())), + }, + None => None, + } + } + _ => { + let name = names[0].clone(); + match current_scope.lookup(&name) { + Some(obj) => { + match &obj.borrow().ty.kind { + kclvm_sema::ty::TypeKind::Schema(schema_type) => { + find_attr_in_schema(schema_type, &node_names[1..], scope_map) + } + kclvm_sema::ty::TypeKind::Module(module_ty) => { + match scope_map.get(&pkgpath_without_prefix!(module_ty.pkgpath)) { + Some(scope) => { + return resolve_var( + &node_names[1..], + &scope.borrow(), + scope_map, + ); + } + None => None, + } + } + kclvm_sema::ty::TypeKind::Dict(_, _) => { + // Todo: find key def in dict + None + } + _ => None, + } + } + None => None, + } + } } +} - res +pub fn find_attr_in_schema( + schema_type: &SchemaType, + names: &[Node], + scope_map: &IndexMap>>, +) -> Option { + let schema_pkg_scope = get_pkg_scope(&schema_type.pkgpath, scope_map); + let names = if schema_type.pkgpath.is_empty() { + &names[1..] + } else { + names + }; + for child in &schema_pkg_scope.children { + let child_scope = child.borrow(); + if let kclvm_sema::resolver::scope::ScopeKind::Schema(schema_name) = &child_scope.kind { + if schema_name == &schema_type.name { + return resolve_var(&names, &child_scope, scope_map); + } + } + } + None } // Convert kcl position to GotoDefinitionResponse. This function will convert to @@ -139,47 +307,6 @@ fn goto_def_for_import( positions_to_goto_def_resp(&positions) } -// Todo: fix ConfigExpr -// ```kcl -// schema Person: -// name: str -// data: Data - -// schema Data: -// id: int - -// person = Person { -// data.id = 1 -// data: { -// id = 1 -// } -// data: Data { -// id = 3 -// } -// } -pub(crate) fn find_def_of_schema_attr( - schema_expr: SchemaExpr, - prog_scope: &ProgramScope, - attr_name: String, -) -> Vec { - let schema_name = get_identifier_last_name(&schema_expr.name.node); - let mut res = vec![]; - for scope in prog_scope.scope_map.values() { - let s = scope.borrow(); - if let Some(scope) = s.search_child_scope_by_name(&schema_name) { - let s = scope.borrow(); - if matches!(s.kind, kclvm_sema::resolver::scope::ScopeKind::Schema(_)) { - for (attr, obj) in &s.elems { - if attr == &attr_name { - res.push(obj.borrow().clone()); - } - } - } - } - } - res -} - pub(crate) fn get_identifier_last_name(id: &Identifier) -> String { match id.names.len() { 0 => "".to_string(), diff --git a/kclvm/tools/src/LSP/src/hover.rs b/kclvm/tools/src/LSP/src/hover.rs index 2d2f948a7..a0c62db72 100644 --- a/kclvm/tools/src/LSP/src/hover.rs +++ b/kclvm/tools/src/LSP/src/hover.rs @@ -4,7 +4,7 @@ use kclvm_error::Position as KCLPos; use kclvm_sema::resolver::scope::{ProgramScope, ScopeObjectKind}; use lsp_types::{Hover, HoverContents, MarkedString}; -use crate::goto_def::find_definition_objs; +use crate::goto_def::find_def; /// Returns a short text describing element at position. /// Specifically, the doc for schema and schema attr(todo) @@ -15,19 +15,20 @@ pub(crate) fn hover( ) -> Option { match program.pos_to_stmt(kcl_pos) { Some(node) => { - let objs = find_definition_objs(node, kcl_pos, prog_scope); let mut docs: IndexSet = IndexSet::new(); - for obj in &objs { - match obj.kind { - ScopeObjectKind::Definition => { - docs.insert(obj.ty.ty_str()); - let doc = obj.ty.into_schema_type().doc.clone(); - if !doc.is_empty() { - docs.insert(doc); + if let Some(def) = find_def(node, kcl_pos, prog_scope) { + if let crate::goto_def::Definition::Object(obj) = def { + match obj.kind { + ScopeObjectKind::Definition => { + docs.insert(obj.ty.ty_str()); + let doc = obj.ty.into_schema_type().doc.clone(); + if !doc.is_empty() { + docs.insert(doc); + } + } + _ => { + docs.insert(obj.ty.ty_str()); } - } - _ => { - docs.insert(obj.ty.ty_str()); } } } diff --git a/kclvm/tools/src/LSP/src/test_data/goto_def_test/goto_def.k b/kclvm/tools/src/LSP/src/test_data/goto_def_test/goto_def.k index 58f3943f0..ba896854e 100644 --- a/kclvm/tools/src/LSP/src/test_data/goto_def_test/goto_def.k +++ b/kclvm/tools/src/LSP/src/test_data/goto_def_test/goto_def.k @@ -12,4 +12,41 @@ schema Person3: p1: pkg.Person p2: [pkg.Person] p3: {str: pkg.Person} - p4: pkg.Person | pkg.Person1 \ No newline at end of file + p4: pkg.Person | pkg.Person1 + + +schema Name: + name: str + +schema Person: + n: Name + +p2 = Person { + n: Name{ + name: pkg.m.name + } +} + +s = p2.n.name + +a: int = 1 + +schema Reviewer: + teams?: [int] + users?: [int] + +reviewers: [Reviewer] = [{team: [1]}] + +schema Fib: + n1 = n - 1 + n2 = n1 - 1 + n: int + value: int + + if n <= 1: + value = 1 + elif n == 2: + value = 1 + else: + value = Fib {n = n1}.value + Fib {n = n2}.value +fib8 = Fib {n = 8}.value \ No newline at end of file diff --git a/kclvm/tools/src/LSP/src/test_data/goto_def_test/pkg/schema_def.k b/kclvm/tools/src/LSP/src/test_data/goto_def_test/pkg/schema_def.k index cf10ad080..13e310f76 100644 --- a/kclvm/tools/src/LSP/src/test_data/goto_def_test/pkg/schema_def.k +++ b/kclvm/tools/src/LSP/src/test_data/goto_def_test/pkg/schema_def.k @@ -4,3 +4,7 @@ schema Person: """ name: str age: int + +m : {str: str} = { + name: "a" +} diff --git a/kclvm/tools/src/LSP/src/tests.rs b/kclvm/tools/src/LSP/src/tests.rs index f7ced6512..46d339319 100644 --- a/kclvm/tools/src/LSP/src/tests.rs +++ b/kclvm/tools/src/LSP/src/tests.rs @@ -9,6 +9,7 @@ use kclvm_error::Position as KCLPos; use kclvm_sema::builtin::MATH_FUNCTION_NAMES; use kclvm_sema::builtin::STRING_MEMBER_FUNCTIONS; use kclvm_sema::resolver::scope::ProgramScope; +use lsp_types::request::GotoTypeDefinitionResponse; use lsp_types::CompletionResponse; use lsp_types::DocumentSymbol; use lsp_types::DocumentSymbolResponse; @@ -38,6 +39,32 @@ fn compile_test_file(testfile: &str) -> (String, Program, ProgramScope, IndexSet (file, program, prog_scope, diags) } +fn compare_goto_res(res: Option, pos: (&String, u32, u32, u32, u32)) { + match res.unwrap() { + lsp_types::GotoDefinitionResponse::Scalar(loc) => { + let got_path = loc.uri.path(); + assert_eq!(got_path, pos.0); + + let (got_start, got_end) = (loc.range.start, loc.range.end); + + let expected_start = Position { + line: pos.1, // zero-based + character: pos.2, + }; + + let expected_end = Position { + line: pos.3, // zero-based + character: pos.4, + }; + assert_eq!(got_start, expected_start); + assert_eq!(got_end, expected_end); + } + _ => { + unreachable!("test error") + } + } +} + #[test] fn diagnostics_test() { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -132,6 +159,43 @@ fn goto_import_file_test() { } } +#[test] +fn goto_pkg_prefix_def_test() { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + let (file, program, prog_scope, _) = + compile_test_file("src/test_data/goto_def_test/goto_def.k"); + + // test goto pkg prefix def: p = pkg.Person { <- pkg + let pos = KCLPos { + filename: file, + line: 4, + column: Some(7), + }; + let res = goto_definition(&program, &pos, &prog_scope); + let mut expeced_files = IndexSet::new(); + let path_str = path.to_str().unwrap(); + let test_files = [ + "src/test_data/goto_def_test/pkg/schema_def1.k", + "src/test_data/goto_def_test/pkg/schema_def.k", + ]; + expeced_files.insert(format!("{}/{}", path_str, test_files[0])); + expeced_files.insert(format!("{}/{}", path_str, test_files[1])); + + match res.unwrap() { + lsp_types::GotoDefinitionResponse::Array(arr) => { + assert_eq!(expeced_files.len(), arr.len()); + for loc in arr { + let got_path = loc.uri.path().to_string(); + assert!(expeced_files.contains(&got_path)); + } + } + _ => { + unreachable!("test error") + } + } +} + #[test] fn goto_schema_def_test() { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -142,41 +206,40 @@ fn goto_schema_def_test() { let mut expected_path = path; expected_path.push("src/test_data/goto_def_test/pkg/schema_def.k"); - // test goto schema definition: p = pkg.Person + // test goto schema definition: p = pkg.Person <- Person let pos = KCLPos { filename: file, line: 4, column: Some(11), }; let res = goto_definition(&program, &pos, &prog_scope); + compare_goto_res( + res, + (&expected_path.to_str().unwrap().to_string(), 0, 0, 7, 0), + ); +} - match res.unwrap() { - lsp_types::GotoDefinitionResponse::Scalar(loc) => { - let got_path = loc.uri.path(); - assert_eq!(got_path, expected_path.to_str().unwrap()); +// todo +// #[test] +// fn goto_dict_key_def_test() { +// let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let (got_start, got_end) = (loc.range.start, loc.range.end); +// let (file, program, prog_scope, _) = +// compile_test_file("src/test_data/goto_def_test/goto_def.k"); - let expected_start = Position { - line: 0, // zero-based - character: 0, - }; +// let mut expected_path = path; +// expected_path.push("src/test_data/goto_def_test/pkg/schema_def.k"); - let expected_end = Position { - line: 5, // zero-based - character: 13, - }; - assert_eq!(got_start, expected_start); - assert_eq!(got_end, expected_end); - } - _ => { - unreachable!("test error") - } - } -} +// let pos = KCLPos { +// filename: file, +// line: 30, +// column: Some(11), +// }; +// let res = goto_definition(&program, &pos, &prog_scope); +// } #[test] -fn goto_identifier_def_test() { +fn goto_schema_attr_def_test() { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let (file, program, prog_scope, _) = @@ -185,6 +248,94 @@ fn goto_identifier_def_test() { let mut expected_path = path; expected_path.push("src/test_data/goto_def_test/pkg/schema_def.k"); + // test goto schema attr definition: name: "alice" + let pos = KCLPos { + filename: file, + line: 5, + column: Some(7), + }; + let res = goto_definition(&program, &pos, &prog_scope); + compare_goto_res( + res, + (&expected_path.to_str().unwrap().to_string(), 4, 4, 4, 8), + ); +} + +#[test] +fn goto_schema_attr_def_test1() { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + let (file, program, prog_scope, _) = + compile_test_file("src/test_data/goto_def_test/goto_def.k"); + + let mut expected_path = path; + expected_path.push("src/test_data/goto_def_test/goto_def.k"); + + // test goto schema attr definition, goto name in: s = p2.n.name + let pos = KCLPos { + filename: file, + line: 30, + column: Some(12), + }; + let res = goto_definition(&program, &pos, &prog_scope); + compare_goto_res( + res, + (&expected_path.to_str().unwrap().to_string(), 18, 1, 18, 5), + ); +} + +#[test] +fn test_goto_identifier_names() { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + let (file, program, prog_scope, _) = + compile_test_file("src/test_data/goto_def_test/goto_def.k"); + + let mut expected_path = path; + expected_path.push("src/test_data/goto_def_test/goto_def.k"); + + // test goto p2 in: s = p2.n.name + let pos = KCLPos { + filename: file.clone(), + line: 30, + column: Some(5), + }; + let res = goto_definition(&program, &pos, &prog_scope); + compare_goto_res( + res, + (&expected_path.to_str().unwrap().to_string(), 23, 0, 23, 2), + ); + + // test goto n in: s = p2.n.name + let pos = KCLPos { + filename: file.clone(), + line: 30, + column: Some(8), + }; + let res = goto_definition(&program, &pos, &prog_scope); + compare_goto_res( + res, + (&expected_path.to_str().unwrap().to_string(), 21, 1, 21, 2), + ); + + // test goto name in: s = p2.n.name + let pos = KCLPos { + filename: file, + line: 30, + column: Some(12), + }; + let res = goto_definition(&program, &pos, &prog_scope); + compare_goto_res( + res, + (&expected_path.to_str().unwrap().to_string(), 18, 1, 18, 5), + ); +} + +#[test] +fn goto_identifier_def_test() { + let (file, program, prog_scope, _) = + compile_test_file("src/test_data/goto_def_test/goto_def.k"); + // test goto identifier definition: p1 = p let pos = KCLPos { filename: file.to_string(), @@ -192,30 +343,28 @@ fn goto_identifier_def_test() { column: Some(6), }; let res = goto_definition(&program, &pos, &prog_scope); - match res.unwrap() { - lsp_types::GotoDefinitionResponse::Scalar(loc) => { - let got_path = loc.uri.path(); - assert_eq!(got_path, file); + compare_goto_res(res, (&file, 3, 0, 3, 1)); +} - let (got_start, got_end) = (loc.range.start, loc.range.end); +#[test] +fn goto_assign_type_test() { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let expected_start = Position { - line: 3, // zero-based - character: 0, - }; + let (file, program, prog_scope, _) = + compile_test_file("src/test_data/goto_def_test/goto_def.k"); - let expected_end = Position { - line: 3, // zero-based - character: 1, - }; + let mut expected_path = path; + expected_path.push("src/test_data/goto_def_test/pkg/schema_def.k"); - assert_eq!(got_start, expected_start); - assert_eq!(got_end, expected_end); - } - _ => { - unreachable!("test error") - } - } + // test goto schema attr definition: name: "alice" + let pos = KCLPos { + filename: file.clone(), + line: 38, + column: Some(17), + }; + + let res = goto_definition(&program, &pos, &prog_scope); + compare_goto_res(res, (&file, 33, 0, 37, 0)); } #[test] @@ -235,30 +384,10 @@ fn goto_schema_attr_ty_def_test() { column: Some(15), }; let res = goto_definition(&program, &pos, &prog_scope); - match res.unwrap() { - lsp_types::GotoDefinitionResponse::Scalar(loc) => { - let got_path = loc.uri.path(); - assert_eq!(got_path, expected_path.to_str().unwrap()); - - let (got_start, got_end) = (loc.range.start, loc.range.end); - - let expected_start = Position { - line: 0, // zero-based - character: 0, - }; - - let expected_end = Position { - line: 5, // zero-based - character: 13, - }; - - assert_eq!(got_start, expected_start); - assert_eq!(got_end, expected_end); - } - _ => { - unreachable!("test error") - } - } + compare_goto_res( + res, + (&expected_path.to_str().unwrap().to_string(), 0, 0, 7, 0), + ); } #[test] @@ -278,30 +407,10 @@ fn goto_schema_attr_ty_def_test1() { column: Some(15), }; let res = goto_definition(&program, &pos, &prog_scope); - match res.unwrap() { - lsp_types::GotoDefinitionResponse::Scalar(loc) => { - let got_path = loc.uri.path(); - assert_eq!(got_path, expected_path.to_str().unwrap()); - - let (got_start, got_end) = (loc.range.start, loc.range.end); - - let expected_start = Position { - line: 0, // zero-based - character: 0, - }; - - let expected_end = Position { - line: 5, // zero-based - character: 13, - }; - - assert_eq!(got_start, expected_start); - assert_eq!(got_end, expected_end); - } - _ => { - unreachable!("test error") - } - } + compare_goto_res( + res, + (&expected_path.to_str().unwrap().to_string(), 0, 0, 7, 0), + ); } #[test] @@ -321,30 +430,10 @@ fn goto_schema_attr_ty_def_test3() { column: Some(22), }; let res = goto_definition(&program, &pos, &prog_scope); - match res.unwrap() { - lsp_types::GotoDefinitionResponse::Scalar(loc) => { - let got_path = loc.uri.path(); - assert_eq!(got_path, expected_path.to_str().unwrap()); - - let (got_start, got_end) = (loc.range.start, loc.range.end); - - let expected_start = Position { - line: 0, // zero-based - character: 0, - }; - - let expected_end = Position { - line: 5, // zero-based - character: 13, - }; - - assert_eq!(got_start, expected_start); - assert_eq!(got_end, expected_end); - } - _ => { - unreachable!("test error") - } - } + compare_goto_res( + res, + (&expected_path.to_str().unwrap().to_string(), 0, 0, 7, 0), + ); } #[test] @@ -364,30 +453,10 @@ fn goto_schema_attr_ty_def_test4() { column: Some(17), }; let res = goto_definition(&program, &pos, &prog_scope); - match res.unwrap() { - lsp_types::GotoDefinitionResponse::Scalar(loc) => { - let got_path = loc.uri.path(); - assert_eq!(got_path, expected_path.to_str().unwrap()); - - let (got_start, got_end) = (loc.range.start, loc.range.end); - - let expected_start = Position { - line: 0, // zero-based - character: 0, - }; - - let expected_end = Position { - line: 5, // zero-based - character: 13, - }; - - assert_eq!(got_start, expected_start); - assert_eq!(got_end, expected_end); - } - _ => { - unreachable!("test error") - } - } + compare_goto_res( + res, + (&expected_path.to_str().unwrap().to_string(), 0, 0, 7, 0), + ); } #[test] @@ -407,28 +476,50 @@ fn goto_schema_attr_ty_def_test5() { column: Some(28), }; let res = goto_definition(&program, &pos, &prog_scope); - match res.unwrap() { - lsp_types::GotoDefinitionResponse::Scalar(loc) => { - let got_path = loc.uri.path(); - assert_eq!(got_path, expected_path.to_str().unwrap()); + compare_goto_res( + res, + (&expected_path.to_str().unwrap().to_string(), 0, 0, 2, 13), + ); +} - let (got_start, got_end) = (loc.range.start, loc.range.end); +#[test] +fn goto_local_var_def_test() { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let expected_start = Position { - line: 0, // zero-based - character: 0, - }; + let (file, program, prog_scope, _) = + compile_test_file("src/test_data/goto_def_test/goto_def.k"); - let expected_end = Position { - line: 2, // zero-based - character: 13, - }; + let mut expected_path = path; + expected_path.push("src/test_data/goto_def_test/pkg/schema_def.k"); - assert_eq!(got_start, expected_start); - assert_eq!(got_end, expected_end); - } - _ => unreachable!("test error"), - } + // test goto local var def + let pos = KCLPos { + filename: file.clone(), + line: 47, + column: Some(11), + }; + + let res = goto_definition(&program, &pos, &prog_scope); + compare_goto_res(res, (&file, 43, 4, 43, 9)); + + let pos = KCLPos { + filename: file.clone(), + line: 49, + column: Some(11), + }; + + let res = goto_definition(&program, &pos, &prog_scope); + compare_goto_res(res, (&file, 43, 4, 43, 9)); + + // todo: fix if stmt position error + // let pos = KCLPos { + // filename: file.clone(), + // line: 51, + // column: Some(11), + // }; + + // let res = goto_definition(&program, &pos, &prog_scope); + // compare_goto_res(res, (&file, 43, 4, 43, 9)); } #[test] diff --git a/kclvm/tools/src/LSP/src/util.rs b/kclvm/tools/src/LSP/src/util.rs index 805538fe6..983a69012 100644 --- a/kclvm/tools/src/LSP/src/util.rs +++ b/kclvm/tools/src/LSP/src/util.rs @@ -1,15 +1,20 @@ +use std::cell::RefCell; use std::path::PathBuf; +use std::rc::Rc; use std::{fs, sync::Arc}; -use indexmap::IndexSet; +use indexmap::{IndexMap, IndexSet}; use kclvm_ast::ast::{ConfigEntry, Expr, Identifier, Node, NodeRef, PosTuple, Program, Stmt, Type}; use kclvm_ast::pos::ContainsPos; +use kclvm_ast::MAIN_PKG; +use kclvm_compiler::pkgpath_without_prefix; use kclvm_config::modfile::KCL_FILE_EXTENSION; use kclvm_driver::kpm_metadata::fetch_metadata; use kclvm_driver::{get_kcl_files, lookup_compile_unit}; use kclvm_error::Diagnostic; use kclvm_error::Position as KCLPos; use kclvm_parser::{load_program, ParseSession}; +use kclvm_sema::resolver::scope::Scope; use kclvm_sema::resolver::{resolve_program, scope::ProgramScope}; use kclvm_utils::pkgpath::rm_external_pkg_name; use lsp_types::Url; @@ -184,34 +189,10 @@ pub(crate) fn inner_most_expr_in_stmt( ) -> (Option>, Option>) { match stmt { Stmt::Assign(assign_stmt) => { - if let Some(ty) = &assign_stmt.type_annotation { - // build a temp identifier with string - return ( - Some(Node::node_with_pos( - Expr::Identifier(Identifier { - names: transfer_ident_names( - vec![ty.node.clone()], - &( - ty.filename.clone(), - ty.line, - ty.column, - ty.end_line, - ty.end_column, - ), - ), - pkgpath: "".to_string(), - ctx: kclvm_ast::ast::ExprContext::Load, - }), - ( - ty.filename.clone(), - ty.line, - ty.column, - ty.end_line, - ty.end_column, - ), - )), - schema_def, - ); + if let Some(ty) = &assign_stmt.ty { + if ty.contains_pos(pos) { + return (build_identifier_from_ty_string(&ty, pos), schema_def); + } } walk_if_contains!(assign_stmt.value, pos, schema_def); @@ -545,7 +526,7 @@ fn inner_most_expr_in_config_entry( if config_entry.node.value.contains_pos(pos) { inner_most_expr(&config_entry.node.value, pos, None) } else { - (None, None) + (None, schema_def) } } @@ -691,3 +672,25 @@ pub(crate) fn get_real_path_from_external( pkgpath.split('.').for_each(|s| real_path.push(s)); real_path } + +/// Error recovery may generate an Identifier with an empty string at the end, e.g., +/// a. => vec["a", ""]. +/// When analyzing in LSP, the empty string needs to be removed and find definition of the second last name("a"). +pub(crate) fn fix_missing_identifier(names: &[Node]) -> Vec> { + if names.len() >= 1 && names.last().unwrap().node == "" { + names[..names.len() - 1].to_vec() + } else { + names.to_vec() + } +} + +pub(crate) fn get_pkg_scope( + pkgpath: &String, + scope_map: &IndexMap>>, +) -> Scope { + scope_map + .get(&pkgpath_without_prefix!(pkgpath)) + .unwrap_or(scope_map.get(MAIN_PKG).unwrap()) + .borrow() + .clone() +}