diff --git a/.github/workflows/wasm_test.yaml b/.github/workflows/wasm_test.yaml new file mode 100644 index 000000000..2b2396ecc --- /dev/null +++ b/.github/workflows/wasm_test.yaml @@ -0,0 +1,29 @@ +name: build-and-test-wasm +on: ["push", "pull_request"] +jobs: + build-and-test: + # Ref: https://github.com/actions/runner-images/tree/main/images/linux + name: Test + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + with: + submodules: "true" + + - name: Install rust nightly toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: 1.76 + override: true + components: clippy, rustfmt + + - name: Unit test + working-directory: ./kclvm + run: rustup target add wasm32-wasi && make build-wasm + shell: bash + + - uses: actions/upload-artifact@v3 + with: + name: kcl-wasm + path: kclvm/target/wasm32-wasi/release/kclvm_cli_cdylib.wasm diff --git a/kclvm/api/src/service/capi.rs b/kclvm/api/src/service/capi.rs index bdd1e75fa..ff5e76f5a 100644 --- a/kclvm/api/src/service/capi.rs +++ b/kclvm/api/src/service/capi.rs @@ -184,6 +184,7 @@ pub(crate) fn kclvm_get_service_fn_ptr_by_name(name: &str) -> u64 { "KclvmService.Rename" => rename as *const () as u64, "KclvmService.RenameCode" => rename_code as *const () as u64, "KclvmService.Test" => test as *const () as u64, + #[cfg(not(target_arch = "wasm32"))] "KclvmService.UpdateDependencies" => update_dependencies as *const () as u64, _ => panic!("unknown method name : {name}"), } @@ -524,6 +525,7 @@ pub(crate) fn test( call!(serv, args, result_len, TestArgs, test) } +#[cfg(not(target_arch = "wasm32"))] /// Service for the dependencies updating /// calling information. /// diff --git a/kclvm/api/src/service/service_impl.rs b/kclvm/api/src/service/service_impl.rs index a114b7671..cce3184e5 100644 --- a/kclvm/api/src/service/service_impl.rs +++ b/kclvm/api/src/service/service_impl.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use std::io::Write; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::string::String; use crate::gpyrpc::*; @@ -9,7 +9,6 @@ use anyhow::anyhow; use kcl_language_server::rename; use kclvm_config::settings::build_settings_pathbuf; use kclvm_driver::canonicalize_input_files; -use kclvm_driver::client::ModClient; use kclvm_loader::option::list_options; use kclvm_loader::{load_packages_with_cache, LoadPackageOptions}; use kclvm_parser::load_program; @@ -957,6 +956,7 @@ impl KclvmServiceImpl { Ok(result) } + #[cfg(not(target_arch = "wasm32"))] /// update_dependencies provides users with the ability to update kcl module dependencies. /// /// # Examples @@ -986,6 +986,8 @@ impl KclvmServiceImpl { &self, args: &UpdateDependenciesArgs, ) -> anyhow::Result { + use kclvm_driver::client::ModClient; + use std::path::Path; let mut client = ModClient::new(&args.manifest_path)?; if args.vendor { client.set_vendor(&Path::new(&args.manifest_path).join("vendor")); diff --git a/kclvm/driver/Cargo.toml b/kclvm/driver/Cargo.toml index 92aacfb6c..47a43a80b 100644 --- a/kclvm/driver/Cargo.toml +++ b/kclvm/driver/Cargo.toml @@ -19,10 +19,12 @@ walkdir = "2" serde = { version = "1.0", features = ["derive"] } anyhow = { version = "1.0.70", features = ["backtrace"] } glob = "0.3.1" -oci-distribution = { default-features = false, version = "0.11.0", features = ["rustls-tls"] } flate2 = "1.0.30" tar = "0.4.40" -tokio = { version = "1.37.0", features = ["full"] } indexmap = "2.2.6" once_cell = "1.19.0" parking_lot = "0.12.3" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +oci-distribution = { default-features = false, version = "0.11.0", features = ["rustls-tls"] } +tokio = { version = "1.37.0", features = ["full"] } diff --git a/kclvm/driver/src/lib.rs b/kclvm/driver/src/lib.rs index 958efb433..7a4a45ae1 100644 --- a/kclvm/driver/src/lib.rs +++ b/kclvm/driver/src/lib.rs @@ -1,4 +1,5 @@ pub mod arguments; +#[cfg(not(target_arch = "wasm32"))] pub mod client; pub mod toolchain; diff --git a/kclvm/driver/src/toolchain.rs b/kclvm/driver/src/toolchain.rs index dc343d83f..dc14e156f 100644 --- a/kclvm/driver/src/toolchain.rs +++ b/kclvm/driver/src/toolchain.rs @@ -1,14 +1,13 @@ -use crate::client::ModClient; use crate::{kcl, lookup_the_nearest_file_dir}; use anyhow::{bail, Result}; use kclvm_config::modfile::KCL_MOD_FILE; use kclvm_parser::LoadProgramOptions; use kclvm_utils::pkgpath::rm_external_pkg_name; -use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::ffi::OsStr; -use std::sync::Arc; use std::{collections::HashMap, path::PathBuf, process::Command}; +#[cfg(not(target_arch = "wasm32"))] +use {crate::client::ModClient, parking_lot::Mutex, std::sync::Arc}; /// `Toolchain` is a trait that outlines a standard set of operations that must be /// implemented for a KCL module (mod), typically involving fetching metadata from, @@ -99,11 +98,13 @@ impl + Send + Sync> Toolchain for CommandToolchain { } } +#[cfg(not(target_arch = "wasm32"))] #[derive(Default)] pub struct NativeToolchain { client: Arc>, } +#[cfg(not(target_arch = "wasm32"))] impl Toolchain for NativeToolchain { fn fetch_metadata(&self, manifest_path: PathBuf) -> Result { let mut client = self.client.lock(); diff --git a/kclvm/sema/src/resolver/arg.rs b/kclvm/sema/src/resolver/arg.rs index c20fd4ee5..e3c7aa0bc 100644 --- a/kclvm/sema/src/resolver/arg.rs +++ b/kclvm/sema/src/resolver/arg.rs @@ -111,7 +111,7 @@ impl<'ctx> Resolver<'ctx> { return; } }; - self.must_assignable_to(ty.clone(), expected_ty, args[i].get_span_pos(), None, true) + self.must_assignable_to(ty.clone(), expected_ty, args[i].get_span_pos(), None) } // Do keyword argument type check for (i, (arg_name, kwarg_ty)) in kwarg_types.iter().enumerate() { @@ -144,7 +144,6 @@ impl<'ctx> Resolver<'ctx> { expected_types[0].clone(), kwargs[i].get_span_pos(), None, - true, ); }; } diff --git a/kclvm/sema/src/resolver/config.rs b/kclvm/sema/src/resolver/config.rs index f0abab6bd..e1b7cfea9 100644 --- a/kclvm/sema/src/resolver/config.rs +++ b/kclvm/sema/src/resolver/config.rs @@ -107,6 +107,38 @@ impl<'ctx> Resolver<'ctx> { }, } } + TypeKind::Union(types) => { + let mut possible_types = vec![]; + for ty in types { + match &ty.kind { + TypeKind::Schema(schema_ty) => { + match schema_ty.get_obj_of_attr(key_name) { + Some(attr_ty_obj) => { + possible_types.push(attr_ty_obj.ty.clone()); + } + None => match &schema_ty.index_signature { + Some(index_signature) => { + possible_types + .push(index_signature.val_ty.clone()); + } + None => continue, + }, + } + } + TypeKind::Dict(DictType { val_ty, .. }) => { + possible_types.push(val_ty.clone()); + } + _ => continue, + } + } + + Some(self.new_config_expr_context_item( + key_name, + crate::ty::sup(&possible_types).into(), + obj.start.clone(), + obj.end.clone(), + )) + } _ => None, }, None => None, @@ -320,7 +352,6 @@ impl<'ctx> Resolver<'ctx> { ty, key.get_span_pos(), Some(obj_last.get_span_pos()), - true, ); } self.clear_config_expr_context(stack_depth, false); @@ -350,19 +381,23 @@ impl<'ctx> Resolver<'ctx> { let mut schema_names = vec![]; let mut total_suggs = vec![]; for ty in types { - if let TypeKind::Schema(schema_ty) = &ty.kind { - if schema_ty.get_obj_of_attr(attr).is_none() - && !schema_ty.is_mixin - && schema_ty.index_signature.is_none() - { - let mut suggs = - suggestions::provide_suggestions(attr, schema_ty.attrs.keys()); - total_suggs.append(&mut suggs); - schema_names.push(schema_ty.name.clone()); - } else { - // If there is a schema attribute that meets the condition, the type check passes - return; + match &ty.kind { + TypeKind::Schema(schema_ty) => { + if schema_ty.get_obj_of_attr(attr).is_none() + && !schema_ty.is_mixin + && schema_ty.index_signature.is_none() + { + let mut suggs = + suggestions::provide_suggestions(attr, schema_ty.attrs.keys()); + total_suggs.append(&mut suggs); + schema_names.push(schema_ty.name.clone()); + } else { + // If there is a schema attribute that meets the condition, the type check passes + return; + } } + TypeKind::Dict(..) => return, + _ => continue, } } if !schema_names.is_empty() { @@ -497,7 +532,11 @@ impl<'ctx> Resolver<'ctx> { let key_ty = if identifier.names.len() == 1 { let name = &identifier.names[0].node; let key_ty = if self.ctx.local_vars.contains(name) { - self.expr(key) + // set key context expected schema as None + self.ctx.config_expr_context.push(None); + let key_ty = self.expr(key); + self.ctx.config_expr_context.pop(); + key_ty } else { Arc::new(Type::str_lit(name)) }; @@ -543,7 +582,10 @@ impl<'ctx> Resolver<'ctx> { val_ty } _ => { + // set key context expected schema as None + self.ctx.config_expr_context.push(None); let key_ty = self.expr(key); + self.ctx.config_expr_context.pop(); let val_ty = self.expr(value); self.check_attr_ty(&key_ty, key.get_span_pos()); if let ast::Expr::StringLit(string_lit) = &key.node { diff --git a/kclvm/sema/src/resolver/node.rs b/kclvm/sema/src/resolver/node.rs index 5b54fa6e3..1e9d4cfa7 100644 --- a/kclvm/sema/src/resolver/node.rs +++ b/kclvm/sema/src/resolver/node.rs @@ -75,7 +75,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty.clone(), unification_stmt.target.get_span_pos(), None, - true, ); if !ty.is_any() && expected_ty.is_any() { self.set_type_to_scope(&names[0].node, ty, &names[0]); @@ -185,7 +184,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty.clone(), target.get_span_pos(), None, - true, ); let upgrade_schema_type = self.upgrade_dict_to_schema( @@ -221,7 +219,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty.clone(), target.get_span_pos(), None, - true, ); let upgrade_schema_type = self.upgrade_dict_to_schema( @@ -290,7 +287,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty, aug_assign_stmt.target.get_span_pos(), None, - true, ); self.ctx.l_value = false; new_target_ty @@ -457,7 +453,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty, schema_attr.name.get_span_pos(), None, - true, ); } // Assign @@ -466,7 +461,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty, schema_attr.name.get_span_pos(), None, - true, ), }, None => bug!("invalid ast schema attr op kind"), @@ -1117,13 +1111,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { let real_ret_ty = self.stmts(&lambda_expr.body); self.leave_scope(); self.ctx.in_lambda_expr.pop(); - self.must_assignable_to( - real_ret_ty.clone(), - ret_ty.clone(), - (start, end), - None, - true, - ); + self.must_assignable_to(real_ret_ty.clone(), ret_ty.clone(), (start, end), None); if !real_ret_ty.is_any() && ret_ty.is_any() && lambda_expr.return_ty.is_none() { ret_ty = real_ret_ty; } diff --git a/kclvm/sema/src/resolver/schema.rs b/kclvm/sema/src/resolver/schema.rs index b09d042f1..01691c904 100644 --- a/kclvm/sema/src/resolver/schema.rs +++ b/kclvm/sema/src/resolver/schema.rs @@ -98,7 +98,6 @@ impl<'ctx> Resolver<'ctx> { expected_ty, index_signature_node.get_span_pos(), None, - true, ); } } diff --git a/kclvm/sema/src/resolver/ty.rs b/kclvm/sema/src/resolver/ty.rs index 8b08c9542..2971ed5a5 100644 --- a/kclvm/sema/src/resolver/ty.rs +++ b/kclvm/sema/src/resolver/ty.rs @@ -93,7 +93,7 @@ impl<'ctx> Resolver<'ctx> { #[inline] pub fn must_be_type(&mut self, expr: &'ctx ast::NodeRef, expected_ty: TypeRef) { let ty = self.expr(expr); - self.must_assignable_to(ty, expected_ty, expr.get_span_pos(), None, true); + self.must_assignable_to(ty, expected_ty, expr.get_span_pos(), None); } /// Must assignable to the expected type. @@ -104,9 +104,8 @@ impl<'ctx> Resolver<'ctx> { expected_ty: TypeRef, range: Range, expected_pos: Option, - emit_error: bool, ) { - if !self.check_type(ty.clone(), expected_ty.clone(), &range, emit_error) && emit_error { + if !self.check_type(ty.clone(), expected_ty.clone(), &range) { let mut msgs = vec![Message { range, style: Style::LineAndColumn, @@ -140,14 +139,8 @@ impl<'ctx> Resolver<'ctx> { range: &Range, ) -> TypeRef { match (&ty.kind, &expected_ty.kind) { - (TypeKind::Dict(DictType { key_ty, val_ty, .. }), TypeKind::Schema(schema_ty)) => { - if self.dict_assignable_to_schema( - key_ty.clone(), - val_ty.clone(), - schema_ty, - range, - false, - ) { + (TypeKind::Dict(DictType { key_ty, .. }), TypeKind::Schema(schema_ty)) => { + if self.upgrade_dict_to_schema_attr_check(key_ty.clone(), schema_ty) { expected_ty } else { ty @@ -169,10 +162,7 @@ impl<'ctx> Resolver<'ctx> { self.upgrade_dict_to_schema(val_ty.clone(), expected_val_ty.clone(), range), ) .into(), - ( - TypeKind::Dict(DictType { key_ty, val_ty, .. }), - TypeKind::Union(expected_union_type), - ) => { + (TypeKind::Dict(DictType { key_ty, .. }), TypeKind::Union(expected_union_type)) => { let types: Vec> = expected_union_type .iter() .filter(|ty| match ty.kind { @@ -180,17 +170,13 @@ impl<'ctx> Resolver<'ctx> { _ => false, }) .filter(|ty| { - self.dict_assignable_to_schema( + self.upgrade_dict_to_schema_attr_check( key_ty.clone(), - val_ty.clone(), &ty.into_schema_type(), - range, - false, ) }) .map(|ty| ty.clone()) .collect(); - crate::ty::sup(&types).into() } _ => ty, @@ -256,29 +242,17 @@ impl<'ctx> Resolver<'ctx> { self.set_type_to_scope(name, target_ty.clone(), &target.node.names[0]); // Check the type of value and the type annotation of target - self.must_assignable_to( - value_ty.clone(), - target_ty, - target.get_span_pos(), - None, - true, - ) + self.must_assignable_to(value_ty.clone(), target_ty, target.get_span_pos(), None) } } } /// The check type main function, returns a boolean result. #[inline] - pub fn check_type( - &mut self, - ty: TypeRef, - expected_ty: TypeRef, - range: &Range, - emit_error: bool, - ) -> bool { + pub fn check_type(&mut self, ty: TypeRef, expected_ty: TypeRef, range: &Range) -> bool { match (&ty.kind, &expected_ty.kind) { (TypeKind::List(item_ty), TypeKind::List(expected_item_ty)) => { - self.check_type(item_ty.clone(), expected_item_ty.clone(), range, emit_error) + self.check_type(item_ty.clone(), expected_item_ty.clone(), range) } ( TypeKind::Dict(DictType { key_ty, val_ty, .. }), @@ -288,23 +262,18 @@ impl<'ctx> Resolver<'ctx> { .. }), ) => { - self.check_type(key_ty.clone(), expected_key_ty.clone(), range, emit_error) - && self.check_type(val_ty.clone(), expected_val_ty.clone(), range, emit_error) + self.check_type(key_ty.clone(), expected_key_ty.clone(), range) + && self.check_type(val_ty.clone(), expected_val_ty.clone(), range) + } + (TypeKind::Dict(DictType { key_ty, val_ty, .. }), TypeKind::Schema(schema_ty)) => { + self.dict_assignable_to_schema(key_ty.clone(), val_ty.clone(), schema_ty, range) } - (TypeKind::Dict(DictType { key_ty, val_ty, .. }), TypeKind::Schema(schema_ty)) => self - .dict_assignable_to_schema( - key_ty.clone(), - val_ty.clone(), - schema_ty, - range, - emit_error, - ), (TypeKind::Union(types), _) => types .iter() - .all(|ty| self.check_type(ty.clone(), expected_ty.clone(), range, emit_error)), - (_, TypeKind::Union(types)) => types.iter().any(|expected_ty| { - self.check_type(ty.clone(), expected_ty.clone(), range, emit_error) - }), + .all(|ty| self.check_type(ty.clone(), expected_ty.clone(), range)), + (_, TypeKind::Union(types)) => types + .iter() + .any(|expected_ty| self.check_type(ty.clone(), expected_ty.clone(), range)), _ => assignable_to(ty, expected_ty), } } @@ -317,16 +286,9 @@ impl<'ctx> Resolver<'ctx> { val_ty: TypeRef, schema_ty: &SchemaType, range: &Range, - emit_error: bool, ) -> bool { if let Some(index_signature) = &schema_ty.index_signature { - if !self.check_type( - val_ty.clone(), - index_signature.val_ty.clone(), - range, - emit_error, - ) && emit_error - { + if !self.check_type(val_ty.clone(), index_signature.val_ty.clone(), range) { self.handler.add_type_error( &format!( "expected schema index signature value type {}, got {}", @@ -337,8 +299,8 @@ impl<'ctx> Resolver<'ctx> { ); } if index_signature.any_other { - return self.check_type(key_ty, index_signature.key_ty.clone(), range, emit_error) - && self.check_type(val_ty, index_signature.val_ty.clone(), range, emit_error); + return self.check_type(key_ty, index_signature.key_ty.clone(), range) + && self.check_type(val_ty, index_signature.val_ty.clone(), range); } true } else { @@ -351,7 +313,6 @@ impl<'ctx> Resolver<'ctx> { attr_obj.ty.clone(), range.clone(), Some(attr_obj.range.clone()), - emit_error, ); return true; } @@ -360,6 +321,37 @@ impl<'ctx> Resolver<'ctx> { } } + /// Judge a dict can be upgrade to schema. + /// More strict than `dict_assign_to_schema()`: schema attr contains all attributes in key + pub fn upgrade_dict_to_schema_attr_check( + &mut self, + key_ty: TypeRef, + schema_ty: &SchemaType, + ) -> bool { + if schema_ty.index_signature.is_some() { + return true; + } + match &key_ty.kind { + // empty dict {} + TypeKind::Any => true, + // single key: {key1: value1} + TypeKind::StrLit(s) => schema_ty.attrs.len() == 1 && schema_ty.attrs.contains_key(s), + // multi key: { + // key1: value1 + // key2: value2 + // ... + // } + TypeKind::Union(types) => { + schema_ty.attrs.len() == types.len() + && types.iter().all(|ty| match &ty.kind { + TypeKind::StrLit(s) => schema_ty.attrs.contains_key(s), + _ => false, + }) + } + _ => false, + } + } + fn upgrade_named_ty_with_scope( &mut self, ty: TypeRef, diff --git a/kclvm/src/lib.rs b/kclvm/src/lib.rs index 186b05b68..d6f953c85 100644 --- a/kclvm/src/lib.rs +++ b/kclvm/src/lib.rs @@ -1,12 +1,15 @@ #![allow(clippy::missing_safety_doc)] +use kclvm_api::{gpyrpc::ExecProgramArgs as ExecProgramOptions, API}; use kclvm_parser::ParseSession; use kclvm_runner::exec_program; use kclvm_runner::runner::*; pub use kclvm_runtime::*; +use std::alloc::{alloc, dealloc, Layout}; use std::ffi::c_char; use std::ffi::c_int; -use std::ffi::CStr; +use std::ffi::{CStr, CString}; +use std::mem; use std::process::ExitCode; use std::sync::Arc; @@ -105,3 +108,74 @@ pub unsafe extern "C" fn kclvm_cli_main(argc: c_int, argv: *const *const c_char) } } } + +/// Exposes a normal kcl run function to the WASM host. +#[no_mangle] +pub unsafe extern "C" fn kcl_run( + filename_ptr: *const c_char, + src_ptr: *const c_char, +) -> *const c_char { + if filename_ptr.is_null() || src_ptr.is_null() { + return std::ptr::null(); + } + let filename = unsafe { CStr::from_ptr(filename_ptr).to_str().unwrap() }; + let src = unsafe { CStr::from_ptr(src_ptr).to_str().unwrap() }; + + match intern_run(filename, src) { + Ok(result) => CString::new(result).unwrap().into_raw(), + Err(err) => CString::new(format!("ERROR:{}", err)).unwrap().into_raw(), + } +} + +fn intern_run(filename: &str, src: &str) -> Result { + let api = API::default(); + let args = &ExecProgramOptions { + k_filename_list: vec![filename.to_string()], + k_code_list: vec![src.to_string()], + ..Default::default() + }; + match api.exec_program(args) { + Ok(result) => { + if result.err_message.is_empty() { + Ok(result.yaml_result) + } else { + Err(result.err_message) + } + } + Err(err) => Err(err.to_string()), + } +} + +/// Exposes an allocation function to the WASM host. +/// +/// _This implementation is copied from wasm-bindgen_ +#[no_mangle] +pub unsafe extern "C" fn kcl_malloc(size: usize) -> *mut u8 { + let align = mem::align_of::(); + let layout = Layout::from_size_align(size, align).expect("Invalid layout"); + if layout.size() > 0 { + let ptr = alloc(layout); + if !ptr.is_null() { + return ptr; + } else { + std::alloc::handle_alloc_error(layout); + } + } else { + return align as *mut u8; + } +} + +/// Expose a deallocation function to the WASM host. +/// +/// _This implementation is copied from wasm-bindgen_ +#[no_mangle] +pub unsafe extern "C" fn kcl_free(ptr: *mut u8, size: usize) { + // This happens for zero-length slices, and in that case `ptr` is + // likely bogus so don't actually send this to the system allocator + if size == 0 { + return; + } + let align = mem::align_of::(); + let layout = Layout::from_size_align_unchecked(size, align); + dealloc(ptr, layout); +} diff --git a/kclvm/tools/src/LSP/src/completion.rs b/kclvm/tools/src/LSP/src/completion.rs index bdb12b17a..049d0a740 100644 --- a/kclvm/tools/src/LSP/src/completion.rs +++ b/kclvm/tools/src/LSP/src/completion.rs @@ -29,12 +29,13 @@ use kclvm_sema::core::global_state::GlobalState; use kclvm_error::Position as KCLPos; use kclvm_sema::builtin::{BUILTIN_FUNCTIONS, STANDARD_SYSTEM_MODULES}; use kclvm_sema::core::package::ModuleInfo; +use kclvm_sema::core::scope::{LocalSymbolScopeKind, ScopeKind}; use kclvm_sema::core::symbol::SymbolKind; use kclvm_sema::resolver::doc::{parse_schema_doc_string, SchemaDoc}; use kclvm_sema::ty::{FunctionType, SchemaType, Type, TypeKind}; use lsp_types::{CompletionItem, CompletionItemKind, InsertTextFormat}; -use crate::util::{inner_most_expr_in_stmt, is_in_docstring, is_in_schema_expr}; +use crate::util::{inner_most_expr_in_stmt, is_in_docstring}; #[derive(Debug, Clone, PartialEq, Hash, Eq)] pub enum KCLCompletionItemKind { @@ -390,47 +391,51 @@ fn completion_newline( return Some(into_completion_items(&completions).into()); } - // todo: judge based on scope kind instead of `is_in_schema_expr` - if let Some(_) = is_in_schema_expr(program, pos) { - // Complete schema attr when input newline in schema - if let Some(scope) = gs.look_up_scope(pos) { - if let Some(defs) = gs.get_all_defs_in_scope(scope) { - for symbol_ref in defs { - match gs.get_symbols().get_symbol(symbol_ref) { - Some(def) => { - let sema_info = def.get_sema_info(); - let name = def.get_name(); - match symbol_ref.get_kind() { - SymbolKind::Attribute => { - completions.insert(KCLCompletionItem { - label: name.clone(), - detail: sema_info - .ty - .as_ref() - .map(|ty| format!("{}: {}", name, ty.ty_str())), - documentation: match &sema_info.doc { - Some(doc) => { - if doc.is_empty() { - None - } else { - Some(doc.clone()) - } - } - None => None, - }, - kind: Some(KCLCompletionItemKind::SchemaAttr), - insert_text: None, - }); + // Complete schema attr when input newline in schema + if let Some(scope) = gs.look_up_scope(pos) { + if let ScopeKind::Local = scope.get_kind() { + if let Some(locol_scope) = gs.get_scopes().try_get_local_scope(&scope) { + if let LocalSymbolScopeKind::SchemaConfig = locol_scope.get_kind() { + if let Some(defs) = gs.get_all_defs_in_scope(scope) { + for symbol_ref in defs { + match gs.get_symbols().get_symbol(symbol_ref) { + Some(def) => { + let sema_info = def.get_sema_info(); + let name = def.get_name(); + match symbol_ref.get_kind() { + SymbolKind::Attribute => { + completions.insert(KCLCompletionItem { + label: name.clone(), + detail: sema_info + .ty + .as_ref() + .map(|ty| format!("{}: {}", name, ty.ty_str())), + documentation: match &sema_info.doc { + Some(doc) => { + if doc.is_empty() { + None + } else { + Some(doc.clone()) + } + } + None => None, + }, + kind: Some(KCLCompletionItemKind::SchemaAttr), + insert_text: None, + }); + } + _ => {} + } } - _ => {} + None => {} } } - None => {} } } } } } + Some(into_completion_items(&completions).into()) } diff --git a/kclvm/tools/src/LSP/src/goto_def.rs b/kclvm/tools/src/LSP/src/goto_def.rs index 7a2e498b0..53747d90a 100644 --- a/kclvm/tools/src/LSP/src/goto_def.rs +++ b/kclvm/tools/src/LSP/src/goto_def.rs @@ -105,7 +105,7 @@ mod tests { use indexmap::IndexSet; use kclvm_error::Position as KCLPos; use proc_macro_crate::bench_test; - use std::path::PathBuf; + use std::path::{Path, PathBuf}; #[test] #[bench_test] @@ -693,12 +693,29 @@ mod tests { line: $line, column: Some($column), }; - goto_def(&pos, &gs) + let res = goto_def(&pos, &gs); + fmt_resp(&res) })); } }; } + fn fmt_resp(resp: &Option) -> String { + let root_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + match resp { + Some(resp) => match resp { + lsp_types::GotoDefinitionResponse::Scalar(loc) => { + let url = file_path_from_url(&loc.uri).unwrap(); + let got_path = Path::new(&url); + let relative_path = got_path.strip_prefix(root_path).unwrap(); + format!("path: {:?}, range: {:?}", relative_path, loc.range) + } + _ => todo!(), + }, + None => "None".to_string(), + } + } + goto_def_test_snapshot!( goto_system_pkg_test, "src/test_data/goto_def_test/goto_sys_pkg_test.k", @@ -711,5 +728,37 @@ mod tests { "src/test_data/goto_def_test/lambda_local_var_test/lambda_local_var_test.k", 2, 9 + goto_dict_to_schema_attr_test1, + "src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k", + 13, + 15 + ); + + goto_def_test_snapshot!( + goto_dict_to_schema_attr_test2, + "src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k", + 15, + 7 + ); + + goto_def_test_snapshot!( + goto_dict_to_schema_attr_test3, + "src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k", + 19, + 7 + ); + + goto_def_test_snapshot!( + goto_dict_to_schema_attr_test4, + "src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k", + 26, + 11 + ); + + goto_def_test_snapshot!( + goto_dict_to_schema_attr_test5, + "src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k", + 33, + 11 ); } diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test1.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test1.snap new file mode 100644 index 000000000..24ace7572 --- /dev/null +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test1.snap @@ -0,0 +1,5 @@ +--- +source: tools/src/LSP/src/goto_def.rs +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\")\n ; let pos = KCLPos\n { filename : file.clone(), line : 13, column : Some(15), } ; let res =\n goto_def(& pos, & gs) ; fmt_resp(& res)\n })" +--- +"path: \"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\", range: Range { start: Position { line: 1, character: 4 }, end: Position { line: 1, character: 8 } }" diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test2.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test2.snap new file mode 100644 index 000000000..31e11326f --- /dev/null +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test2.snap @@ -0,0 +1,5 @@ +--- +source: tools/src/LSP/src/goto_def.rs +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\")\n ; let pos = KCLPos\n { filename : file.clone(), line : 15, column : Some(7), } ; let res =\n goto_def(& pos, & gs) ; fmt_resp(& res)\n })" +--- +"path: \"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\", range: Range { start: Position { line: 4, character: 4 }, end: Position { line: 4, character: 8 } }" diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test3.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test3.snap new file mode 100644 index 000000000..c16327319 --- /dev/null +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test3.snap @@ -0,0 +1,5 @@ +--- +source: tools/src/LSP/src/goto_def.rs +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\")\n ; let pos = KCLPos\n { filename : file.clone(), line : 19, column : Some(7), } ; let res =\n goto_def(& pos, & gs) ; fmt_resp(& res)\n })" +--- +"path: \"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\", range: Range { start: Position { line: 9, character: 4 }, end: Position { line: 9, character: 8 } }" diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test4.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test4.snap new file mode 100644 index 000000000..8ba371b50 --- /dev/null +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test4.snap @@ -0,0 +1,5 @@ +--- +source: tools/src/LSP/src/goto_def.rs +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\")\n ; let pos = KCLPos\n { filename : file.clone(), line : 26, column : Some(11), } ; let res =\n goto_def(& pos, & gs) ; fmt_resp(& res)\n })" +--- +"path: \"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\", range: Range { start: Position { line: 4, character: 4 }, end: Position { line: 4, character: 8 } }" diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test5.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test5.snap new file mode 100644 index 000000000..0c8a57a2f --- /dev/null +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test5.snap @@ -0,0 +1,5 @@ +--- +source: tools/src/LSP/src/goto_def.rs +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\")\n ; let pos = KCLPos\n { filename : file.clone(), line : 33, column : Some(11), } ; let res =\n goto_def(& pos, & gs) ; fmt_resp(& res)\n })" +--- +"path: \"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\", range: Range { start: Position { line: 9, character: 4 }, end: Position { line: 9, character: 8 } }" diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_system_pkg_test.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_system_pkg_test.snap index 1fddab521..1cd5643cd 100644 --- a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_system_pkg_test.snap +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_system_pkg_test.snap @@ -1,5 +1,5 @@ --- source: tools/src/LSP/src/goto_def.rs -expression: "format!(\"{:?}\", res)" +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/goto_def.k\") ; let pos\n = KCLPos { filename : file.clone(), line : 3, column : Some(1), } ;\n let res = goto_def(& pos, & gs) ; fmt_resp(& res)\n })" --- -None +"None" diff --git a/kclvm/tools/src/LSP/src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k b/kclvm/tools/src/LSP/src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k new file mode 100644 index 000000000..1165e5ce3 --- /dev/null +++ b/kclvm/tools/src/LSP/src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k @@ -0,0 +1,37 @@ +schema Name1: + name: str + +schema Name2: + name: str + age: int + +schema Name3: + [...str]: str + name: str + age: int + +a: Name1 = {name: ""} +b: Name1 | Name2 = { + name: "a" + age: 1 +} +c: Name1 | Name2 | Name3 = { + name: "a" + age: 1 + "c": "c" +} + +d: Name1 | Name2 | {str:Name1} | {str:Name2} = { + "b": { + name: "a" + age: 1 + } +} + +e: Name1 | Name2 | {str:Name1} | {str:Name2} | {str: Name3} = { + "b": { + name: "a" + age: 1 + "c": "c" + } +} diff --git a/kclvm/tools/src/LSP/src/util.rs b/kclvm/tools/src/LSP/src/util.rs index aba2493ea..aab9c7661 100644 --- a/kclvm/tools/src/LSP/src/util.rs +++ b/kclvm/tools/src/LSP/src/util.rs @@ -1,7 +1,6 @@ use indexmap::IndexSet; use kclvm_ast::ast::{ - ConfigEntry, Expr, Identifier, Node, NodeRef, PosTuple, Program, SchemaExpr, SchemaStmt, Stmt, - Type, + ConfigEntry, Expr, Identifier, Node, NodeRef, PosTuple, Program, SchemaStmt, Stmt, Type, }; use kclvm_ast::node_ref; use kclvm_ast::pos::ContainsPos; @@ -644,25 +643,6 @@ fn inner_most_expr_in_config_entry( } } -pub(crate) fn is_in_schema_expr( - program: &Program, - pos: &KCLPos, -) -> Option<(Node, SchemaExpr)> { - match program.pos_to_stmt(pos) { - Some(node) => { - let parent_expr = inner_most_expr_in_stmt(&node.node, pos, None).1; - match parent_expr { - Some(expr) => match expr.node { - Expr::Schema(schema) => Some((node, schema)), - _ => None, - }, - None => None, - } - } - None => None, - } -} - pub(crate) fn is_in_docstring( program: &Program, pos: &KCLPos,