From: Brendan Hansen Date: Wed, 30 Dec 2020 03:34:32 +0000 (-0600) Subject: working on making structs better as a whole X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=02bbc29f04a0281cc419ea0316320d7402b6449b;p=onyx.git working on making structs better as a whole --- diff --git a/docs/bugs b/docs/bugs index 10e43eaa..b5c6a35d 100644 --- a/docs/bugs +++ b/docs/bugs @@ -53,7 +53,7 @@ List of known bugs: } ``` -[ ] Polymorphic structs do not recognize default values for members. +[X] Polymorphic structs do not recognize default values for members. [ ] `use` on struct members does not work correctly if the type is a union. ``` diff --git a/docs/struct_fixes b/docs/struct_fixes new file mode 100644 index 00000000..e9310b70 --- /dev/null +++ b/docs/struct_fixes @@ -0,0 +1,49 @@ +Currently, there are many janky things about structs in Onyx. I want +to revisit all of the code related to them and fix all the issues I +am having. To recap, here are all of the features that are required +for structs to work: + * #union + * #size + * #align + * `use` members + * struct literals + * polymorphic structs + +All of these features work well individually, but when used together +(such as struct literals over polymoprhic structs), they have many +issues. + +Here is a brief explanation of the current data model: + * AstStruct contains: + - an array of AstStructMembers + - the minimum size + - the minimum alignment + + * AstStructMembers contain: + - A symbol (token) for the name + - the initial value + + * AstStructLiterals contain: + - an array of values (unnamed) + - an array of AstStructMembers representing named values + + * AstPolyStructs contain: + - the base AstStruct + - an array of tokens representing the parameters + - the scope the struct was defined in + +In principle, this is the simple way each of the features should work: + * #union + - use 'max' instead of '+' when computing struct size + - all members have offset 0 + * #size + - the size is the minimum of the size given and the computed size + * #align + - the alignment is the minimum of the alignment given and the computed alignment + * `use` members + - adds members of used type to member list, with proper offsets + * struct literals + - ensures all values needed for the struct are present (no + automatic zero values) + * polymorphic structs + - copy the base struct, introduce the type symbols, resolve symbols, win diff --git a/include/onyxtypes.h b/include/onyxtypes.h index 15bf0856..54251639 100644 --- a/include/onyxtypes.h +++ b/include/onyxtypes.h @@ -67,6 +67,10 @@ typedef struct StructMember { // be many struct members, and iterating through an array would be // easier and less costly. - brendanfh 2020/09/17 char *name; + + struct AstTyped* initial_value; + + b32 member_was_used : 1; } StructMember; #define TYPE_KINDS \ diff --git a/onyx b/onyx index 217ea22c..2722db43 100755 Binary files a/onyx and b/onyx differ diff --git a/progs/odin_example.onyx b/progs/odin_example.onyx index 9de1b6d2..090eadd6 100644 --- a/progs/odin_example.onyx +++ b/progs/odin_example.onyx @@ -93,7 +93,7 @@ main :: proc (args: [] cstr) { printf("%p\n", a.int); printf("%f\n", a.float); - e := Entity.{ pos = Vec2.{ 1, 2 } }; + e := Entity.{ Vec2.{ 1, 2 } }; { foo : [5] [2] u32; diff --git a/src/onyxchecker.c b/src/onyxchecker.c index 8a2610ed..40be1780 100644 --- a/src/onyxchecker.c +++ b/src/onyxchecker.c @@ -1008,12 +1008,11 @@ b32 check_range_literal(AstRangeLiteral** prange) { } if (range->step == NULL) { - // HACK: This relies on the third member of the 'range' struct to exist, be the step, - // and have an initial_value. - AstStructMember* step_member = ((AstStructType *) builtin_range_type)->members[2]; - if (check_expression(&step_member->initial_value)) return 1; + type_lookup_member(expected_range_type, "step", &smem); + assert(smem.initial_value != NULL); + if (check_expression(&smem.initial_value)) return 1; - range->step = step_member->initial_value; + range->step = smem.initial_value; } return 0; diff --git a/src/onyxsymres.c b/src/onyxsymres.c index 5f7a668a..2d0921c7 100644 --- a/src/onyxsymres.c +++ b/src/onyxsymres.c @@ -356,21 +356,24 @@ static void symres_struct_literal(AstStructLiteral* sl) { return; } + if (s.member_was_used) { + (*smem)->flags |= Ast_Flag_Struct_Mem_Used; + } + sl->values[s.idx] = (*smem)->initial_value; } if (sl->type->kind == Type_Kind_Struct) { - AstStructType* st = (AstStructType *) sl->type_node; bh_arr_each(StructMember*, smem, sl->type->Struct.memarr) { u32 idx = (*smem)->idx; if (sl->values[idx] == NULL) { - if (st->kind != Ast_Kind_Struct_Type || st->members[idx]->initial_value == NULL) { + if ((*smem)->initial_value == NULL) { onyx_report_error(sl->token->pos, "No value was given for the field '%s'.", (*smem)->name); return; } - sl->values[idx] = st->members[idx]->initial_value; + sl->values[idx] = (*smem)->initial_value; } } } diff --git a/src/onyxtypes.c b/src/onyxtypes.c index 0145f72e..31ffcc04 100644 --- a/src/onyxtypes.c +++ b/src/onyxtypes.c @@ -367,6 +367,8 @@ Type* type_build_from_ast(bh_allocator alloc, AstType* type_node) { .type = (*member)->type, .idx = idx, .name = bh_strdup(alloc, (*member)->token->text), + .member_was_used = ((*member)->flags & Ast_Flag_Struct_Mem_Used) != 0, + .initial_value = (*member)->initial_value, }; bh_table_put(StructMember, s_type->Struct.members, (*member)->token->text, smem); @@ -647,6 +649,9 @@ u32 type_get_alignment_log2(Type* type) { b32 type_lookup_member(Type* type, char* member, StructMember* smem) { if (type->kind == Type_Kind_Pointer) type = type->Pointer.elem; + smem->member_was_used = 0; + smem->initial_value = NULL; + switch (type->kind) { case Type_Kind_Struct: { TypeStruct* stype = &type->Struct; diff --git a/tests/struct_robustness b/tests/struct_robustness new file mode 100644 index 00000000..e78fd204 --- /dev/null +++ b/tests/struct_robustness @@ -0,0 +1,25 @@ +Testing a simple structure. +SimpleStruct<16, 4>(41, 67, Steve) + + +Testing a simple union. +0x3F000000 == 0x3F000000 + + +Testing a struct with default values. +DefaultedStruct(0, 1.0, 2, 3.0) +DefaultedStruct(3, 1.0, 2, 0.0) + + +Testing a struct with `use`. +StructWithUse(1234, (1.0, 2.0), 5678) +StructWithUse(1234, (1.0, 2.0), 5678) + + +Testing a polymorphic struct. +PolyStruct(1234, 5678.0) +PolyStruct(1234.0, 5678) + + +Testing a polymorphic struct with default values. +PolyStruct(1234, 5678.0) diff --git a/tests/struct_robustness.onyx b/tests/struct_robustness.onyx new file mode 100644 index 00000000..1391139e --- /dev/null +++ b/tests/struct_robustness.onyx @@ -0,0 +1,145 @@ +use package core + +main :: proc (args: [] cstr) { + + test_simple_struct(); + test_simple_union(); + test_default_values(); + test_simple_use(); + test_polymorphic(); + test_polymorphic_with_defaults(); + + test_simple_struct :: proc () { + println("Testing a simple structure."); + + SimpleStruct :: struct { + age : u16; + height : u32; + + name : str; + } + + ss := SimpleStruct.{ 41, 67, "Steve" }; + + printf("SimpleStruct<%i, %i>(%i, %i, %s)\n", + sizeof SimpleStruct, + alignof SimpleStruct, + cast(u32) ss.age, ss.height, ss.name); + } + + test_simple_union :: proc () { + println("\n\nTesting a simple union."); + + SimpleUnion :: struct #union { + int_val : i32; + float_val : f32; + } + + u : SimpleUnion; + u.float_val = 0.5; + + printf("%p == 0x3F000000\n", u.int_val); + } + + test_default_values :: proc () { + println("\n\nTesting a struct with default values."); + + DefaultedStruct :: struct { + i : i32 = 0; + f : f32 = 1; + l : i64 = 2; + d : f64 = 3; + } + + ds1 := DefaultedStruct.{}; + print_defaulted(ds1); + + ds2 := DefaultedStruct.{ i = 3, d = 0 }; + print_defaulted(ds2); + + print_defaulted :: proc (use ds: DefaultedStruct) { + printf("DefaultedStruct(%i, %f, %l, %d)\n", i, f, l, d); + } + } + + test_simple_use :: proc () { + println("\n\nTesting a struct with `use`."); + + StructWithUse :: struct { + first_member : i32; + + use used_member : UsedMember; + + last_member : i32; + } + + UsedMember :: struct { + x : f32; + y : f32; + } + + // This does not work, but it should. + // swu := StructWithUse.{ 1234, UsedMember.{ 1, 2 }, 5678 }; + + // Neither does this, but it also should. + // swu := StructWithUse.{ + // first_member = 1234, + // used_member = UsedMember.{ 1, 2 }, + // last_member = 5678, + // }; + + // This does, when all non-used members are listed out. + swu := StructWithUse.{ 1234, 1, 2, 5678 }; + + // This also does. + swu2 := StructWithUse.{ + first_member = 1234, + x = 1, + y = 2, + last_member = 5678, + }; + + print_swu :: proc (use swu: StructWithUse) { + printf("StructWithUse(%i, (%f, %f), %i)\n", + first_member, x, y, last_member); + } + + print_swu(swu); + print_swu(swu2); + } + + test_polymorphic :: proc () { + println("\n\nTesting a polymorphic struct."); + + PolyStruct :: struct ($T, $R) { + t_data : T; + r_data : R; + } + + ps1 : PolyStruct(i32, f32); + ps1.t_data = 1234; + ps1.r_data = 5678; + + printf("PolyStruct(%i, %f)\n", ps1.t_data, ps1.r_data); + + // Currently, this is how you have to do this. + ps2 := (#type PolyStruct(f32, i32)).{ 1234, 5678 }; + printf("PolyStruct(%f, %i)\n", ps2.t_data, ps2.r_data); + } + + test_polymorphic_with_defaults :: proc () { + println("\n\nTesting a polymorphic struct with default values."); + + PolyStruct :: struct ($T, $R) { + t_data : T = 1234; + r_data : R = 5678; + } + + PolyStructTyped :: #type PolyStruct(i32, f32); + + ps := PolyStructTyped.{}; + printf("PolyStruct(%i, %f)\n", ps.t_data, ps.r_data); + } +} + +#include_file "core/std/js" \ No newline at end of file