diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 475b23930..8e0a8df7a 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -364,7 +364,7 @@ objc_find_selector :: proc($name: string) -> objc_SEL --- objc_register_selector :: proc($name: string) -> objc_SEL --- objc_find_class :: proc($name: string) -> objc_Class --- objc_register_class :: proc($name: string) -> objc_Class --- -objc_ivar_get :: proc(self: ^$T, $U: typeid) -> ^U --- +objc_ivar_get :: proc(self: ^$T) -> ^$U --- valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr --- diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 3e531a309..024289169 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -391,7 +391,6 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan case BuiltinProc_objc_ivar_get: { Type *self_type = nullptr; - Type *ivar_type = nullptr; Operand self = {}; check_expr_or_type(c, &self, ce->args[0]); @@ -416,40 +415,21 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan if (!(self_type->kind == Type_Named && self_type->Named.type_name != nullptr && - self_type->Named.type_name->TypeName.objc_class_name != "")) { + self_type->Named.type_name->TypeName.objc_class_name != "")) { gbString t = type_to_string(self_type); error(self.expr, "'%.*s' expected a named type with the attribute @(objc_class=) , got type %s", LIT(builtin_name), t); gb_string_free(t); return false; } - if (self_type->Named.type_name->TypeName.objc_ivar == nullptr) { + Type *ivar_type = self_type->Named.type_name->TypeName.objc_ivar; + if (ivar_type == nullptr) { gbString t = type_to_string(self_type); error(self.expr, "'%.*s' requires that type %s have the attribute @(objc_ivar=).", LIT(builtin_name), t); gb_string_free(t); return false; } - Operand ivar = {}; - check_expr_or_type(c, &ivar, ce->args[1]); - if (ivar.mode == Addressing_Type) { - ivar_type = ivar.type; - } else { - return false; - } - - if (self_type->Named.type_name->TypeName.objc_ivar != ivar_type) { - gbString name_self = type_to_string(self_type); - gbString name_expected = type_to_string(self_type->Named.type_name->TypeName.objc_ivar); - gbString name_given = type_to_string(ivar_type); - error(self.expr, "'%.*s' ivar type %s does not match @objc_ivar type %s on Objective-C class %s.", - LIT(builtin_name), name_given, name_expected, name_self); - gb_string_free(name_self); - gb_string_free(name_expected); - gb_string_free(name_given); - return false; - } - if (type_hint != nullptr && type_hint->kind == Type_Pointer && type_hint->Pointer.elem == ivar_type) { operand->type = type_hint; } else { @@ -457,8 +437,8 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan } operand->mode = Addressing_Value; - return true; + } break; } } diff --git a/src/check_decl.cpp b/src/check_decl.cpp index a37d20f56..18dc5e6b0 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -565,31 +565,41 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info); if (super->kind != Type_Named) { + // TODO(harold): Show the current superclass token too error(e->token, "@(objc_superclass) Referenced type must be a named struct"); break; } Type* named_type = base_type(super->Named.type_name->type); if (!is_type_objc_object(named_type)) { + // TODO(harold): Show the current superclass token too error(e->token, "@(objc_superclass) Superclass must be an Objective-C class"); break; } + if (named_type->Named.type_name->TypeName.objc_class_name == "") { + // TODO(harold): Show the current superclass token too + error(e->token, "@(objc_superclass) Superclass must be have a valid @(objc_class) attribute"); + break; + } + super = named_type->Named.type_name->TypeName.objc_superclass; } } else { if (ac.objc_superclass != nullptr) { - error(e->token, "@(objc_superclass) can only be applied when the @(obj_implement) attribute is also applied"); + error(e->token, "@(objc_superclass) may only be applied when the @(obj_implement) attribute is also applied"); } else if (ac.objc_ivar != nullptr) { - error(e->token, "@(objc_ivar) can only be applied when the @(obj_implement) attribute is also applied"); + error(e->token, "@(objc_ivar) may only be applied when the @(obj_implement) attribute is also applied"); } else if (ac.objc_context_provider != nullptr) { - error(e->token, "@(objc_context_provider) can only be applied when the @(obj_implement) attribute is also applied"); + error(e->token, "@(objc_context_provider) may only be applied when the @(obj_implement) attribute is also applied"); } } if (type_size_of(e->type) > 0) { error(e->token, "@(objc_class) marked type must be of zero size"); } + } else if (ac.objc_is_implementation) { + error(e->token, "@(objc_implement) may only be applied when the @(objc_class) attribute is also applied"); } } @@ -994,7 +1004,7 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon error(e->token, "@(objc_name) is required with @(objc_type)"); } else { Type *t = ac.objc_type; - if (t->kind == Type_Named) { + if (t->kind == Type_Named) { // TODO(harold): Shouldn't this be an error otherwise? Or is it checked elsehwere? Entity *tn = t->Named.type_name; GB_ASSERT(tn->kind == Entity_TypeName); @@ -1003,20 +1013,32 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope"); } else { - if (ac.objc_is_implementation) { + // Enable implementation by default if the class is an implementer too and + // @objc_implement was not set to false explicitly in this proc. + bool implement = tn->TypeName.objc_is_implementation; + if (ac.objc_is_disabled_implement) { + implement = false; + } + + if (implement) { GB_ASSERT(e->kind == Entity_Procedure); - Type *proc_type = e->type; + auto &proc = e->type->Proc; + Type &first_param = proc.param_count > 0 ? proc.params[0] : *t_untyped_nil; if (!tn->TypeName.objc_is_implementation) { error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied"); - } else if (proc_type->Proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) { + } else if (!ac.objc_is_class_method && !(first_param.kind == Type_Pointer && first_param.Pointer.elem == t)) { + error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)"); + } else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) { error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set"); - } else if (ac.objc_is_class_method && proc_type->Proc.calling_convention != ProcCC_CDecl) { + } else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) { error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention"); + } else if (proc.result_count > 1) { + error(e->token, "Objective-C method implementations may return at most 1 value"); } else { - auto method = ObjcMethodData{ ac, e }; + auto method = ObjcMethodData{ ac, e }; method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name; CheckerInfo *info = ctx->info; @@ -1033,6 +1055,8 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon map_set(&info->objc_method_implementations, t, list); } } + } else if (ac.objc_selector != "") { + error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C implementations."); } mutex_lock(&global_type_name_objc_metadata_mutex); diff --git a/src/checker.cpp b/src/checker.cpp index 15d19fb1a..0b093936d 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3352,7 +3352,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { ac->test = true; return true; } else if (name == "export") { - if (ac->objc_is_implementation) { + if (ac->objc_is_implementation) { // TODO(harold): Remove from here, this needs to be checked after all attributes are set. error(value, "Setting @(export) explicitly is not allowed when @(objc_implement) is set. It is exported implicitly."); return false; } @@ -3369,7 +3369,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { return true; } else if (name == "linkage") { - if (ac->objc_is_implementation) { + if (ac->objc_is_implementation) { // TODO(harold): Remove from here, this needs to be checked after all attributes are set. error(value, "Explicit linkage not allowed when @(objc_implement) is set. It is set implicitly"); return false; } @@ -3684,6 +3684,10 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind == ExactValue_Bool) { ac->objc_is_implementation = ev.value_bool; + + if (!ac->objc_is_implementation) { + ac->objc_is_disabled_implement = true; + } } else if (ev.kind == ExactValue_Invalid) { ac->objc_is_implementation = true; } else { @@ -3970,7 +3974,7 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) { } else if (name == "objc_ivar") { Type *objc_ivar = check_type(c, value); - if (objc_ivar != nullptr) { + if (objc_ivar != nullptr && objc_ivar->kind == Type_Named) { ac->objc_ivar = objc_ivar; } else { error(value, "'%.*s' expected a named type", LIT(name)); @@ -6488,36 +6492,33 @@ gb_internal void check_objc_context_provider_procedures(Checker *c) { Entity *proc_entity = e->TypeName.objc_context_provider; GB_ASSERT(proc_entity->kind == Entity_Procedure); - Type *proc_type = proc_entity->type; + auto &proc = proc_entity->type->Proc; - // TODO(harold): Give better errors here (specify exactly what's wrong) - const char *signature_error = "The procedure for @(objc_context_provider) has an incorrect signature."; + Type *return_type = proc.result_count != 1 ? t_untyped_nil : base_named_type(proc.results->Tuple.variables[0]->type); + if (return_type != t_context) { + error(proc_entity->token, "The @(objc_context_provider) procedure must only return a context."); + } - if (proc_type->Proc.param_count != 1 || proc_type->Proc.result_count != 1) { - error(proc_entity->token, signature_error); - } else { - Type *self_param = base_type(proc_type->Proc.params->Tuple.variables[0]->type); - Type *return_type = base_named_type(proc_type->Proc.results->Tuple.variables[0]->type); + const char *self_param_err = "The @(objc_context_provider) procedure must take as a parameter a single pointer to the @(objc_type) value."; + if (proc.param_count != 1) { + error(proc_entity->token, self_param_err); + } - if (self_param->kind != Type_Pointer) { - error(proc_entity->token, signature_error); - continue; - } + Type *self_param = base_type(proc.params->Tuple.variables[0]->type); + if (self_param->kind == Type_Pointer) { + error(proc_entity->token, self_param_err); + } - self_param = base_named_type(self_param->Pointer.elem); - - if (return_type != t_context) { - error(e->token, signature_error); - } else if (!internal_check_is_assignable_to(self_param, e->type) && - (e->TypeName.objc_ivar && !internal_check_is_assignable_to(self_param, e->TypeName.objc_ivar)) - ) { - error(e->token, signature_error); - } else if (proc_type->Proc.calling_convention != ProcCC_CDecl && - proc_type->Proc.calling_convention != ProcCC_Contextless) { - error(e->token, signature_error); - } else if (proc_type->Proc.is_polymorphic) { - error(e->token, signature_error); - } + Type *self_type = base_named_type(self_param->Pointer.elem); + if (!internal_check_is_assignable_to(self_type, e->type) && + !(e->TypeName.objc_ivar && internal_check_is_assignable_to(self_type, e->TypeName.objc_ivar))) { + error(proc_entity->token, self_param_err); + } + if (proc.calling_convention != ProcCC_CDecl && proc.calling_convention != ProcCC_Contextless) { + error(e->token, self_param_err); + } + if (proc.is_polymorphic) { + error(e->token, self_param_err); } } } diff --git a/src/checker.hpp b/src/checker.hpp index 0f7e1cb59..fc3ff455b 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -154,7 +154,8 @@ struct AttributeContext { Type * objc_ivar; Entity *objc_context_provider; bool objc_is_class_method; - bool objc_is_implementation; // This struct or proc provides a class/method implementation, not a binding to an existing type. + bool objc_is_implementation; // This struct or proc provides a class/method implementation, not a binding to an existing type. + bool objc_is_disabled_implement; // This means the method explicitly set @objc_implement to false so it won't be inferred from the class' attribute. String require_target_feature; // required by the target micro-architecture String enable_target_feature; // will be enabled for the procedure only diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 5a7a63ae8..cbb17be65 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -674,7 +674,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("objc_find_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, - {STR_LIT("objc_ivar_get"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("objc_ivar_get"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},