added: basics of tagged unions
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 22 May 2023 03:50:47 +0000 (22:50 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 22 May 2023 03:50:47 +0000 (22:50 -0500)
13 files changed:
compiler/include/astnodes.h
compiler/include/lex.h
compiler/include/types.h
compiler/src/astnodes.c
compiler/src/checker.c
compiler/src/entities.c
compiler/src/lex.c
compiler/src/parser.c
compiler/src/symres.c
compiler/src/types.c
compiler/src/wasm_emit.c
docs/ideas/tagged_unions.md [new file with mode: 0644]
tests/tagged_unions.onyx

index c88cfc8072c379b89b5d5068bb3843d869cdcc4a..fc9ecacf31b81ab9a920164c4efe725efc2da44b 100644 (file)
@@ -70,6 +70,9 @@
     NODE(PolyStructType)       \
     NODE(PolyStructParam)      \
     NODE(PolyCallType)         \
+    NODE(UnionType)            \
+    NODE(UnionVariant)         \
+    NODE(PolyUnionType)        \
     NODE(EnumType)             \
     NODE(EnumValue)            \
     NODE(TypeAlias)            \
@@ -172,6 +175,8 @@ typedef enum AstKind {
     Ast_Kind_Struct_Type,
     Ast_Kind_Poly_Struct_Type,
     Ast_Kind_Poly_Call_Type,
+    Ast_Kind_Union_Type,
+    Ast_Kind_Poly_Union_Type,
     Ast_Kind_Enum_Type,
     Ast_Kind_Type_Alias,
     Ast_Kind_Type_Raw_Alias,
@@ -181,6 +186,7 @@ typedef enum AstKind {
     Ast_Kind_Type_End,
 
     Ast_Kind_Struct_Member,
+    Ast_Kind_Union_Variant,
     Ast_Kind_Enum_Value,
 
     Ast_Kind_NumLit,
@@ -1017,6 +1023,39 @@ struct AstPolyCallType {
     // NOTE: These nodes can be either AstTypes, or AstTyped expressions.
     bh_arr(AstNode *) params;
 };
+struct AstUnionType {
+    AstType_base;
+    char *name;
+
+    bh_arr(AstUnionVariant *) variants;
+    bh_arr(AstTyped *) meta_tags;
+
+    // NOTE: Used to cache the actual type, since building
+    // a union type is kind of complicated and should
+    // only happen once.
+    Type *utcache;
+
+    // NOTE: This type is used when the union has not been
+    // completely generated, but is a valid pointer to where the
+    // type will be generated to.
+    Type *pending_type;
+
+    // NOTE: Used to store statically bound expressions in the union.
+    Scope* scope;
+
+    OnyxFilePos polymorphic_error_loc;
+    ConstraintContext constraints;
+
+    bh_arr(AstType *)       polymorphic_argument_types;
+    bh_arr(AstPolySolution) polymorphic_arguments;
+
+    b32 pending_type_is_valid : 1;
+    // b32 ready_to_build_type   : 1;
+};
+struct AstUnionVariant {
+    AstTyped_base;
+    bh_arr(AstTyped *) meta_tags;
+};
 struct AstEnumType {
     AstType_base;
     char *name;
index 23818d58a11711dc768ed1a8ef9e745891ea91d3..37c4e084c55598ea00bb943949ede1c6d34f8ccd 100644 (file)
@@ -17,6 +17,7 @@ typedef enum TokenType {
     Token_Type_Keyword_Start,
     Token_Type_Keyword_Package,
     Token_Type_Keyword_Struct,
+    Token_Type_Keyword_Union,
     Token_Type_Keyword_Enum,
     Token_Type_Keyword_Use,
     Token_Type_Keyword_If,
index 7581780cf5a296ec4a74cd7423f57c4c49cd3c9d..430e4f8fdf30d9ae9c0274c24915477d0be772c7 100644 (file)
@@ -103,6 +103,14 @@ typedef enum StructProcessingStatus {
     SPS_Uses_Done,
 } StructProcessingStatus;
 
+typedef struct UnionVariant {
+    char *name;
+    Type *type;
+    u32 tag_value;
+    bh_arr(struct AstTyped *) meta_tags;
+    struct OnyxToken *token;
+} UnionVariant;
+
 #define TYPE_KINDS \
     TYPE_KIND(Basic, TypeBasic)                                   \
     TYPE_KIND(Pointer, struct {                                   \
@@ -153,9 +161,20 @@ typedef enum StructProcessingStatus {
     TYPE_KIND(Distinct, struct {                                  \
         char* name;                                               \
         Type* base_type;                                          \
+    })                                                            \
+    TYPE_KIND(Union, struct {                                     \
+        u32 size;                                                 \
+        u32 alignment;                                            \
+        char* name;                                               \
+        Type* tag_type;                                           \
+        Table(UnionVariant *) variants;                           \
+        bh_arr(struct AstPolySolution) poly_sln;                  \
+        struct AstType *constructed_from;                         \
+        bh_arr(struct AstTyped *) meta_tags;                      \
     })
 
 
+
 typedef enum TypeKind {
     Type_Kind_Invalid,
 
index 5b687cb04b57b10f4215f7ea9e7972d43d4ae218..213650e9e53ea01c3985bea9139216d70c8efa42 100644 (file)
@@ -46,6 +46,8 @@ static const char* ast_node_names[] = {
     "STRUCT TYPE",
     "POLYMORPHIC STRUCT TYPE",
     "POLYMORPHIC STRUCT CALL TYPE",
+    "UNION TYPE",
+    "POLYMORPHIC UNION TYPE",
     "ENUM TYPE",
     "TYPE_ALIAS",
     "TYPE RAW ALIAS",
@@ -55,6 +57,7 @@ static const char* ast_node_names[] = {
     "TYPE_END (BAD)",
 
     "STRUCT MEMBER",
+    "UNION VARIANT",
     "ENUM VALUE",
 
     "NUMERIC LITERAL",
index e59cf45139bfea4583a77aa57e92e8dcbacfda70..4f5a6cc4db9e0f365707cd0c79d875f5777bd325 100644 (file)
@@ -1440,6 +1440,40 @@ CheckStatus check_struct_literal(AstStructLiteral* sl) {
         if (sl->type == NULL)
             YIELD(sl->token->pos, "Trying to resolve type of struct literal.");
     }
+    
+    if (sl->type->kind == Type_Kind_Union) {
+        if (bh_arr_length(sl->args.values) != 0 || bh_arr_length(sl->args.named_values) != 1) {
+            ERROR_(sl->token->pos, "Expected exactly one named member when constructing an instance of a union type, '%s'.", type_get_name(sl->type));
+        }
+
+        AstNamedValue* value = sl->args.named_values[0];
+        token_toggle_end(value->token);
+
+        Type *union_type = sl->type;
+        UnionVariant *matched_variant = union_type->Union.variants[
+            shgeti(union_type->Union.variants, value->token->text)
+        ].value;
+        token_toggle_end(value->token);
+
+        if (!matched_variant) {
+            ERROR_(value->token->pos, "'%b' is not a variant of '%s'.",
+                    value->token->text, value->token->length, type_get_name(union_type));
+        }
+
+        TYPE_CHECK(&value->value, matched_variant->type) {
+            ERROR_(value->token->pos,
+                   "Mismatched type in initialized type. Expected something of type '%s', got '%s'.",
+                   type_get_name(matched_variant->type),
+                   type_get_name(value->value->type));
+        }
+
+        AstNumLit *tag_value = make_int_literal(context.ast_alloc, matched_variant->tag_value);
+        tag_value->type = union_type->Union.tag_type;
+
+        bh_arr_push(sl->args.values, (AstTyped *) tag_value);
+        bh_arr_push(sl->args.values, value->value);
+        return Check_Success;
+    }
 
     if (!type_is_structlike_strict(sl->type)) {
         //
@@ -2668,6 +2702,38 @@ CheckStatus check_overloaded_function(AstOverloadedFunction* ofunc) {
     return Check_Success;
 }
 
+static void mark_all_functions_used_in_scope(Scope *scope) {
+    //
+    // This ensures that all procedures defined inside of a structure are
+    // not pruned and omitted from the binary. This is because a large
+    // use-case of procedures in structures is dynamically linking them
+    // using the type info data.
+    if (scope) {
+        fori (i, 0, shlen(scope->symbols)) {
+            AstNode* node = scope->symbols[i].value;
+            if (node->kind == Ast_Kind_Function) {
+                node->flags |= Ast_Flag_Function_Used;
+            }
+        }
+    }
+}
+
+CheckStatus check_meta_tags(bh_arr(AstTyped *) tags) {
+    if (tags) {
+        bh_arr_each(AstTyped *, meta, tags) {
+            CHECK(expression, meta);
+            resolve_expression_type(*meta);
+
+            if (((*meta)->flags & Ast_Flag_Comptime) == 0) {
+                onyx_report_error((*meta)->token->pos, Error_Critical, "#tag expressions are expected to be compile-time known.");
+                return Check_Error;
+            }
+        }
+    }
+
+    return Check_Success;
+}
+
 CheckStatus check_struct(AstStructType* s_node) {
     if (s_node->entity_defaults && s_node->entity_defaults->state < Entity_State_Check_Types)
         YIELD(s_node->token->pos, "Waiting for struct member defaults to pass symbol resolution.");
@@ -2709,19 +2775,7 @@ CheckStatus check_struct(AstStructType* s_node) {
         CHECK(constraint_context, &s_node->constraints, s_node->scope, pos);
     }
 
-    //
-    // This ensures that all procedures defined inside of a structure are
-    // not pruned and omitted from the binary. This is because a large
-    // use-case of procedures in structures is dynamically linking them
-    // using the type info data.
-    if (s_node->scope) {
-        fori (i, 0, shlen(s_node->scope->symbols)) {
-            AstNode* node = s_node->scope->symbols[i].value;
-            if (node->kind == Ast_Kind_Function) {
-                node->flags |= Ast_Flag_Function_Used;
-            }
-        }
-    }
+    mark_all_functions_used_in_scope(s_node->scope);
 
     bh_arr_each(AstStructMember *, smem, s_node->members) {
         if ((*smem)->type_node != NULL) {
@@ -2776,17 +2830,7 @@ CheckStatus check_struct_defaults(AstStructType* s_node) {
     if (s_node->entity_type && s_node->entity_type->state == Entity_State_Failed)
         return Check_Failed;
 
-    if (s_node->meta_tags) {
-        bh_arr_each(AstTyped *, meta, s_node->meta_tags) {
-            CHECK(expression, meta);
-            resolve_expression_type(*meta);
-
-            if (((*meta)->flags & Ast_Flag_Comptime) == 0) {
-                onyx_report_error((*meta)->token->pos, Error_Critical, "#tag expressions are expected to be compile-time known.");
-                return Check_Error;
-            }
-        }
-    }
+    CHECK(meta_tags, s_node->meta_tags);
 
     bh_arr_each(StructMember *, smem, s_node->stcache->Struct.memarr) {
         if ((*smem)->initial_value && *(*smem)->initial_value) {
@@ -2802,19 +2846,37 @@ CheckStatus check_struct_defaults(AstStructType* s_node) {
             resolve_expression_type(*(*smem)->initial_value);
         }
 
-        if ((*smem)->meta_tags) {
-            bh_arr_each(AstTyped *, meta, (*smem)->meta_tags) {
-                CHECK(expression, meta);
-                resolve_expression_type(*meta);
+        CHECK(meta_tags, (*smem)->meta_tags);
+    }
 
-                if (((*meta)->flags & Ast_Flag_Comptime) == 0) {
-                    onyx_report_error((*meta)->token->pos, Error_Critical, "#tag expressions are expected to be compile-time known.");
-                    return Check_Error;
-                }
-            }
+    return Check_Success;
+}
+
+CheckStatus check_union(AstUnionType *u_node) {
+    if (u_node->constraints.constraints) {
+        u_node->constraints.produce_errors = (u_node->flags & Ast_Flag_Header_Check_No_Error) == 0;
+
+        OnyxFilePos pos = u_node->token->pos;
+        if (u_node->polymorphic_error_loc.filename) {
+            pos = u_node->polymorphic_error_loc;
         }
+        CHECK(constraint_context, &u_node->constraints, u_node->scope, pos);
+    }
+
+    mark_all_functions_used_in_scope(u_node->scope);
+
+    CHECK(meta_tags, u_node->meta_tags);
+
+    bh_arr_each(AstUnionVariant *, variant, u_node->variants) {
+        CHECK(type, &(* variant)->type_node);
+        CHECK(meta_tags, (* variant)->meta_tags);
     }
 
+    type_build_from_ast(context.ast_alloc, (AstType *) u_node);
+    if (u_node->pending_type == NULL || !u_node->pending_type_is_valid)
+        YIELD(u_node->token->pos, "Waiting for type to be constructed.");
+
+    u_node->utcache = u_node->pending_type;
     return Check_Success;
 }
 
index c02ab13f2b0bbc549d2877aee3ab02fd154c69e3..f3c877dc2cd47c93b738a2c9f9a2c0279317642a 100644 (file)
@@ -291,6 +291,7 @@ void add_entities_for_node(bh_arr(Entity *) *target_arr, AstNode* node, Scope* s
             // fallthrough
         }
 
+        case Ast_Kind_Union_Type:
         case Ast_Kind_Poly_Struct_Type:
         case Ast_Kind_Type_Alias: {
             ent.type = Entity_Type_Type_Alias;
index 20126a1d066748a06d8c17129ccf08028a2062b2..4ce31361e7a85226c78aa38effd312f120aa22ce 100644 (file)
@@ -12,6 +12,7 @@ static const char* token_type_names[] = {
     "", // start
     "package",
     "struct",
+    "union",
     "enum",
     "use",
     "if",
@@ -380,6 +381,7 @@ whitespace_skipped:
         break;
     case 'u':
         LITERAL_TOKEN("use",         1, Token_Type_Keyword_Use);
+        LITERAL_TOKEN("union",       1, Token_Type_Keyword_Union);
         break;
     case 'w':
         LITERAL_TOKEN("while",       1, Token_Type_Keyword_While);
index 2328529b1f4279f1991bef508c41c5396612024b..358083ae5ecb896082b44e834cf7758d17972e10 100644 (file)
@@ -2082,20 +2082,38 @@ static AstTypeOf* parse_typeof(OnyxParser* parser) {
     return type_of;
 }
 
-static void struct_type_create_scope(OnyxParser *parser, AstStructType *s_node) {
-    if (!s_node->scope) {
-        s_node->scope = scope_create(context.ast_alloc, parser->current_scope, s_node->token->pos);
+static void type_create_scope(OnyxParser *parser, Scope ** scope, OnyxToken* token) {
+    if (scope && !*scope) {
+        *scope = scope_create(context.ast_alloc, parser->current_scope, token->pos);
 
         if (bh_arr_length(parser->current_symbol_stack) == 0) {
-            s_node->scope->name = "<anonymous>";
+            (*scope)->name = "<anonymous>";
 
         } else {
             OnyxToken* current_symbol = bh_arr_last(parser->current_symbol_stack);
-            s_node->scope->name = bh_aprintf(global_heap_allocator, "%b", current_symbol->text, current_symbol->length);
+            (*scope)->name = bh_aprintf(global_heap_allocator, "%b", current_symbol->text, current_symbol->length);
         }
     }
 }
 
+static void parse_meta_tags(OnyxParser *parser, bh_arr(AstTyped *) *out_arr) {
+    bh_arr(AstTyped *) meta_tags = NULL;
+    while (parse_possible_directive(parser, "tag") || consume_token_if_next(parser, '@')) {
+        if (meta_tags == NULL) bh_arr_new(global_heap_allocator, meta_tags, 1);
+
+        parser->tag_depth += 1;
+
+        do {
+            AstTyped* expr = parse_expression(parser, 0);
+            bh_arr_push(meta_tags, expr);
+        } while (consume_token_if_next(parser, ','));
+
+        parser->tag_depth -= 1;
+    }
+
+    *out_arr = meta_tags;
+}
+
 static AstStructType* parse_struct(OnyxParser* parser) {
     OnyxToken *s_token = expect_token(parser, Token_Type_Keyword_Struct);
 
@@ -2107,7 +2125,7 @@ static AstStructType* parse_struct(OnyxParser* parser) {
 
     flush_stored_tags(parser, &s_node->meta_tags);
 
-    struct_type_create_scope(parser, s_node);
+    type_create_scope(parser, &s_node->scope, s_node->token);
     Scope *scope_to_restore_parser_to = parser->current_scope;
     Scope *scope_symbols_in_structures_should_be_bound_to = s_node->scope;
 
@@ -2210,18 +2228,7 @@ static AstStructType* parse_struct(OnyxParser* parser) {
         }
 
         bh_arr(AstTyped *) meta_tags=NULL;
-        while (parse_possible_directive(parser, "tag") || consume_token_if_next(parser, '@')) {
-            if (meta_tags == NULL) bh_arr_new(global_heap_allocator, meta_tags, 1);
-
-            parser->tag_depth += 1;
-
-            do {
-                AstTyped* expr = parse_expression(parser, 0);
-                bh_arr_push(meta_tags, expr);
-            } while (consume_token_if_next(parser, ','));
-
-            parser->tag_depth -= 1;
-        }
+        parse_meta_tags(parser, &meta_tags);
 
         if (parser->curr->type == '}') {
             consume_token(parser);
@@ -2293,6 +2300,80 @@ static AstStructType* parse_struct(OnyxParser* parser) {
     }
 }
 
+static AstUnionType* parse_union(OnyxParser* parser) {
+    OnyxToken* union_token = expect_token(parser, Token_Type_Keyword_Union);
+
+    AstUnionType* u_node;
+    u_node = make_node(AstUnionType, Ast_Kind_Union_Type);
+    u_node->token = union_token;
+
+    flush_stored_tags(parser, &u_node->meta_tags);
+
+    type_create_scope(parser, &u_node->scope, u_node->token);
+    Scope *scope_to_restore_parser_to = parser->current_scope;
+    Scope *scope_symbols_in_unions_should_be_bound_to = u_node->scope;
+
+    // Parse constraints clause
+    if (parser->curr->type == Token_Type_Keyword_Where) {
+        parse_constraints(parser, &u_node->constraints);
+    }
+
+    parser->current_scope = scope_symbols_in_unions_should_be_bound_to;
+    bh_arr_new(global_heap_allocator, u_node->variants, 4);
+
+    expect_token(parser, '{');
+    while (!consume_token_if_next(parser, '}')) {
+        if (parser->hit_unexpected_token) return u_node;
+
+        if (next_tokens_are(parser, 3, Token_Type_Symbol, ':', ':')) {
+            OnyxToken* binding_name = expect_token(parser, Token_Type_Symbol);
+            consume_token(parser);
+
+            AstBinding* binding = parse_top_level_binding(parser, binding_name);
+            if (binding) ENTITY_SUBMIT(binding);
+
+            consume_token_if_next(parser, ';');
+            continue;
+        }
+
+        bh_arr(AstTyped *) meta_tags=NULL;
+        parse_meta_tags(parser, &meta_tags);
+
+        if (parser->curr->type == '}') {
+            consume_token(parser);
+            break;
+        }
+
+        AstUnionVariant *variant = make_node(AstUnionVariant, Ast_Kind_Union_Variant);
+        variant->meta_tags = meta_tags;
+        variant->token = expect_token(parser, Token_Type_Symbol);
+
+        expect_token(parser, ':');
+
+        variant->type_node = parse_type(parser);
+
+        bh_arr_push(u_node->variants, variant);
+
+        if (peek_token(0)->type != '}') {
+            expect_token(parser, ';');
+        }
+    }
+
+    parser->current_scope = scope_to_restore_parser_to;
+
+    ENTITY_SUBMIT(u_node);
+    return u_node;
+
+    //if (poly_struct != NULL) {
+    //    // NOTE: Not a StructType
+    //    return (AstStructType *) poly_struct;
+
+    //} else {
+    //    ENTITY_SUBMIT(s_node);
+    //    return s_node;
+    //}
+}
+
 static AstInterface* parse_interface(OnyxParser* parser) {
     AstInterface *interface = make_node(AstInterface, Ast_Kind_Interface);
     interface->token = expect_token(parser, Token_Type_Keyword_Interface);
@@ -3126,6 +3207,7 @@ static AstTyped* parse_top_level_expression(OnyxParser* parser) {
     if (parser->curr->type == Token_Type_Keyword_Interface) return (AstTyped *) parse_interface(parser);
     if (parser->curr->type == Token_Type_Keyword_Enum)      return (AstTyped *) parse_enum_declaration(parser);
     if (parser->curr->type == Token_Type_Keyword_Macro)     return (AstTyped *) parse_macro(parser);
+    if (parser->curr->type == Token_Type_Keyword_Union)     return (AstTyped *) parse_union(parser);
 
     if (parser->curr->type == '#') {
         if (parse_possible_directive(parser, "type")) {
@@ -3224,11 +3306,15 @@ static AstBinding* parse_top_level_binding(OnyxParser* parser, OnyxToken* symbol
         case Ast_Kind_StrLit:
             break;
 
+        // This makes a large assumption that the "name" member is in the same
+        // place on all of these structures. It is, but maybe there should be a
+        // "base" struct that these structure "inherit" from, so that is guaranteed?
         case Ast_Kind_Interface:
         case Ast_Kind_Struct_Type:
         case Ast_Kind_Poly_Struct_Type:
         case Ast_Kind_Enum_Type:
         case Ast_Kind_Distinct_Type:
+        case Ast_Kind_Union_Type:
             ((AstStructType *) node)->name = generate_name_within_scope(parser, symbol);
             goto default_case;
 
index 214a8a410539a14910e08988fe32aa8b87f11fa5..50b39221e14075eb7862c7e767c20460aa292c66 100644 (file)
@@ -147,13 +147,66 @@ static SymresStatus symres_struct_type(AstStructType* s_node) {
             SymresStatus ss = symres_type(&member->type_node);
             if (ss != Symres_Success) {
                 s_node->flags &= ~Ast_Flag_Type_Is_Resolved;
-                if (s_node->scope) scope_leave();
+                scope_leave();
                 return ss;
             }
         }
     }
 
-    if (s_node->scope) scope_leave();
+    scope_leave();
+    return Symres_Success;
+}
+
+static SymresStatus symres_union_type(AstUnionType* u_node) {
+    if (u_node->flags & Ast_Flag_Type_Is_Resolved) return Symres_Success;
+    u_node->flags |= Ast_Flag_Comptime;
+
+    if (u_node->meta_tags) {
+        bh_arr_each(AstTyped *, meta, u_node->meta_tags) {
+            SYMRES(expression, meta);
+        }
+    }
+
+    u_node->flags |= Ast_Flag_Type_Is_Resolved;
+
+    assert(u_node->scope);
+    scope_enter(u_node->scope);
+    
+    // if (u_node->polymorphic_argument_types) {
+    //     assert(u_node->polymorphic_arguments);
+
+    //     SymresStatus ss = Symres_Success, result;
+    //     fori (i, 0, (i64) bh_arr_length(u_node->polymorphic_argument_types)) {
+    //         result = symres_type(&u_node->polymorphic_argument_types[i]);
+    //         if (result > ss) ss = result;
+
+    //         if (u_node->polymorphic_arguments[i].value) {
+    //             result = symres_expression(&u_node->polymorphic_arguments[i].value);
+    //             if (result > ss) ss = result;
+    //         }
+    //     }
+    // }
+
+    if (u_node->constraints.constraints) {
+        bh_arr_each(AstConstraint *, constraint, u_node->constraints.constraints) {
+            SYMRES(constraint, *constraint);
+        }
+    }
+
+    fori (i, 0, bh_arr_length(u_node->variants)) {
+        AstUnionVariant *variant = u_node->variants[i];
+        // track_declaration_for_symbol_info(member->token->pos, (AstNode *) member);
+
+        assert(variant->type_node);
+        SymresStatus ss = symres_type(&variant->type_node);
+        if (ss != Symres_Success) {
+            u_node->flags &= ~Ast_Flag_Type_Is_Resolved;
+            scope_leave();
+            return ss;
+        }
+    }
+
+    scope_leave();
     return Symres_Success;
 }
 
@@ -190,6 +243,7 @@ static SymresStatus symres_type(AstType** type) {
         }
 
         case Ast_Kind_Struct_Type: SYMRES(struct_type, (AstStructType *) *type); break;
+        case Ast_Kind_Union_Type:  SYMRES(union_type, (AstUnionType *) *type); break;
         case Ast_Kind_Array_Type: {
             AstArrayType* a_node = (AstArrayType *) *type;
 
@@ -1190,11 +1244,14 @@ static SymresStatus symres_package(AstPackage* package) {
 }
 
 static SymresStatus symres_enum(AstEnumType* enum_node) {
-    if (enum_node->backing->kind == Ast_Kind_Symbol) SYMRES(symbol, (AstNode **) &enum_node->backing);
-    if (enum_node->backing == NULL) return Symres_Error;
+    if (!enum_node->backing_type) {
+        if (enum_node->backing == NULL) return Symres_Error;
+        if (enum_node->backing->kind == Ast_Kind_Symbol) SYMRES(symbol, (AstNode **) &enum_node->backing);
 
-    if (enum_node->scope == NULL) {
         enum_node->backing_type = type_build_from_ast(context.ast_alloc, enum_node->backing);
+    }
+
+    if (enum_node->scope == NULL) {
         enum_node->scope = scope_create(context.ast_alloc, current_scope, enum_node->token->pos);
 
         type_build_from_ast(context.ast_alloc, (AstType *) enum_node);
index 6e8302347c307e8d4276a3b469602573737a96da..6b92c7c83591bb1e05552296864d41fbb2c14000 100644 (file)
@@ -4,6 +4,7 @@
 #include "astnodes.h"
 #include "utils.h"
 #include "errors.h"
+#include "parser.h"
 
 // NOTE: These have to be in the same order as Basic
 Type basic_types[] = {
@@ -263,6 +264,7 @@ u32 type_size_of(Type* type) {
         case Type_Kind_DynArray: return POINTER_SIZE + 8 + 8 + 2 * POINTER_SIZE; // data (8), count (4), capacity (4), allocator { func (4 + 4 + 8), data (8) }
         case Type_Kind_Compound: return type->Compound.size;
         case Type_Kind_Distinct: return type_size_of(type->Distinct.base_type);
+        case Type_Kind_Union:    return type->Union.size;
         default:                 return 0;
     }
 }
@@ -283,6 +285,7 @@ u32 type_alignment_of(Type* type) {
         case Type_Kind_DynArray: return POINTER_SIZE;
         case Type_Kind_Compound: return 4; // HACK
         case Type_Kind_Distinct: return type_alignment_of(type->Distinct.base_type);
+        case Type_Kind_Union:    return type->Union.alignment;
         default: return 1;
     }
 }
@@ -395,6 +398,7 @@ static Type* type_build_from_ast_inner(bh_allocator alloc, AstType* type_node, b
                 s_type->Struct.mem_count = bh_arr_length(s_node->members);
                 s_type->Struct.meta_tags = s_node->meta_tags;
                 s_type->Struct.constructed_from = NULL;
+                s_type->Struct.poly_sln = NULL;
                 s_type->Struct.status = SPS_Start;
                 type_register(s_type);
 
@@ -406,8 +410,6 @@ static Type* type_build_from_ast_inner(bh_allocator alloc, AstType* type_node, b
                 s_type = s_node->pending_type;
             }
 
-            s_type->Struct.poly_sln = NULL;
-
             bh_arr_clear(s_type->Struct.memarr);
             shfree(s_type->Struct.members);
             sh_new_arena(s_type->Struct.members);
@@ -650,12 +652,7 @@ static Type* type_build_from_ast_inner(bh_allocator alloc, AstType* type_node, b
                 return type_of->resolved_type;
             }
 
-            // Why does this have to be here?
-            if (type_of->expr->type != NULL) {
-                return type_of->expr->type;
-            }
-
-            return NULL;
+            return type_of->expr->type;
         }
 
         case Ast_Kind_Distinct_Type: {
@@ -664,10 +661,6 @@ static Type* type_build_from_ast_inner(bh_allocator alloc, AstType* type_node, b
 
             Type *base_type = type_build_from_ast(alloc, distinct->base_type);
             if (base_type == NULL) return NULL;
-            // if (base_type->kind != Type_Kind_Basic && base_type->kind != Type_Kind_Pointer) {
-            //     onyx_report_error(distinct->token->pos, Error_Critical, "Distinct types can only be made out of primitive types. '%s' is not a primitive type.", type_get_name(base_type));
-            //     return NULL;
-            // }
 
             Type *distinct_type = type_create(Type_Kind_Distinct, alloc, 0);
             distinct_type->Distinct.base_type = base_type;
@@ -678,6 +671,115 @@ static Type* type_build_from_ast_inner(bh_allocator alloc, AstType* type_node, b
             type_register(distinct_type);
             return distinct_type;
         }
+
+        case Ast_Kind_Union_Type: {
+            AstUnionType* union_ = (AstUnionType *) type_node;
+            if (union_->utcache) return union_->utcache;
+            if (union_->pending_type && union_->pending_type_is_valid) return union_->pending_type;
+
+            Type *u_type;
+            if (union_->pending_type == NULL) {
+                u_type = type_create(Type_Kind_Union, alloc, 0);
+                union_->pending_type = u_type;
+
+                u_type->ast_type = type_node;
+                u_type->Union.name = union_->name;
+                u_type->Union.meta_tags = union_->meta_tags;
+                u_type->Union.constructed_from = NULL;
+                type_register(u_type);
+
+                u_type->Union.variants = NULL;
+                sh_new_arena(u_type->Union.variants);
+            } else {
+                u_type = union_->pending_type;
+            }
+
+            //
+            // All variants need to have type know.
+            bh_arr_each(AstUnionVariant *, pvariant, union_->variants) {
+                AstUnionVariant *variant = *pvariant;
+                if (!variant->type) {
+                    variant->type = type_build_from_ast(alloc, variant->type_node);
+                }
+
+                if (!variant->type) {
+                    if (context.cycle_detected) {
+                        onyx_report_error(variant->token->pos, Error_Critical, "Unable to figure out the type of this union variant.");
+                    }
+
+                    union_->pending_type_is_valid = 0;
+                    return accept_partial_types ? union_->pending_type : NULL;
+                }
+
+                if (variant->type->kind == Type_Kind_Struct && variant->type->Struct.status == SPS_Start) {
+                    union_->pending_type_is_valid = 0;
+                    return accept_partial_types ? union_->pending_type : NULL;
+                }
+            }
+
+            // From this point forward, there is no chance we will return early
+            // in a yielding fashion. Everything is either straight success or
+            // failure.
+
+            u32 size = 0;
+            u32 alignment = 0;
+            u32 next_tag_value = 1;
+
+            AstEnumType* tag_enum_node = onyx_ast_node_new(alloc, sizeof(AstEnumType), Ast_Kind_Enum_Type);
+            tag_enum_node->token = union_->token;
+            tag_enum_node->name = bh_aprintf(alloc, "%s.tag_enum", union_->name);
+            tag_enum_node->backing_type = &basic_types[Basic_Kind_U32];
+            bh_arr_new(alloc, tag_enum_node->values, bh_arr_length(union_->variants));
+
+            void add_entities_for_node(bh_arr(Entity *) *target_arr, AstNode* node, Scope* scope, Package* package); // HACK
+            add_entities_for_node(NULL, (AstNode *) tag_enum_node, union_->entity->scope, union_->entity->package);
+
+            //
+            // Create variant instances
+            bh_arr_each(AstUnionVariant *, pvariant, union_->variants) {
+                AstUnionVariant *variant = *pvariant;
+                assert(variant->type);
+
+                u32 var_alignment = type_alignment_of(variant->type);
+                if (var_alignment <= 0) {
+                    onyx_report_error(variant->token->pos, Error_Critical, "Invalid variant type '%s', has alignment %d", type_get_name(variant->type), var_alignment);
+                    return NULL;
+                }
+
+                if (var_alignment > alignment) alignment = var_alignment;
+
+                token_toggle_end(variant->token);
+                if (shgeti(u_type->Union.variants, variant->token->text) != -1) {
+                    onyx_report_error(variant->token->pos, Error_Critical, "Duplicate union variant, '%s'.", variant->token->text);
+                    token_toggle_end(variant->token);
+                    return NULL;
+                }
+
+                u32 type_size = type_size_of(variant->type);
+                size = bh_max(size, type_size);
+
+                UnionVariant* uv = bh_alloc_item(alloc, UnionVariant);
+                uv->name = bh_strdup(alloc, variant->token->text);
+                uv->token = variant->token;
+                uv->tag_value = next_tag_value++;
+                uv->meta_tags = variant->meta_tags;
+                uv->type = variant->type;
+
+                shput(u_type->Union.variants, variant->token->text, uv);
+                token_toggle_end(variant->token);
+
+                AstEnumValue *ev = onyx_ast_node_new(alloc, sizeof(AstEnumValue), Ast_Kind_Enum_Value);
+                ev->token = uv->token;
+                ev->value = (AstTyped *) make_int_literal(alloc, uv->tag_value);
+                bh_arr_push(tag_enum_node->values, ev);
+            }
+
+            u_type->Union.alignment = alignment;
+            u_type->Union.size = size + alignment; // Add the size of the tag
+            u_type->Union.tag_type = type_build_from_ast(alloc, (AstType *) tag_enum_node);
+
+            return u_type;
+        }
     }
 
     return NULL;
@@ -1089,6 +1191,11 @@ const char* type_get_unique_name(Type* type) {
                 return bh_aprintf(global_scratch_allocator, "%s@%l", type->Enum.name, type->id);
             else
                 return bh_aprintf(global_scratch_allocator, "%s@%l", "<anonymous enum>", type->id);
+        case Type_Kind_Union:
+            if (type->Union.name)
+                return bh_aprintf(global_scratch_allocator, "%s@%l", type->Union.name, type->id);
+            else
+                return bh_aprintf(global_scratch_allocator, "%s@%l", "<anonymous union>", type->id);
 
         case Type_Kind_Slice: return bh_aprintf(global_scratch_allocator, "[] %s", type_get_unique_name(type->Slice.elem));
         case Type_Kind_VarArgs: return bh_aprintf(global_scratch_allocator, "..%s", type_get_unique_name(type->VarArgs.elem));
@@ -1162,6 +1269,12 @@ const char* type_get_name(Type* type) {
             else
                 return "<anonymous enum>";
 
+        case Type_Kind_Union:
+            if (type->Union.name)
+                return type->Union.name;
+            else
+                return "<anonymous union>";
+
         case Type_Kind_Slice: return bh_aprintf(global_scratch_allocator, "[] %s", type_get_name(type->Slice.elem));
         case Type_Kind_VarArgs: return bh_aprintf(global_scratch_allocator, "..%s", type_get_name(type->VarArgs.elem));
         case Type_Kind_DynArray: return bh_aprintf(global_scratch_allocator, "[..] %s", type_get_name(type->DynArray.elem));
@@ -1261,6 +1374,10 @@ static const StructMember func_members[] = {
     { 2 * POINTER_SIZE, 2, &basic_types[Basic_Kind_U32],    "closure_size", NULL, NULL, -1, 0, 0 },
 };
 
+static const StructMember union_members[] = {
+    // { 0, 0, NULL, "tag", NULL, NULL, -1, 0, 0 },
+};
+
 b32 type_lookup_member(Type* type, char* member, StructMember* smem) {
     if (type->kind == Type_Kind_Pointer) type = type->Pointer.elem;
 
@@ -1309,6 +1426,19 @@ b32 type_lookup_member(Type* type, char* member, StructMember* smem) {
             }
         }
 
+        case Type_Kind_Union: {
+            // fori (i, 0, (i64) (sizeof(array_members) / sizeof(StructMember))) {
+            //     if (strcmp(array_members[i].name, member) == 0) {
+            //         *smem = array_members[i];
+            //         if (smem->idx == 0) smem->type = type->Union.tag_enum;
+
+            //         return 1;
+            //     }
+            // }
+
+            return 0;
+        }
+
         default: return 0;
     }
 }
@@ -1354,6 +1484,22 @@ b32 type_lookup_member_by_idx(Type* type, i32 idx, StructMember* smem) {
             return 1;
         }
 
+        case Type_Kind_Union: {
+            if (idx > 2) return 0;
+
+            if (idx == 0) {
+                smem->type = type->Union.tag_type;
+                smem->offset = 0;
+            }
+
+            if (idx == 1) {
+                smem->type = NULL;
+                smem->offset = type->Union.alignment;
+            }
+
+            return 1;
+        }
+
         default: return 0;
     }
 }
@@ -1583,6 +1729,7 @@ b32 type_is_structlike_strict(Type* type) {
     if (type->kind == Type_Kind_DynArray) return 1;
     if (type->kind == Type_Kind_Function) return 1;
     if (type->kind == Type_Kind_VarArgs)  return 1;
+    if (type->kind == Type_Kind_Union)    return 1;
     return 0;
 }
 
@@ -1595,6 +1742,7 @@ u32 type_structlike_mem_count(Type* type) {
         case Type_Kind_Function: return 3;
         case Type_Kind_DynArray: return 4;
         case Type_Kind_Distinct: return 1;
+        case Type_Kind_Union:    return 2;
         default: return 0;
     }
 }
@@ -1605,8 +1753,6 @@ u32 type_structlike_is_simple(Type* type) {
         case Type_Kind_Slice:    return 1;
         case Type_Kind_VarArgs:  return 1;
         case Type_Kind_Function: return 1;
-        case Type_Kind_DynArray: return 0;
-        case Type_Kind_Struct:   return 0;
         default: return 0;
     }
 }
@@ -1618,6 +1764,7 @@ b32 type_is_sl_constructable(Type* type) {
         case Type_Kind_Slice:    return 1;
         case Type_Kind_DynArray: return 1;
         case Type_Kind_Function: return 1;
+        case Type_Kind_Union:    return 1;
         default: return 0;
     }
 }
index 25e4656b84f018a3ea5921a7d205700933d8fb24..a42c15e9001f564062dd75cbf52d7c2f503cdb9e 100644 (file)
@@ -34,7 +34,8 @@ static b32 onyx_type_is_stored_in_memory(Type *type) {
     if (type_struct_is_just_one_basic_value(type)) return 0;
 
     return type->kind == Type_Kind_Struct
-        || type->kind == Type_Kind_DynArray;
+        || type->kind == Type_Kind_DynArray
+        || type->kind == Type_Kind_Union;
 }
 
 static WasmType onyx_type_to_wasm_type(Type* type) {
diff --git a/docs/ideas/tagged_unions.md b/docs/ideas/tagged_unions.md
new file mode 100644 (file)
index 0000000..fcce5e3
--- /dev/null
@@ -0,0 +1,37 @@
+Tagged Unions
+-------------
+
+Declaring a union of two types.
+
+        Error :: union {
+            io: struct { ... };
+            network: struct { ... };
+        }
+
+Creating a value of that union.
+Struct literal must have EXACTLY ONE NAMED member.
+
+        err := Error.{ io = .{ ... } };
+
+Accessing data.
+
+        switch err {
+            case .io => io_error {
+                // use io_error
+            }
+
+            case .network => network_error {
+                // use network_error
+            }
+        }
+
+Fields.
+        
+        Error.tag_enum    // Implicit enum created for every union. would be enum {io, network}
+        
+        Error.tag_enum.io // Enum value
+
+        err.io            // Same as Error.tag_enum.io
+
+        cast(Error.tag_enum) err // Get the tagged value out of the union
+
index c93eb8931a67595b76be1dbc4938ab1d8a82a420..95a13dcc622d893207ba9378f9cf0d1f8f0abd50 100644 (file)
 
 use core {*}
 
-/*
-Optional :: struct (T: type_expr) {
-    has_value: bool;
-    value: T;
+SimpleUnion :: union {
+    a: i32;
+    b: struct {
+        c, d: str;
+    };
 }
-*/
 
-Optional :: union (T: type_expr) {
-    None: void;
-    Some: T;
-}
-
-#inject Optional {
-    with :: macro (o: Optional($T), code: Code) {
-        switch o {
-            case .Some => v {
-                #unquote code(v);
-            }
-        }
-    }
-
-    or_default :: macro (o: Optional($T), default: T) -> T {
-        switch o {
-            case .Some => v do return v;
-            case .None      do return default;
-        }
-    }
-}
-
-// | tag type (Tag_Enum) | data... |
+simple_test :: () {
+    u := SimpleUnion.{ a = 123 };
 
+    println(sizeof str);
+    println(sizeof str * 2 + sizeof i32);
 
-Config :: struct {
-    name: Optional(str);
-}
-
-main :: () {
-    x := Optional(i32).{ Some = 123 };
-
-    c: Config;
-    c.name = .{ Some = "test" };
-
-    printf("{}.{}", typeof c.name.Some, c.name.Some);   // "Optional.tag_enum.Some"
-
-    x->with(#quote [v] {
-        printf("x has the value: {}\n", v);
-    });
-
-    y := x->or_default(0);
-    
-    switch x {
-        case .None {
-
-        }
-
-        case .Some => &value {
-            printf("x has the value: {}\n", value);
-        }
-    }
+    __byte_dump(&u, sizeof typeof u);
+    println(u.a);
 }
 
+main :: () {simple_test();}
+
+
+// Link :: union {
+//     End: void;
+//     Next: struct {
+//         data: i32;
+//         next: &Link;
+//     }
+// }
+// 
+// print_links :: (l: Link) {
+//     walker := l;
+//     while true {
+//         switch walker {
+//             case .End do break;
+//             case .Next {
+//                 next: struct {data: i32; next: rawptr};
+//                 printf("{} ", next.data);
+//                 walker = next.next;
+//             }
+//         }
+//     }
+// }
+// 
+// link_test :: () {
+//     l := Link.{
+//         Next = .{
+//             data = 123,
+//             next = &Link.{
+//                 End = .{}
+//             }
+//         }
+//     };
+//     
+//     print_links(l);
+// }
+// 
+// main :: () { link_test(); }
+
+// Optional :: union (T: type_expr) {
+//     None: void;
+//     Some: T;
+// }
+// 
+// #inject Optional {
+//     with :: macro (o: Optional($T), code: Code) {
+//         switch o {
+//             case .Some => v {
+//                 #unquote code(v);
+//             }
+//         }
+//     }
+// 
+//     or_default :: macro (o: Optional($T), default: T) -> T {
+//         switch o {
+//             case .Some => v do return v;
+//             case .None      do return default;
+//         }
+//     }
+// }
 
+// | tag type (Tag_Enum) | data... |
 
 
+// Config :: struct {
+//     name: Optional(str);
+// }
+// 
+// main :: () {
+//     x := Optional(i32).{ Some = 123 };
+// 
+//     c: Config;
+//     c.name = .{ Some = "test" };
+// 
+//     printf("{}.{}", typeof c.name.Some, c.name.Some);   // "Optional.tag_enum.Some"
+// 
+//     x->with(#quote {
+//         printf("x has the value: {}\n", it);
+//     });
+// 
+//     y := x->or_default(0);
+// 
+//     switch x {
+//         case .None {
+//             printf("x has nothing....\n");
+//         }
+// 
+//         case .Some => &value {
+//             printf("x has the value: {}\n", value);
+//         }
+//     }
+// }
+// 
+// 
+// 
+//