mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-29 00:31:48 -07:00
Add more objc attributes
This commit is contained in:
@@ -287,15 +287,13 @@ bool check_builtin_objc_procedure(CheckerContext *c, Operand *operand, Ast *call
|
||||
Operand self = {};
|
||||
check_expr_or_type(c, &self, ce->args[1]);
|
||||
if (self.mode == Addressing_Type) {
|
||||
if (!internal_check_is_assignable_to(self.type, t_objc_object)) {
|
||||
if (!is_type_objc_object(self.type)) {
|
||||
gbString t = type_to_string(self.type);
|
||||
error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got type %s", LIT(builtin_name), t);
|
||||
gb_string_free(t);
|
||||
return false;
|
||||
}
|
||||
if (!(self.type->kind == Type_Named &&
|
||||
self.type->Named.type_name != nullptr &&
|
||||
self.type->Named.type_name->TypeName.objc_class_name != "")) {
|
||||
if (!has_type_got_objc_class_attribute(self.type)) {
|
||||
gbString t = type_to_string(self.type);
|
||||
error(self.expr, "'%.*s' expected a named type with the attribute @(obj_class=<string>) , got type %s", LIT(builtin_name), t);
|
||||
gb_string_free(t);
|
||||
@@ -306,7 +304,7 @@ bool check_builtin_objc_procedure(CheckerContext *c, Operand *operand, Ast *call
|
||||
} else if (!is_operand_value(self) || !check_is_assignable_to(c, &self, t_objc_id)) {
|
||||
gbString e = expr_to_string(self.expr);
|
||||
gbString t = type_to_string(self.type);
|
||||
error(self.expr, "'%.*s'3 expected a type or value derived from intrinsics.objc_object, got '%s' of type %s %d", LIT(builtin_name), e, t, self.type->kind);
|
||||
error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got '%s' of type %s %d", LIT(builtin_name), e, t, self.type->kind);
|
||||
gb_string_free(t);
|
||||
gb_string_free(e);
|
||||
return false;
|
||||
|
||||
@@ -340,6 +340,10 @@ void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, Type *def)
|
||||
check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac);
|
||||
if (e->kind == Entity_TypeName && ac.objc_class != "") {
|
||||
e->TypeName.objc_class_name = ac.objc_class;
|
||||
|
||||
if (type_size_of(e->type) > 0) {
|
||||
error(e->token, "@(objc_class) marked type must be of zero size");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,6 +826,65 @@ 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_class_name.len || ac.objc_type) {
|
||||
if (ac.objc_class_name.len && ac.objc_name.len) {
|
||||
error(e->token, "@(objc_class_name) and @(objc_name) may not be allowed at the same time");
|
||||
} else if (ac.objc_type == nullptr) {
|
||||
if (ac.objc_name.len) {
|
||||
error(e->token, "@(objc_name) requires that @(objc_type) to be set");
|
||||
} else {
|
||||
error(e->token, "@(objc_class_name) requires that @(objc_type) to be set");
|
||||
}
|
||||
} 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) and @(objc_class_name) attributes 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_name.len) {
|
||||
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_class_name) {
|
||||
error(e->token, "Previous declaration of @(objc_class_name=\"%.*s\")", LIT(ac.objc_class_name));
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_class_name, e});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch (e->Procedure.optimization_mode) {
|
||||
case ProcedureOptimizationMode_None:
|
||||
|
||||
@@ -325,6 +325,8 @@ void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, Type *named_t
|
||||
named_type->Named.type_name = e;
|
||||
GB_ASSERT(original_type->kind == Type_Named);
|
||||
e->TypeName.objc_class_name = original_type->Named.type_name->TypeName.objc_class_name;
|
||||
// TODO(bill): Is this even correct? Or should the metadata be copied?
|
||||
e->TypeName.objc_metadata = original_type->Named.type_name->TypeName.objc_metadata;
|
||||
|
||||
mutex_lock(&ctx->info->gen_types_mutex);
|
||||
auto *found_gen_types = map_get(&ctx->info->gen_types, original_type);
|
||||
|
||||
+49
-1
@@ -4,7 +4,7 @@
|
||||
void check_expr(CheckerContext *c, Operand *operand, Ast *expression);
|
||||
void check_expr_or_type(CheckerContext *c, Operand *operand, Ast *expression, Type *type_hint=nullptr);
|
||||
void add_comparison_procedures_for_fields(CheckerContext *c, Type *t);
|
||||
|
||||
Type *check_type(CheckerContext *ctx, Ast *e);
|
||||
|
||||
bool is_operand_value(Operand o) {
|
||||
switch (o.mode) {
|
||||
@@ -2740,6 +2740,14 @@ ExactValue check_decl_attribute_value(CheckerContext *c, Ast *value) {
|
||||
return ev;
|
||||
}
|
||||
|
||||
Type *check_decl_attribute_type(CheckerContext *c, Ast *value) {
|
||||
if (value != nullptr) {
|
||||
return check_type(c, value);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
#define ATTRIBUTE_USER_TAG_NAME "tag"
|
||||
|
||||
|
||||
@@ -3039,6 +3047,46 @@ DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
|
||||
error(elem, "Expected a string 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_class_name") {
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind == ExactValue_String) {
|
||||
if (string_is_valid_identifier(ev.value_string)) {
|
||||
ac->objc_class_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_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=<string>), got type %s", LIT(name), t);
|
||||
gb_string_free(t);
|
||||
} else {
|
||||
ac->objc_type = objc_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+5
-1
@@ -107,7 +107,6 @@ struct AttributeContext {
|
||||
String thread_local_model;
|
||||
String deprecated_message;
|
||||
String warning_message;
|
||||
String objc_class;
|
||||
DeferredProcedure deferred_procedure;
|
||||
bool is_export : 1;
|
||||
bool is_static : 1;
|
||||
@@ -119,6 +118,11 @@ struct AttributeContext {
|
||||
bool init : 1;
|
||||
bool set_cold : 1;
|
||||
u32 optimization_mode; // ProcedureOptimizationMode
|
||||
|
||||
String objc_class;
|
||||
String objc_name;
|
||||
String objc_class_name;
|
||||
Type * objc_type;
|
||||
};
|
||||
|
||||
AttributeContext make_attribute_context(String link_prefix) {
|
||||
|
||||
@@ -122,6 +122,28 @@ enum ProcedureOptimizationMode : u32 {
|
||||
ProcedureOptimizationMode_Speed,
|
||||
};
|
||||
|
||||
|
||||
BlockingMutex global_type_name_objc_metadata_mutex;
|
||||
|
||||
struct TypeNameObjCMetadataEntry {
|
||||
String name;
|
||||
Entity *entity;
|
||||
};
|
||||
struct TypeNameObjCMetadata {
|
||||
BlockingMutex *mutex;
|
||||
Array<TypeNameObjCMetadataEntry> type_entries;
|
||||
Array<TypeNameObjCMetadataEntry> value_entries;
|
||||
};
|
||||
|
||||
TypeNameObjCMetadata *create_type_name_obj_c_metadata() {
|
||||
TypeNameObjCMetadata *md = gb_alloc_item(permanent_allocator(), TypeNameObjCMetadata);
|
||||
md->mutex = gb_alloc_item(permanent_allocator(), BlockingMutex);
|
||||
mutex_init(md->mutex);
|
||||
array_init(&md->type_entries, heap_allocator());
|
||||
array_init(&md->value_entries, heap_allocator());
|
||||
return md;
|
||||
}
|
||||
|
||||
// An Entity is a named "thing" in the language
|
||||
struct Entity {
|
||||
EntityKind kind;
|
||||
@@ -187,6 +209,7 @@ struct Entity {
|
||||
String ir_mangled_name;
|
||||
bool is_type_alias;
|
||||
String objc_class_name;
|
||||
TypeNameObjCMetadata *objc_metadata;
|
||||
} TypeName;
|
||||
struct {
|
||||
u64 tags;
|
||||
|
||||
@@ -3320,7 +3320,12 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) {
|
||||
|
||||
Type *type = base_type(tav.type);
|
||||
if (tav.mode == Addressing_Type) { // Addressing_Type
|
||||
GB_PANIC("Unreachable");
|
||||
Selection sel = lookup_field(tav.type, selector, true);
|
||||
if (sel.pseudo_field) {
|
||||
GB_ASSERT(sel.entity->kind == Entity_Procedure);
|
||||
return lb_addr(lb_find_value_from_entity(p->module, sel.entity));
|
||||
}
|
||||
GB_PANIC("Unreachable %.*s", LIT(selector));
|
||||
}
|
||||
|
||||
if (se->swizzle_count > 0) {
|
||||
@@ -3347,6 +3352,10 @@ lbAddr lb_build_addr(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);
|
||||
return lb_addr(lb_find_value_from_entity(p->module, sel.entity));
|
||||
}
|
||||
|
||||
{
|
||||
lbAddr addr = lb_build_addr(p, se->expr);
|
||||
|
||||
+1
-31
@@ -585,37 +585,6 @@ void usage(String argv0) {
|
||||
print_usage_line(1, "e.g. odin build -help");
|
||||
}
|
||||
|
||||
|
||||
bool string_is_valid_identifier(String str) {
|
||||
if (str.len <= 0) return false;
|
||||
|
||||
isize rune_count = 0;
|
||||
|
||||
isize w = 0;
|
||||
isize offset = 0;
|
||||
while (offset < str.len) {
|
||||
Rune r = 0;
|
||||
w = utf8_decode(str.text, str.len, &r);
|
||||
if (r == GB_RUNE_INVALID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rune_count == 0) {
|
||||
if (!rune_is_letter(r)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!rune_is_letter(r) && !rune_is_digit(r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
rune_count += 1;
|
||||
offset += w;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum BuildFlagKind {
|
||||
BuildFlag_Invalid,
|
||||
|
||||
@@ -2447,6 +2416,7 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
virtual_memory_init();
|
||||
mutex_init(&fullpath_mutex);
|
||||
mutex_init(&hash_exact_value_mutex);
|
||||
mutex_init(&global_type_name_objc_metadata_mutex);
|
||||
|
||||
init_string_buffer_memory();
|
||||
init_string_interner();
|
||||
|
||||
@@ -781,3 +781,34 @@ i32 unquote_string(gbAllocator a, String *s_, u8 quote=0, bool has_carriage_retu
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool string_is_valid_identifier(String str) {
|
||||
if (str.len <= 0) return false;
|
||||
|
||||
isize rune_count = 0;
|
||||
|
||||
isize w = 0;
|
||||
isize offset = 0;
|
||||
while (offset < str.len) {
|
||||
Rune r = 0;
|
||||
w = utf8_decode(str.text, str.len, &r);
|
||||
if (r == GB_RUNE_INVALID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rune_count == 0) {
|
||||
if (!rune_is_letter(r)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!rune_is_letter(r) && !rune_is_digit(r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
rune_count += 1;
|
||||
offset += w;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@ struct Selection {
|
||||
bool indirect; // Set if there was a pointer deref anywhere down the line
|
||||
u8 swizzle_count; // maximum components = 4
|
||||
u8 swizzle_indices; // 2 bits per component, representing which swizzle index
|
||||
bool pseudo_field;
|
||||
};
|
||||
Selection empty_selection = {0};
|
||||
|
||||
@@ -2782,6 +2783,7 @@ Selection lookup_field_from_index(Type *type, i64 index) {
|
||||
}
|
||||
|
||||
Entity *scope_lookup_current(Scope *s, String const &name);
|
||||
bool has_type_got_objc_class_attribute(Type *t);
|
||||
|
||||
Selection lookup_field_with_selection(Type *type_, String field_name, bool is_type, Selection sel, bool allow_blank_ident) {
|
||||
GB_ASSERT(type_ != nullptr);
|
||||
@@ -2794,9 +2796,40 @@ Selection lookup_field_with_selection(Type *type_, String field_name, bool is_ty
|
||||
bool is_ptr = type != type_;
|
||||
sel.indirect = sel.indirect || is_ptr;
|
||||
|
||||
Type *original_type = type;
|
||||
|
||||
type = base_type(type);
|
||||
|
||||
if (is_type) {
|
||||
if (has_type_got_objc_class_attribute(original_type) && original_type->kind == Type_Named) {
|
||||
Entity *e = original_type->Named.type_name;
|
||||
GB_ASSERT(e->kind == Entity_TypeName);
|
||||
if (e->TypeName.objc_metadata) {
|
||||
auto *md = e->TypeName.objc_metadata;
|
||||
mutex_lock(md->mutex);
|
||||
defer (mutex_unlock(md->mutex));
|
||||
for (TypeNameObjCMetadataEntry const &entry : md->type_entries) {
|
||||
GB_ASSERT(entry.entity->kind == Entity_Procedure);
|
||||
if (entry.name == field_name) {
|
||||
sel.entity = entry.entity;
|
||||
sel.pseudo_field = true;
|
||||
return sel;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type->kind == Type_Struct) {
|
||||
for_array(i, type->Struct.fields) {
|
||||
Entity *f = type->Struct.fields[i];
|
||||
if (f->flags&EntityFlag_Using) {
|
||||
sel = lookup_field_with_selection(f->type, field_name, is_type, sel, allow_blank_ident);
|
||||
if (sel.entity) {
|
||||
return sel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_type_enum(type)) {
|
||||
// NOTE(bill): These may not have been added yet, so check in case
|
||||
for_array(i, type->Enum.fields) {
|
||||
@@ -2843,6 +2876,24 @@ Selection lookup_field_with_selection(Type *type_, String field_name, bool is_ty
|
||||
} else if (type->kind == Type_Union) {
|
||||
|
||||
} else if (type->kind == Type_Struct) {
|
||||
if (has_type_got_objc_class_attribute(original_type) && original_type->kind == Type_Named) {
|
||||
Entity *e = original_type->Named.type_name;
|
||||
GB_ASSERT(e->kind == Entity_TypeName);
|
||||
if (e->TypeName.objc_metadata) {
|
||||
auto *md = e->TypeName.objc_metadata;
|
||||
mutex_lock(md->mutex);
|
||||
defer (mutex_unlock(md->mutex));
|
||||
for (TypeNameObjCMetadataEntry const &entry : md->value_entries) {
|
||||
GB_ASSERT(entry.entity->kind == Entity_Procedure);
|
||||
if (entry.name == field_name) {
|
||||
sel.entity = entry.entity;
|
||||
sel.pseudo_field = true;
|
||||
return sel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for_array(i, type->Struct.fields) {
|
||||
Entity *f = type->Struct.fields[i];
|
||||
if (f->kind != Entity_Variable || (f->flags & EntityFlag_Field) == 0) {
|
||||
@@ -3792,6 +3843,17 @@ bool is_type_subtype_of(Type *src, Type *dst) {
|
||||
}
|
||||
|
||||
|
||||
bool has_type_got_objc_class_attribute(Type *t) {
|
||||
return t->kind == Type_Named && t->Named.type_name != nullptr && t->Named.type_name->TypeName.objc_class_name != "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool is_type_objc_object(Type *t) {
|
||||
bool internal_check_is_assignable_to(Type *src, Type *dst);
|
||||
|
||||
return internal_check_is_assignable_to(t, t_objc_object);
|
||||
}
|
||||
|
||||
Type *get_struct_field_type(Type *t, isize index) {
|
||||
t = base_type(type_deref(t));
|
||||
|
||||
Reference in New Issue
Block a user