Skip to content

Commit

Permalink
feat: enhance the schema expr type check and impl the schema factory …
Browse files Browse the repository at this point in the history
…using the dynamic type

Signed-off-by: peefy <xpf6677@163.com>
  • Loading branch information
Peefy committed May 3, 2024
1 parent 904f74d commit 73d79ef
Show file tree
Hide file tree
Showing 24 changed files with 189 additions and 30 deletions.
9 changes: 6 additions & 3 deletions kclvm/evaluator/src/calculation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions kclvm/evaluator/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand Down Expand Up @@ -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()) {
Expand Down
2 changes: 1 addition & 1 deletion kclvm/evaluator/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 8 additions & 5 deletions kclvm/evaluator/src/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
}
Expand Down
10 changes: 5 additions & 5 deletions kclvm/runtime/src/value/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion kclvm/runtime/src/value/val_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ fn handle_schema(ctx: &Context, value: &ValueRef) -> Vec<ValueRef> {
}

/// 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 {
Expand Down
37 changes: 26 additions & 11 deletions kclvm/runtime/src/value/val_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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)
}
}
}
}
}
Expand Down Expand Up @@ -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();
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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))
}
}

Expand Down Expand Up @@ -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;
}
}
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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);
}
}

Expand Down
3 changes: 3 additions & 0 deletions kclvm/sema/src/resolver/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion kclvm/sema/src/resolver/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[package]

[dependencies]
k8s = "1.28"
k8s = { oci = "oci://ghcr.io/kcl-lang/k8s", tag = "1.28" }
19 changes: 19 additions & 0 deletions test/grammar/schema/factory/test_0/main.k
Original file line number Diff line number Diff line change
@@ -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 {}
15 changes: 15 additions & 0 deletions test/grammar/schema/factory/test_0/stdout.golden
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions test/grammar/schema/factory/test_1/main.k
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions test/grammar/schema/factory/test_1/stdout.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
foo:
foo: foo
bar:
bar: bar
19 changes: 19 additions & 0 deletions test/grammar/schema/factory/test_2/main.k
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions test/grammar/schema/factory/test_2/stdout.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
foo:
foo: foo
bar:
bar: bar
Empty file.
3 changes: 3 additions & 0 deletions test/grammar/types/type_as/type_as_5/main.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import pkg
foo: pkg.Foo | pkg.Bar = pkg.Foo{}
bar = foo as pkg.Foo
8 changes: 8 additions & 0 deletions test/grammar/types/type_as/type_as_5/pkg/pkg.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
schema Foo:
foo: int = 1

schema Bar:
bar: int = 1

foo: Foo | Bar = Foo{}
bar = foo as Foo
4 changes: 4 additions & 0 deletions test/grammar/types/type_as/type_as_5/stdout.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
foo:
foo: 1
bar:
foo: 1
Loading

0 comments on commit 73d79ef

Please sign in to comment.