diff --git a/kclvm/sema/src/resolver/config.rs b/kclvm/sema/src/resolver/config.rs index aaa8a3df5..9a2407d09 100644 --- a/kclvm/sema/src/resolver/config.rs +++ b/kclvm/sema/src/resolver/config.rs @@ -201,7 +201,7 @@ impl<'ctx> Resolver<'ctx> { /// Pop method for the 'config_expr_context' stack /// /// Returns: - /// the item poped from stack. + /// the item popped from stack. #[inline] pub(crate) fn restore_config_expr_context(&mut self) -> Option { match self.ctx.config_expr_context.pop() { @@ -258,9 +258,7 @@ impl<'ctx> Resolver<'ctx> { if !name.is_empty() { if let Some(Some(obj)) = self.ctx.config_expr_context.last() { let obj = obj.clone(); - if let TypeKind::Schema(schema_ty) = &obj.ty.kind { - self.check_config_attr(name, &key.get_span_pos(), schema_ty); - } + self.must_check_config_attr(name, &key.get_span_pos(), &obj.ty); } } } @@ -343,6 +341,52 @@ impl<'ctx> Resolver<'ctx> { (suggs, suggestion) } + /// Check config attr has been defined. + pub(crate) fn must_check_config_attr(&mut self, attr: &str, range: &Range, ty: &TypeRef) { + if let TypeKind::Schema(schema_ty) = &ty.kind { + self.check_config_attr(attr, range, schema_ty) + } else if let TypeKind::Union(types) = &ty.kind { + 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; + } + } + } + if !schema_names.is_empty() { + self.handler.add_compile_error_with_suggestions( + &format!( + "Cannot add member '{}' to '{}'{}", + attr, + if schema_names.len() > 1 { + format!("schemas {:?}", schema_names) + } else { + format!("schema {}", schema_names[0]) + }, + if total_suggs.is_empty() { + "".to_string() + } else { + format!(", did you mean '{:?}'?", total_suggs) + }, + ), + range.clone(), + Some(total_suggs), + ); + } + } + } + /// Check config attr has been defined. pub(crate) fn check_config_attr(&mut self, attr: &str, range: &Range, schema_ty: &SchemaType) { let runtime_type = kclvm_runtime::schema_runtime_type(&schema_ty.name, &schema_ty.pkgpath); diff --git a/kclvm/sema/src/resolver/node.rs b/kclvm/sema/src/resolver/node.rs index 50af0b492..1c1d694d0 100644 --- a/kclvm/sema/src/resolver/node.rs +++ b/kclvm/sema/src/resolver/node.rs @@ -160,6 +160,18 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { value_ty = self.expr(&assign_stmt.value); self.clear_config_expr_context(init_stack_depth as usize, false) } + TypeKind::List(_) | TypeKind::Dict(_) | TypeKind::Union(_) => { + let obj = self.new_config_expr_context_item( + "[]", + expected_ty.clone(), + start.clone(), + end.clone(), + ); + let init_stack_depth = self.switch_config_expr_context(Some(obj)); + value_ty = self.expr(&assign_stmt.value); + self.check_assignment_type_annotation(assign_stmt, value_ty.clone()); + self.clear_config_expr_context(init_stack_depth as usize, false) + } _ => { value_ty = self.expr(&assign_stmt.value); // Check type annotation if exists. diff --git a/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_0.k b/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_0.k new file mode 100644 index 000000000..1750f4f3f --- /dev/null +++ b/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_0.k @@ -0,0 +1,11 @@ +schema Name: + name: str + +schema Config: + n: {str:Name} + +Config { + n = { + n.n = "n" + } +} diff --git a/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_1.k b/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_1.k new file mode 100644 index 000000000..66f6324ba --- /dev/null +++ b/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_1.k @@ -0,0 +1,11 @@ +schema Name: + name: str + +schema Config: + n: [Name] + +Config { + n = [{ + n = "n" + }] +} diff --git a/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_2.k b/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_2.k new file mode 100644 index 000000000..77ff72246 --- /dev/null +++ b/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_2.k @@ -0,0 +1,11 @@ +schema Name: + name: str + +schema Config: + n: {str:[Name]} + +Config { + n = {n = [{ + n = "n" + }]} +} diff --git a/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_3.k b/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_3.k new file mode 100644 index 000000000..8b7f9eff4 --- /dev/null +++ b/kclvm/sema/src/resolver/test_fail_data/unmatched_nest_schema_attr_3.k @@ -0,0 +1,14 @@ +schema Name1: + name1: str + +schema Name2: + name2: str + +schema Config: + n: Name1 | Name2 + +Config { + n = { + n = "n" + } +} diff --git a/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_0.k b/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_0.k new file mode 100644 index 000000000..f311d3092 --- /dev/null +++ b/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_0.k @@ -0,0 +1,6 @@ +schema Name: + name: str + +n: {str:Name} = { + n.n = "n" +} diff --git a/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_1.k b/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_1.k new file mode 100644 index 000000000..bc80e9a91 --- /dev/null +++ b/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_1.k @@ -0,0 +1,6 @@ +schema Name: + name: str + +n: [Name] = [{ + n = "n" +}] diff --git a/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_2.k b/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_2.k new file mode 100644 index 000000000..6522cdd80 --- /dev/null +++ b/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_2.k @@ -0,0 +1,6 @@ +schema Name: + name: str + +n: {str:[Name]} = {n = [{ + n = "n" +}]} diff --git a/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_3.k b/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_3.k new file mode 100644 index 000000000..819b2751e --- /dev/null +++ b/kclvm/sema/src/resolver/test_fail_data/unmatched_schema_attr_3.k @@ -0,0 +1,9 @@ +schema Name1: + name1: str + +schema Name2: + name2: str + +n: Name1 | Name2 = { + n = "n" +} diff --git a/kclvm/sema/src/resolver/tests.rs b/kclvm/sema/src/resolver/tests.rs index d5c5b4763..0944913ca 100644 --- a/kclvm/sema/src/resolver/tests.rs +++ b/kclvm/sema/src/resolver/tests.rs @@ -159,6 +159,14 @@ fn test_resolve_program_fail() { "unique_key_error_1.k", "unmatched_index_sign_default_value.k", "unmatched_args.k", + "unmatched_nest_schema_attr_0.k", + "unmatched_nest_schema_attr_1.k", + "unmatched_nest_schema_attr_2.k", + "unmatched_nest_schema_attr_3.k", + "unmatched_schema_attr_0.k", + "unmatched_schema_attr_1.k", + "unmatched_schema_attr_2.k", + "unmatched_schema_attr_3.k", ]; for case in cases { let path = Path::new(work_dir).join(case); diff --git a/kclvm/sema/src/resolver/var.rs b/kclvm/sema/src/resolver/var.rs index 1f650766d..63b3a393e 100644 --- a/kclvm/sema/src/resolver/var.rs +++ b/kclvm/sema/src/resolver/var.rs @@ -1,5 +1,4 @@ use crate::resolver::Resolver; -use crate::ty::TypeKind; use indexmap::IndexMap; use kclvm_ast::pos::GetPos; use kclvm_error::diagnostic::Range; @@ -115,9 +114,7 @@ impl<'ctx> Resolver<'ctx> { for name in &names[1..] { // Store and config attr check if self.ctx.l_value { - if let TypeKind::Schema(schema_ty) = &ty.kind { - self.check_config_attr(name, &range, schema_ty); - } + self.must_check_config_attr(name, &range, &ty); } ty = self.load_attr(ty, name, range.clone()); tys.push(ty.clone()); diff --git a/kclvm/tools/src/LSP/src/completion.rs b/kclvm/tools/src/LSP/src/completion.rs index c592f1d0a..1f7a8dd9e 100644 --- a/kclvm/tools/src/LSP/src/completion.rs +++ b/kclvm/tools/src/LSP/src/completion.rs @@ -1,5 +1,5 @@ //! Complete for KCL -//! Now supports code completion in treigger mode (triggered when user enters `.`, `:` and `=`), schema attr and global variables +//! Now supports code completion in trigger mode (triggered when user enters `.`, `:` and `=`), schema attr and global variables //! and the content of the completion includes: //! + variable //! + schema attr name @@ -7,7 +7,7 @@ //! + import path //! + schema attr //! + builtin function(str function) -//! + defitions in pkg +//! + definitions in pkg //! + system module functions //! + assign(=, :) //! + schema attr value @@ -110,8 +110,8 @@ pub(crate) fn completion( // Complete builtin functions in root scope and lambda match scope.get_kind() { kclvm_sema::core::scope::ScopeKind::Local => { - if let Some(locol_scope) = gs.get_scopes().try_get_local_scope(&scope) { - match locol_scope.get_kind() { + if let Some(local_scope) = gs.get_scopes().try_get_local_scope(&scope) { + match local_scope.get_kind() { kclvm_sema::core::scope::LocalSymbolScopeKind::Lambda => { completions.extend(BUILTIN_FUNCTIONS.iter().map( |(name, ty)| KCLCompletionItem {