From: Brendan Hansen Date: Tue, 23 May 2023 02:39:36 +0000 (-0500) Subject: changed: `Optional` is now a `union` X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=d56054084dc9bb8e8d63a77de896c826139a5066;p=onyx.git changed: `Optional` is now a `union` --- diff --git a/compiler/include/types.h b/compiler/include/types.h index 190435f3..b04f4d6f 100644 --- a/compiler/include/types.h +++ b/compiler/include/types.h @@ -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 diff --git a/compiler/src/astnodes.c b/compiler/src/astnodes.c index 1055ddad..01558e5c 100644 --- a/compiler/src/astnodes.c +++ b/compiler/src/astnodes.c @@ -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; } diff --git a/compiler/src/checker.c b/compiler/src/checker.c index 8c002ab4..c6318ad2 100644 --- a/compiler/src/checker.c +++ b/compiler/src/checker.c @@ -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; diff --git a/compiler/src/clone.c b/compiler/src/clone.c index 3ba0fc54..20897a40 100644 --- a/compiler/src/clone.c +++ b/compiler/src/clone.c @@ -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: diff --git a/compiler/src/parser.c b/compiler/src/parser.c index 11bfd107..ecd261f8 100644 --- a/compiler/src/parser.c +++ b/compiler/src/parser.c @@ -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; } diff --git a/compiler/src/polymorph.h b/compiler/src/polymorph.h index 0fc8716b..ca1f84e9 100644 --- a/compiler/src/polymorph.h +++ b/compiler/src/polymorph.h @@ -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); } diff --git a/compiler/src/types.c b/compiler/src/types.c index 9b6c04fa..919ad7f7 100644 --- a/compiler/src/types.c +++ b/compiler/src/types.c @@ -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) { diff --git a/compiler/src/utils.c b/compiler/src/utils.c index 58bf2f21..abff4de0 100644 --- a/compiler/src/utils.c +++ b/compiler/src/utils.c @@ -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); diff --git a/core/builtin.onyx b/core/builtin.onyx index c75adcee..379cedd4 100644 --- a/core/builtin.onyx +++ b/core/builtin.onyx @@ -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; } diff --git a/core/container/optional.onyx b/core/container/optional.onyx index a544c3c7..f049a70b 100644 --- a/core/container/optional.onyx +++ b/core/container/optional.onyx @@ -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; diff --git a/core/conv/format.onyx b/core/conv/format.onyx index d87e34f5..f16b97c9 100644 --- a/core/conv/format.onyx +++ b/core/conv/format.onyx @@ -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(")"); + } } } } diff --git a/tests/first_class_optional.onyx b/tests/first_class_optional.onyx index 7b1723b1..bacb9d94 100644 --- a/tests/first_class_optional.onyx +++ b/tests/first_class_optional.onyx @@ -18,4 +18,4 @@ main :: () { println(bar(foo(.{}))); println(bar(foo(20))); -} \ No newline at end of file +} diff --git a/tests/tagged_unions.onyx b/tests/tagged_unions.onyx index 3365fa0d..50917f1e 100644 --- a/tests/tagged_unions.onyx +++ b/tests/tagged_unions.onyx @@ -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 {