From b11aafdb476ccd61cdf5f01b5883643b8f4ad609 Mon Sep 17 00:00:00 2001 From: peefy Date: Mon, 5 Aug 2024 11:59:19 +0800 Subject: [PATCH] fix: runtime type check can ignore type package name Signed-off-by: peefy --- ...ith-include-schema-type-path.response.json | 8 +- kclvm/api/src/testdata/rename/main.k | 0 kclvm/compiler/src/codegen/llvm/context.rs | 23 ++++- kclvm/compiler/src/codegen/llvm/node.rs | 4 +- kclvm/evaluator/src/context.rs | 5 +- kclvm/evaluator/src/ty.rs | 23 +++-- kclvm/runtime/src/api/kclvm.rs | 2 +- kclvm/runtime/src/stdlib/builtin.rs | 2 +- kclvm/runtime/src/value/mod.rs | 1 - kclvm/runtime/src/value/val_is_in.rs | 11 +++ kclvm/runtime/src/value/val_json.rs | 2 +- kclvm/runtime/src/value/val_plan.rs | 89 ++++++++++++------- kclvm/runtime/src/value/val_type.rs | 59 ++++++------ kclvm/sema/src/resolver/calculation.rs | 4 +- kclvm/sema/src/ty/into.rs | 2 +- kclvm/sema/src/ty/mod.rs | 31 ++++++- kclvm/sema/src/ty/unify.rs | 2 +- .../types/type_as/type_as_err_2/stderr.golden | 6 ++ .../type_as/type_as_err_2/stderr.golden.py | 19 ---- .../types/type_as/type_as_err_3/base/base.k | 10 +++ .../types/type_as/type_as_err_3/child/child.k | 17 ++++ .../types/type_as/type_as_err_3/kcl.mod | 4 + .../types/type_as/type_as_err_3/main.k | 19 ++++ .../types/type_as/type_as_err_3/stderr.golden | 6 ++ .../types/type_as/type_as_err_4/base/base.k | 10 +++ .../types/type_as/type_as_err_4/child/child.k | 9 ++ .../types/type_as/type_as_err_4/kcl.mod | 4 + .../types/type_as/type_as_err_4/main.k | 19 ++++ .../types/type_as/type_as_err_4/stderr.golden | 6 ++ 29 files changed, 293 insertions(+), 104 deletions(-) delete mode 100644 kclvm/api/src/testdata/rename/main.k create mode 100644 test/grammar/types/type_as/type_as_err_2/stderr.golden delete mode 100644 test/grammar/types/type_as/type_as_err_2/stderr.golden.py create mode 100644 test/grammar/types/type_as/type_as_err_3/base/base.k create mode 100644 test/grammar/types/type_as/type_as_err_3/child/child.k create mode 100644 test/grammar/types/type_as/type_as_err_3/kcl.mod create mode 100644 test/grammar/types/type_as/type_as_err_3/main.k create mode 100644 test/grammar/types/type_as/type_as_err_3/stderr.golden create mode 100644 test/grammar/types/type_as/type_as_err_4/base/base.k create mode 100644 test/grammar/types/type_as/type_as_err_4/child/child.k create mode 100644 test/grammar/types/type_as/type_as_err_4/kcl.mod create mode 100644 test/grammar/types/type_as/type_as_err_4/main.k create mode 100644 test/grammar/types/type_as/type_as_err_4/stderr.golden diff --git a/kclvm/api/src/testdata/exec-program-with-include-schema-type-path.response.json b/kclvm/api/src/testdata/exec-program-with-include-schema-type-path.response.json index aece90375..d37182c19 100644 --- a/kclvm/api/src/testdata/exec-program-with-include-schema-type-path.response.json +++ b/kclvm/api/src/testdata/exec-program-with-include-schema-type-path.response.json @@ -1,4 +1,6 @@ { - "json_result": "{\"alice\": {\"age\": 18, \"_type\": \"__main__.Person\"}}", - "yaml_result": "alice:\n age: 18\n _type: __main__.Person" -} + "json_result": "{\"alice\": {\"age\": 18, \"_type\": \"Person\"}}", + "yaml_result": "alice:\n age: 18\n _type: Person", + "log_message": "", + "err_message": "" +} \ No newline at end of file diff --git a/kclvm/api/src/testdata/rename/main.k b/kclvm/api/src/testdata/rename/main.k deleted file mode 100644 index e69de29bb..000000000 diff --git a/kclvm/compiler/src/codegen/llvm/context.rs b/kclvm/compiler/src/codegen/llvm/context.rs index f36a30e82..0f31dde83 100644 --- a/kclvm/compiler/src/codegen/llvm/context.rs +++ b/kclvm/compiler/src/codegen/llvm/context.rs @@ -30,6 +30,8 @@ use kclvm_sema::pkgpath_without_prefix; use kclvm_sema::plugin; use crate::codegen::abi::Align; +use crate::codegen::llvm::utils; +use crate::codegen::OBJECT_FILE_SUFFIX; use crate::codegen::{error as kcl_error, EmitOptions}; use crate::codegen::{ traits::*, ENTRY_NAME, GLOBAL_VAL_ALIGNMENT, MODULE_NAME, PKG_INIT_FUNCTION_SUFFIX, @@ -37,8 +39,6 @@ use crate::codegen::{ use crate::codegen::{CodeGenContext, GLOBAL_LEVEL}; use crate::value; -use crate::codegen::OBJECT_FILE_SUFFIX; - /// SCALAR_KEY denotes the temp scalar key for the global variable json plan process. const SCALAR_KEY: &str = ""; /// Float type string width mapping @@ -1164,6 +1164,7 @@ impl<'ctx> TypeCodeGen for LLVMCodeGenContext<'ctx> {} impl<'ctx> ProgramCodeGen for LLVMCodeGenContext<'ctx> { /// Current package path + #[inline] fn current_pkgpath(&self) -> String { self.pkgpath_stack .borrow_mut() @@ -1173,6 +1174,7 @@ impl<'ctx> ProgramCodeGen for LLVMCodeGenContext<'ctx> { } /// Current filename + #[inline] fn current_filename(&self) -> String { self.filename_stack .borrow_mut() @@ -1385,7 +1387,7 @@ impl<'ctx> LLVMCodeGenContext<'ctx> { if self.no_link && !has_main_pkg { for pkgpath in self.program.pkgs.keys() { let pkgpath = format!("{}{}", kclvm_runtime::PKG_PATH_PREFIX, pkgpath); - self.pkgpath_stack.borrow_mut().push(pkgpath.clone()); + self.push_pkgpath(&pkgpath); } } if !self.import_names.is_empty() { @@ -1413,7 +1415,7 @@ impl<'ctx> LLVMCodeGenContext<'ctx> { // pkgs may not contains main pkg in no link mode for (pkgpath, modules) in &self.program.pkgs { let pkgpath = format!("{}{}", kclvm_runtime::PKG_PATH_PREFIX, pkgpath); - self.pkgpath_stack.borrow_mut().push(pkgpath.clone()); + self.push_pkgpath(&pkgpath); // Init all builtin functions. self.init_scope(pkgpath.as_str()); self.compile_ast_modules(modules); @@ -2206,6 +2208,19 @@ impl<'ctx> LLVMCodeGenContext<'ctx> { var_map } + #[inline] + pub(crate) fn push_pkgpath(&self, pkgpath: &str) { + self.pkgpath_stack.borrow_mut().push(pkgpath.to_string()); + utils::update_ctx_pkgpath(self, pkgpath); + } + + #[inline] + pub(crate) fn pop_pkgpath(&self) { + if let Some(pkgpath) = self.pkgpath_stack.borrow_mut().pop() { + utils::update_ctx_pkgpath(self, &pkgpath); + } + } + /// Load value from name. pub fn load_value(&self, pkgpath: &str, names: &[&str]) -> CompileResult<'ctx> { if names.is_empty() { diff --git a/kclvm/compiler/src/codegen/llvm/node.rs b/kclvm/compiler/src/codegen/llvm/node.rs index e3353a184..a8bdf4d7a 100644 --- a/kclvm/compiler/src/codegen/llvm/node.rs +++ b/kclvm/compiler/src/codegen/llvm/node.rs @@ -316,7 +316,7 @@ impl<'ctx> TypedResultWalker<'ctx> for LLVMCodeGenContext<'ctx> { return self.ok_result(); } else { let pkgpath = format!("{}{}", PKG_PATH_PREFIX, import_stmt.path.node); - self.pkgpath_stack.borrow_mut().push(pkgpath.clone()); + self.push_pkgpath(&pkgpath); let has_pkgpath = self.program.pkgs.contains_key(&import_stmt.path.node); let func_before_block = if self.no_link { if has_pkgpath { @@ -364,7 +364,7 @@ impl<'ctx> TypedResultWalker<'ctx> for LLVMCodeGenContext<'ctx> { .expect(kcl_error::INTERNAL_ERROR_MSG), ); } - self.pkgpath_stack.borrow_mut().pop(); + self.pop_pkgpath(); if self.no_link { let name = format!( "${}.{}", diff --git a/kclvm/evaluator/src/context.rs b/kclvm/evaluator/src/context.rs index 98730ff50..10658c0c2 100644 --- a/kclvm/evaluator/src/context.rs +++ b/kclvm/evaluator/src/context.rs @@ -193,11 +193,14 @@ impl<'ctx> Evaluator<'ctx> { #[inline] pub(crate) fn push_pkgpath(&self, pkgpath: &str) { self.pkgpath_stack.borrow_mut().push(pkgpath.to_string()); + self.runtime_ctx.borrow_mut().set_kcl_pkgpath(pkgpath); } #[inline] pub(crate) fn pop_pkgpath(&self) { - self.pkgpath_stack.borrow_mut().pop(); + if let Some(pkgpath) = self.pkgpath_stack.borrow_mut().pop() { + self.runtime_ctx.borrow_mut().set_kcl_pkgpath(&pkgpath); + } } /// Append a global body into the scope. diff --git a/kclvm/evaluator/src/ty.rs b/kclvm/evaluator/src/ty.rs index 9bdd2f973..6b7b694d3 100644 --- a/kclvm/evaluator/src/ty.rs +++ b/kclvm/evaluator/src/ty.rs @@ -1,7 +1,7 @@ use kclvm_runtime::{ check_type, dereference_type, is_dict_type, is_list_type, is_type_union, schema_config_meta, - schema_runtime_type, separate_kv, split_type_union, ConfigEntryOperationKind, ValueRef, - BUILTIN_TYPES, KCL_TYPE_ANY, PKG_PATH_PREFIX, + schema_runtime_type, separate_kv, split_type_union, val_plan, ConfigEntryOperationKind, + ValueRef, BUILTIN_TYPES, KCL_TYPE_ANY, PKG_PATH_PREFIX, }; use scopeguard::defer; @@ -77,13 +77,21 @@ pub fn type_pack_and_check( converted_value = convert_collection_value(s, value, tpe); } // Runtime type check - checked = check_type(&converted_value, tpe, strict); + checked = check_type( + &converted_value, + &s.runtime_ctx.borrow().panic_info.kcl_pkgpath, + tpe, + strict, + ); if checked { break; } } if !checked { - panic!("expect {expected_type}, got {}", value.type_str()); + panic!( + "expect {expected_type}, got {}", + val_plan::type_of(value, true) + ); } converted_value } @@ -198,7 +206,12 @@ 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, false) { + if check_type( + &value, + &s.runtime_ctx.borrow().panic_info.kcl_pkgpath, + tpe, + false, + ) { return value; } } diff --git a/kclvm/runtime/src/api/kclvm.rs b/kclvm/runtime/src/api/kclvm.rs index 773014a34..4f7779a30 100644 --- a/kclvm/runtime/src/api/kclvm.rs +++ b/kclvm/runtime/src/api/kclvm.rs @@ -1,6 +1,6 @@ //! Copyright The KCL Authors. All rights reserved. -use crate::{new_mut_ptr, IndexMap, PlanOptions}; +use crate::{new_mut_ptr, val_plan::PlanOptions, IndexMap}; use generational_arena::Index; use indexmap::IndexSet; use serde::{Deserialize, Serialize}; diff --git a/kclvm/runtime/src/stdlib/builtin.rs b/kclvm/runtime/src/stdlib/builtin.rs index 4c49632ee..8ecf6506e 100644 --- a/kclvm/runtime/src/stdlib/builtin.rs +++ b/kclvm/runtime/src/stdlib/builtin.rs @@ -619,7 +619,7 @@ pub fn type_of(x: &ValueRef, full_name: &ValueRef) -> ValueRef { result += full_type_str; result += "."; } - result += &x.type_str(); + result += &schema.name; return ValueRef::str(result.as_str()); } } else if x.is_none() { diff --git a/kclvm/runtime/src/value/mod.rs b/kclvm/runtime/src/value/mod.rs index 2522b3270..41ab89858 100644 --- a/kclvm/runtime/src/value/mod.rs +++ b/kclvm/runtime/src/value/mod.rs @@ -58,7 +58,6 @@ pub mod val_unary; pub mod val_bin; pub mod val_plan; -pub use val_plan::*; pub mod val_str; diff --git a/kclvm/runtime/src/value/val_is_in.rs b/kclvm/runtime/src/value/val_is_in.rs index 86896d016..e93ee9d08 100644 --- a/kclvm/runtime/src/value/val_is_in.rs +++ b/kclvm/runtime/src/value/val_is_in.rs @@ -58,6 +58,17 @@ impl ValueRef { ) } + #[inline] + pub fn is_builtin(&self) -> bool { + matches!( + &*self.rc.borrow(), + Value::int_value(_) + | Value::float_value(_) + | Value::bool_value(_) + | Value::str_value(_) + ) + } + #[inline] pub fn is_config(&self) -> bool { matches!( diff --git a/kclvm/runtime/src/value/val_json.rs b/kclvm/runtime/src/value/val_json.rs index ec19f626d..f51fec886 100644 --- a/kclvm/runtime/src/value/val_json.rs +++ b/kclvm/runtime/src/value/val_json.rs @@ -7,7 +7,7 @@ use serde::{ Deserialize, Serialize, }; -use crate::{ConfigEntryOperationKind, Context, ValueRef, KCL_PRIVATE_VAR_PREFIX}; +use crate::{val_plan::KCL_PRIVATE_VAR_PREFIX, ConfigEntryOperationKind, Context, ValueRef}; macro_rules! tri { ($e:expr $(,)?) => { diff --git a/kclvm/runtime/src/value/val_plan.rs b/kclvm/runtime/src/value/val_plan.rs index 7227c11a7..82bef6d6f 100644 --- a/kclvm/runtime/src/value/val_plan.rs +++ b/kclvm/runtime/src/value/val_plan.rs @@ -166,28 +166,36 @@ fn handle_schema(ctx: &Context, value: &ValueRef) -> Vec { /// Returns the type path of the runtime value `v`. pub(crate) fn value_type_path(v: &ValueRef, full_name: bool) -> String { - match v.get_potential_schema_type() { - Some(ty_str) => { - if full_name { - match ty_str.strip_prefix('@') { - Some(ty_str) => ty_str.to_string(), - None => ty_str.to_string(), - } - } else { - let parts: Vec<&str> = ty_str.rsplit('.').collect(); - match parts.first() { - Some(v) => v.to_string(), - None => type_of(v, full_name), + if v.is_schema() { + type_of(v, full_name) + } else { + match v.get_potential_schema_type() { + Some(ty_str) => { + let ty = if full_name { + match ty_str.strip_prefix('@') { + Some(ty_str) => ty_str.to_string(), + None => ty_str.to_string(), + } + } else { + let parts: Vec<&str> = ty_str.rsplit('.').collect(); + match parts.first() { + Some(v) => v.to_string(), + None => type_of(v, full_name), + } + }; + match ty.strip_prefix(&format!("{MAIN_PKG_PATH}.")) { + Some(ty) => ty.to_string(), + None => ty, } } + None => type_of(v, full_name), } - None => type_of(v, full_name), } } /// Returns the type path of the runtime value `v`. #[inline] -fn type_of(v: &ValueRef, full_name: bool) -> String { +pub fn type_of(v: &ValueRef, full_name: bool) -> String { builtin::type_of(v, &ValueRef::bool(full_name)).as_str() } @@ -272,7 +280,7 @@ impl ValueRef { #[cfg(test)] mod test_value_plan { - use crate::{schema_runtime_type, Context, PlanOptions, ValueRef, MAIN_PKG_PATH}; + use crate::{schema_runtime_type, val_plan::PlanOptions, Context, ValueRef, MAIN_PKG_PATH}; use super::filter_results; @@ -292,6 +300,20 @@ mod test_value_plan { schema } + fn get_test_schema_value_with_pkg() -> ValueRef { + let mut schema = ValueRef::dict(None).dict_to_schema( + TEST_SCHEMA_NAME, + "pkg", + &[], + &ValueRef::dict(None), + &ValueRef::dict(None), + None, + None, + ); + schema.set_potential_schema_type(&schema_runtime_type(TEST_SCHEMA_NAME, MAIN_PKG_PATH)); + schema + } + #[test] fn test_filter_results() { let ctx = Context::new(); @@ -346,9 +368,8 @@ mod test_value_plan { fn test_value_plan_with_options() { let mut ctx = Context::new(); ctx.plan_opts = PlanOptions::default(); - let schema = get_test_schema_value(); let mut config = ValueRef::dict(None); - config.dict_update_key_value("data", schema); + config.dict_update_key_value("data", get_test_schema_value()); config.dict_update_key_value("_hidden", ValueRef::int(1)); config.dict_update_key_value("vec", ValueRef::list(None)); config.dict_update_key_value("empty", ValueRef::none()); @@ -360,53 +381,55 @@ mod test_value_plan { let (json_string, yaml_string) = config.plan(&ctx); assert_eq!( json_string, - "{\"data\": {\"_type\": \"__main__.Data\"}, \"vec\": [], \"empty\": null}" - ); - assert_eq!( - yaml_string, - "data:\n _type: __main__.Data\nvec: []\nempty: null" + "{\"data\": {\"_type\": \"Data\"}, \"vec\": [], \"empty\": null}" ); + assert_eq!(yaml_string, "data:\n _type: Data\nvec: []\nempty: null"); ctx.plan_opts.show_hidden = true; let (json_string, yaml_string) = config.plan(&ctx); assert_eq!( json_string, - "{\"data\": {\"_type\": \"__main__.Data\"}, \"_hidden\": 1, \"vec\": [], \"empty\": null}" + "{\"data\": {\"_type\": \"Data\"}, \"_hidden\": 1, \"vec\": [], \"empty\": null}" ); assert_eq!( yaml_string, - "data:\n _type: __main__.Data\n_hidden: 1\nvec: []\nempty: null" + "data:\n _type: Data\n_hidden: 1\nvec: []\nempty: null" ); ctx.plan_opts.sort_keys = true; let (json_string, yaml_string) = config.plan(&ctx); assert_eq!( json_string, - "{\"_hidden\": 1, \"data\": {\"_type\": \"__main__.Data\"}, \"empty\": null, \"vec\": []}" + "{\"_hidden\": 1, \"data\": {\"_type\": \"Data\"}, \"empty\": null, \"vec\": []}" ); assert_eq!( yaml_string, - "_hidden: 1\ndata:\n _type: __main__.Data\nempty: null\nvec: []" + "_hidden: 1\ndata:\n _type: Data\nempty: null\nvec: []" ); ctx.plan_opts.disable_none = true; let (json_string, yaml_string) = config.plan(&ctx); assert_eq!( json_string, - "{\"_hidden\": 1, \"data\": {\"_type\": \"__main__.Data\"}, \"vec\": []}" - ); - assert_eq!( - yaml_string, - "_hidden: 1\ndata:\n _type: __main__.Data\nvec: []" + "{\"_hidden\": 1, \"data\": {\"_type\": \"Data\"}, \"vec\": []}" ); + assert_eq!(yaml_string, "_hidden: 1\ndata:\n _type: Data\nvec: []"); ctx.plan_opts.disable_empty_list = true; let (json_string, yaml_string) = config.plan(&ctx); assert_eq!( json_string, - "{\"_hidden\": 1, \"data\": {\"_type\": \"__main__.Data\"}}" + "{\"_hidden\": 1, \"data\": {\"_type\": \"Data\"}}" + ); + assert_eq!(yaml_string, "_hidden: 1\ndata:\n _type: Data"); + + config.dict_update_key_value("data_with_pkg", get_test_schema_value_with_pkg()); + let (json_string, yaml_string) = config.plan(&ctx); + assert_eq!(json_string, "{\"_hidden\": 1, \"data\": {\"_type\": \"Data\"}, \"data_with_pkg\": {\"_type\": \"pkg.Data\"}}"); + assert_eq!( + yaml_string, + "_hidden: 1\ndata:\n _type: Data\ndata_with_pkg:\n _type: pkg.Data" ); - assert_eq!(yaml_string, "_hidden: 1\ndata:\n _type: __main__.Data"); ctx.plan_opts.query_paths = vec!["data".to_string()]; let (json_string, yaml_string) = config.plan(&ctx); diff --git a/kclvm/runtime/src/value/val_type.rs b/kclvm/runtime/src/value/val_type.rs index 27c3b31b4..8b19a2d1d 100644 --- a/kclvm/runtime/src/value/val_type.rs +++ b/kclvm/runtime/src/value/val_type.rs @@ -185,13 +185,16 @@ pub fn type_pack_and_check( converted_value = convert_collection_value(ctx, value, &tpe); } // Runtime type check - checked = check_type(&converted_value, &tpe, strict); + checked = check_type(&converted_value, &ctx.panic_info.kcl_pkgpath, &tpe, strict); if checked { break; } } if !checked { - panic!("expect {expected_type}, got {}", value.type_str()); + panic!( + "expect {expected_type}, got {}", + val_plan::type_of(value, true) + ); } converted_value } @@ -375,7 +378,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, false) { + if check_type(&value, &ctx.panic_info.kcl_pkgpath, tpe, false) { return value; } } @@ -384,7 +387,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, strict: bool) -> bool { +pub fn check_type(value: &ValueRef, pkgpath: &str, tpe: &str, strict: bool) -> bool { if tpe.is_empty() || tpe == KCL_TYPE_ANY { return true; } @@ -392,7 +395,7 @@ pub fn check_type(value: &ValueRef, tpe: &str, strict: bool) -> bool { return true; } if is_type_union(tpe) { - return check_type_union(value, tpe); + return check_type_union(value, pkgpath, tpe); } if check_type_literal(value, tpe) { @@ -404,11 +407,11 @@ pub fn check_type(value: &ValueRef, tpe: &str, strict: bool) -> bool { } // if value type is a dict type e.g. {"k": "v"} else if value.is_dict() { - return check_type_dict(value, tpe); + return check_type_dict(value, pkgpath, tpe); } // if value type is a list type e.g. [1, 2, 3] else if value.is_list() { - return check_type_list(value, tpe); + return check_type_list(value, pkgpath, tpe); } else if !value.is_none_or_undefined() { // if value type is a built-in type e.g. str, int, float, bool if match_builtin_type(value, tpe) { @@ -418,7 +421,12 @@ pub fn check_type(value: &ValueRef, tpe: &str, strict: bool) -> bool { } if value.is_schema() { if strict { - let value_ty = crate::val_plan::value_type_path(value, tpe.contains('.')); + let value_ty = crate::val_plan::value_type_path(value, true); + let tpe = if pkgpath != "" && pkgpath != MAIN_PKG_PATH && !tpe.contains(".") { + format!("{pkgpath}.{tpe}") + } else { + tpe.to_string() + }; let tpe = match tpe.strip_prefix('@') { Some(ty_str) => ty_str.to_string(), None => tpe.to_string(), @@ -438,14 +446,14 @@ pub fn check_type(value: &ValueRef, tpe: &str, strict: bool) -> bool { } /// check_type_union returns the value wether match the given the union type string -pub fn check_type_union(value: &ValueRef, tpe: &str) -> bool { +pub fn check_type_union(value: &ValueRef, pkgpath: &str, tpe: &str) -> bool { let expected_types = split_type_union(tpe); if expected_types.len() <= 1 { false } else { expected_types .iter() - .any(|tpe| check_type(value, tpe, false)) + .any(|tpe| check_type(value, pkgpath, tpe, false)) } } @@ -492,7 +500,7 @@ pub fn check_number_multiplier_type(value: &ValueRef, tpe: &str) -> bool { } /// check_type_dict returns the value wether match the given the dict type string -pub fn check_type_dict(value: &ValueRef, tpe: &str) -> bool { +pub fn check_type_dict(value: &ValueRef, pkgpath: &str, tpe: &str) -> bool { if tpe.is_empty() { return true; } @@ -503,7 +511,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, false) { + if !check_type(v, pkgpath, &expected_value_type, false) { return false; } } @@ -511,7 +519,7 @@ pub fn check_type_dict(value: &ValueRef, tpe: &str) -> bool { } /// check_type_list returns the value wether match the given the list type string -pub fn check_type_list(value: &ValueRef, tpe: &str) -> bool { +pub fn check_type_list(value: &ValueRef, pkgpath: &str, tpe: &str) -> bool { if tpe.is_empty() { return true; } @@ -521,27 +529,26 @@ 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, false) { + if !check_type(v, pkgpath, &expected_type, false) { return false; } } true } -/// match_builtin_type returns the value wether match the given the type string +/// match_builtin_type returns the value wether match the given the type string. #[inline] pub fn match_builtin_type(value: &ValueRef, tpe: &str) -> bool { - value.type_str() == *tpe || (value.type_str() == BUILTIN_TYPE_INT && tpe == BUILTIN_TYPE_FLOAT) + (value.is_builtin() && value.type_str() == *tpe) + || (value.type_str() == BUILTIN_TYPE_INT && tpe == BUILTIN_TYPE_FLOAT) } -/// match_function_type returns the value wether match the given the function type string +/// match_function_type returns the value wether match the given the function type string. #[inline] pub fn match_function_type(value: &ValueRef, tpe: &str) -> bool { - value.type_str() == *tpe - || (value.type_str() == KCL_TYPE_FUNCTION - && tpe.contains("(") - && tpe.contains(")") - && tpe.contains("->")) + value.type_str() == KCL_TYPE_FUNCTION + && (tpe == KCL_TYPE_FUNCTION + || (tpe.contains("(") && tpe.contains(")") && tpe.contains("->"))) } /// is_literal_type returns the type string whether is a literal type @@ -754,7 +761,7 @@ mod test_value_type { (ValueRef::str("0"), "int", false), ]; for (value, tpe, expected) in cases { - assert_eq!(check_type(&value, tpe, false), expected); + assert_eq!(check_type(&value, "", tpe, false), expected,); } } @@ -775,7 +782,7 @@ mod test_value_type { (ValueRef::bool(true), "int|str", false), ]; for (value, tpe, expected) in cases { - assert_eq!(check_type_union(&value, tpe), expected); + assert_eq!(check_type_union(&value, "", tpe), expected); } } @@ -827,7 +834,7 @@ mod test_value_type { (ValueRef::dict_int(&[("key", 1)]), "{str:str}", false), ]; for (value, tpe, expected) in cases { - assert_eq!(check_type_dict(&value, tpe), expected); + assert_eq!(check_type_dict(&value, "", tpe), expected); } } @@ -866,7 +873,7 @@ mod test_value_type { (ValueRef::list_int(&[1, 2, 3]), "[bool]", false), ]; for (value, tpe, expected) in cases { - assert_eq!(check_type_list(&value, tpe), expected); + assert_eq!(check_type_list(&value, "", tpe), expected); } } diff --git a/kclvm/sema/src/resolver/calculation.rs b/kclvm/sema/src/resolver/calculation.rs index fbff910bc..c056e8038 100644 --- a/kclvm/sema/src/resolver/calculation.rs +++ b/kclvm/sema/src/resolver/calculation.rs @@ -185,8 +185,8 @@ impl<'ctx> Resolver<'ctx> { self.handler.add_type_error( &format!( "Conversion of type '{}' to type '{}' may be a mistake because neither type sufficiently overlaps with the other", - t1.ty_str(), - t2.ty_str() + t1.full_ty_str(), + t2.full_ty_str() ), range.clone(), ); diff --git a/kclvm/sema/src/ty/into.rs b/kclvm/sema/src/ty/into.rs index f08fb49d9..b6f96d19c 100644 --- a/kclvm/sema/src/ty/into.rs +++ b/kclvm/sema/src/ty/into.rs @@ -102,7 +102,7 @@ impl Type { .map(|ty| ty.into_type_annotation_str()) .collect::>() .join(" | "), - TypeKind::Schema(schema_ty) => schema_ty.ty_str_with_pkgpath(), + TypeKind::Schema(schema_ty) => schema_ty.ty_str_with_at_pkgpath_prefix(), TypeKind::NumberMultiplier(number_multiplier) => { if number_multiplier.is_literal { format!( diff --git a/kclvm/sema/src/ty/mod.rs b/kclvm/sema/src/ty/mod.rs index d4b4e34b8..52a488bf0 100644 --- a/kclvm/sema/src/ty/mod.rs +++ b/kclvm/sema/src/ty/mod.rs @@ -48,7 +48,7 @@ impl Type { pub fn contains_flags(&self, flag: TypeFlags) -> bool { self.flags.contains(flag) } - /// Returns the type string used for error handler. + /// Returns the type string used for the error handler. pub fn ty_str(&self) -> String { match &self.kind { TypeKind::None => NONE_TYPE_STR.to_string(), @@ -89,6 +89,23 @@ impl Type { } } + /// Returns the full type string with the package path used for the error handler. + pub fn full_ty_str(&self) -> String { + match &self.kind { + TypeKind::List(item_ty) => format!("[{}]", item_ty.full_ty_str()), + TypeKind::Dict(DictType { key_ty, val_ty, .. }) => { + format!("{{{}:{}}}", key_ty.full_ty_str(), val_ty.full_ty_str()) + } + TypeKind::Union(types) => types + .iter() + .map(|ty| ty.full_ty_str()) + .collect::>() + .join(" | "), + TypeKind::Schema(schema_ty) => schema_ty.full_ty_str(), + _ => self.ty_str(), + } + } + pub fn ty_doc(&self) -> Option { match &self.kind { TypeKind::Schema(schema) => Some(schema.doc.clone()), @@ -232,14 +249,22 @@ pub struct SchemaType { } impl SchemaType { - /// Get the object type string with pkgpath - pub fn ty_str_with_pkgpath(&self) -> String { + /// Get the object type string with @pkgpath prefix. + pub fn ty_str_with_at_pkgpath_prefix(&self) -> String { if self.pkgpath.is_empty() || self.pkgpath == MAIN_PKG { self.name.clone() } else { format!("@{}.{}", self.pkgpath, self.name) } } + /// Get the object type string. + pub fn full_ty_str(&self) -> String { + if self.pkgpath.is_empty() || self.pkgpath == MAIN_PKG { + self.name.clone() + } else { + format!("{}.{}", self.pkgpath, self.name) + } + } /// Is `name` a schema member function pub fn is_member_functions(&self, name: &str) -> bool { !self.is_instance && SCHEMA_MEMBER_FUNCTIONS.contains(&name) diff --git a/kclvm/sema/src/ty/unify.rs b/kclvm/sema/src/ty/unify.rs index 4c09de8e7..3d1c49de6 100644 --- a/kclvm/sema/src/ty/unify.rs +++ b/kclvm/sema/src/ty/unify.rs @@ -102,7 +102,7 @@ pub fn equal(ty_lhs: TypeRef, ty_rhs: TypeRef) -> bool { /// Whether the schema is sub schema of another schema. pub fn is_sub_schema_of(schema_ty_lhs: &SchemaType, schema_ty_rhs: &SchemaType) -> bool { - if schema_ty_lhs.ty_str_with_pkgpath() == schema_ty_rhs.ty_str_with_pkgpath() { + if schema_ty_lhs.full_ty_str() == schema_ty_rhs.full_ty_str() { true } else { match &schema_ty_lhs.base { diff --git a/test/grammar/types/type_as/type_as_err_2/stderr.golden b/test/grammar/types/type_as/type_as_err_2/stderr.golden new file mode 100644 index 000000000..9d5e5183e --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_2/stderr.golden @@ -0,0 +1,6 @@ +error[E3M38]: EvaluationError + --> ${CWD}/main.k:8:1 + | +8 | bar = foo as Bar + | expect Bar, got Foo + | 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 deleted file mode 100644 index 13a4cff5b..000000000 --- a/test/grammar/types/type_as/type_as_err_2/stderr.golden.py +++ /dev/null @@ -1,19 +0,0 @@ -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 -) diff --git a/test/grammar/types/type_as/type_as_err_3/base/base.k b/test/grammar/types/type_as/type_as_err_3/base/base.k new file mode 100644 index 000000000..ed80aae04 --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_3/base/base.k @@ -0,0 +1,10 @@ +schema Base: + name: str + +schema A(Base): + bar: str + +a = A { + name: "base.A" + bar: "xxx" +} diff --git a/test/grammar/types/type_as/type_as_err_3/child/child.k b/test/grammar/types/type_as/type_as_err_3/child/child.k new file mode 100644 index 000000000..c3faf942b --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_3/child/child.k @@ -0,0 +1,17 @@ +import base + +schema A(base.Base): + foo: str + +a = A { + name: "child.A" + foo: "test" +} + +# Cast to used for skip compile-time type check +_base_a: base.Base = base.a + +# Must fail at runtime: typeof(_base_a) == 'base.A' +base_a: A = _base_a as A +base_a_type = typeof(base_a, True) +child_a_type = typeof(a, True) diff --git a/test/grammar/types/type_as/type_as_err_3/kcl.mod b/test/grammar/types/type_as/type_as_err_3/kcl.mod new file mode 100644 index 000000000..d29a2e503 --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_3/kcl.mod @@ -0,0 +1,4 @@ +[package] +name = "__main__" +edition = "0.0.1" +version = "0.0.1" diff --git a/test/grammar/types/type_as/type_as_err_3/main.k b/test/grammar/types/type_as/type_as_err_3/main.k new file mode 100644 index 000000000..1b2085fe8 --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_3/main.k @@ -0,0 +1,19 @@ +import base +import child + +schema A(base.Base): + name: str + main: str + +a = A { + name: "main.A" + main: "123" +} + +type AA = child.A + +# Cast to / used for skip compile-time type check +_child_a: base.Base = child.a +# Must fail at runtime: typeof(_child_a) == 'child.A' +child_a = _child_a as A +child_a_type = typeof(_child_a,True) diff --git a/test/grammar/types/type_as/type_as_err_3/stderr.golden b/test/grammar/types/type_as/type_as_err_3/stderr.golden new file mode 100644 index 000000000..8dc529e3c --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_3/stderr.golden @@ -0,0 +1,6 @@ +error[E3M38]: EvaluationError + --> ${CWD}/child/child.k:15:1 + | +15 | base_a: A = _base_a as A + | expect A, got base.A + | diff --git a/test/grammar/types/type_as/type_as_err_4/base/base.k b/test/grammar/types/type_as/type_as_err_4/base/base.k new file mode 100644 index 000000000..ed80aae04 --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_4/base/base.k @@ -0,0 +1,10 @@ +schema Base: + name: str + +schema A(Base): + bar: str + +a = A { + name: "base.A" + bar: "xxx" +} diff --git a/test/grammar/types/type_as/type_as_err_4/child/child.k b/test/grammar/types/type_as/type_as_err_4/child/child.k new file mode 100644 index 000000000..13d27abf5 --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_4/child/child.k @@ -0,0 +1,9 @@ +import base + +schema A(base.Base): + foo: str + +a = A { + name: "child.A" + foo: "test" +} diff --git a/test/grammar/types/type_as/type_as_err_4/kcl.mod b/test/grammar/types/type_as/type_as_err_4/kcl.mod new file mode 100644 index 000000000..d29a2e503 --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_4/kcl.mod @@ -0,0 +1,4 @@ +[package] +name = "__main__" +edition = "0.0.1" +version = "0.0.1" diff --git a/test/grammar/types/type_as/type_as_err_4/main.k b/test/grammar/types/type_as/type_as_err_4/main.k new file mode 100644 index 000000000..1b2085fe8 --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_4/main.k @@ -0,0 +1,19 @@ +import base +import child + +schema A(base.Base): + name: str + main: str + +a = A { + name: "main.A" + main: "123" +} + +type AA = child.A + +# Cast to / used for skip compile-time type check +_child_a: base.Base = child.a +# Must fail at runtime: typeof(_child_a) == 'child.A' +child_a = _child_a as A +child_a_type = typeof(_child_a,True) diff --git a/test/grammar/types/type_as/type_as_err_4/stderr.golden b/test/grammar/types/type_as/type_as_err_4/stderr.golden new file mode 100644 index 000000000..857ba7d21 --- /dev/null +++ b/test/grammar/types/type_as/type_as_err_4/stderr.golden @@ -0,0 +1,6 @@ +error[E3M38]: EvaluationError + --> ${CWD}/main.k:18:1 + | +18 | child_a = _child_a as A + | expect A, got child.A + |