From a1a3dc50c8e7edab861f8e27553129c1cbe9207a Mon Sep 17 00:00:00 2001 From: Vic Nightfall Date: Sat, 20 Apr 2024 22:20:16 +0200 Subject: [PATCH] Add embeds --- src/compiler.pr | 199 ++++++++++++++++++++++++++++++++---------- src/debug.pr | 6 ++ src/parser.pr | 25 ++++-- src/typechecking.pr | 90 +++++++++++++++---- test/test_compiler.pr | 22 +++++ test/test_parser.pr | 126 ++++++++++++++++++++++++++ test/test_runtime.pr | 50 +++++++++++ 7 files changed, 446 insertions(+), 72 deletions(-) diff --git a/src/compiler.pr b/src/compiler.pr index 5b4c2f0f..50547a42 100644 --- a/src/compiler.pr +++ b/src/compiler.pr @@ -1522,7 +1522,7 @@ def convert_ref_to_ptr(tpe: &typechecking::Type, value: Value, loc: &Value, stat return bitcast_ret } -def convert_value_to_ref(tpe: &typechecking::Type, value: Value, loc: &Value, state: &State) -> Value { +def convert_value_to_ref(tpe: &typechecking::Type, value: Value, loc: &Value, state: &State, initial_ref_count: size_t = 0) -> Value { if tpe.tpe and value.tpe.kind != tpe.tpe.kind { value = convert_to(loc, value, tpe.tpe, state) } @@ -1564,7 +1564,7 @@ def convert_value_to_ref(tpe: &typechecking::Type, value: Value, loc: &Value, st let store1 = make_insn_dbg(InsnKind::STORE, loc) store1.value.store = [ loc = refcount, - value = [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value + value = [ kind = ValueKind::INT, tpe = builtins::int64_, i = initial_ref_count ] !Value ] !InsnStore push_insn(store1, state) } @@ -2077,6 +2077,19 @@ def convert_to(kind: InsnKind, loc: &Value, value: Value, tpe: &typechecking::Ty return ret } +def get_embed_field(left: &typechecking::Type, right: &typechecking::Type, state: &State) -> &typechecking::StructMember { + if is_interface(left) and is_struct(right) { + if typechecking::implements(right, left, state.module, check_embed = false) { return null } + for var field in @right.fields { + if field.is_embed and typechecking::implements(field.tpe, left, state.module, check_embed = false) { + return field + } + } + } + + return null +} + // value gets loaded by this function def convert_to(loc: &Value, value: Value, tpe: &typechecking::Type, state: &State) -> Value { if not value.tpe or not tpe { return NO_VALUE } @@ -2114,6 +2127,42 @@ def convert_to(loc: &Value, value: Value, tpe: &typechecking::Type, state: &Stat return value } } + let left = tpe.tpe if is_ref(tpe) else tpe + let right = value.tpe.tpe if is_ref(value.tpe) else value.tpe + let embed_field = get_embed_field(left, right, state) + + // Try to convert to embedded struct / reference + if is_struct(right) and (is_struct(left) or embed_field) { + var is_embed = false + var field: StructMember + if embed_field { + is_embed = true + field = @embed_field + } else { + for var f in @right.fields { + if f.is_embed and (equals(f.tpe, tpe) or equals(f.tpe, tpe.tpe)) { + is_embed = true + field = f + break + } + } + } + if is_embed { + var unwrap = load_value(value, loc, state) + // Unwrap reference on the value side + if is_ref(value.tpe) { + let ref = state.extract_value(pointer(right), unwrap, [1], loc) + unwrap = state.load(right, ref, loc) + } + // Extract element + var elem = state.extract_value(field.tpe, unwrap, [field.index !int], loc) + // Wrap in reference if needed + if is_ref(tpe) and not is_ref(field.tpe) { + elem = convert_value_to_ref(tpe, elem, loc, state, 1) + } + return elem + } + } if tpe.kind == value.tpe.kind and value.tpe.is_anon and typechecking::is_struct(value.tpe) { return convert_anon_to_struct(tpe, value, loc, state) } @@ -2322,7 +2371,18 @@ def walk_StructLitUnion(node: &parser::Node, state: &State) -> Value { return load_ret } +// TODO add loc to this def locals_to_insert_value(value: &Value, state: &State) { + import_cstd_function("malloc", state) + + var aref = is_ref(value.tpe) + let reftpe = value.tpe + var new_value = value + if aref { + new_value = @value + new_value.tpe = value.tpe.tpe + } + let values = value.values for var i in 0..values.size { let val = values(i) @@ -2332,22 +2392,87 @@ def locals_to_insert_value(value: &Value, state: &State) { tpe = val.tpe ] !Value - let ret = make_local_value(value.tpe, null, state) + let ret = make_local_value(new_value.tpe, null, state) let index = allocate_ref(int, 1) index(0) = i let insert = make_insn(InsnKind::INSERTVALUE) (@insert).value.insert_value = [ ret = ret, - value = @value, + value = @new_value, element = val, index = index ] !InsnInsertValue push_insn(insert, state) - @value = ret + @new_value = ret } } + if aref { + @value = convert_value_to_ref(reftpe, @new_value, null, state, 1) + } +} + +def struct_lit_create_value(tpe: &typechecking::Type, kwargs: &Vector(&parser::Node), loc: &Value, state: &State) -> &[Value] { + if not tpe { return null } + if is_ref(tpe) { tpe = tpe.tpe } + + var types = vector::make(type &typechecking::Type) + if tpe.kind == typechecking::TypeKind::TUPLE { + types = tpe.return_t + } else { + for var field in @tpe.fields { + types.push(field.tpe) + } + } + + let values = allocate_ref(Value, types.length) + for var i in 0..values.size { + values(i) = [ + kind = ValueKind::ZEROINITIALIZER, + tpe = types(i) + ] !Value + } + + if is_struct(tpe) { + for var k in 0..tpe.fields.size { + let field = tpe.fields(k) + if field.is_embed { + let new_values = struct_lit_create_value(field.tpe, kwargs, loc, state) + let new_value = [ + kind = ValueKind::STRUCT, + values = new_values, + tpe = field.tpe + ] !&Value + + locals_to_insert_value(new_value, state) + + values(k) = @new_value + } + } + } + + for var i in 0..vector::length(kwargs) { + let kwarg = kwargs(i) + let name = typechecking::last_ident_to_str((@kwarg).value.named_arg.name) + let value = walk_expression((@kwarg).value.named_arg.value, state) + + for var j in 0..tpe.fields.size { + let field = tpe.fields(j) + if field.name == name { + values(j) = convert_to(kwarg, value, field.tpe, state) + if typechecking::is_ref(field.tpe) { + increase_ref_count_of_value(values(j), loc, state) + } else if typechecking::has_copy_constructor(field.tpe) { + let ret = state.alloca(values(j).tpe, loc, no_yield_capture = true) + insert_copy_constructor(ret, values(j), loc, state) + values(j) = state.load(values(j).tpe, ret, loc) + } + break + } + } + } + return values } def walk_StructLit(node: &parser::Node, state: &State) -> Value { @@ -2367,23 +2492,8 @@ def walk_StructLit(node: &parser::Node, state: &State) -> Value { } else if tpe.kind == typechecking::TypeKind::UNION { value = walk_StructLitUnion(node, state) } else { - var types = vector::make(type &typechecking::Type) - if tpe.kind == typechecking::TypeKind::TUPLE { - types = tpe.return_t - } else { - for var field in @tpe.fields { - types.push(field.tpe) - } - } - - let values = allocate_ref(Value, types.length) - for var i in 0..values.size { - values(i) = [ - kind = ValueKind::ZEROINITIALIZER, - tpe = types(i) - ] !Value - } - for var i in 0..vector::length(args) { + // args no longer valid + /*for var i in 0..vector::length(args) { let arg = args(i) let arg_tpe = types(i) let value = walk_expression(arg, state) @@ -2395,27 +2505,10 @@ def walk_StructLit(node: &parser::Node, state: &State) -> Value { insert_copy_constructor(ret, values(i), loc, state) values(i) = state.load(values(i).tpe, ret, loc) } - } - for var i in 0..vector::length(kwargs) { - let kwarg = kwargs(i) - let name = typechecking::last_ident_to_str((@kwarg).value.named_arg.name) - let value = walk_expression((@kwarg).value.named_arg.value, state) - - for var j in 0..tpe.fields.size { - let field = tpe.fields(j) - if field.name == name { - values(j) = convert_to(kwarg, value, field.tpe, state) - if typechecking::is_ref(field.tpe) { - increase_ref_count_of_value(values(j), loc, state) - } else if typechecking::has_copy_constructor(field.tpe) { - let ret = state.alloca(values(j).tpe, loc, no_yield_capture = true) - insert_copy_constructor(ret, values(j), loc, state) - values(j) = state.load(values(j).tpe, ret, loc) - } - break - } - } - } + }*/ + + let values = struct_lit_create_value(tpe, kwargs, loc, state) + value = [ kind = ValueKind::STRUCT, values = values, @@ -3987,6 +4080,7 @@ def walk_MemberAccess_gep(node: &parser::Node, tpe: &typechecking::Type, type Member = struct { index: int tpe: &typechecking::Type + is_embed: bool } // This list needs to be reversed to find the actual indices @@ -4005,11 +4099,14 @@ def resolve_member(vec: &Vector(Member), tpe: &typechecking::Type, name: Str) -> return true } } else { - let found = resolve_member(vec, field.tpe, name) + var tpe = field.tpe + if field.is_embed and is_ref(tpe) { tpe = tpe.tpe } + let found = resolve_member(vec, tpe, name) if found { let member = [ index = field.index !int, - tpe = field.tpe + tpe = field.tpe, + is_embed = field.is_embed ] !Member vec.push(member) return true @@ -4028,7 +4125,6 @@ def walk_MemberAccess_struct(node: &parser::Node, tpe: &typechecking::Type, memb } if tpe.kind == typechecking::TypeKind::UNION { - let index = allocate_ref(Value, 2) index(0) = make_int_value(0) index(1) = make_int_value(0) @@ -4057,8 +4153,17 @@ def walk_MemberAccess_struct(node: &parser::Node, tpe: &typechecking::Type, memb let index = allocate_ref(Value, 2) index(0) = make_int_value(0) index(1) = make_int_value((@member).index) - return walk_MemberAccess_gep(node, tpe, member_type, value, index, state) + let res = walk_MemberAccess_gep(node, tpe, member_type, value, index, state) + + if member.is_embed and is_ref(member.tpe) { + let ref = state.load(member.tpe, @res.addr, loc) + let ptr = state.extract_value(pointer(member.tpe.tpe), ref, [1], loc) + return make_address_value(pointer(member.tpe.tpe), ptr, state) + } + + return res } + } def walk_MemberAccess(node: &parser::Node, state: &State) -> Value { diff --git a/src/debug.pr b/src/debug.pr index b4786055..4a10132f 100644 --- a/src/debug.pr +++ b/src/debug.pr @@ -95,6 +95,12 @@ def id_decl_to_json(node: &parser::Node, types: bool) -> &Json { def id_decl_struct_to_json(node: &parser::Node, types: bool) -> &Json { let res = json::make_object() res("kind") = "IdDeclStruct" + if node.value.id_decl_struct.is_embed { + res("is_embed") = true + } + if node.value.id_decl_struct.is_bitfield { + res("bit_size") = node.value.id_decl_struct.bit_size + } res("ident") = node_to_json(node.value.id_decl_struct.ident, types) res("tpe") = node_to_json(node.value.id_decl_struct.tpe, types) return res diff --git a/src/parser.pr b/src/parser.pr index 2a3953c1..0f1ef5f2 100644 --- a/src/parser.pr +++ b/src/parser.pr @@ -317,6 +317,7 @@ export type NodeIdDeclStruct = struct { ident: &Node tpe: &Node is_bitfield: bool + is_embed: bool bit_size: size_t } @@ -1955,6 +1956,7 @@ def parse_id_decl_struct(parse_state: &ParseState) -> &Node { skip_newline(parse_state) token = peek(parse_state) + var is_embed = false var ident: &Node = null var tpe: &Node = null @@ -1963,14 +1965,24 @@ def parse_id_decl_struct(parse_state: &ParseState) -> &Node { skip_newline(parse_state) tpe = expect_type(parse_state) } else { - ident = expect_identifier(parse_state) - skip_newline(parse_state) - expect(parse_state, lexer::TokenType::COLON, "Expected ':'") - skip_newline(parse_state) - tpe = expect_type(parse_state) + if token.tpe == lexer::TokenType::OP_BAND { + is_embed = true + tpe = expect_type(parse_state) + } else { + ident = expect_identifier(parse_state) + token = peek(parse_state) + if token.tpe == lexer::TokenType::COLON { + pop(parse_state) + tpe = expect_type(parse_state) + } else { + tpe = ident + ident = null + is_embed = true + } + } } - if not ident and not is_bitfield { + if not ident and not is_bitfield and not is_embed { errors::errort(token, parse_state, "Expected identifier") } @@ -1979,6 +1991,7 @@ def parse_id_decl_struct(parse_state: &ParseState) -> &Node { ident = ident, tpe = tpe, is_bitfield = is_bitfield, + is_embed = is_embed, bit_size = bit_size ] !NodeIdDeclStruct node._hash = combine_hashes(node.kind !uint64, is_bitfield !uint64, bit_size, hash(ident), hash(tpe)) diff --git a/src/typechecking.pr b/src/typechecking.pr index 440a1cdb..bafa520f 100644 --- a/src/typechecking.pr +++ b/src/typechecking.pr @@ -84,6 +84,7 @@ export type StructMember = struct { is_bitfield: bool bit_size: size_t bit_offset: size_t + is_embed: bool } export type StructuralTypeMember = struct { @@ -899,7 +900,7 @@ export def clear_type_cache { } } -export def iterate_member_functions(tpe: &Type, visited: &Set(&Type) = null) -> TypeEntryMember { +export def iterate_member_functions(tpe: &Type, visited: &Set(&Type) = null, check_embed: bool = true) -> TypeEntryMember { if not tpe { return } let name = debug::type_to_str(tpe, full_name = true) @@ -908,6 +909,13 @@ export def iterate_member_functions(tpe: &Type, visited: &Set(&Type) = null) -> for var i in 0..entry.functions.length { yield entry.functions(i) } + if check_embed and entry.tpe and is_struct(entry.tpe) { + for var field in @entry.tpe.fields { + if field.is_embed { + yield from iterate_member_functions(field.tpe, visited) + } + } + } } let module = get_module(tpe) @@ -1256,7 +1264,7 @@ def is_getter(mb: StructuralTypeMember) -> bool { return mb.parameter_t.length == 0 } -def has_function(entry: &TypeEntry, intf: &Type, mb: StructuralTypeMember, module: &toolchain::Module, visited: &Set(&Type) = null) -> bool { +def has_function(entry: &TypeEntry, intf: &Type, mb: StructuralTypeMember, module: &toolchain::Module, visited: &Set(&Type) = null, check_embed: bool = true) -> bool { var tpe = entry.tpe if tpe.kind == typechecking::TypeKind::STRUCT { @@ -1280,7 +1288,7 @@ def has_function(entry: &TypeEntry, intf: &Type, mb: StructuralTypeMember, modul } } - for var member in iterate_member_functions(entry.tpe, visited) { + for var member in iterate_member_functions(entry.tpe, visited, check_embed) { let function = member.function if function.name != mb.name { continue } if module != member.module and not member.exported { continue } @@ -1326,7 +1334,7 @@ def has_function(entry: &TypeEntry, intf: &Type, mb: StructuralTypeMember, modul // Returns true if a implements b // b needs to be a structural type -export def implements(a: &Type, b: &Type, module: &toolchain::Module, visited: &Set(&Type) = null) -> bool { +export def implements(a: &Type, b: &Type, module: &toolchain::Module, visited: &Set(&Type) = null, check_embed: bool = true) -> bool { if not a or not b { return false } assert b.kind == TypeKind::STRUCTURAL if a.kind == typechecking::TypeKind::REFERENCE and equals(b, a.tpe) { @@ -1336,7 +1344,7 @@ export def implements(a: &Type, b: &Type, module: &toolchain::Module, visited: & var type_entry = create_type_entry(a) if not type_entry { return false } let nameb = debug::type_to_str(b, full_name = true) - if type_entry.cached.contains(nameb) { + if type_entry.cached.contains(nameb) and check_embed { return type_entry.cached(nameb) == CacheEntry::CONTAINS } @@ -1378,7 +1386,7 @@ export def implements(a: &Type, b: &Type, module: &toolchain::Module, visited: & found = true } if not found { - found = has_function(type_entry, b, mb, module, visited.copy()) + found = has_function(type_entry, b, mb, module, visited.copy(), check_embed) } if not found { type_entry.cached(nameb) = CacheEntry::NOT_CONTAINS @@ -1391,7 +1399,7 @@ export def implements(a: &Type, b: &Type, module: &toolchain::Module, visited: & for var i in 0..vector::length(b.members) { let mb = b.members(i) - if not has_function(type_entry, b, mb, module, visited.copy()) { + if not has_function(type_entry, b, mb, module, visited.copy(), check_embed) { type_entry.cached(nameb) = CacheEntry::NOT_CONTAINS return false } @@ -1562,6 +1570,20 @@ export def convert_type_score(a: &Type, b: &Type, module: &toolchain::Module, is } } + // Embedded structs + if b.kind == TypeKind::STRUCT or is_ref(b) and b.tpe and b.tpe.kind == TypeKind::STRUCT { + var nb = b.tpe if is_ref(b) else b + for var field in @nb.fields { + if field.is_embed { + if equals(field.tpe, a) { + return 7 + } else if is_ref(a) and equals(field.tpe, a.tpe) { + return 8 + } + } + } + } + // We need to check if they are actually compatible elsewhere if a.kind == TypeKind::TYPE_DEF { return convert_type_score(a.tpe, b, module, true) @@ -2634,19 +2656,25 @@ export def do_type_lookup(node: &parser::Node, state: &State, current_type: &Typ var field_tpe: &Type = null var name: Str var is_bitfield = false + var is_embed = field.value.id_decl_struct.is_embed var bit_size = 0 !size_t var field_type: &Type = null - if current_type and current_type.kind != TypeKind::STUB { field_type = current_type.fields(i).tpe } + if not is_embed and current_type and current_type.kind != TypeKind::STUB { field_type = current_type.fields(i).tpe } if (@field).kind == parser::NodeKind::ID_DECL_STRUCT { - let ident = (@field).value.id_decl_struct.ident - name = last_ident_to_str(ident) - if field.value.id_decl_struct.tpe { field.value.id_decl_struct.tpe.parent = field } - field_tpe = lookup_field_type((@field).value.id_decl_struct.tpe, state, field_type, cache) - if field.value.id_decl_struct.is_bitfield { - is_bitfield = true - bit_size = field.value.id_decl_struct.bit_size + if field.value.id_decl_struct.is_embed { + let ftpe = field.value.id_decl_struct.tpe + field_tpe = type_lookup(ftpe, state, ftpe.tpe, false, cache) + } else { + let ident = (@field).value.id_decl_struct.ident + name = last_ident_to_str(ident) + if field.value.id_decl_struct.tpe { field.value.id_decl_struct.tpe.parent = field } + field_tpe = lookup_field_type((@field).value.id_decl_struct.tpe, state, field_type, cache) + if field.value.id_decl_struct.is_bitfield { + is_bitfield = true + bit_size = field.value.id_decl_struct.bit_size + } } } else if (@field).kind == parser::NodeKind::STRUCT_T or (@field).kind == parser::NodeKind::UNION_T { @@ -2658,7 +2686,8 @@ export def do_type_lookup(node: &parser::Node, state: &State, current_type: &Typ line = line, name = name, tpe = field_tpe, - is_bitfield = is_bitfield, + is_bitfield = is_bitfield, + is_embed = is_embed, bit_size = bit_size ] !StructMember } @@ -5588,7 +5617,11 @@ def resolve_member(fields: &[StructMember], name: String) -> &Type { return member.tpe } } else { - let member = resolve_member((@member.tpe).fields, name) + var tpe = member.tpe + if member.is_embed and is_ref(member.tpe) { + tpe = member.tpe.tpe + } + let member = resolve_member(tpe.fields, name) if member { return member } } } @@ -5687,6 +5720,26 @@ def walk_MemberAccess_aggregate(node: &parser::Node, ucs: bool, state: &State) - return true } +// TODO Make this a generator, doesn't work for some reason +def flatten_fields(tpe: &Type, state: &State, v: &Vector(StructMember) = null) -> &Vector(StructMember) { + if not v { v = vector::make(StructMember) } + if not tpe { return v } + if not is_struct(tpe) { + errors::errorn(tpe.node, "Invalid embed, needs to be a struct or a reference to a struct") + return v + } + + for var field in @tpe.fields { + if field.is_embed { + let tpe = field.tpe.tpe if is_box(field.tpe) else field.tpe + flatten_fields(tpe, state, v) + } else { + v.push(field) + } + } + return v +} + def walk_StructLit(node: &parser::Node, state: &State) { let prev_tpe = node.tpe var tpe = prev_tpe @@ -5841,8 +5894,7 @@ def walk_StructLit(node: &parser::Node, state: &State) { let name = last_ident_to_str((@kwarg).value.named_arg.name) var found = false - for var j in 0..(@tpe).fields.size { - let field = (@tpe).fields(j) + for var field in flatten_fields(tpe, state) { if field.name == name { found = true diff --git a/test/test_compiler.pr b/test/test_compiler.pr index 5e6b4594..f718e8d1 100644 --- a/test/test_compiler.pr +++ b/test/test_compiler.pr @@ -1055,4 +1055,26 @@ def #test test_lambda { make_adder(10)(20)(30) """ assert compile(str) != null +} + +def #test test_embed { + var str = """ + type E = struct { + a: int + b: int + } + + type T = struct { + E + x: int + y: int + } + + def takes_e(e: E) {} + + let t = [a = 10, b = 20, x = 1, y = 2] !T + + takes_e(t) + """ + assert compile(str) != null } \ No newline at end of file diff --git a/test/test_parser.pr b/test/test_parser.pr index 8d24e093..5e92d2ce 100644 --- a/test/test_parser.pr +++ b/test/test_parser.pr @@ -3003,6 +3003,132 @@ def #test test_struct { }""")) } +def #test test_embed { + assert parse(""" + type T = struct { + X + a: int + } + """) == program(json::parse("""{ + "kind": "TypeDecl", + "share": "NONE", + "left": [ + { + "kind": "Identifier", + "path": [ + "T" + ], + "prefixed": false, + "args": null + } + ], + "right": [ + { + "kind": "Struct", + "body": [ + { + "kind": "IdDeclStruct", + "is_embed": true, + "ident": null, + "tpe": { + "kind": "Identifier", + "path": [ + "X" + ], + "prefixed": false, + "args": null + } + }, + { + "kind": "IdDeclStruct", + "ident": { + "kind": "Identifier", + "path": [ + "a" + ], + "prefixed": false, + "args": null + }, + "tpe": { + "kind": "Identifier", + "path": [ + "int" + ], + "prefixed": false, + "args": null + } + } + ] + } + ] + } + """)) + + assert parse(""" + type T = struct { + &X + a: int + } + """) == program(json::parse("""{ + "kind": "TypeDecl", + "share": "NONE", + "left": [ + { + "kind": "Identifier", + "path": [ + "T" + ], + "prefixed": false, + "args": null + } + ], + "right": [ + { + "kind": "Struct", + "body": [ + { + "kind": "IdDeclStruct", + "is_embed": true, + "ident": null, + "tpe": { + "kind": "RefT", + "kw": "VAR", + "tpe": { + "kind": "Identifier", + "path": [ + "X" + ], + "prefixed": false, + "args": null + } + } + }, + { + "kind": "IdDeclStruct", + "ident": { + "kind": "Identifier", + "path": [ + "a" + ], + "prefixed": false, + "args": null + }, + "tpe": { + "kind": "Identifier", + "path": [ + "int" + ], + "prefixed": false, + "args": null + } + } + ] + } + ] + } + """)) +} + def #test test_interface { assert parse(""" type I = interface { diff --git a/test/test_runtime.pr b/test/test_runtime.pr index 29206e7f..e956656f 100644 --- a/test/test_runtime.pr +++ b/test/test_runtime.pr @@ -869,5 +869,55 @@ def #test test_lambda { assert make_adder(10)(20)(30) == 60 """ + assert run_source(str) == 0 +} + +def #test test_embed { + var str = """ + type Base = struct { a: int } + type T = struct { + Base + b: int + } + type U = struct { + &Base + b: int + } + + def takes_1(b: &Base) -> int { return b.a } + def takes_2(b: Base) -> int { return b.a } + + let t = [a = 10, b = 20] !T + let u = [a = 20, b = 30] !U + + assert takes_1(t) == 10 + assert takes_1(u) == 20 + assert takes_2(t) == 10 + + assert t.a == 10 + assert u.a == 20 + assert t.b == 20 + assert u.b == 30 + """ + + assert run_source(str) == 0 + + str = """ + type I = interface { def method } + type Base = struct { a: int } + type E = struct { + &Base + b: int + } + + def method(b: &Base) {} + def takes(i: &I) { + i.method() + } + + let e = [ a = 20, b = 30 ] !E + takes(e) + """ + assert run_source(str) == 0 } \ No newline at end of file