From 29266c059602c71668a5992f04db8f7829e925c8 Mon Sep 17 00:00:00 2001 From: He1pa <18012015693@163.com> Date: Tue, 14 Jan 2025 22:44:25 +0800 Subject: [PATCH] feat: enhance evaluator func type_pack_and_check err msg (#1822) * feat: enhance evaluator func type_pack_and_check err msg Signed-off-by: he1pa <18012015693@163.com> * insert missing attr msg Signed-off-by: he1pa <18012015693@163.com> * fix optional attr Signed-off-by: he1pa <18012015693@163.com> * fix ut Signed-off-by: he1pa <18012015693@163.com> * fix ut Signed-off-by: he1pa <18012015693@163.com> --------- Signed-off-by: he1pa <18012015693@163.com> --- kclvm/evaluator/src/schema.rs | 23 +++++++ kclvm/evaluator/src/ty.rs | 69 +++++++++++++++++-- .../runtime_ty/runtime_ty_err_0/stderr.golden | 4 +- .../types/type_as/type_as_err_2/stderr.golden | 4 +- .../types/type_as/type_as_err_3/stderr.golden | 4 +- .../types/type_as/type_as_err_4/stderr.golden | 4 +- 6 files changed, 98 insertions(+), 10 deletions(-) diff --git a/kclvm/evaluator/src/schema.rs b/kclvm/evaluator/src/schema.rs index 93d832e75..a67d4f368 100644 --- a/kclvm/evaluator/src/schema.rs +++ b/kclvm/evaluator/src/schema.rs @@ -205,6 +205,29 @@ impl SchemaEvalContext { false } + /// Get all attribute from schema + pub fn get_attrs(s: &Evaluator, ctx: &SchemaEvalContextRef) -> Vec<(String, bool)> { + let mut attrs = vec![]; + for stmt in &ctx.borrow().node.body { + if let ast::Stmt::SchemaAttr(attr) = &stmt.node { + attrs.push((attr.name.node.clone(), attr.is_optional)); + } + } + if let Some(index) = ctx.borrow().parent { + let frame = { + let frames = s.frames.borrow(); + frames + .get(index) + .expect(kcl_error::INTERNAL_ERROR_MSG) + .clone() + }; + if let Proxy::Schema(schema) = &frame.proxy { + attrs.extend(SchemaEvalContext::get_attrs(s, &schema.ctx)); + } + } + attrs + } + /// Whether the index signature is the schema context. pub fn has_index_signature(s: &Evaluator, ctx: &SchemaEvalContextRef) -> bool { if ctx.borrow().node.index_signature.is_some() { diff --git a/kclvm/evaluator/src/ty.rs b/kclvm/evaluator/src/ty.rs index 6b7b694d3..e66063f9b 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, val_plan, ConfigEntryOperationKind, - ValueRef, BUILTIN_TYPES, KCL_TYPE_ANY, PKG_PATH_PREFIX, + check_type, dereference_type, is_dict_type, is_list_type, is_schema_type, is_type_union, + schema_config_meta, schema_runtime_type, separate_kv, split_type_union, val_plan, + ConfigEntryOperationKind, ValueRef, BUILTIN_TYPES, KCL_TYPE_ANY, PKG_PATH_PREFIX, }; use scopeguard::defer; @@ -72,7 +72,7 @@ pub fn type_pack_and_check( let mut checked = false; let mut converted_value = value.clone(); let expected_type = &expected_types.join(" | ").replace('@', ""); - for tpe in expected_types { + for tpe in &expected_types { if !is_schema { converted_value = convert_collection_value(s, value, tpe); } @@ -88,9 +88,66 @@ pub fn type_pack_and_check( } } if !checked { + let mut error_msgs = vec![]; + for tpe in &expected_types { + if is_schema_type(tpe) { + let schema_type_name = if tpe.contains('.') { + if tpe.starts_with(PKG_PATH_PREFIX) { + tpe.to_string() + } else { + format!("{PKG_PATH_PREFIX}{tpe}") + } + } else { + format!("{}.{}", s.current_pkgpath(), tpe) + }; + + if let Some(index) = s.schemas.borrow().get(&schema_type_name) { + let frame = { + let frames = s.frames.borrow(); + frames + .get(*index) + .expect(kcl_error::INTERNAL_ERROR_MSG) + .clone() + }; + if let Proxy::Schema(caller) = &frame.proxy { + if value.is_config() { + let config = value.as_dict_ref(); + for (key, _) in &config.values { + let no_such_attr = + !SchemaEvalContext::has_attr(s, &caller.ctx, key) + && !key.starts_with('_'); + let has_index_signature = + SchemaEvalContext::has_index_signature(s, &caller.ctx); + if !has_index_signature && no_such_attr { + error_msgs.push(format!( + "Schema {} does not contain attribute {}", + tpe, key + )); + } + } + + for (attr, is_optional) in SchemaEvalContext::get_attrs(s, &caller.ctx) + { + if !config.values.contains_key(&attr) && !is_optional { + error_msgs.push(format!( + "Schema {}'s attribute {} is missing", + tpe, attr + )); + } + } + } + } + } + } + } panic!( - "expect {expected_type}, got {}", - val_plan::type_of(value, true) + "expect {expected_type}, got {}{}", + val_plan::type_of(value, true), + if error_msgs.is_empty() { + "".to_string() + } else { + format!(". For details:\n{}", error_msgs.join("\n")) + } ); } converted_value diff --git a/test/grammar/types/runtime_ty/runtime_ty_err_0/stderr.golden b/test/grammar/types/runtime_ty/runtime_ty_err_0/stderr.golden index 95f352159..aa232308f 100644 --- a/test/grammar/types/runtime_ty/runtime_ty_err_0/stderr.golden +++ b/test/grammar/types/runtime_ty/runtime_ty_err_0/stderr.golden @@ -2,5 +2,7 @@ error[E3M38]: EvaluationError --> ${CWD}/main.k:7:1 | 7 | person: Person = json.decode('{"err_name": "Alice", "age": 18}') - | expect Person, got dict + | expect Person, got dict. For details: +Schema Person does not contain attribute err_name +Schema Person's attribute name is missing | \ No newline at end of file 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 index 9d5e5183e..35e4fac94 100644 --- a/test/grammar/types/type_as/type_as_err_2/stderr.golden +++ b/test/grammar/types/type_as/type_as_err_2/stderr.golden @@ -2,5 +2,7 @@ error[E3M38]: EvaluationError --> ${CWD}/main.k:8:1 | 8 | bar = foo as Bar - | expect Bar, got Foo + | expect Bar, got Foo. For details: +Schema Bar does not contain attribute foo +Schema Bar's attribute bar is missing | 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 index 8dc529e3c..d67a20374 100644 --- a/test/grammar/types/type_as/type_as_err_3/stderr.golden +++ b/test/grammar/types/type_as/type_as_err_3/stderr.golden @@ -2,5 +2,7 @@ error[E3M38]: EvaluationError --> ${CWD}/child/child.k:15:1 | 15 | base_a: A = _base_a as A - | expect A, got base.A + | expect A, got base.A. For details: +Schema A does not contain attribute bar +Schema A's attribute foo is missing | 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 index 857ba7d21..d363e7192 100644 --- a/test/grammar/types/type_as/type_as_err_4/stderr.golden +++ b/test/grammar/types/type_as/type_as_err_4/stderr.golden @@ -2,5 +2,7 @@ error[E3M38]: EvaluationError --> ${CWD}/main.k:18:1 | 18 | child_a = _child_a as A - | expect A, got child.A + | expect A, got child.A. For details: +Schema A does not contain attribute foo +Schema A's attribute main is missing |