From 73d79ef5aac88336e3dc256a9bda0701255379d9 Mon Sep 17 00:00:00 2001 From: peefy Date: Fri, 3 May 2024 23:00:40 +0300 Subject: [PATCH] feat: enhance the schema expr type check and impl the schema factory using the dynamic type Signed-off-by: peefy --- kclvm/evaluator/src/calculation.rs | 9 +++-- kclvm/evaluator/src/node.rs | 5 ++- kclvm/evaluator/src/schema.rs | 2 +- kclvm/evaluator/src/ty.rs | 13 ++++--- kclvm/runtime/src/value/api.rs | 10 ++--- kclvm/runtime/src/value/val_plan.rs | 2 +- kclvm/runtime/src/value/val_type.rs | 37 +++++++++++++------ kclvm/sema/src/resolver/node.rs | 3 ++ kclvm/sema/src/resolver/tests.rs | 2 +- .../completion_test/import/external/kcl.mod | 2 +- test/grammar/schema/factory/test_0/main.k | 19 ++++++++++ .../schema/factory/test_0/stdout.golden | 15 ++++++++ test/grammar/schema/factory/test_1/main.k | 19 ++++++++++ .../schema/factory/test_1/stdout.golden | 4 ++ test/grammar/schema/factory/test_2/main.k | 19 ++++++++++ .../schema/factory/test_2/stdout.golden | 4 ++ test/grammar/types/type_as/type_as_5/kcl.mod | 0 test/grammar/types/type_as/type_as_5/main.k | 3 ++ .../grammar/types/type_as/type_as_5/pkg/pkg.k | 8 ++++ .../types/type_as/type_as_5/stdout.golden | 4 ++ test/grammar/types/type_as/type_as_6/main.k | 8 ++++ .../types/type_as/type_as_6/stdout.golden | 4 ++ .../types/type_as/type_as_err_2/main.k | 8 ++++ .../type_as/type_as_err_2/stderr.golden.py | 19 ++++++++++ 24 files changed, 189 insertions(+), 30 deletions(-) create mode 100644 test/grammar/schema/factory/test_0/main.k create mode 100644 test/grammar/schema/factory/test_0/stdout.golden create mode 100644 test/grammar/schema/factory/test_1/main.k create mode 100644 test/grammar/schema/factory/test_1/stdout.golden create mode 100644 test/grammar/schema/factory/test_2/main.k create mode 100644 test/grammar/schema/factory/test_2/stdout.golden create mode 100644 test/grammar/types/type_as/type_as_5/kcl.mod create mode 100644 test/grammar/types/type_as/type_as_5/main.k create mode 100644 test/grammar/types/type_as/type_as_5/pkg/pkg.k create mode 100644 test/grammar/types/type_as/type_as_5/stdout.golden create mode 100644 test/grammar/types/type_as/type_as_6/main.k create mode 100644 test/grammar/types/type_as/type_as_6/stdout.golden create mode 100644 test/grammar/types/type_as/type_as_err_2/main.k create mode 100644 test/grammar/types/type_as/type_as_err_2/stderr.golden.py diff --git a/kclvm/evaluator/src/calculation.rs b/kclvm/evaluator/src/calculation.rs index 39d3b9f4c..be392a692 100644 --- a/kclvm/evaluator/src/calculation.rs +++ b/kclvm/evaluator/src/calculation.rs @@ -120,7 +120,7 @@ impl<'ctx> Evaluator<'ctx> { /// lhs as rhs #[inline] pub(crate) fn r#as(&self, lhs: ValueRef, rhs: ValueRef) -> ValueRef { - type_pack_and_check(self, &lhs, vec![&rhs.as_str()]) + type_pack_and_check(self, &lhs, vec![&rhs.as_str()], true) } /// lhs is rhs #[inline] @@ -175,7 +175,10 @@ impl<'ctx> Evaluator<'ctx> { // Has type annotation if let Some(ty) = attr_map.get(k) { let value = lhs.dict_get_value(k).unwrap(); - lhs.dict_update_key_value(k, type_pack_and_check(self, &value, vec![ty])); + lhs.dict_update_key_value( + k, + type_pack_and_check(self, &value, vec![ty], false), + ); } } lhs.clone() @@ -240,7 +243,7 @@ impl<'ctx> Evaluator<'ctx> { } }; if attr_map.contains_key(key) { - let v = type_pack_and_check(self, value, vec![attr_map.get(key).unwrap()]); + let v = type_pack_and_check(self, value, vec![attr_map.get(key).unwrap()], false); self.dict_merge_key_value_pair(dict, key, &v, op, insert_index, false); } else { self.dict_merge_key_value_pair(dict, key, value, op, insert_index, false); diff --git a/kclvm/evaluator/src/node.rs b/kclvm/evaluator/src/node.rs index fe34c768f..267daa745 100644 --- a/kclvm/evaluator/src/node.rs +++ b/kclvm/evaluator/src/node.rs @@ -139,7 +139,7 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { // Runtime type cast if exists the type annotation. if let Some(ty) = &assign_stmt.ty { let is_in_schema = self.is_in_schema() || self.is_in_schema_expr(); - value = type_pack_and_check(self, &value, vec![&ty.node.to_string()]); + value = type_pack_and_check(self, &value, vec![&ty.node.to_string()], false); // Schema required attribute validating. if !is_in_schema { walk_value_mut(&value, &mut |value: &ValueRef| { @@ -309,7 +309,8 @@ impl<'ctx> TypedResultWalker<'ctx> for Evaluator<'ctx> { }; // Add function to the global state let index = self.add_rule(caller); - let function = self.proxy_function_value(index); + let runtime_type = schema_runtime_type(&rule_stmt.name.node, &self.current_pkgpath()); + let function = self.proxy_function_value_with_type(index, &runtime_type); // Store or add the variable in the scope let name = &rule_stmt.name.node; if !self.store_variable(name, function.clone()) { diff --git a/kclvm/evaluator/src/schema.rs b/kclvm/evaluator/src/schema.rs index bdbda32e7..ac37ee5e8 100644 --- a/kclvm/evaluator/src/schema.rs +++ b/kclvm/evaluator/src/schema.rs @@ -655,7 +655,7 @@ fn schema_value_check( let value = schema_value.dict_get_value(key).unwrap(); schema_value.dict_update_key_value( key.as_str(), - type_pack_and_check(s, &value, vec![value_type]), + type_pack_and_check(s, &value, vec![value_type], false), ); } } else if !has_index_signature && no_such_attr { diff --git a/kclvm/evaluator/src/ty.rs b/kclvm/evaluator/src/ty.rs index 8907d2b28..536b7ac19 100644 --- a/kclvm/evaluator/src/ty.rs +++ b/kclvm/evaluator/src/ty.rs @@ -49,15 +49,18 @@ pub fn resolve_schema(s: &Evaluator, schema: &ValueRef, keys: &[String]) -> Valu } else { schema.clone() }; - // ctx.panic_info = now_panic_info; return schema; } - // ctx.panic_info = now_panic_info; schema.clone() } /// Type pack and check ValueRef with the expected type vector -pub fn type_pack_and_check(s: &Evaluator, value: &ValueRef, expected_types: Vec<&str>) -> ValueRef { +pub fn type_pack_and_check( + s: &Evaluator, + value: &ValueRef, + expected_types: Vec<&str>, + strict: bool, +) -> ValueRef { if value.is_none_or_undefined() || expected_types.is_empty() { return value.clone(); } @@ -71,7 +74,7 @@ pub fn type_pack_and_check(s: &Evaluator, value: &ValueRef, expected_types: Vec< converted_value = convert_collection_value(s, value, tpe); } // Runtime type check - checked = check_type(&converted_value, tpe); + checked = check_type(&converted_value, tpe, strict); if checked { break; } @@ -192,7 +195,7 @@ pub fn convert_collection_value_with_union_types( for tpe in types { // Try match every type and convert the value, if matched, return the value. let value = convert_collection_value(s, value, tpe); - if check_type(&value, tpe) { + if check_type(&value, tpe, false) { return value; } } diff --git a/kclvm/runtime/src/value/api.rs b/kclvm/runtime/src/value/api.rs index 85f71b9fc..160a5787f 100644 --- a/kclvm/runtime/src/value/api.rs +++ b/kclvm/runtime/src/value/api.rs @@ -1167,7 +1167,7 @@ pub unsafe extern "C" fn kclvm_dict_merge( } }; if attr_map.contains_key(key) { - let v = type_pack_and_check(ctx, v, vec![attr_map.get(key).unwrap()]); + let v = type_pack_and_check(ctx, v, vec![attr_map.get(key).unwrap()], false); p.dict_merge( ctx, key, @@ -1489,7 +1489,7 @@ pub unsafe extern "C" fn kclvm_value_as( let b = ptr_as_ref(b); let ty_str = b.as_str(); let ctx = mut_ptr_as_ref(ctx); - let value = type_pack_and_check(ctx, a, vec![ty_str.as_str()]); + let value = type_pack_and_check(ctx, a, vec![ty_str.as_str()], true); value.into_raw(ctx) } @@ -1869,7 +1869,7 @@ pub unsafe extern "C" fn kclvm_value_union( // Has type annotation if let Some(ty) = attr_map.get(k) { let value = a.dict_get_value(k).unwrap(); - a.dict_update_key_value(k, type_pack_and_check(ctx, &value, vec![ty])); + a.dict_update_key_value(k, type_pack_and_check(ctx, &value, vec![ty], false)); } } a.clone().into_raw(ctx) @@ -2173,7 +2173,7 @@ pub unsafe extern "C" fn kclvm_schema_value_check( let value = schema_value.dict_get_value(key).unwrap(); schema_value.dict_update_key_value( key.as_str(), - type_pack_and_check(ctx, &value, vec![value_type]), + type_pack_and_check(ctx, &value, vec![value_type], false), ); } } else if !has_index_signature && no_such_attr { @@ -2407,7 +2407,7 @@ pub unsafe extern "C" fn kclvm_convert_collection_value( let value = ptr_as_ref(value); let ctx = mut_ptr_as_ref(ctx); let tpe = c2str(tpe); - let value = type_pack_and_check(ctx, value, vec![tpe]); + let value = type_pack_and_check(ctx, value, vec![tpe], false); let is_in_schema = ptr_as_ref(is_in_schema); // Schema required attribute validating. if !is_in_schema.is_truthy() { diff --git a/kclvm/runtime/src/value/val_plan.rs b/kclvm/runtime/src/value/val_plan.rs index b743586af..7227c11a7 100644 --- a/kclvm/runtime/src/value/val_plan.rs +++ b/kclvm/runtime/src/value/val_plan.rs @@ -165,7 +165,7 @@ fn handle_schema(ctx: &Context, value: &ValueRef) -> Vec { } /// Returns the type path of the runtime value `v`. -fn value_type_path(v: &ValueRef, full_name: bool) -> String { +pub(crate) fn value_type_path(v: &ValueRef, full_name: bool) -> String { match v.get_potential_schema_type() { Some(ty_str) => { if full_name { diff --git a/kclvm/runtime/src/value/val_type.rs b/kclvm/runtime/src/value/val_type.rs index 41c0a69e8..8caa08f3b 100644 --- a/kclvm/runtime/src/value/val_type.rs +++ b/kclvm/runtime/src/value/val_type.rs @@ -21,6 +21,7 @@ pub const KCL_TYPE_ANY: &str = "any"; pub const KCL_TYPE_LIST: &str = "list"; pub const KCL_TYPE_DICT: &str = "dict"; pub const KCL_TYPE_FUNCTION: &str = "function"; +pub const KCL_TYPE_TYPE: &str = "type"; pub const KCL_TYPE_NUMBER_MULTIPLY: &str = "number_multiplier"; pub const KCL_NAME_CONSTANT_NONE: &str = "None"; pub const KCL_NAME_CONSTANT_UNDEFINED: &str = "Undefined"; @@ -58,7 +59,13 @@ impl ValueRef { Value::list_value(..) => String::from(KCL_TYPE_LIST), Value::dict_value(..) => String::from(KCL_TYPE_DICT), Value::schema_value(ref v) => v.name.clone(), - Value::func_value(..) => String::from(KCL_TYPE_FUNCTION), + Value::func_value(func) => { + if func.runtime_type.is_empty() { + String::from(KCL_TYPE_FUNCTION) + } else { + String::from(KCL_TYPE_TYPE) + } + } } } } @@ -155,6 +162,7 @@ pub fn type_pack_and_check( ctx: &mut Context, value: &ValueRef, expected_types: Vec<&str>, + strict: bool, ) -> ValueRef { if value.is_none_or_undefined() || expected_types.is_empty() { return value.clone(); @@ -178,7 +186,7 @@ pub fn type_pack_and_check( converted_value = convert_collection_value(ctx, value, &tpe); } // Runtime type check - checked = check_type(&converted_value, &tpe); + checked = check_type(&converted_value, &tpe, strict); if checked { break; } @@ -368,7 +376,7 @@ pub fn convert_collection_value_with_union_types( for tpe in types { // Try match every type and convert the value, if matched, return the value. let value = convert_collection_value(ctx, value, tpe); - if check_type(&value, tpe) { + if check_type(&value, tpe, false) { return value; } } @@ -377,7 +385,7 @@ pub fn convert_collection_value_with_union_types( } /// check_type returns the value wether match the given the type string -pub fn check_type(value: &ValueRef, tpe: &str) -> bool { +pub fn check_type(value: &ValueRef, tpe: &str, strict: bool) -> bool { if tpe.is_empty() || tpe == KCL_TYPE_ANY { return true; } @@ -408,9 +416,14 @@ pub fn check_type(value: &ValueRef, tpe: &str) -> bool { return true; } if value.is_schema() { - // not list/dict, not built-in type, treat as user defined schema, - // do not check user schema type because it has been checked at compile time - return is_schema_type(tpe); + if strict { + let value_ty = crate::val_plan::value_type_path(value, tpe.contains('.')); + return value_ty == tpe; + } else { + // not list/dict, not built-in type, treat as user defined schema, + // do not check user schema type because it has been checked at compile time + return is_schema_type(tpe); + } } // Type error return false; @@ -425,7 +438,9 @@ pub fn check_type_union(value: &ValueRef, tpe: &str) -> bool { if expected_types.len() <= 1 { false } else { - expected_types.iter().any(|tpe| check_type(value, tpe)) + expected_types + .iter() + .any(|tpe| check_type(value, tpe, false)) } } @@ -483,7 +498,7 @@ pub fn check_type_dict(value: &ValueRef, tpe: &str) -> bool { let (_, expected_value_type) = separate_kv(&expected_type); let dict_ref = value.as_dict_ref(); for (_, v) in &dict_ref.values { - if !check_type(v, &expected_value_type) { + if !check_type(v, &expected_value_type, false) { return false; } } @@ -501,7 +516,7 @@ pub fn check_type_list(value: &ValueRef, tpe: &str) -> bool { let expected_type = dereference_type(tpe); let list_ref = value.as_list_ref(); for v in &list_ref.values { - if !check_type(v, &expected_type) { + if !check_type(v, &expected_type, false) { return false; } } @@ -724,7 +739,7 @@ mod test_value_type { (ValueRef::str("0"), "int", false), ]; for (value, tpe, expected) in cases { - assert_eq!(check_type(&value, tpe), expected); + assert_eq!(check_type(&value, tpe, false), expected); } } diff --git a/kclvm/sema/src/resolver/node.rs b/kclvm/sema/src/resolver/node.rs index 8264e71af..f37ba76e4 100644 --- a/kclvm/sema/src/resolver/node.rs +++ b/kclvm/sema/src/resolver/node.rs @@ -955,6 +955,9 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { } self.any_ty() } + TypeKind::Any => { + return self.any_ty(); + } _ => { range.0.filename = self.ctx.filename.clone(); range.1.filename = self.ctx.filename.clone(); diff --git a/kclvm/sema/src/resolver/tests.rs b/kclvm/sema/src/resolver/tests.rs index e25823bf0..0823551ba 100644 --- a/kclvm/sema/src/resolver/tests.rs +++ b/kclvm/sema/src/resolver/tests.rs @@ -857,7 +857,7 @@ fn test_pkg_asname() { .program; let scope = resolve_program(&mut program); let diags = scope.handler.diagnostics; - assert_eq!(diags.len(), 6); + assert_eq!(diags.len(), 4); assert_eq!(diags[0].messages[0].message, "name 'pkg' is not defined"); assert_eq!(diags[2].messages[0].message, "name 'subpkg' is not defined"); } diff --git a/kclvm/tools/src/LSP/src/test_data/completion_test/import/external/kcl.mod b/kclvm/tools/src/LSP/src/test_data/completion_test/import/external/kcl.mod index b7ca134eb..4ae72f07f 100644 --- a/kclvm/tools/src/LSP/src/test_data/completion_test/import/external/kcl.mod +++ b/kclvm/tools/src/LSP/src/test_data/completion_test/import/external/kcl.mod @@ -1,4 +1,4 @@ [package] [dependencies] -k8s = "1.28" +k8s = { oci = "oci://ghcr.io/kcl-lang/k8s", tag = "1.28" } diff --git a/test/grammar/schema/factory/test_0/main.k b/test/grammar/schema/factory/test_0/main.k new file mode 100644 index 000000000..a7469c78b --- /dev/null +++ b/test/grammar/schema/factory/test_0/main.k @@ -0,0 +1,19 @@ +schema DataA: + id?: int = 1 + value?: str = "value" + +schema DataB: + name?: str = "DataB" + +_dataFactory: {str:} = { + A = DataA + B = DataB +} +TypeA = _dataFactory["A"] +TypeB = _dataFactory["B"] +data0 = TypeA() +data1 = TypeB() +data2 = TypeA() {} +data3 = TypeB {} +data4 = TypeA {} +data5 = TypeB {} diff --git a/test/grammar/schema/factory/test_0/stdout.golden b/test/grammar/schema/factory/test_0/stdout.golden new file mode 100644 index 000000000..8d3b11720 --- /dev/null +++ b/test/grammar/schema/factory/test_0/stdout.golden @@ -0,0 +1,15 @@ +data0: + id: 1 + value: value +data1: + name: DataB +data2: + id: 1 + value: value +data3: + name: DataB +data4: + id: 1 + value: value +data5: + name: DataB diff --git a/test/grammar/schema/factory/test_1/main.k b/test/grammar/schema/factory/test_1/main.k new file mode 100644 index 000000000..420d34b19 --- /dev/null +++ b/test/grammar/schema/factory/test_1/main.k @@ -0,0 +1,19 @@ +schema Foo: + foo: str = "foo" + +schema Bar: + bar: str = "bar" + +factory = lambda type: any, attrs: {:} = {} -> Foo | Bar { + assert typeof(type) == "type" + func = $type + instance = func() {**attrs} +} + +_foo = factory(Foo) +_bar = factory(Bar) +if typeof(_foo) == "Foo": + foo = _foo as Foo + +if typeof(_bar) == "Bar": + bar = _bar as Bar diff --git a/test/grammar/schema/factory/test_1/stdout.golden b/test/grammar/schema/factory/test_1/stdout.golden new file mode 100644 index 000000000..be88aeb52 --- /dev/null +++ b/test/grammar/schema/factory/test_1/stdout.golden @@ -0,0 +1,4 @@ +foo: + foo: foo +bar: + bar: bar diff --git a/test/grammar/schema/factory/test_2/main.k b/test/grammar/schema/factory/test_2/main.k new file mode 100644 index 000000000..6dd6274b7 --- /dev/null +++ b/test/grammar/schema/factory/test_2/main.k @@ -0,0 +1,19 @@ +schema Foo: + foo: str + +schema Bar: + bar: str + +factory = lambda type: any, attrs: {:} = {} -> Foo | Bar { + assert typeof(type) == "type" + func = $type + instance = func() {**attrs} +} + +_foo = factory(Foo, {foo = "foo"}) # Note we set attributes here. +_bar = factory(Bar, {bar = "bar"}) # Note we set attributes here. +if typeof(_foo) == "Foo": + foo = _foo as Foo + +if typeof(_bar) == "Bar": + bar = _bar as Bar diff --git a/test/grammar/schema/factory/test_2/stdout.golden b/test/grammar/schema/factory/test_2/stdout.golden new file mode 100644 index 000000000..be88aeb52 --- /dev/null +++ b/test/grammar/schema/factory/test_2/stdout.golden @@ -0,0 +1,4 @@ +foo: + foo: foo +bar: + bar: bar diff --git a/test/grammar/types/type_as/type_as_5/kcl.mod b/test/grammar/types/type_as/type_as_5/kcl.mod new file mode 100644 index 000000000..e69de29bb diff --git a/test/grammar/types/type_as/type_as_5/main.k b/test/grammar/types/type_as/type_as_5/main.k new file mode 100644 index 000000000..748279ee9 --- /dev/null +++ b/test/grammar/types/type_as/type_as_5/main.k @@ -0,0 +1,3 @@ +import pkg +foo: pkg.Foo | pkg.Bar = pkg.Foo{} +bar = foo as pkg.Foo diff --git a/test/grammar/types/type_as/type_as_5/pkg/pkg.k b/test/grammar/types/type_as/type_as_5/pkg/pkg.k new file mode 100644 index 000000000..d6f2188c8 --- /dev/null +++ b/test/grammar/types/type_as/type_as_5/pkg/pkg.k @@ -0,0 +1,8 @@ +schema Foo: + foo: int = 1 + +schema Bar: + bar: int = 1 + +foo: Foo | Bar = Foo{} +bar = foo as Foo diff --git a/test/grammar/types/type_as/type_as_5/stdout.golden b/test/grammar/types/type_as/type_as_5/stdout.golden new file mode 100644 index 000000000..1ded5182a --- /dev/null +++ b/test/grammar/types/type_as/type_as_5/stdout.golden @@ -0,0 +1,4 @@ +foo: + foo: 1 +bar: + foo: 1 diff --git a/test/grammar/types/type_as/type_as_6/main.k b/test/grammar/types/type_as/type_as_6/main.k new file mode 100644 index 000000000..d6f2188c8 --- /dev/null +++ b/test/grammar/types/type_as/type_as_6/main.k @@ -0,0 +1,8 @@ +schema Foo: + foo: int = 1 + +schema Bar: + bar: int = 1 + +foo: Foo | Bar = Foo{} +bar = foo as Foo diff --git a/test/grammar/types/type_as/type_as_6/stdout.golden b/test/grammar/types/type_as/type_as_6/stdout.golden new file mode 100644 index 000000000..1ded5182a --- /dev/null +++ b/test/grammar/types/type_as/type_as_6/stdout.golden @@ -0,0 +1,4 @@ +foo: + foo: 1 +bar: + foo: 1 diff --git a/test/grammar/types/type_as/type_as_err_2/main.k b/test/grammar/types/type_as/type_as_err_2/main.k new file mode 100644 index 000000000..391dd040e --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_2/main.k @@ -0,0 +1,8 @@ +schema Foo: + foo: int = 1 + +schema Bar: + bar: int = 1 + +foo: Foo | Bar = Foo{} +bar = foo as Bar diff --git a/test/grammar/types/type_as/type_as_err_2/stderr.golden.py b/test/grammar/types/type_as/type_as_err_2/stderr.golden.py new file mode 100644 index 000000000..13a4cff5b --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_2/stderr.golden.py @@ -0,0 +1,19 @@ +import sys +import kclvm.kcl.error as kcl_error +import os + +cwd = os.path.dirname(os.path.realpath(__file__)) + +kcl_error.print_kcl_error_message( + kcl_error.get_exception( + err_type=kcl_error.ErrType.Evaluation_Error, + file_msgs=[ + kcl_error.ErrFileMsg( + filename=cwd + "/main.k", + line_no=2, + ) + ], + arg_msg="expect Bar, got Foo" + ), + file=sys.stdout +)