diff --git a/kclvm/api/src/service/ty.rs b/kclvm/api/src/service/ty.rs index 73443e5cd..d8fcd571d 100644 --- a/kclvm/api/src/service/ty.rs +++ b/kclvm/api/src/service/ty.rs @@ -1,7 +1,7 @@ use crate::gpyrpc::{Decorator, KclType}; use indexmap::IndexSet; use kclvm_runtime::SCHEMA_SETTINGS_ATTR_NAME; -use kclvm_sema::ty::{SchemaType, Type}; +use kclvm_sema::ty::{DictType, SchemaType, Type}; use std::collections::HashMap; /// Convert the kcl sematic type to the kcl protobuf type. @@ -12,7 +12,7 @@ pub(crate) fn kcl_ty_to_pb_ty(ty: &Type) -> KclType { item: Some(Box::new(kcl_ty_to_pb_ty(item_ty))), ..Default::default() }, - kclvm_sema::ty::TypeKind::Dict(key_ty, val_ty) => KclType { + kclvm_sema::ty::TypeKind::Dict(DictType { key_ty, val_ty, .. }) => KclType { r#type: "dict".to_string(), key: Some(Box::new(kcl_ty_to_pb_ty(key_ty))), item: Some(Box::new(kcl_ty_to_pb_ty(val_ty))), diff --git a/kclvm/sema/src/resolver/attr.rs b/kclvm/sema/src/resolver/attr.rs index 958eb0bcc..de30b2c0b 100644 --- a/kclvm/sema/src/resolver/attr.rs +++ b/kclvm/sema/src/resolver/attr.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use crate::builtin::system_module::{get_system_module_members, UNITS, UNITS_NUMBER_MULTIPLIER}; use crate::builtin::STRING_MEMBER_FUNCTIONS; use crate::resolver::Resolver; -use crate::ty::{ModuleKind, Type, TypeKind}; +use crate::ty::{DictType, ModuleKind, Type, TypeKind}; use kclvm_error::diagnostic::Range; use kclvm_error::*; @@ -46,7 +46,17 @@ impl<'ctx> Resolver<'ctx> { Some(ty) => (true, Rc::new(ty.clone())), None => (false, self.any_ty()), }, - TypeKind::Dict(_, val_ty) => (true, Rc::new(val_ty.as_ref().clone())), + TypeKind::Dict(DictType { + key_ty: _, + val_ty, + attrs, + }) => ( + true, + attrs + .get(attr) + .map(|attr| attr.ty.clone()) + .unwrap_or(Rc::new(val_ty.as_ref().clone())), + ), // union type load attr based the type guard. e.g, a: str|int; if a is str: xxx; if a is int: xxx; // return sup([self.load_attr_type(t, attr, filename, line, column) for t in obj.types]) TypeKind::Union(_) => (true, self.any_ty()), diff --git a/kclvm/sema/src/resolver/config.rs b/kclvm/sema/src/resolver/config.rs index 4754fea72..5e50550d8 100644 --- a/kclvm/sema/src/resolver/config.rs +++ b/kclvm/sema/src/resolver/config.rs @@ -5,9 +5,10 @@ use super::{ scope::{ScopeKind, ScopeObject, ScopeObjectKind}, Resolver, }; -use crate::ty::sup; -use crate::ty::SchemaType; +use crate::ty::{sup, DictType}; +use crate::ty::{Attr, SchemaType}; use crate::ty::{Type, TypeKind}; +use indexmap::IndexMap; use kclvm_ast::ast; use kclvm_ast::pos::GetPos; use kclvm_error::{diagnostic::Range, ErrorKind, Message, Position, Style}; @@ -73,7 +74,9 @@ impl<'ctx> Resolver<'ctx> { let obj = obj.clone(); match obj { Some(obj) => match &obj.ty.kind { - TypeKind::Dict(_, val_ty) => Some(self.new_config_expr_context_item( + TypeKind::Dict(DictType { + key_ty: _, val_ty, .. + }) => Some(self.new_config_expr_context_item( key_name, val_ty.clone(), obj.start.clone(), @@ -394,6 +397,7 @@ impl<'ctx> Resolver<'ctx> { ); let mut key_types: Vec = vec![]; let mut val_types: Vec = vec![]; + let mut attrs = IndexMap::new(); for item in entries { let key = &item.node.key; let value = &item.node.value; @@ -417,6 +421,13 @@ impl<'ctx> Resolver<'ctx> { Rc::new(Type::str_lit(name)) }; self.check_attr_ty(&key_ty, key.get_span_pos()); + attrs.insert( + name.to_string(), + Attr { + ty: val_ty.clone(), + range: key.get_span_pos(), + }, + ); self.insert_object( name, ScopeObject { @@ -475,7 +486,7 @@ impl<'ctx> Resolver<'ctx> { TypeKind::None | TypeKind::Any => { val_types.push(val_ty.clone()); } - TypeKind::Dict(key_ty, val_ty) => { + TypeKind::Dict(DictType { key_ty, val_ty, .. }) => { key_types.push(key_ty.clone()); val_types.push(val_ty.clone()); } @@ -534,6 +545,6 @@ impl<'ctx> Resolver<'ctx> { self.leave_scope(); let key_ty = sup(&key_types); let val_ty = sup(&val_types); - Type::dict_ref(key_ty, val_ty) + Type::dict_ref_with_attrs(key_ty, val_ty, attrs) } } diff --git a/kclvm/sema/src/resolver/loop.rs b/kclvm/sema/src/resolver/loop.rs index cdff8b773..8dae0d945 100644 --- a/kclvm/sema/src/resolver/loop.rs +++ b/kclvm/sema/src/resolver/loop.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use crate::resolver::Resolver; -use crate::ty::{sup, Type, TypeKind}; +use crate::ty::{sup, DictType, Type, TypeKind}; use kclvm_ast::ast; use kclvm_ast::pos::GetPos; use kclvm_error::diagnostic::Range; @@ -53,7 +53,7 @@ impl<'ctx> Resolver<'ctx> { ); } } - TypeKind::Dict(key_ty, val_ty) => { + TypeKind::Dict(DictType { key_ty, val_ty, .. }) => { first_var_ty = sup(&[key_ty.clone(), first_var_ty.clone()]); self.set_type_to_scope( first_var_name.as_ref().unwrap(), diff --git a/kclvm/sema/src/resolver/node.rs b/kclvm/sema/src/resolver/node.rs index 4c645175b..3967f5c3b 100644 --- a/kclvm/sema/src/resolver/node.rs +++ b/kclvm/sema/src/resolver/node.rs @@ -6,7 +6,9 @@ use kclvm_error::*; use std::rc::Rc; use crate::info::is_private_field; -use crate::ty::{sup, Parameter, Type, TypeInferMethods, TypeKind, RESERVED_TYPE_IDENTIFIERS}; +use crate::ty::{ + sup, DictType, Parameter, Type, TypeInferMethods, TypeKind, RESERVED_TYPE_IDENTIFIERS, +}; use super::format::VALID_FORMAT_SPEC_SET; use super::scope::{ScopeKind, ScopeObject, ScopeObjectKind}; @@ -575,7 +577,9 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { } } } - TypeKind::Dict(_, val_ty) => { + TypeKind::Dict(DictType { + key_ty: _, val_ty, .. + }) => { if let Some(index) = &subscript.index { let index_key_ty = self.expr(index); if index_key_ty.is_none_or_any() { @@ -705,7 +709,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { match &ty.kind { TypeKind::None | TypeKind::Any => (ty.clone(), true), TypeKind::List(item_ty) => (item_ty.clone(), true), - TypeKind::Dict(key_ty, _) => (key_ty.clone(), true), + TypeKind::Dict(DictType { key_ty, .. }) => (key_ty.clone(), true), TypeKind::Schema(schema_ty) => (schema_ty.key_ty(), true), TypeKind::Union(types) => { let results = types @@ -821,7 +825,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { } let mut range = schema_expr.name.get_span_pos(); let ret_ty = match &def_ty.kind { - TypeKind::Dict(_, _) => { + TypeKind::Dict(DictType { .. }) => { let obj = self.new_config_expr_context_item( "", def_ty.clone(), diff --git a/kclvm/sema/src/resolver/ty.rs b/kclvm/sema/src/resolver/ty.rs index e9c754f62..c7504d6ab 100644 --- a/kclvm/sema/src/resolver/ty.rs +++ b/kclvm/sema/src/resolver/ty.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use crate::resolver::Resolver; use crate::ty::parser::parse_type_str; -use crate::ty::{assignable_to, SchemaType, Type, TypeKind}; +use crate::ty::{assignable_to, Attr, DictType, SchemaType, Type, TypeKind}; use indexmap::IndexMap; use kclvm_ast::ast; use kclvm_ast::pos::GetPos; @@ -123,11 +123,18 @@ impl<'ctx> Resolver<'ctx> { (TypeKind::List(item_ty), TypeKind::List(expected_item_ty)) => { self.check_type(item_ty.clone(), expected_item_ty.clone(), range) } - (TypeKind::Dict(key_ty, val_ty), TypeKind::Dict(expected_key_ty, expected_val_ty)) => { + ( + TypeKind::Dict(DictType { key_ty, val_ty, .. }), + TypeKind::Dict(DictType { + key_ty: expected_key_ty, + val_ty: expected_val_ty, + .. + }), + ) => { self.check_type(key_ty.clone(), expected_key_ty.clone(), range) && self.check_type(val_ty.clone(), expected_val_ty.clone(), range) } - (TypeKind::Dict(key_ty, val_ty), TypeKind::Schema(schema_ty)) => { + (TypeKind::Dict(DictType { key_ty, val_ty, .. }), TypeKind::Schema(schema_ty)) => { self.dict_assignable_to_schema(key_ty.clone(), val_ty.clone(), schema_ty, range) } (TypeKind::Union(types), _) => types @@ -175,9 +182,25 @@ impl<'ctx> Resolver<'ctx> { TypeKind::List(item_ty) => { Type::list_ref(self.upgrade_named_ty_with_scope(item_ty.clone(), range)) } - TypeKind::Dict(key_ty, val_ty) => Type::dict_ref( + TypeKind::Dict(DictType { + key_ty, + val_ty, + attrs, + }) => Type::dict_ref_with_attrs( self.upgrade_named_ty_with_scope(key_ty.clone(), range), self.upgrade_named_ty_with_scope(val_ty.clone(), range), + attrs + .into_iter() + .map(|(key, attr)| { + ( + key.to_string(), + Attr { + ty: self.upgrade_named_ty_with_scope(val_ty.clone(), range), + range: attr.range.clone(), + }, + ) + }) + .collect(), ), TypeKind::Union(types) => Type::union_ref( &types diff --git a/kclvm/sema/src/ty/constructor.rs b/kclvm/sema/src/ty/constructor.rs index caa773690..24581b1d6 100644 --- a/kclvm/sema/src/ty/constructor.rs +++ b/kclvm/sema/src/ty/constructor.rs @@ -33,7 +33,11 @@ impl Type { #[inline] pub fn dict(key_ty: Rc, val_ty: Rc) -> Type { Type { - kind: TypeKind::Dict(key_ty, val_ty), + kind: TypeKind::Dict(DictType { + key_ty, + val_ty, + attrs: IndexMap::new(), + }), flags: TypeFlags::DICT, is_type_alias: false, } @@ -43,6 +47,32 @@ impl Type { pub fn dict_ref(key_ty: Rc, val_ty: Rc) -> Rc { Rc::new(Self::dict(key_ty, val_ty)) } + /// Construct a dict type with attrs + #[inline] + pub fn dict_with_attrs( + key_ty: TypeRef, + val_ty: TypeRef, + attrs: IndexMap, + ) -> Type { + Type { + kind: TypeKind::Dict(DictType { + key_ty, + val_ty, + attrs, + }), + flags: TypeFlags::DICT, + is_type_alias: false, + } + } + /// Construct a dict type reference with attrs + #[inline] + pub fn dict_ref_with_attrs( + key_ty: TypeRef, + val_ty: TypeRef, + attrs: IndexMap, + ) -> TypeRef { + Rc::new(Self::dict_with_attrs(key_ty, val_ty, attrs)) + } /// Construct a bool literal type. #[inline] pub fn bool_lit(val: bool) -> Type { @@ -310,7 +340,7 @@ impl Type { | TypeKind::Str | TypeKind::StrLit(_) | TypeKind::List(_) - | TypeKind::Dict(_, _) + | TypeKind::Dict(DictType { .. }) | TypeKind::Union(_) | TypeKind::Schema(_) | TypeKind::NumberMultiplier(_) diff --git a/kclvm/sema/src/ty/context.rs b/kclvm/sema/src/ty/context.rs index 7d2a1a95d..49591cedc 100644 --- a/kclvm/sema/src/ty/context.rs +++ b/kclvm/sema/src/ty/context.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::rc::Rc; -use super::{sup, Type, TypeFlags, TypeKind}; +use super::{sup, Attr, DictType, Type, TypeFlags, TypeKind}; use petgraph::algo::is_cyclic_directed; use petgraph::graph::{DiGraph, NodeIndex}; @@ -166,9 +166,25 @@ impl TypeInferMethods for TypeContext { TypeKind::StrLit(_) => self.builtin_types.str.clone(), TypeKind::List(item_ty) => Type::list_ref(self.infer_to_variable_type(item_ty.clone())), // Dict type e.g., {str:1|2} -> {str:int} - TypeKind::Dict(key_ty, val_ty) => Type::dict_ref( + TypeKind::Dict(DictType { + key_ty, + val_ty, + attrs, + }) => Type::dict_ref_with_attrs( self.infer_to_variable_type(key_ty.clone()), self.infer_to_variable_type(val_ty.clone()), + attrs + .into_iter() + .map(|(key, attr)| { + ( + key.to_string(), + Attr { + ty: self.infer_to_variable_type(attr.ty.clone()), + range: attr.range.clone(), + }, + ) + }) + .collect(), ), // Union type e.g., 1|2|"s" -> int|str TypeKind::Union(types) => sup(&types diff --git a/kclvm/sema/src/ty/into.rs b/kclvm/sema/src/ty/into.rs index bfb090a11..b5fa28cdf 100644 --- a/kclvm/sema/src/ty/into.rs +++ b/kclvm/sema/src/ty/into.rs @@ -13,7 +13,7 @@ impl Type { #[inline] pub fn dict_entry_ty(&self) -> (Rc, Rc) { match &self.kind { - TypeKind::Dict(key_ty, val_ty) => (key_ty.clone(), val_ty.clone()), + TypeKind::Dict(DictType { key_ty, val_ty, .. }) => (key_ty.clone(), val_ty.clone()), _ => bug!("invalid dict type {}", self.ty_str()), } } @@ -21,7 +21,7 @@ impl Type { #[inline] pub fn config_key_ty(&self) -> Rc { match &self.kind { - TypeKind::Dict(key_ty, _) => key_ty.clone(), + TypeKind::Dict(DictType { key_ty, .. }) => key_ty.clone(), TypeKind::Schema(schema_ty) => schema_ty.key_ty(), _ => bug!("invalid config type {}", self.ty_str()), } @@ -30,7 +30,9 @@ impl Type { #[inline] pub fn config_val_ty(&self) -> Rc { match &self.kind { - TypeKind::Dict(_, val_ty) => val_ty.clone(), + TypeKind::Dict(DictType { + key_ty: _, val_ty, .. + }) => val_ty.clone(), TypeKind::Schema(schema_ty) => schema_ty.val_ty(), _ => bug!("invalid config type {}", self.ty_str()), } @@ -79,7 +81,7 @@ impl Type { } TypeKind::StrLit(v) => format!("\"{}\"", v.replace('"', "\\\"")), TypeKind::List(item_ty) => format!("[{}]", item_ty.into_type_annotation_str()), - TypeKind::Dict(key_ty, val_ty) => { + TypeKind::Dict(DictType { key_ty, val_ty, .. }) => { format!( "{{{}:{}}}", key_ty.into_type_annotation_str(), diff --git a/kclvm/sema/src/ty/mod.rs b/kclvm/sema/src/ty/mod.rs index 4336e859f..9f68bdbb6 100644 --- a/kclvm/sema/src/ty/mod.rs +++ b/kclvm/sema/src/ty/mod.rs @@ -14,6 +14,7 @@ pub use context::{TypeContext, TypeInferMethods}; use indexmap::IndexMap; use kclvm_ast::ast; use kclvm_ast::MAIN_PKG; +use kclvm_error::diagnostic::Range; use kclvm_error::Position; pub use unify::*; pub use walker::walk_type; @@ -21,6 +22,11 @@ pub use walker::walk_type; #[cfg(test)] mod tests; +/// TypeRef represents a reference to a type that exists to avoid copying types everywhere affecting +/// performance. For example, for two instances that are both integer types, there is actually no +/// difference between them. +pub type TypeRef = Rc; + #[derive(Debug, Clone, PartialEq)] pub struct Type { // The type kind. @@ -62,7 +68,7 @@ impl Type { TypeKind::Str => STR_TYPE_STR.to_string(), TypeKind::StrLit(v) => format!("{}({})", STR_TYPE_STR, v), TypeKind::List(item_ty) => format!("[{}]", item_ty.ty_str()), - TypeKind::Dict(key_ty, val_ty) => { + TypeKind::Dict(DictType { key_ty, val_ty, .. }) => { format!("{{{}:{}}}", key_ty.ty_str(), val_ty.ty_str()) } TypeKind::Union(types) => types @@ -105,7 +111,7 @@ pub enum TypeKind { /// The pointer of an array slice. Written as `[T]`. List(Rc), /// A map type. Written as `{kT, vT}`. - Dict(Rc, Rc), + Dict(DictType), /// A union type. Written as ty1 | ty2 | ... | tyn Union(Vec>), /// A schema type. @@ -145,6 +151,19 @@ bitflags::bitflags! { } } +#[derive(Debug, Clone, PartialEq)] +pub struct DictType { + pub key_ty: TypeRef, + pub val_ty: TypeRef, + pub attrs: IndexMap, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Attr { + pub ty: TypeRef, + pub range: Range, +} + /// The schema type. #[derive(Debug, Clone, PartialEq)] pub struct SchemaType { diff --git a/kclvm/sema/src/ty/walker.rs b/kclvm/sema/src/ty/walker.rs index 3f996ff6c..8ab45772b 100644 --- a/kclvm/sema/src/ty/walker.rs +++ b/kclvm/sema/src/ty/walker.rs @@ -1,15 +1,31 @@ use std::rc::Rc; -use super::Type; +use super::{Attr, DictType, Type}; /// Walk one type recursively and deal the type using the `walk_fn` pub fn walk_type(ty: &Type, walk_fn: impl Fn(&Type) -> Rc + Copy) -> Rc { let ty = walk_fn(ty); match &ty.kind { super::TypeKind::List(item_ty) => Rc::new(Type::list(walk_type(item_ty, walk_fn))), - super::TypeKind::Dict(key_ty, val_ty) => Rc::new(Type::dict( + super::TypeKind::Dict(DictType { + key_ty, + val_ty, + attrs, + }) => Rc::new(Type::dict_with_attrs( walk_type(key_ty, walk_fn), walk_type(val_ty, walk_fn), + attrs + .into_iter() + .map(|(key, attr)| { + ( + key.to_string(), + Attr { + ty: walk_type(&attr.ty, walk_fn), + range: attr.range.clone(), + }, + ) + }) + .collect(), )), super::TypeKind::Union(types) => Rc::new(Type::union( &types diff --git a/kclvm/tools/src/LSP/src/goto_def.rs b/kclvm/tools/src/LSP/src/goto_def.rs index 5bb5bfd7a..358bf7deb 100644 --- a/kclvm/tools/src/LSP/src/goto_def.rs +++ b/kclvm/tools/src/LSP/src/goto_def.rs @@ -15,7 +15,7 @@ use kclvm_compiler::pkgpath_without_prefix; use kclvm_error::Position as KCLPos; use kclvm_sema::resolver::scope::{ProgramScope, Scope, ScopeObject}; -use kclvm_sema::ty::SchemaType; +use kclvm_sema::ty::{DictType, SchemaType}; use lsp_types::{GotoDefinitionResponse, Url}; use lsp_types::{Location, Range}; use std::cell::RefCell; @@ -217,7 +217,7 @@ pub(crate) fn resolve_var( None => None, } } - kclvm_sema::ty::TypeKind::Dict(_, _) => { + kclvm_sema::ty::TypeKind::Dict(DictType { attrs: _, .. }) => { // Todo: find key def in dict None } diff --git a/test/grammar/types/union_ty/union_ty_11/main.k b/test/grammar/types/union_ty/union_ty_11/main.k new file mode 100644 index 000000000..0ecbbb635 --- /dev/null +++ b/test/grammar/types/union_ty/union_ty_11/main.k @@ -0,0 +1,6 @@ +metadata = { + name = "app" + labels.app = "app" +} +labels: {str:str} = metadata.labels + diff --git a/test/grammar/types/union_ty/union_ty_11/stdout.golden b/test/grammar/types/union_ty/union_ty_11/stdout.golden new file mode 100644 index 000000000..b230162b2 --- /dev/null +++ b/test/grammar/types/union_ty/union_ty_11/stdout.golden @@ -0,0 +1,6 @@ +metadata: + name: app + labels: + app: app +labels: + app: app diff --git a/test/grammar/types/union_ty/union_ty_12/main.k b/test/grammar/types/union_ty/union_ty_12/main.k new file mode 100644 index 000000000..44a97d4e9 --- /dev/null +++ b/test/grammar/types/union_ty/union_ty_12/main.k @@ -0,0 +1,6 @@ +metadata = { + name = "app" + labels.app = "app" +} +labels: {str:str} = {**metadata.labels} + diff --git a/test/grammar/types/union_ty/union_ty_12/stdout.golden b/test/grammar/types/union_ty/union_ty_12/stdout.golden new file mode 100644 index 000000000..b230162b2 --- /dev/null +++ b/test/grammar/types/union_ty/union_ty_12/stdout.golden @@ -0,0 +1,6 @@ +metadata: + name: app + labels: + app: app +labels: + app: app