changed: `Optional` is now a `union`
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 23 May 2023 02:39:36 +0000 (21:39 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 23 May 2023 02:39:36 +0000 (21:39 -0500)
13 files changed:
compiler/include/types.h
compiler/src/astnodes.c
compiler/src/checker.c
compiler/src/clone.c
compiler/src/parser.c
compiler/src/polymorph.h
compiler/src/types.c
compiler/src/utils.c
core/builtin.onyx
core/container/optional.onyx
core/conv/format.onyx
tests/first_class_optional.onyx
tests/tagged_unions.onyx

index 190435f3e9cfb4c9ce46818717cb2c5444a6a739..b04f4d6faadaa186e08ffea7b872a31d59b0611e 100644 (file)
@@ -281,7 +281,7 @@ b32 type_is_structlike_strict(Type* type);
 u32 type_structlike_mem_count(Type* type);
 u32 type_structlike_is_simple(Type* type);
 b32 type_is_sl_constructable(Type* type);
-b32 type_struct_constructed_from_poly_struct(Type* struct_type, struct AstType* from);
+b32 type_constructed_from_poly(Type* base, struct AstType* from);
 Type* type_struct_is_just_one_basic_value(Type *type);
 
 #endif // #ifndef ONYX_TYPES
index 1055ddad3422d40b22d36ea26938ea7be3be407f..01558e5c49fd4ba3ddcaa75271f56cc9b741575f 100644 (file)
@@ -762,8 +762,8 @@ TypeMatch unify_node_and_type_(AstTyped** pnode, Type* type, b32 permanent) {
     // If the destination type is an optional, and the node's type is a value of
     // the same underlying type, then we can construct an optional with a value
     // implicitly. This makes working with optionals barable.
-    if (type_struct_constructed_from_poly_struct(type, builtin_optional_type)) {
-        TypeMatch match = unify_node_and_type_(pnode, type->Struct.poly_sln[0].type, permanent);
+    if (type_constructed_from_poly(type, builtin_optional_type)) {
+        TypeMatch match = unify_node_and_type_(pnode, type->Union.poly_sln[0].type, permanent);
         if (match == TYPE_MATCH_SUCCESS) {
             if (permanent) {
                 AstStructLiteral *opt_lit = make_optional_literal_some(context.ast_alloc, node, type);
@@ -1598,10 +1598,11 @@ AstStructLiteral* make_optional_literal_some(bh_allocator a, AstTyped *expr, Typ
 
     arguments_initialize(&opt_lit->args);
     arguments_ensure_length(&opt_lit->args, 2);
-    opt_lit->args.values[0] = (AstTyped *) make_bool_literal(a, 1);
+    opt_lit->args.values[0] = (AstTyped *) make_int_literal(a, 2);
     opt_lit->args.values[1] = expr;
 
     opt_lit->type = opt_type;
+    opt_lit->args.values[0]->type = opt_type->Union.tag_type;
     return opt_lit;
 }
 
index 8c002ab4c697d2ebafb5f4c96e7790b856ded63d..c6318ad2038d83c1d3ddf7c6acba8c62dc51603c 100644 (file)
@@ -319,7 +319,7 @@ CheckStatus check_for(AstFor* fornode) {
 
         fornode->loop_type = For_Loop_DynArr;
     }
-    else if (type_struct_constructed_from_poly_struct(iter_type, builtin_iterator_type)) {
+    else if (type_constructed_from_poly(iter_type, builtin_iterator_type)) {
         if (fornode->by_pointer) {
             ERROR(error_loc, "Cannot iterate by pointer over an iterator.");
         }
@@ -348,7 +348,7 @@ fornode_expr_checked:
     old_inside_for_iterator = context.checker.inside_for_iterator;
     context.checker.inside_for_iterator = 0;
     iter_type = fornode->iter->type;
-    if (type_struct_constructed_from_poly_struct(iter_type, builtin_iterator_type)) {
+    if (type_constructed_from_poly(iter_type, builtin_iterator_type)) {
         context.checker.inside_for_iterator = 1;
     }
 
@@ -1490,6 +1490,22 @@ CheckStatus check_struct_literal(AstStructLiteral* sl) {
     if (sl->type->kind == Type_Kind_Union) {
         if ((sl->flags & Ast_Flag_Has_Been_Checked) != 0) return Check_Success;
 
+        Type *union_type = sl->type;
+
+        if (bh_arr_length(sl->args.values) == 0 && bh_arr_length(sl->args.named_values) == 0) {
+            // Produce an empty value of the first union type.
+            UnionVariant *uv = union_type->Union.variants[0].value;
+
+            AstNumLit *tag_value = make_int_literal(context.ast_alloc, uv->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, (AstTyped *) make_zero_value(context.ast_alloc, sl->token, uv->type));
+
+            sl->flags |= Ast_Flag_Has_Been_Checked;
+            return Check_Success;
+        }
+
         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));
         }
@@ -1497,7 +1513,6 @@ CheckStatus check_struct_literal(AstStructLiteral* sl) {
         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;
index 3ba0fc54f8839a4478025780e611653908d8dc04..20897a40251294485fdb1293e27882b296c46a84 100644 (file)
@@ -26,6 +26,7 @@ static inline b32 should_clone(AstNode* node) {
         case Ast_Kind_Macro:
         case Ast_Kind_Symbol:
         case Ast_Kind_Poly_Struct_Type:
+        case Ast_Kind_Poly_Union_Type:
         case Ast_Kind_Basic_Type:
         case Ast_Kind_Enum_Type:
         case Ast_Kind_Enum_Value:
index 11bfd107ff43fb7408b5feb2c27ccdb8ff0e78eb..ecd261f828ef5599f42d606efcfc5b45227964aa 100644 (file)
@@ -1997,9 +1997,9 @@ static AstType* parse_type(OnyxParser* parser) {
                     pc_type->params = params;
 
                     *next_insertion = (AstType *) pc_type;
+                } else {
+                    next_insertion = NULL;
                 }
-
-                next_insertion = NULL;
                 break;
             }
 
@@ -2075,9 +2075,18 @@ static AstType* parse_type(OnyxParser* parser) {
                 }
             }
 
-            default:
-                onyx_report_error(parser->curr->pos, Error_Critical, "unexpected token '%b'.", parser->curr->text, parser->curr->length);
+            case '.': {
                 consume_token(parser);
+                AstFieldAccess* field = make_node(AstFieldAccess, Ast_Kind_Field_Access);
+                field->token = expect_token(parser, Token_Type_Symbol);
+                field->expr  = (AstTyped *) *next_insertion;
+
+                *next_insertion = (AstType *) field;
+                break;
+            }
+
+            default:
+                next_insertion = NULL;
                 break;
         }
 
index 0fc8716b1d2fc03f123c1895172e583e21171da2..ca1f84e93c9cbd03b1c880b184cc28fa4c6858fc 100644 (file)
@@ -1064,10 +1064,10 @@ char* build_poly_struct_name(char *name, Type* type) {
 
 
     // Special case for `? T`
-    if (type->kind == Type_Kind_Struct
-        && type->Struct.constructed_from == builtin_optional_type) {
+    if (type->kind == Type_Kind_Union
+        && type->Union.constructed_from == builtin_optional_type) {
         strncat(name_buf, "? ", 255);
-        strncat(name_buf, type_get_name(type->Struct.poly_sln[0].type), 255);
+        strncat(name_buf, type_get_name(type->Union.poly_sln[0].type), 255);
 
         return bh_aprintf(global_heap_allocator, "%s", name_buf);
     }
index 9b6c04fa35682e292ac8f7c1e54f378df268c7c3..919ad7f7d245c96ea5c2dc2de6263d686128fad4 100644 (file)
@@ -1803,11 +1803,17 @@ b32 type_is_sl_constructable(Type* type) {
     }
 }
 
-b32 type_struct_constructed_from_poly_struct(Type* struct_type, struct AstType* from) {
-    if (struct_type == NULL) return 0;
-    if (struct_type->kind != Type_Kind_Struct) return 0;
+b32 type_constructed_from_poly(Type* base, struct AstType* from) {
+    if (base == NULL) return 0;
+    if (base->kind == Type_Kind_Struct) {
+        return base->Struct.constructed_from == from;
+    }
+
+    if (base->kind == Type_Kind_Union) {
+        return base->Union.constructed_from == from;
+    }
 
-    return struct_type->Struct.constructed_from == from;
+    return 0;
 }
 
 Type* type_struct_is_just_one_basic_value(Type *type) {
index 58bf2f2181d1f89f078c2a592e5a52ec0081e94d..abff4de0c602c575b08ef9ccc166564546429aab 100644 (file)
@@ -366,6 +366,11 @@ all_types_peeled_off:
             return symbol_raw_resolve(stype->scope, symbol);
         }
 
+        case Ast_Kind_Poly_Union_Type: {
+            AstPolyUnionType* utype = ((AstPolyUnionType *) node);
+            return symbol_raw_resolve(utype->scope, symbol);
+        }
+
         case Ast_Kind_Poly_Call_Type: {
             AstPolyCallType* pctype = (AstPolyCallType *) node;
             if (pctype->resolved_type) {
@@ -1388,6 +1393,11 @@ all_types_peeled_off:
             return &utype->scope;
         }
 
+        case Ast_Kind_Poly_Union_Type: {
+            AstPolyUnionType* putype = (AstPolyUnionType *) node;
+            return &putype->scope;
+        }
+
         case Ast_Kind_Poly_Call_Type: {
             AstPolyCallType* pctype = (AstPolyCallType *) node;
             Type *t = type_build_from_ast(context.ast_alloc, (AstType *) pctype);
index c75adcee6c5fb69a10e65904c89e6676f1f88f6a..379cedd4680d43ea0804bd97a94235332397c7e0 100644 (file)
@@ -423,9 +423,9 @@ Iterator :: struct (Iter_Type: type_expr) {
     for types like '? i32'. In other words, '? i32' is equivalent to
     'Optional(i32)'.
 """
-Optional :: struct (Value_Type: type_expr) {
-    has_value: bool;
-    value: Value_Type;
+Optional :: union (Value_Type: type_expr) {
+    None: void;
+    Some: Value_Type;
 }
 
 
index a544c3c7d322d9e88d3844e3052b42d2a4d889e4..f049a70b1a9a761897a8728d57a705acb0e5400f 100644 (file)
@@ -20,8 +20,8 @@ use core
         the type will be inferred from the parameter type.
     """
     make :: #match #locked {
-        ((x: $T) => (?T).{ has_value = true, value = x }),
-        ($T: type_expr, x: T) => ((?T).{ has_value = true, value = x })
+        ((x: $T) => (?T).{ Some = x }),
+        ($T: type_expr, x: T) => ((?T).{ Some = x })
     }
 
     #doc """
@@ -29,7 +29,7 @@ use core
         is mostly useless, because you can use `.{}` in type inferred
         places to avoid having to specify the type.
     """
-    empty :: macro (T: type_expr) => (?T).{}; 
+    empty :: macro (T: type_expr) => (?T).{ None = .{} }; 
 
     #doc """
         Converts a pointer to an optional by defining `null` to be `None`,
@@ -39,7 +39,7 @@ use core
     from_ptr :: macro (p: &$T) -> ?T {
         p_ := p;
         if p_ do return *p_;
-        return .{};
+        return .{ None = .{} };
     }
 
     #doc """
@@ -47,32 +47,36 @@ use core
         no value is present.
     """
     value_or :: (o: ?$T, default: T) -> T {
-        if !o.has_value do return default;
-        return o.value;
+        switch o {
+            case .Some => v do return v;
+            case #default do return default;
+        }
     }
 
     #doc "Clears the value in the Optional, zeroing the memory of the value."
     reset :: (o: &?$T) {
-        o.has_value = false;
-        core.memory.set(&o.value, 0, sizeof T);
+        *o = .{ None = .{} };
     }
 
     #doc "Sets the value in the Optional."
     set :: (o: &?$T, value: T) {
-        o.has_value = true;
-        o.value = value;
+        *o = .{ Some = value };
     }
 
     #doc "Monadic chaining operation."
     and_then :: (o: ?$T, transform: (T) -> ?$R) -> ?R {
-        if !o.has_value do return .{};
-        return transform(o.value);
+        switch o {
+            case .Some => v do return transform(v);
+            case #default do return .{ None = .{} };
+        }
     }
 
     #doc "Changes the value inside the optional, if present."
     transform :: (o: ?$T, transform: (T) -> $R) -> ?R {
-        if !o.has_value do return .{};
-        return Optional.make(transform(o.value));
+        switch o {
+            case .Some => v do return .{ Some = transform(v) };
+            case #default do return .{ None = .{} };
+        }
     }
 
     #doc """
@@ -80,8 +84,10 @@ use core
         provide a function to generate a value.
     """
     or_else :: (o: ?$T, generate: () -> ?T) -> ?T {
-        if o.has_value do return o;
-        return generate();
+        switch o {
+            case .Some => v do return o;
+            case #default do return generate();
+        }
     }
 
     #doc """
@@ -90,20 +96,41 @@ use core
         handler must take care of it.
     """
     unwrap :: (o: ?$T) -> T {
-        if o.has_value do return o.value;
-        assert(false, "Unwrapping empty Optional.");
+        switch o {
+            case .Some => v do return v;
+            case #default {
+                assert(false, "Unwrapping empty Optional.");
+            }
+        }
     }
 
-    or_return :: macro (o: ?$T) -> T {
-        value := o;
-        if value.has_value do return value.value;
+    #doc """
+        Returns a pointer to the value inside the optional, if there is one.
+        If not, an assertion is thrown and the context's assert handler must
+        take care of it.
+    """
+    unwrap_ptr :: (o: ?$T) -> &T {
+        switch o {
+            case .Some => &v do return v;
+            case #default {
+                assert(false, "Unwrapping empty Optional.");
+            }
+        }
+    }
 
-        return return .{};
+    or_return :: macro (o: ?$T) -> T {
+        switch value := o; value {
+            case .Some => v do return v;
+            case #default {
+                return return .{};
+            }
+        }
     }
 
     catch :: macro (o: ?$T, body: Code) -> T {
-        value := o;
-        if value.has_value do return value.value;
+        switch value := o; value {
+            case .Some => v do return v;
+        }
 
         #unquote body;
     }
@@ -161,39 +188,47 @@ use core
 
 
     hash :: (o: ?$T/core.hash.Hashable) -> u32 {
-        if !o.has_value do return 0;
-        return core.hash.hash(o.value);
+        switch o {
+            case .Some => v do return core.hash.hash(v);
+            case #default do return 0;
+        }
     }
 }
 
 #operator == (o1, o2: ?$T) -> bool {
-    if o1.has_value != o2.has_value do return false;
-    if !o1.has_value do return true;
-    return o1.value == o2.value;
+    if cast(Optional(T).tag_enum, o1) != cast(Optional(T).tag_enum, o2) do return false;
+    switch o1 {
+        case .None do return true;
+        case .Some => v1 {
+            v2 := o2->unwrap();
+            return v1 == v2;
+        }
+    }
 }
 
 #operator ?? macro (opt: ?$T, default: T) -> T {
-    value := opt;
-    if value do return value.value;
-
-    return default;
+    switch value := opt; value {
+        case .Some => v do return v;
+        case #default do return default;
+    }
 }
 
 #operator ?? macro (opt: ?$T, catch: Code) -> T {
-    value := opt;
-    if value do return value.value;
+    switch value := opt; value {
+        case .Some => v do return v;
+    }
 
     #unquote catch;
 }
 
 #operator ? macro (opt: ?$T) -> T {
-    value := opt;
-    if value do return value.value;
-
-    return return .{};
+    switch value := opt; value {
+        case .Some => v do return v;
+        case #default do return return .{};
+    }
 }
 
 
 #overload
-__implicit_bool_cast :: macro (o: ?$T) => o.has_value;
+__implicit_bool_cast :: macro (o: ?$T) => cast(Optional(T).tag_enum, o) == .Some;
 
index d87e34f57c84ee3f21a1a2366de331c8f05427cb..f16b97c961249ded89a4edf9d7bda05b095ebbd9 100644 (file)
@@ -552,24 +552,6 @@ format_any :: (output: &Format_Output, formatting: &Format, v: any) {
             if info.kind == .Struct {
                 s := cast(&Type_Info_Struct) info;
 
-                if s.constructed_from == Optional {
-                    opt := cast(&?bool) v.data;
-
-                    if opt.has_value {
-                        format := *formatting;
-                        format.quote_strings = true;
-
-                        output->write("Some(");
-                        format_any(output, &format, .{ cast([&] u8) v.data + s.members[1].offset, s.members[1].type });
-                        output->write(")");
-
-                    } else {
-                        output->write("None");
-                    }
-
-                    return;
-                }
-
                 if s.name.count > 0 {
                     output->write(s.name);
                     output->write(" { ");
@@ -755,11 +737,11 @@ format_any :: (output: &Format_Output, formatting: &Format, v: any) {
                 }
 
                 output->write(variant.name);
-                output->write("(");
-
-                format_any(output, formatting, any.{ cast([&] u8) v.data + u.alignment, variant.type });
-
-                output->write(")");
+                if variant.type != void {
+                    output->write("(");
+                    format_any(output, formatting, any.{ cast([&] u8) v.data + u.alignment, variant.type });
+                    output->write(")");
+                }
             }
         }
     }
index 7b1723b10450e6d05dbbed548f6e6e8ae15da0b4..bacb9d94f6d93135eccefca59cda7ff3382e0ccd 100644 (file)
@@ -18,4 +18,4 @@ main :: () {
 
     println(bar(foo(.{})));
     println(bar(foo(20)));
-}
\ No newline at end of file
+}
index 3365fa0d028c625a9a69569a2188aa87a06b3480..50917f1edc577fa46162fc71650568d5ea0963b3 100644 (file)
@@ -1,29 +1,17 @@
 
 use core {*}
 
-NewOptional :: union (T: type_expr) {
-    None: void;
-    Some: T;
-}
-
-unwrap_optional :: (o: NewOptional($T)) -> T {
-    switch o {
-        case .Some => v do return v;
-        case .None do return .{};
-    }
-}
-
-create_optional :: () -> NewOptional(i32) {
-    return .{ Some = i32 };
+create_optional :: () -> ? i32 {
+    return .{ Some = 123 };
 }
 
 new_optional_test :: () {
     v := create_optional();
-    v2 := NewOptional(str).{ None = .{} };
+    v2 := Optional(str).{ None = .{} };
     println(v);
     println(v2);
 
-    println(unwrap_optional(v));
+    println(v->unwrap());
 }
 
 union_is :: macro (u: $U, $variant: U.tag_enum) -> bool {