From 0b0d8d67f7e6a56490a036d7896a8c04ca458f86 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Sun, 21 May 2023 22:50:47 -0500 Subject: [PATCH] added: basics of tagged unions --- compiler/include/astnodes.h | 39 ++++++++ compiler/include/lex.h | 1 + compiler/include/types.h | 19 ++++ compiler/src/astnodes.c | 3 + compiler/src/checker.c | 128 +++++++++++++++++++------- compiler/src/entities.c | 1 + compiler/src/lex.c | 2 + compiler/src/parser.c | 122 +++++++++++++++++++++---- compiler/src/symres.c | 67 ++++++++++++-- compiler/src/types.c | 175 +++++++++++++++++++++++++++++++++--- compiler/src/wasm_emit.c | 3 +- docs/ideas/tagged_unions.md | 37 ++++++++ tests/tagged_unions.onyx | 160 +++++++++++++++++++++------------ 13 files changed, 631 insertions(+), 126 deletions(-) create mode 100644 docs/ideas/tagged_unions.md diff --git a/compiler/include/astnodes.h b/compiler/include/astnodes.h index c88cfc80..fc9ecacf 100644 --- a/compiler/include/astnodes.h +++ b/compiler/include/astnodes.h @@ -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; diff --git a/compiler/include/lex.h b/compiler/include/lex.h index 23818d58..37c4e084 100644 --- a/compiler/include/lex.h +++ b/compiler/include/lex.h @@ -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, diff --git a/compiler/include/types.h b/compiler/include/types.h index 7581780c..430e4f8f 100644 --- a/compiler/include/types.h +++ b/compiler/include/types.h @@ -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, diff --git a/compiler/src/astnodes.c b/compiler/src/astnodes.c index 5b687cb0..213650e9 100644 --- a/compiler/src/astnodes.c +++ b/compiler/src/astnodes.c @@ -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", diff --git a/compiler/src/checker.c b/compiler/src/checker.c index e59cf451..4f5a6cc4 100644 --- a/compiler/src/checker.c +++ b/compiler/src/checker.c @@ -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; } diff --git a/compiler/src/entities.c b/compiler/src/entities.c index c02ab13f..f3c877dc 100644 --- a/compiler/src/entities.c +++ b/compiler/src/entities.c @@ -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; diff --git a/compiler/src/lex.c b/compiler/src/lex.c index 20126a1d..4ce31361 100644 --- a/compiler/src/lex.c +++ b/compiler/src/lex.c @@ -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); diff --git a/compiler/src/parser.c b/compiler/src/parser.c index 2328529b..358083ae 100644 --- a/compiler/src/parser.c +++ b/compiler/src/parser.c @@ -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 = ""; + (*scope)->name = ""; } 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; diff --git a/compiler/src/symres.c b/compiler/src/symres.c index 214a8a41..50b39221 100644 --- a/compiler/src/symres.c +++ b/compiler/src/symres.c @@ -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); diff --git a/compiler/src/types.c b/compiler/src/types.c index 6e830234..6b92c7c8 100644 --- a/compiler/src/types.c +++ b/compiler/src/types.c @@ -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", "", 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", "", 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 ""; + case Type_Kind_Union: + if (type->Union.name) + return type->Union.name; + else + return ""; + 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; } } diff --git a/compiler/src/wasm_emit.c b/compiler/src/wasm_emit.c index 25e4656b..a42c15e9 100644 --- a/compiler/src/wasm_emit.c +++ b/compiler/src/wasm_emit.c @@ -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 index 00000000..fcce5e33 --- /dev/null +++ b/docs/ideas/tagged_unions.md @@ -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 + diff --git a/tests/tagged_unions.onyx b/tests/tagged_unions.onyx index c93eb893..95a13dcc 100644 --- a/tests/tagged_unions.onyx +++ b/tests/tagged_unions.onyx @@ -1,67 +1,117 @@ 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); +// } +// } +// } +// +// +// +// -- 2.25.1