mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-06 23:58:50 +00:00
Implement all checker specification for Objective-C class implementations and objc_ivar_get intrinsic
This commit is contained in:
@@ -364,7 +364,7 @@ objc_find_selector :: proc($name: string) -> objc_SEL ---
|
|||||||
objc_register_selector :: proc($name: string) -> objc_SEL ---
|
objc_register_selector :: proc($name: string) -> objc_SEL ---
|
||||||
objc_find_class :: proc($name: string) -> objc_Class ---
|
objc_find_class :: proc($name: string) -> objc_Class ---
|
||||||
objc_register_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 ---
|
valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
|
||||||
|
|
||||||
|
|||||||
+4
-24
@@ -391,7 +391,6 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan
|
|||||||
case BuiltinProc_objc_ivar_get:
|
case BuiltinProc_objc_ivar_get:
|
||||||
{
|
{
|
||||||
Type *self_type = nullptr;
|
Type *self_type = nullptr;
|
||||||
Type *ivar_type = nullptr;
|
|
||||||
|
|
||||||
Operand self = {};
|
Operand self = {};
|
||||||
check_expr_or_type(c, &self, ce->args[0]);
|
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 &&
|
if (!(self_type->kind == Type_Named &&
|
||||||
self_type->Named.type_name != nullptr &&
|
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);
|
gbString t = type_to_string(self_type);
|
||||||
error(self.expr, "'%.*s' expected a named type with the attribute @(objc_class=<string>) , got type %s", LIT(builtin_name), t);
|
error(self.expr, "'%.*s' expected a named type with the attribute @(objc_class=<string>) , got type %s", LIT(builtin_name), t);
|
||||||
gb_string_free(t);
|
gb_string_free(t);
|
||||||
return false;
|
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);
|
gbString t = type_to_string(self_type);
|
||||||
error(self.expr, "'%.*s' requires that type %s have the attribute @(objc_ivar=<ivar_type_name>).", LIT(builtin_name), t);
|
error(self.expr, "'%.*s' requires that type %s have the attribute @(objc_ivar=<ivar_type_name>).", LIT(builtin_name), t);
|
||||||
gb_string_free(t);
|
gb_string_free(t);
|
||||||
return false;
|
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) {
|
if (type_hint != nullptr && type_hint->kind == Type_Pointer && type_hint->Pointer.elem == ivar_type) {
|
||||||
operand->type = type_hint;
|
operand->type = type_hint;
|
||||||
} else {
|
} else {
|
||||||
@@ -457,8 +437,8 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan
|
|||||||
}
|
}
|
||||||
|
|
||||||
operand->mode = Addressing_Value;
|
operand->mode = Addressing_Value;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-9
@@ -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);
|
check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info);
|
||||||
|
|
||||||
if (super->kind != Type_Named) {
|
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");
|
error(e->token, "@(objc_superclass) Referenced type must be a named struct");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Type* named_type = base_type(super->Named.type_name->type);
|
Type* named_type = base_type(super->Named.type_name->type);
|
||||||
if (!is_type_objc_object(named_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");
|
error(e->token, "@(objc_superclass) Superclass must be an Objective-C class");
|
||||||
break;
|
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;
|
super = named_type->Named.type_name->TypeName.objc_superclass;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ac.objc_superclass != nullptr) {
|
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) {
|
} 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) {
|
} 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) {
|
if (type_size_of(e->type) > 0) {
|
||||||
error(e->token, "@(objc_class) marked type must be of zero size");
|
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)");
|
error(e->token, "@(objc_name) is required with @(objc_type)");
|
||||||
} else {
|
} else {
|
||||||
Type *t = ac.objc_type;
|
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;
|
Entity *tn = t->Named.type_name;
|
||||||
|
|
||||||
GB_ASSERT(tn->kind == Entity_TypeName);
|
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");
|
error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope");
|
||||||
} else {
|
} 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);
|
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) {
|
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");
|
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");
|
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");
|
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 {
|
} else {
|
||||||
|
|
||||||
auto method = ObjcMethodData{ ac, e };
|
auto method = ObjcMethodData{ ac, e };
|
||||||
method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name;
|
method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name;
|
||||||
|
|
||||||
CheckerInfo *info = ctx->info;
|
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);
|
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);
|
mutex_lock(&global_type_name_objc_metadata_mutex);
|
||||||
|
|||||||
+30
-29
@@ -3352,7 +3352,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
|
|||||||
ac->test = true;
|
ac->test = true;
|
||||||
return true;
|
return true;
|
||||||
} else if (name == "export") {
|
} 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.");
|
error(value, "Setting @(export) explicitly is not allowed when @(objc_implement) is set. It is exported implicitly.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -3369,7 +3369,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
|
|||||||
return true;
|
return true;
|
||||||
} else if (name == "linkage") {
|
} 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");
|
error(value, "Explicit linkage not allowed when @(objc_implement) is set. It is set implicitly");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -3684,6 +3684,10 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
|
|||||||
ExactValue ev = check_decl_attribute_value(c, value);
|
ExactValue ev = check_decl_attribute_value(c, value);
|
||||||
if (ev.kind == ExactValue_Bool) {
|
if (ev.kind == ExactValue_Bool) {
|
||||||
ac->objc_is_implementation = ev.value_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) {
|
} else if (ev.kind == ExactValue_Invalid) {
|
||||||
ac->objc_is_implementation = true;
|
ac->objc_is_implementation = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -3970,7 +3974,7 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) {
|
|||||||
} else if (name == "objc_ivar") {
|
} else if (name == "objc_ivar") {
|
||||||
Type *objc_ivar = check_type(c, value);
|
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;
|
ac->objc_ivar = objc_ivar;
|
||||||
} else {
|
} else {
|
||||||
error(value, "'%.*s' expected a named type", LIT(name));
|
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;
|
Entity *proc_entity = e->TypeName.objc_context_provider;
|
||||||
GB_ASSERT(proc_entity->kind == Entity_Procedure);
|
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)
|
Type *return_type = proc.result_count != 1 ? t_untyped_nil : base_named_type(proc.results->Tuple.variables[0]->type);
|
||||||
const char *signature_error = "The procedure for @(objc_context_provider) has an incorrect signature.";
|
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) {
|
const char *self_param_err = "The @(objc_context_provider) procedure must take as a parameter a single pointer to the @(objc_type) value.";
|
||||||
error(proc_entity->token, signature_error);
|
if (proc.param_count != 1) {
|
||||||
} else {
|
error(proc_entity->token, self_param_err);
|
||||||
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);
|
|
||||||
|
|
||||||
if (self_param->kind != Type_Pointer) {
|
Type *self_param = base_type(proc.params->Tuple.variables[0]->type);
|
||||||
error(proc_entity->token, signature_error);
|
if (self_param->kind == Type_Pointer) {
|
||||||
continue;
|
error(proc_entity->token, self_param_err);
|
||||||
}
|
}
|
||||||
|
|
||||||
self_param = base_named_type(self_param->Pointer.elem);
|
Type *self_type = base_named_type(self_param->Pointer.elem);
|
||||||
|
if (!internal_check_is_assignable_to(self_type, e->type) &&
|
||||||
if (return_type != t_context) {
|
!(e->TypeName.objc_ivar && internal_check_is_assignable_to(self_type, e->TypeName.objc_ivar))) {
|
||||||
error(e->token, signature_error);
|
error(proc_entity->token, self_param_err);
|
||||||
} 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))
|
if (proc.calling_convention != ProcCC_CDecl && proc.calling_convention != ProcCC_Contextless) {
|
||||||
) {
|
error(e->token, self_param_err);
|
||||||
error(e->token, signature_error);
|
}
|
||||||
} else if (proc_type->Proc.calling_convention != ProcCC_CDecl &&
|
if (proc.is_polymorphic) {
|
||||||
proc_type->Proc.calling_convention != ProcCC_Contextless) {
|
error(e->token, self_param_err);
|
||||||
error(e->token, signature_error);
|
|
||||||
} else if (proc_type->Proc.is_polymorphic) {
|
|
||||||
error(e->token, signature_error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -154,7 +154,8 @@ struct AttributeContext {
|
|||||||
Type * objc_ivar;
|
Type * objc_ivar;
|
||||||
Entity *objc_context_provider;
|
Entity *objc_context_provider;
|
||||||
bool objc_is_class_method;
|
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 require_target_feature; // required by the target micro-architecture
|
||||||
String enable_target_feature; // will be enabled for the procedure only
|
String enable_target_feature; // will be enabled for the procedure only
|
||||||
|
|||||||
@@ -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_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_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_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},
|
{STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user