Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enhance the schema expr type check and impl the schema factory using the dynamic type #1269

Merged
merged 1 commit into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 2 additions & 2 deletions kclvm/sema/src/resolver/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -857,9 +857,9 @@ 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");
assert_eq!(diags[1].messages[0].message, "name 'subpkg' is not defined");
}

#[test]
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
Loading