diff --git a/kclvm/sema/src/resolver/arg.rs b/kclvm/sema/src/resolver/arg.rs index c20fd4ee5..e3c7aa0bc 100644 --- a/kclvm/sema/src/resolver/arg.rs +++ b/kclvm/sema/src/resolver/arg.rs @@ -111,7 +111,7 @@ impl<'ctx> Resolver<'ctx> { return; } }; - self.must_assignable_to(ty.clone(), expected_ty, args[i].get_span_pos(), None, true) + self.must_assignable_to(ty.clone(), expected_ty, args[i].get_span_pos(), None) } // Do keyword argument type check for (i, (arg_name, kwarg_ty)) in kwarg_types.iter().enumerate() { @@ -144,7 +144,6 @@ impl<'ctx> Resolver<'ctx> { expected_types[0].clone(), kwargs[i].get_span_pos(), None, - true, ); }; } diff --git a/kclvm/sema/src/resolver/config.rs b/kclvm/sema/src/resolver/config.rs index f0abab6bd..e1b7cfea9 100644 --- a/kclvm/sema/src/resolver/config.rs +++ b/kclvm/sema/src/resolver/config.rs @@ -107,6 +107,38 @@ impl<'ctx> Resolver<'ctx> { }, } } + TypeKind::Union(types) => { + let mut possible_types = vec![]; + for ty in types { + match &ty.kind { + TypeKind::Schema(schema_ty) => { + match schema_ty.get_obj_of_attr(key_name) { + Some(attr_ty_obj) => { + possible_types.push(attr_ty_obj.ty.clone()); + } + None => match &schema_ty.index_signature { + Some(index_signature) => { + possible_types + .push(index_signature.val_ty.clone()); + } + None => continue, + }, + } + } + TypeKind::Dict(DictType { val_ty, .. }) => { + possible_types.push(val_ty.clone()); + } + _ => continue, + } + } + + Some(self.new_config_expr_context_item( + key_name, + crate::ty::sup(&possible_types).into(), + obj.start.clone(), + obj.end.clone(), + )) + } _ => None, }, None => None, @@ -320,7 +352,6 @@ impl<'ctx> Resolver<'ctx> { ty, key.get_span_pos(), Some(obj_last.get_span_pos()), - true, ); } self.clear_config_expr_context(stack_depth, false); @@ -350,19 +381,23 @@ impl<'ctx> Resolver<'ctx> { let mut schema_names = vec![]; let mut total_suggs = vec![]; for ty in types { - if let TypeKind::Schema(schema_ty) = &ty.kind { - if schema_ty.get_obj_of_attr(attr).is_none() - && !schema_ty.is_mixin - && schema_ty.index_signature.is_none() - { - let mut suggs = - suggestions::provide_suggestions(attr, schema_ty.attrs.keys()); - total_suggs.append(&mut suggs); - schema_names.push(schema_ty.name.clone()); - } else { - // If there is a schema attribute that meets the condition, the type check passes - return; + match &ty.kind { + TypeKind::Schema(schema_ty) => { + if schema_ty.get_obj_of_attr(attr).is_none() + && !schema_ty.is_mixin + && schema_ty.index_signature.is_none() + { + let mut suggs = + suggestions::provide_suggestions(attr, schema_ty.attrs.keys()); + total_suggs.append(&mut suggs); + schema_names.push(schema_ty.name.clone()); + } else { + // If there is a schema attribute that meets the condition, the type check passes + return; + } } + TypeKind::Dict(..) => return, + _ => continue, } } if !schema_names.is_empty() { @@ -497,7 +532,11 @@ impl<'ctx> Resolver<'ctx> { let key_ty = if identifier.names.len() == 1 { let name = &identifier.names[0].node; let key_ty = if self.ctx.local_vars.contains(name) { - self.expr(key) + // set key context expected schema as None + self.ctx.config_expr_context.push(None); + let key_ty = self.expr(key); + self.ctx.config_expr_context.pop(); + key_ty } else { Arc::new(Type::str_lit(name)) }; @@ -543,7 +582,10 @@ impl<'ctx> Resolver<'ctx> { val_ty } _ => { + // set key context expected schema as None + self.ctx.config_expr_context.push(None); let key_ty = self.expr(key); + self.ctx.config_expr_context.pop(); let val_ty = self.expr(value); self.check_attr_ty(&key_ty, key.get_span_pos()); if let ast::Expr::StringLit(string_lit) = &key.node { diff --git a/kclvm/sema/src/resolver/node.rs b/kclvm/sema/src/resolver/node.rs index 5b54fa6e3..1e9d4cfa7 100644 --- a/kclvm/sema/src/resolver/node.rs +++ b/kclvm/sema/src/resolver/node.rs @@ -75,7 +75,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty.clone(), unification_stmt.target.get_span_pos(), None, - true, ); if !ty.is_any() && expected_ty.is_any() { self.set_type_to_scope(&names[0].node, ty, &names[0]); @@ -185,7 +184,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty.clone(), target.get_span_pos(), None, - true, ); let upgrade_schema_type = self.upgrade_dict_to_schema( @@ -221,7 +219,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty.clone(), target.get_span_pos(), None, - true, ); let upgrade_schema_type = self.upgrade_dict_to_schema( @@ -290,7 +287,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty, aug_assign_stmt.target.get_span_pos(), None, - true, ); self.ctx.l_value = false; new_target_ty @@ -457,7 +453,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty, schema_attr.name.get_span_pos(), None, - true, ); } // Assign @@ -466,7 +461,6 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { expected_ty, schema_attr.name.get_span_pos(), None, - true, ), }, None => bug!("invalid ast schema attr op kind"), @@ -1117,13 +1111,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { let real_ret_ty = self.stmts(&lambda_expr.body); self.leave_scope(); self.ctx.in_lambda_expr.pop(); - self.must_assignable_to( - real_ret_ty.clone(), - ret_ty.clone(), - (start, end), - None, - true, - ); + self.must_assignable_to(real_ret_ty.clone(), ret_ty.clone(), (start, end), None); if !real_ret_ty.is_any() && ret_ty.is_any() && lambda_expr.return_ty.is_none() { ret_ty = real_ret_ty; } diff --git a/kclvm/sema/src/resolver/schema.rs b/kclvm/sema/src/resolver/schema.rs index b09d042f1..01691c904 100644 --- a/kclvm/sema/src/resolver/schema.rs +++ b/kclvm/sema/src/resolver/schema.rs @@ -98,7 +98,6 @@ impl<'ctx> Resolver<'ctx> { expected_ty, index_signature_node.get_span_pos(), None, - true, ); } } diff --git a/kclvm/sema/src/resolver/ty.rs b/kclvm/sema/src/resolver/ty.rs index 8b08c9542..2971ed5a5 100644 --- a/kclvm/sema/src/resolver/ty.rs +++ b/kclvm/sema/src/resolver/ty.rs @@ -93,7 +93,7 @@ impl<'ctx> Resolver<'ctx> { #[inline] pub fn must_be_type(&mut self, expr: &'ctx ast::NodeRef, expected_ty: TypeRef) { let ty = self.expr(expr); - self.must_assignable_to(ty, expected_ty, expr.get_span_pos(), None, true); + self.must_assignable_to(ty, expected_ty, expr.get_span_pos(), None); } /// Must assignable to the expected type. @@ -104,9 +104,8 @@ impl<'ctx> Resolver<'ctx> { expected_ty: TypeRef, range: Range, expected_pos: Option, - emit_error: bool, ) { - if !self.check_type(ty.clone(), expected_ty.clone(), &range, emit_error) && emit_error { + if !self.check_type(ty.clone(), expected_ty.clone(), &range) { let mut msgs = vec![Message { range, style: Style::LineAndColumn, @@ -140,14 +139,8 @@ impl<'ctx> Resolver<'ctx> { range: &Range, ) -> TypeRef { match (&ty.kind, &expected_ty.kind) { - (TypeKind::Dict(DictType { key_ty, val_ty, .. }), TypeKind::Schema(schema_ty)) => { - if self.dict_assignable_to_schema( - key_ty.clone(), - val_ty.clone(), - schema_ty, - range, - false, - ) { + (TypeKind::Dict(DictType { key_ty, .. }), TypeKind::Schema(schema_ty)) => { + if self.upgrade_dict_to_schema_attr_check(key_ty.clone(), schema_ty) { expected_ty } else { ty @@ -169,10 +162,7 @@ impl<'ctx> Resolver<'ctx> { self.upgrade_dict_to_schema(val_ty.clone(), expected_val_ty.clone(), range), ) .into(), - ( - TypeKind::Dict(DictType { key_ty, val_ty, .. }), - TypeKind::Union(expected_union_type), - ) => { + (TypeKind::Dict(DictType { key_ty, .. }), TypeKind::Union(expected_union_type)) => { let types: Vec> = expected_union_type .iter() .filter(|ty| match ty.kind { @@ -180,17 +170,13 @@ impl<'ctx> Resolver<'ctx> { _ => false, }) .filter(|ty| { - self.dict_assignable_to_schema( + self.upgrade_dict_to_schema_attr_check( key_ty.clone(), - val_ty.clone(), &ty.into_schema_type(), - range, - false, ) }) .map(|ty| ty.clone()) .collect(); - crate::ty::sup(&types).into() } _ => ty, @@ -256,29 +242,17 @@ impl<'ctx> Resolver<'ctx> { self.set_type_to_scope(name, target_ty.clone(), &target.node.names[0]); // Check the type of value and the type annotation of target - self.must_assignable_to( - value_ty.clone(), - target_ty, - target.get_span_pos(), - None, - true, - ) + self.must_assignable_to(value_ty.clone(), target_ty, target.get_span_pos(), None) } } } /// The check type main function, returns a boolean result. #[inline] - pub fn check_type( - &mut self, - ty: TypeRef, - expected_ty: TypeRef, - range: &Range, - emit_error: bool, - ) -> bool { + pub fn check_type(&mut self, ty: TypeRef, expected_ty: TypeRef, range: &Range) -> bool { match (&ty.kind, &expected_ty.kind) { (TypeKind::List(item_ty), TypeKind::List(expected_item_ty)) => { - self.check_type(item_ty.clone(), expected_item_ty.clone(), range, emit_error) + self.check_type(item_ty.clone(), expected_item_ty.clone(), range) } ( TypeKind::Dict(DictType { key_ty, val_ty, .. }), @@ -288,23 +262,18 @@ impl<'ctx> Resolver<'ctx> { .. }), ) => { - self.check_type(key_ty.clone(), expected_key_ty.clone(), range, emit_error) - && self.check_type(val_ty.clone(), expected_val_ty.clone(), range, emit_error) + self.check_type(key_ty.clone(), expected_key_ty.clone(), range) + && self.check_type(val_ty.clone(), expected_val_ty.clone(), range) + } + (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::Dict(DictType { key_ty, val_ty, .. }), TypeKind::Schema(schema_ty)) => self - .dict_assignable_to_schema( - key_ty.clone(), - val_ty.clone(), - schema_ty, - range, - emit_error, - ), (TypeKind::Union(types), _) => types .iter() - .all(|ty| self.check_type(ty.clone(), expected_ty.clone(), range, emit_error)), - (_, TypeKind::Union(types)) => types.iter().any(|expected_ty| { - self.check_type(ty.clone(), expected_ty.clone(), range, emit_error) - }), + .all(|ty| self.check_type(ty.clone(), expected_ty.clone(), range)), + (_, TypeKind::Union(types)) => types + .iter() + .any(|expected_ty| self.check_type(ty.clone(), expected_ty.clone(), range)), _ => assignable_to(ty, expected_ty), } } @@ -317,16 +286,9 @@ impl<'ctx> Resolver<'ctx> { val_ty: TypeRef, schema_ty: &SchemaType, range: &Range, - emit_error: bool, ) -> bool { if let Some(index_signature) = &schema_ty.index_signature { - if !self.check_type( - val_ty.clone(), - index_signature.val_ty.clone(), - range, - emit_error, - ) && emit_error - { + if !self.check_type(val_ty.clone(), index_signature.val_ty.clone(), range) { self.handler.add_type_error( &format!( "expected schema index signature value type {}, got {}", @@ -337,8 +299,8 @@ impl<'ctx> Resolver<'ctx> { ); } if index_signature.any_other { - return self.check_type(key_ty, index_signature.key_ty.clone(), range, emit_error) - && self.check_type(val_ty, index_signature.val_ty.clone(), range, emit_error); + return self.check_type(key_ty, index_signature.key_ty.clone(), range) + && self.check_type(val_ty, index_signature.val_ty.clone(), range); } true } else { @@ -351,7 +313,6 @@ impl<'ctx> Resolver<'ctx> { attr_obj.ty.clone(), range.clone(), Some(attr_obj.range.clone()), - emit_error, ); return true; } @@ -360,6 +321,37 @@ impl<'ctx> Resolver<'ctx> { } } + /// Judge a dict can be upgrade to schema. + /// More strict than `dict_assign_to_schema()`: schema attr contains all attributes in key + pub fn upgrade_dict_to_schema_attr_check( + &mut self, + key_ty: TypeRef, + schema_ty: &SchemaType, + ) -> bool { + if schema_ty.index_signature.is_some() { + return true; + } + match &key_ty.kind { + // empty dict {} + TypeKind::Any => true, + // single key: {key1: value1} + TypeKind::StrLit(s) => schema_ty.attrs.len() == 1 && schema_ty.attrs.contains_key(s), + // multi key: { + // key1: value1 + // key2: value2 + // ... + // } + TypeKind::Union(types) => { + schema_ty.attrs.len() == types.len() + && types.iter().all(|ty| match &ty.kind { + TypeKind::StrLit(s) => schema_ty.attrs.contains_key(s), + _ => false, + }) + } + _ => false, + } + } + fn upgrade_named_ty_with_scope( &mut self, ty: TypeRef, diff --git a/kclvm/tools/src/LSP/src/goto_def.rs b/kclvm/tools/src/LSP/src/goto_def.rs index 3fa145b50..bcb2dd7fe 100644 --- a/kclvm/tools/src/LSP/src/goto_def.rs +++ b/kclvm/tools/src/LSP/src/goto_def.rs @@ -105,7 +105,7 @@ mod tests { use indexmap::IndexSet; use kclvm_error::Position as KCLPos; use proc_macro_crate::bench_test; - use std::path::PathBuf; + use std::path::{Path, PathBuf}; #[test] #[bench_test] @@ -708,16 +708,68 @@ mod tests { line: $line, column: Some($column), }; - goto_def(&pos, &gs) + let res = goto_def(&pos, &gs); + fmt_resp(&res) })); } }; } + fn fmt_resp(resp: &Option) -> String { + let root_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + match resp { + Some(resp) => match resp { + lsp_types::GotoDefinitionResponse::Scalar(loc) => { + let url = file_path_from_url(&loc.uri).unwrap(); + let got_path = Path::new(&url); + let relative_path = got_path.strip_prefix(root_path).unwrap(); + format!("path: {:?}, range: {:?}", relative_path, loc.range) + } + _ => todo!(), + }, + None => "None".to_string(), + } + } + goto_def_test_snapshot!( goto_system_pkg_test, "src/test_data/goto_def_test/goto_def.k", 3, 1 ); + + goto_def_test_snapshot!( + goto_dict_to_schema_attr_test1, + "src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k", + 13, + 15 + ); + + goto_def_test_snapshot!( + goto_dict_to_schema_attr_test2, + "src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k", + 15, + 7 + ); + + goto_def_test_snapshot!( + goto_dict_to_schema_attr_test3, + "src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k", + 19, + 7 + ); + + goto_def_test_snapshot!( + goto_dict_to_schema_attr_test4, + "src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k", + 26, + 11 + ); + + goto_def_test_snapshot!( + goto_dict_to_schema_attr_test5, + "src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k", + 33, + 11 + ); } diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test1.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test1.snap new file mode 100644 index 000000000..24ace7572 --- /dev/null +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test1.snap @@ -0,0 +1,5 @@ +--- +source: tools/src/LSP/src/goto_def.rs +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\")\n ; let pos = KCLPos\n { filename : file.clone(), line : 13, column : Some(15), } ; let res =\n goto_def(& pos, & gs) ; fmt_resp(& res)\n })" +--- +"path: \"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\", range: Range { start: Position { line: 1, character: 4 }, end: Position { line: 1, character: 8 } }" diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test2.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test2.snap new file mode 100644 index 000000000..31e11326f --- /dev/null +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test2.snap @@ -0,0 +1,5 @@ +--- +source: tools/src/LSP/src/goto_def.rs +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\")\n ; let pos = KCLPos\n { filename : file.clone(), line : 15, column : Some(7), } ; let res =\n goto_def(& pos, & gs) ; fmt_resp(& res)\n })" +--- +"path: \"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\", range: Range { start: Position { line: 4, character: 4 }, end: Position { line: 4, character: 8 } }" diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test3.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test3.snap new file mode 100644 index 000000000..c16327319 --- /dev/null +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test3.snap @@ -0,0 +1,5 @@ +--- +source: tools/src/LSP/src/goto_def.rs +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\")\n ; let pos = KCLPos\n { filename : file.clone(), line : 19, column : Some(7), } ; let res =\n goto_def(& pos, & gs) ; fmt_resp(& res)\n })" +--- +"path: \"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\", range: Range { start: Position { line: 9, character: 4 }, end: Position { line: 9, character: 8 } }" diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test4.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test4.snap new file mode 100644 index 000000000..8ba371b50 --- /dev/null +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test4.snap @@ -0,0 +1,5 @@ +--- +source: tools/src/LSP/src/goto_def.rs +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\")\n ; let pos = KCLPos\n { filename : file.clone(), line : 26, column : Some(11), } ; let res =\n goto_def(& pos, & gs) ; fmt_resp(& res)\n })" +--- +"path: \"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\", range: Range { start: Position { line: 4, character: 4 }, end: Position { line: 4, character: 8 } }" diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test5.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test5.snap new file mode 100644 index 000000000..0c8a57a2f --- /dev/null +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_dict_to_schema_attr_test5.snap @@ -0,0 +1,5 @@ +--- +source: tools/src/LSP/src/goto_def.rs +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\")\n ; let pos = KCLPos\n { filename : file.clone(), line : 33, column : Some(11), } ; let res =\n goto_def(& pos, & gs) ; fmt_resp(& res)\n })" +--- +"path: \"src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k\", range: Range { start: Position { line: 9, character: 4 }, end: Position { line: 9, character: 8 } }" diff --git a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_system_pkg_test.snap b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_system_pkg_test.snap index 1fddab521..1cd5643cd 100644 --- a/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_system_pkg_test.snap +++ b/kclvm/tools/src/LSP/src/snapshots/kcl_language_server__goto_def__tests__goto_system_pkg_test.snap @@ -1,5 +1,5 @@ --- source: tools/src/LSP/src/goto_def.rs -expression: "format!(\"{:?}\", res)" +expression: "format!(\"{:?}\",\n {\n let(file, _program, _, gs) =\n compile_test_file(\"src/test_data/goto_def_test/goto_def.k\") ; let pos\n = KCLPos { filename : file.clone(), line : 3, column : Some(1), } ;\n let res = goto_def(& pos, & gs) ; fmt_resp(& res)\n })" --- -None +"None" diff --git a/kclvm/tools/src/LSP/src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k b/kclvm/tools/src/LSP/src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k new file mode 100644 index 000000000..1165e5ce3 --- /dev/null +++ b/kclvm/tools/src/LSP/src/test_data/goto_def_test/dict_to_schema/dict_to_schema.k @@ -0,0 +1,37 @@ +schema Name1: + name: str + +schema Name2: + name: str + age: int + +schema Name3: + [...str]: str + name: str + age: int + +a: Name1 = {name: ""} +b: Name1 | Name2 = { + name: "a" + age: 1 +} +c: Name1 | Name2 | Name3 = { + name: "a" + age: 1 + "c": "c" +} + +d: Name1 | Name2 | {str:Name1} | {str:Name2} = { + "b": { + name: "a" + age: 1 + } +} + +e: Name1 | Name2 | {str:Name1} | {str:Name2} | {str: Name3} = { + "b": { + name: "a" + age: 1 + "c": "c" + } +}