diff --git a/.gitignore b/.gitignore index 824e0c203..3528af624 100644 --- a/.gitignore +++ b/.gitignore @@ -270,6 +270,7 @@ bin/ # - Linux/MacOS odin +!odin/ odin.dSYM *.bin demo.bin @@ -286,4 +287,4 @@ shared/ *.sublime-workspace examples/bug/ build.sh -!core/debug/ \ No newline at end of file +!core/debug/ diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin index e73a39660..cfd35ab05 100644 --- a/core/mem/virtual/arena.odin +++ b/core/mem/virtual/arena.odin @@ -242,7 +242,7 @@ arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, min return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size) } -// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. +// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy. @(require_results) arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) { bootstrap: Arena @@ -258,7 +258,7 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt return } -// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. +// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy. @(require_results) arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) { return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved) @@ -271,7 +271,7 @@ arena_allocator :: proc(arena: ^Arena) -> mem.Allocator { return mem.Allocator{arena_allocator_proc, arena} } -// The allocator procedured by an `Allocator` produced by `arena_allocator` +// The allocator procedure used by an `Allocator` produced by `arena_allocator` arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, @@ -328,7 +328,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, -// An `Arena_Temp` is a way to produce temporary watermarks to reset a arena to a previous state. +// An `Arena_Temp` is a way to produce temporary watermarks to reset an arena to a previous state. // All uses of an `Arena_Temp` must be handled by ending them with `arena_temp_end` or ignoring them with `arena_temp_ignore`. Arena_Temp :: struct { arena: ^Arena, diff --git a/core/sync/primitives.odin b/core/sync/primitives.odin index b8bcfad70..5e71f6336 100644 --- a/core/sync/primitives.odin +++ b/core/sync/primitives.odin @@ -7,10 +7,21 @@ current_thread_id :: proc "contextless" () -> int { return _current_thread_id() } -// A Mutex is a mutual exclusion lock -// The zero value for a Mutex is an unlocked mutex +// A Mutex is a [[mutual exclusion lock; https://en.wikipedia.org/wiki/Mutual_exclusion]] +// It can be used to prevent more than one thread from executing the same piece of code, +// and thus prevent access to same piece of memory by multiple threads, at the same time. // -// A Mutex must not be copied after first use +// A Mutex's zero value represents an initial, *unlocked* state. +// +// If another thread tries to take the lock while another thread holds it, it will pause +// until the lock is released. Code or memory that is "surrounded" by a mutex lock is said +// to be "guarded by a mutex". +// +// A Mutex must not be copied after first use (e.g., after locking it the first time). +// This is because, in order to coordinate with other threads, all threads must watch +// the same memory address to know when the lock has been released. Trying to use a +// copy of the lock at a different memory address will result in broken and unsafe +// behavior. For this reason, Mutexes are marked as `#no_copy`. Mutex :: struct #no_copy { impl: _Mutex, } diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index beed3a7e5..fcd9e55ed 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -159,6 +159,11 @@ foreign kernel32 { WaitForSingleObject :: proc(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD --- Sleep :: proc(dwMilliseconds: DWORD) --- GetProcessId :: proc(handle: HANDLE) -> DWORD --- + CopyFileW :: proc( + lpExistingFileName: LPCWSTR, + lpNewFileName: LPCWSTR, + bFailIfExists: BOOL, + ) -> BOOL --- CopyFileExW :: proc( lpExistingFileName: LPCWSTR, lpNewFileName: LPCWSTR, diff --git a/core/sys/windows/ws2_32.odin b/core/sys/windows/ws2_32.odin index 631ef4241..7b9cf1b89 100644 --- a/core/sys/windows/ws2_32.odin +++ b/core/sys/windows/ws2_32.odin @@ -206,4 +206,14 @@ foreign ws2_32 { optval: ^c_char, optlen: ^c_int, ) -> c_int --- + // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-ntohl) + ntohl :: proc(netlong: c_ulong) -> c_ulong --- + // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-ntohs) + ntohs :: proc(netshort: c_ushort) -> c_ushort --- + // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-htonl) + @(deprecated="Use endian specific integers instead, https://odin-lang.org/docs/overview/#basic-types") + htonl :: proc(hostlong: c_ulong) -> c_ulong --- + // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-htons) + @(deprecated="Use endian specific integers instead, https://odin-lang.org/docs/overview/#basic-types") + htons :: proc(hostshort: c_ushort) -> c_ushort --- } diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 0ceb58e33..1ba05315c 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -4,6 +4,9 @@ import "core:fmt" import "core:io" import "core:time" import "core:intrinsics" +import "core:reflect" + +_ :: reflect // alias reflect to nothing to force visibility for -vet // IMPORTANT NOTE: Compiler requires this layout Test_Signature :: proc(^T) @@ -89,7 +92,7 @@ expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bo return ok } expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { - ok := value == expected + ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) if !ok { errorf(t, "expected %v, got %v", expected, value, loc=loc) } @@ -100,4 +103,4 @@ expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> boo set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { _fail_timeout(t, duration, loc) -} \ No newline at end of file +} diff --git a/src/check_decl.cpp b/src/check_decl.cpp index b651e33e6..2b2fb867c 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -757,6 +757,66 @@ gb_internal String handle_link_name(CheckerContext *ctx, Token token, String lin return link_name; } +gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext const &ac) { + if (!(ac.objc_name.len || ac.objc_is_class_method || ac.objc_type)) { + return; + } + if (ac.objc_name.len == 0 && ac.objc_is_class_method) { + error(e->token, "@(objc_name) is required with @(objc_is_class_method)"); + } else if (ac.objc_type == nullptr) { + error(e->token, "@(objc_name) requires that @(objc_type) to be set"); + } else if (ac.objc_name.len == 0 && ac.objc_type) { + error(e->token, "@(objc_name) is required with @(objc_type)"); + } else { + Type *t = ac.objc_type; + if (t->kind == Type_Named) { + Entity *tn = t->Named.type_name; + + GB_ASSERT(tn->kind == Entity_TypeName); + + if (tn->scope != e->scope) { + error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope"); + } else { + mutex_lock(&global_type_name_objc_metadata_mutex); + defer (mutex_unlock(&global_type_name_objc_metadata_mutex)); + + if (!tn->TypeName.objc_metadata) { + tn->TypeName.objc_metadata = create_type_name_obj_c_metadata(); + } + auto *md = tn->TypeName.objc_metadata; + mutex_lock(md->mutex); + defer (mutex_unlock(md->mutex)); + + if (!ac.objc_is_class_method) { + bool ok = true; + for (TypeNameObjCMetadataEntry const &entry : md->value_entries) { + if (entry.name == ac.objc_name) { + error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); + ok = false; + break; + } + } + if (ok) { + array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); + } + } else { + bool ok = true; + for (TypeNameObjCMetadataEntry const &entry : md->type_entries) { + if (entry.name == ac.objc_name) { + error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); + ok = false; + break; + } + } + if (ok) { + array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); + } + } + } + } + } +} + gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { GB_ASSERT(e->type == nullptr); if (d->proc_lit->kind != Ast_ProcLit) { @@ -840,62 +900,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } e->Procedure.optimization_mode = cast(ProcedureOptimizationMode)ac.optimization_mode; - if (ac.objc_name.len || ac.objc_is_class_method || ac.objc_type) { - if (ac.objc_name.len == 0 && ac.objc_is_class_method) { - error(e->token, "@(objc_name) is required with @(objc_is_class_method)"); - } else if (ac.objc_type == nullptr) { - error(e->token, "@(objc_name) requires that @(objc_type) to be set"); - } else if (ac.objc_name.len == 0 && ac.objc_type) { - error(e->token, "@(objc_name) is required with @(objc_type)"); - } else { - Type *t = ac.objc_type; - if (t->kind == Type_Named) { - Entity *tn = t->Named.type_name; - - GB_ASSERT(tn->kind == Entity_TypeName); - - if (tn->scope != e->scope) { - error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope"); - } else { - mutex_lock(&global_type_name_objc_metadata_mutex); - defer (mutex_unlock(&global_type_name_objc_metadata_mutex)); - - if (!tn->TypeName.objc_metadata) { - tn->TypeName.objc_metadata = create_type_name_obj_c_metadata(); - } - auto *md = tn->TypeName.objc_metadata; - mutex_lock(md->mutex); - defer (mutex_unlock(md->mutex)); - - if (!ac.objc_is_class_method) { - bool ok = true; - for (TypeNameObjCMetadataEntry const &entry : md->value_entries) { - if (entry.name == ac.objc_name) { - error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); - ok = false; - break; - } - } - if (ok) { - array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); - } - } else { - bool ok = true; - for (TypeNameObjCMetadataEntry const &entry : md->type_entries) { - if (entry.name == ac.objc_name) { - error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); - ok = false; - break; - } - } - if (ok) { - array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); - } - } - } - } - } - } + check_objc_methods(ctx, e, ac); if (ac.require_target_feature.len != 0 && ac.enable_target_feature.len != 0) { error(e->token, "Attributes @(require_target_feature=...) and @(enable_target_feature=...) cannot be used together"); @@ -1241,7 +1246,7 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast check_rtti_type_disallowed(e->token, e->type, "A variable declaration is using a type, %s, which has been disallowed"); } -gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *&pg_entity, DeclInfo *d) { +gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, DeclInfo *d) { GB_ASSERT(pg_entity->kind == Entity_ProcGroup); auto *pge = &pg_entity->ProcGroup; String proc_group_name = pg_entity->token.string; @@ -1366,6 +1371,11 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *&pg_entity, } } + AttributeContext ac = {}; + check_decl_attributes(ctx, d->attributes, proc_group_attribute, &ac); + check_objc_methods(ctx, pg_entity, ac); + + } gb_internal void check_entity_decl(CheckerContext *ctx, Entity *e, DeclInfo *d, Type *named_type) { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index b662c231f..98154f33d 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6136,7 +6136,6 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, { // NOTE(bill, 2019-07-13): This code is used to improve the type inference for procedure groups // where the same positional parameter has the same type value (and ellipsis) - bool proc_arg_count_all_equal = true; isize proc_arg_count = -1; for (Entity *p : procs) { Type *pt = base_type(p->type); @@ -6144,15 +6143,12 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, if (proc_arg_count < 0) { proc_arg_count = pt->Proc.param_count; } else { - if (proc_arg_count != pt->Proc.param_count) { - proc_arg_count_all_equal = false; - break; - } + proc_arg_count = gb_min(proc_arg_count, pt->Proc.param_count); } } } - if (proc_arg_count >= 0 && proc_arg_count_all_equal) { + if (proc_arg_count >= 0) { lhs_count = proc_arg_count; if (lhs_count > 0) { lhs = gb_alloc_array(heap_allocator(), Entity *, lhs_count); @@ -6258,14 +6254,18 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, } isize index = i; + ValidIndexAndScore item = {}; + item.score = data.score; + if (data.gen_entity != nullptr) { array_add(&proc_entities, data.gen_entity); index = proc_entities.count-1; + + // prefer non-polymorphic procedures over polymorphic + item.score += assign_score_function(1); } - ValidIndexAndScore item = {}; item.index = index; - item.score = data.score; array_add(&valids, item); } } @@ -6328,9 +6328,44 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, print_argument_types(); } + if (procs.count == 0) { + procs = proc_group_entities_cloned(c, *operand); + } if (procs.count > 0) { error_line("Did you mean to use one of the following:\n"); } + isize max_name_length = 0; + isize max_type_length = 0; + for (Entity *proc : procs) { + Type *t = base_type(proc->type); + if (t == t_invalid) continue; + String prefix = {}; + String prefix_sep = {}; + if (proc->pkg) { + prefix = proc->pkg->name; + prefix_sep = str_lit("."); + } + String name = proc->token.string; + max_name_length = gb_max(max_name_length, prefix.len + prefix_sep.len + name.len); + + gbString pt; + if (t->Proc.node != nullptr) { + pt = expr_to_string(t->Proc.node); + } else { + pt = type_to_string(t); + } + + max_type_length = gb_max(max_type_length, gb_string_length(pt)); + gb_string_free(pt); + } + + isize max_spaces = gb_max(max_name_length, max_type_length); + char *spaces = gb_alloc_array(temporary_allocator(), char, max_spaces+1); + for (isize i = 0; i < max_spaces; i++) { + spaces[i] = ' '; + } + spaces[max_spaces] = 0; + for (Entity *proc : procs) { TokenPos pos = proc->token.pos; Type *t = base_type(proc->type); @@ -6350,12 +6385,23 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, prefix_sep = str_lit("."); } String name = proc->token.string; + isize len = prefix.len + prefix_sep.len + name.len; + + int name_padding = cast(int)gb_max(max_name_length - len, 0); + int type_padding = cast(int)gb_max(max_type_length - gb_string_length(pt), 0); char const *sep = "::"; if (proc->kind == Entity_Variable) { sep = ":="; } - error_line("\t%.*s%.*s%.*s %s %s at %s\n", LIT(prefix), LIT(prefix_sep), LIT(name), sep, pt, token_pos_to_string(pos)); + error_line("\t%.*s%.*s%.*s %.*s%s %s %.*sat %s\n", + LIT(prefix), LIT(prefix_sep), LIT(name), + name_padding, spaces, + sep, + pt, + type_padding, spaces, + token_pos_to_string(pos) + ); } if (procs.count > 0) { error_line("\n"); @@ -6369,8 +6415,8 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, error(operand->expr, "Ambiguous procedure group call '%s' that match with the given arguments", expr_name); print_argument_types(); - for (isize i = 0; i < valids.count; i++) { - Entity *proc = proc_entities[valids[i].index]; + for (auto const &valid : valids) { + Entity *proc = proc_entities[valid.index]; GB_ASSERT(proc != nullptr); TokenPos pos = proc->token.pos; Type *t = base_type(proc->type); GB_ASSERT(t->kind == Type_Proc); @@ -9315,13 +9361,13 @@ gb_internal ExprKind check_selector_call_expr(CheckerContext *c, Operand *o, Ast ExprKind kind = check_expr_base(c, &x, se->expr, nullptr); c->allow_arrow_right_selector_expr = allow_arrow_right_selector_expr; - if (x.mode == Addressing_Invalid || x.type == t_invalid) { + if (x.mode == Addressing_Invalid || (x.type == t_invalid && x.mode != Addressing_ProcGroup)) { o->mode = Addressing_Invalid; o->type = t_invalid; o->expr = node; return kind; } - if (!is_type_proc(x.type)) { + if (!is_type_proc(x.type) && x.mode != Addressing_ProcGroup) { gbString type_str = type_to_string(x.type); error(se->call, "Selector call expressions expect a procedure type for the call, got '%s'", type_str); gb_string_free(type_str); @@ -9344,76 +9390,76 @@ gb_internal ExprKind check_selector_call_expr(CheckerContext *c, Operand *o, Ast first_arg->state_flags |= StateFlag_SelectorCallExpr; } - Type *pt = base_type(x.type); - GB_ASSERT(pt->kind == Type_Proc); - Type *first_type = nullptr; - String first_arg_name = {}; - if (pt->Proc.param_count > 0) { - Entity *f = pt->Proc.params->Tuple.variables[0]; - first_type = f->type; - first_arg_name = f->token.string; - } - if (first_arg_name.len == 0) { - first_arg_name = str_lit("_"); - } + if (e->kind != Entity_ProcGroup) { + Type *pt = base_type(x.type); + GB_ASSERT_MSG(pt->kind == Type_Proc, "%.*s %.*s %s", LIT(e->token.string), LIT(entity_strings[e->kind]), type_to_string(x.type)); + Type *first_type = nullptr; + String first_arg_name = {}; + if (pt->Proc.param_count > 0) { + Entity *f = pt->Proc.params->Tuple.variables[0]; + first_type = f->type; + first_arg_name = f->token.string; + } + if (first_arg_name.len == 0) { + first_arg_name = str_lit("_"); + } - if (first_type == nullptr) { - error(se->call, "Selector call expressions expect a procedure type for the call with at least 1 parameter"); - o->mode = Addressing_Invalid; - o->type = t_invalid; - o->expr = node; - return Expr_Stmt; - } + if (first_type == nullptr) { + error(se->call, "Selector call expressions expect a procedure type for the call with at least 1 parameter"); + o->mode = Addressing_Invalid; + o->type = t_invalid; + o->expr = node; + return Expr_Stmt; + } - Operand y = {}; - y.mode = first_arg->tav.mode; - y.type = first_arg->tav.type; - y.value = first_arg->tav.value; + Operand y = {}; + y.mode = first_arg->tav.mode; + y.type = first_arg->tav.type; + y.value = first_arg->tav.value; - if (check_is_assignable_to(c, &y, first_type)) { - // Do nothing, it's valid - } else { - Operand z = y; - z.type = type_deref(y.type); - if (check_is_assignable_to(c, &z, first_type)) { - // NOTE(bill): AST GENERATION HACK! - Token op = {Token_Pointer}; - first_arg = ast_deref_expr(first_arg->file(), first_arg, op); - } else if (y.mode == Addressing_Variable) { - Operand w = y; - w.type = alloc_type_pointer(y.type); - if (check_is_assignable_to(c, &w, first_type)) { + if (check_is_assignable_to(c, &y, first_type)) { + // Do nothing, it's valid + } else { + Operand z = y; + z.type = type_deref(y.type); + if (check_is_assignable_to(c, &z, first_type)) { // NOTE(bill): AST GENERATION HACK! - Token op = {Token_And}; - first_arg = ast_unary_expr(first_arg->file(), op, first_arg); + Token op = {Token_Pointer}; + first_arg = ast_deref_expr(first_arg->file(), first_arg, op); + } else if (y.mode == Addressing_Variable) { + Operand w = y; + w.type = alloc_type_pointer(y.type); + if (check_is_assignable_to(c, &w, first_type)) { + // NOTE(bill): AST GENERATION HACK! + Token op = {Token_And}; + first_arg = ast_unary_expr(first_arg->file(), op, first_arg); + } + } + } + + if (ce->args.count > 0) { + bool fail = false; + bool first_is_field_value = (ce->args[0]->kind == Ast_FieldValue); + for (Ast *arg : ce->args) { + bool mix = false; + if (first_is_field_value) { + mix = arg->kind != Ast_FieldValue; + } else { + mix = arg->kind == Ast_FieldValue; + } + if (mix) { + fail = true; + break; + } + } + if (!fail && first_is_field_value) { + Token op = {Token_Eq}; + AstFile *f = first_arg->file(); + first_arg = ast_field_value(f, ast_ident(f, make_token_ident(first_arg_name)), first_arg, op); } } } - if (ce->args.count > 0) { - bool fail = false; - bool first_is_field_value = (ce->args[0]->kind == Ast_FieldValue); - for (Ast *arg : ce->args) { - bool mix = false; - if (first_is_field_value) { - mix = arg->kind != Ast_FieldValue; - } else { - mix = arg->kind == Ast_FieldValue; - } - if (mix) { - fail = true; - break; - } - } - if (!fail && first_is_field_value) { - Token op = {Token_Eq}; - AstFile *f = first_arg->file(); - first_arg = ast_field_value(f, ast_ident(f, make_token_ident(first_arg_name)), first_arg, op); - } - } - - - auto modified_args = slice_make(heap_allocator(), ce->args.count+1); modified_args[0] = first_arg; slice_copy(&modified_args, ce->args, 1); diff --git a/src/checker.cpp b/src/checker.cpp index 396a04d7a..2a2cb5c42 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2935,6 +2935,54 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_block_decl_attribute) { return false; } +gb_internal DECL_ATTRIBUTE_PROC(proc_group_attribute) { + if (name == ATTRIBUTE_USER_TAG_NAME) { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind != ExactValue_String) { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; + } else if (name == "objc_name") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_String) { + if (string_is_valid_identifier(ev.value_string)) { + ac->objc_name = ev.value_string; + } else { + error(elem, "Invalid identifier for '%.*s', got '%.*s'", LIT(name), LIT(ev.value_string)); + } + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; + } else if (name == "objc_is_class_method") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_Bool) { + ac->objc_is_class_method = ev.value_bool; + } else { + error(elem, "Expected a boolean value for '%.*s'", LIT(name)); + } + return true; + } else if (name == "objc_type") { + if (value == nullptr) { + error(elem, "Expected a type for '%.*s'", LIT(name)); + } else { + Type *objc_type = check_type(c, value); + if (objc_type != nullptr) { + if (!has_type_got_objc_class_attribute(objc_type)) { + gbString t = type_to_string(objc_type); + error(value, "'%.*s' expected a named type with the attribute @(obj_class=), got type %s", LIT(name), t); + gb_string_free(t); + } else { + ac->objc_type = objc_type; + } + } + } + return true; + } + return false; +} + + gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { if (name == ATTRIBUTE_USER_TAG_NAME) { ExactValue ev = check_decl_attribute_value(c, value); diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 12abe7b16..938c9b2ac 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1825,25 +1825,28 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star TEMPORARY_ALLOCATOR_GUARD(); auto args = array_make(temporary_allocator(), 1); args[0] = lb_addr_load(p, all_tests_slice); - lb_emit_call(p, runner, args); + lbValue result = lb_emit_call(p, runner, args); + + lbValue exit_runner = lb_find_package_value(m, str_lit("os"), str_lit("exit")); + auto exit_args = array_make(temporary_allocator(), 1); + exit_args[0] = lb_emit_select(p, result, lb_const_int(m, t_int, 0), lb_const_int(m, t_int, 1)); + lb_emit_call(p, exit_runner, exit_args, ProcInlining_none); } else { if (m->info->entry_point != nullptr) { lbValue entry_point = lb_find_procedure_value_from_entity(m, m->info->entry_point); lb_emit_call(p, entry_point, {}, ProcInlining_no_inline); } - } + if (call_cleanup) { + lbValue cleanup_runtime_value = {cleanup_runtime->value, cleanup_runtime->type}; + lb_emit_call(p, cleanup_runtime_value, {}, ProcInlining_none); + } - if (call_cleanup) { - lbValue cleanup_runtime_value = {cleanup_runtime->value, cleanup_runtime->type}; - lb_emit_call(p, cleanup_runtime_value, {}, ProcInlining_none); - } - - - if (is_dll_main) { - LLVMBuildRet(p->builder, LLVMConstInt(lb_type(m, t_i32), 1, false)); - } else { - LLVMBuildRet(p->builder, LLVMConstInt(lb_type(m, t_i32), 0, false)); + if (is_dll_main) { + LLVMBuildRet(p->builder, LLVMConstInt(lb_type(m, t_i32), 1, false)); + } else { + LLVMBuildRet(p->builder, LLVMConstInt(lb_type(m, t_i32), 0, false)); + } } lb_end_procedure_body(p); diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index c30170553..5e6831fc2 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4171,7 +4171,7 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { // HACK TODO(bill): THIS IS A MASSIVE HACK!!!! if (is_type_union(ft) && !are_types_identical(fet, ft) && !is_type_untyped(fet)) { - GB_ASSERT_MSG(union_variant_index(ft, fet) > 0, "%s", type_to_string(fet)); + GB_ASSERT_MSG(union_variant_index(ft, fet) >= 0, "%s", type_to_string(fet)); lb_emit_store_union_variant(p, gep, field_expr, fet); } else { @@ -4519,8 +4519,9 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { Selection sel = lookup_field(type, selector, false); GB_ASSERT(sel.entity != nullptr); if (sel.pseudo_field) { - GB_ASSERT(sel.entity->kind == Entity_Procedure); + GB_ASSERT(sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup); Entity *e = entity_of_node(sel_node); + GB_ASSERT(e->kind == Entity_Procedure); return lb_addr(lb_find_value_from_entity(p->module, e)); } diff --git a/src/types.cpp b/src/types.cpp index a0881b3d1..ff8c42d83 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -3081,7 +3081,7 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name mutex_lock(md->mutex); defer (mutex_unlock(md->mutex)); for (TypeNameObjCMetadataEntry const &entry : md->value_entries) { - GB_ASSERT(entry.entity->kind == Entity_Procedure); + GB_ASSERT(entry.entity->kind == Entity_Procedure || entry.entity->kind == Entity_ProcGroup); if (entry.name == field_name) { sel.entity = entry.entity; sel.pseudo_field = true; diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 105d474e3..63d722e09 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -14,6 +14,7 @@ set COMMON=-collection:tests=..\.. ..\..\..\odin build ..\test_issue_2113.odin %COMMON% -file -debug || exit /b ..\..\..\odin test ..\test_issue_2466.odin %COMMON% -file || exit /b ..\..\..\odin test ..\test_issue_2615.odin %COMMON% -file || exit /b +..\..\..\odin test ..\test_issue_2637.odin %COMMON% -file || exit /b @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index c4c53e7e1..7d2101dc6 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -17,6 +17,7 @@ $ODIN test ../test_issue_2087.odin $COMMON -file $ODIN build ../test_issue_2113.odin $COMMON -file -debug $ODIN test ../test_issue_2466.odin $COMMON -file $ODIN test ../test_issue_2615.odin $COMMON -file +$ODIN test ../test_issue_2637.odin $COMMON -file if [[ $($ODIN build ../test_issue_2395.odin $COMMON -file 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then echo "SUCCESSFUL 1/1" else diff --git a/tests/issues/test_issue_2637.odin b/tests/issues/test_issue_2637.odin new file mode 100644 index 000000000..c170fc444 --- /dev/null +++ b/tests/issues/test_issue_2637.odin @@ -0,0 +1,13 @@ +// Tests issue #2637 https://github.com/odin-lang/Odin/issues/2637 +package test_issues + +import "core:testing" + +Foo :: Maybe(string) + +@(test) +test_expect_value_succeeds_with_nil :: proc(t: ^testing.T) { + x: Foo + testing.expect(t, x == nil) // Succeeds + testing.expect_value(t, x, nil) // Fails, "expected nil, got nil" +} diff --git a/vendor/ggpo/ggpo.odin b/vendor/ggpo/ggpo.odin index d17c33638..b38f4fb65 100644 --- a/vendor/ggpo/ggpo.odin +++ b/vendor/ggpo/ggpo.odin @@ -50,7 +50,7 @@ Player :: struct { player_num: c.int, using u: struct #raw_union { local: struct {}, - remove: struct { + remote: struct { ip_address: [32]byte, port: u16, }, diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index 2bedf77c4..4236839f0 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -1133,7 +1133,7 @@ foreign lib { SetGesturesEnabled :: proc(flags: Gestures) --- // Enable a set of gestures using flags IsGestureDetected :: proc(gesture: Gesture) -> bool --- // Check if a gesture have been detected - GetGestureDetected :: proc() -> Gesture --- // Get latest detected gesture + GetGestureDetected :: proc() -> Gestures --- // Get latest detected gesture GetGestureHoldDuration :: proc() -> f32 --- // Get gesture hold time in milliseconds GetGestureDragVector :: proc() -> Vector2 --- // Get gesture drag vector GetGestureDragAngle :: proc() -> f32 --- // Get gesture drag angle