void check_stmt (CheckerContext *ctx, Ast *node, u32 flags); // NOTE(bill): 'content_name' is for debugging and error messages Type *check_init_variable(CheckerContext *ctx, Entity *e, Operand *operand, String context_name) { if (operand->mode == Addressing_Invalid || operand->type == t_invalid || e->type == t_invalid) { if (operand->mode == Addressing_Builtin) { gbString expr_str = expr_to_string(operand->expr); // TODO(bill): is this a good enough error message? // TODO(bill): Actually allow built in procedures to be passed around and thus be created on use error(operand->expr, "Cannot assign built-in procedure '%s' in %.*s", expr_str, LIT(context_name)); operand->mode = Addressing_Invalid; gb_string_free(expr_str); } if (operand->mode == Addressing_ProcGroup) { if (e->type == nullptr) { error(operand->expr, "Cannot determine type from overloaded procedure '%.*s'", LIT(operand->proc_group->token.string)); } else { check_assignment(ctx, operand, e->type, str_lit("variable assignment")); if (operand->mode != Addressing_Type) { return operand->type; } } } if (e->type == nullptr) { e->type = t_invalid; } return nullptr; } if (e->kind == Entity_Variable) { e->Variable.init_expr = operand->expr; } if (operand->mode == Addressing_Type) { if (e->type != nullptr && is_type_typeid(e->type)) { add_type_info_type(ctx, operand->type); add_type_and_value(ctx->info, operand->expr, Addressing_Value, e->type, exact_value_typeid(operand->type)); return e->type; } else { gbString t = type_to_string(operand->type); defer (gb_string_free(t)); error(operand->expr, "Cannot assign a type '%s' to variable '%.*s'", t, LIT(e->token.string)); if (e->type == nullptr) { error_line("\tThe type of the variable '%.*s' cannot be inferred as a type does not have a default type\n", LIT(e->token.string)); } e->type = operand->type; return nullptr; } } if (e->type == nullptr) { // NOTE(bill): Use the type of the operand Type *t = operand->type; if (is_type_untyped(t)) { if (t == t_invalid || is_type_untyped_nil(t)) { error(e->token, "Invalid use of untyped nil in %.*s", LIT(context_name)); e->type = t_invalid; return nullptr; } if (t == t_invalid || is_type_untyped_undef(t)) { error(e->token, "Invalid use of --- in %.*s", LIT(context_name)); e->type = t_invalid; return nullptr; } t = default_type(t); } if (is_type_asm_proc(t)) { error(e->token, "Invalid use of inline asm in %.*s", LIT(context_name)); e->type = t_invalid; return nullptr; } else if (is_type_polymorphic(t)) { gbString str = type_to_string(t); defer (gb_string_free(str)); error(e->token, "Invalid use of a polymorphic type '%s' in %.*s", str, LIT(context_name)); e->type = t_invalid; return nullptr; } else if (is_type_empty_union(t)) { gbString str = type_to_string(t); defer (gb_string_free(str)); error(e->token, "An empty union '%s' cannot be instantiated in %.*s", str, LIT(context_name)); e->type = t_invalid; return nullptr; } GB_ASSERT(is_type_typed(t)); e->type = t; } e->parent_proc_decl = ctx->curr_proc_decl; check_assignment(ctx, operand, e->type, context_name); if (operand->mode == Addressing_Invalid) { return nullptr; } return e->type; } void check_init_variables(CheckerContext *ctx, Entity **lhs, isize lhs_count, Slice const &inits, String context_name) { if ((lhs == nullptr || lhs_count == 0) && inits.count == 0) { return; } // NOTE(bill): If there is a bad syntax error, rhs > lhs which would mean there would need to be // an extra allocation auto operands = array_make(temporary_allocator(), 0, 2*lhs_count); check_unpack_arguments(ctx, lhs, lhs_count, &operands, inits, true, false); isize rhs_count = operands.count; for_array(i, operands) { if (operands[i].mode == Addressing_Invalid) { // TODO(bill): Should I ignore invalid parameters? // rhs_count--; } } isize max = gb_min(lhs_count, rhs_count); for (isize i = 0; i < max; i++) { Entity *e = lhs[i]; DeclInfo *d = decl_info_of_entity(e); Operand *o = &operands[i]; check_init_variable(ctx, e, o, context_name); if (d != nullptr) { d->init_expr = o->expr; } } if (rhs_count > 0 && lhs_count != rhs_count) { error(lhs[0]->token, "Assignment count mismatch '%td' = '%td'", lhs_count, rhs_count); } } void check_init_constant(CheckerContext *ctx, Entity *e, Operand *operand) { if (operand->mode == Addressing_Invalid || operand->type == t_invalid || e->type == t_invalid) { if (e->type == nullptr) { e->type = t_invalid; } return; } if (operand->mode != Addressing_Constant) { // TODO(bill): better error gbString str = expr_to_string(operand->expr); error(operand->expr, "'%s' is not a constant", str); gb_string_free(str); if (e->type == nullptr) { e->type = t_invalid; } return; } if (e->type == nullptr) { // NOTE(bill): type inference e->type = operand->type; } check_assignment(ctx, operand, e->type, str_lit("constant declaration")); if (operand->mode == Addressing_Invalid) { return; } if (is_type_proc(e->type)) { error(e->token, "Illegal declaration of a constant procedure value"); } e->parent_proc_decl = ctx->curr_proc_decl; e->Constant.value = operand->value; } bool is_type_distinct(Ast *node) { for (;;) { if (node == nullptr) { return false; } if (node->kind == Ast_ParenExpr) { node = node->ParenExpr.expr; } else if (node->kind == Ast_HelperType) { node = node->HelperType.type; } else { break; } } switch (node->kind) { case Ast_DistinctType: return true; case Ast_StructType: case Ast_UnionType: case Ast_EnumType: case Ast_ProcType: return true; case Ast_PointerType: case Ast_ArrayType: case Ast_DynamicArrayType: case Ast_MapType: return false; } return false; } Ast *remove_type_alias_clutter(Ast *node) { for (;;) { if (node == nullptr) { return nullptr; } if (node->kind == Ast_ParenExpr) { node = node->ParenExpr.expr; } else if (node->kind == Ast_DistinctType) { node = node->DistinctType.type; } else { return node; } } } isize total_attribute_count(DeclInfo *decl) { isize attribute_count = 0; for_array(i, decl->attributes) { Ast *attr = decl->attributes[i]; if (attr->kind != Ast_Attribute) continue; attribute_count += attr->Attribute.elems.count; } return attribute_count; } Type *clone_enum_type(CheckerContext *ctx, Type *original_enum_type, Type *named_type) { // NOTE(bill, 2022-02-05): Stupid edge case for `distinct` declarations // // X :: enum {A, B, C} // Y :: distinct X // // To make Y be just like X, it will need to copy the elements of X and change their type // so that they match Y rather than X. GB_ASSERT(original_enum_type != nullptr); GB_ASSERT(named_type != nullptr); GB_ASSERT(original_enum_type->kind == Type_Enum); GB_ASSERT(named_type->kind == Type_Named); Scope *parent = original_enum_type->Enum.scope->parent; Scope *scope = create_scope(nullptr, parent); Type *et = alloc_type_enum(); et->Enum.base_type = original_enum_type->Enum.base_type; et->Enum.min_value = original_enum_type->Enum.min_value; et->Enum.max_value = original_enum_type->Enum.max_value; et->Enum.min_value_index = original_enum_type->Enum.min_value_index; et->Enum.max_value_index = original_enum_type->Enum.max_value_index; et->Enum.scope = scope; auto fields = array_make(permanent_allocator(), original_enum_type->Enum.fields.count); for_array(i, fields) { Entity *old = original_enum_type->Enum.fields[i]; Entity *e = alloc_entity_constant(scope, old->token, named_type, old->Constant.value); e->file = old->file; e->identifier = clone_ast(old->identifier); e->flags |= EntityFlag_Visited; e->state = EntityState_Resolved; e->Constant.flags = old->Constant.flags; e->Constant.docs = old->Constant.docs; e->Constant.comment = old->Constant.comment; fields[i] = e; add_entity(ctx, scope, nullptr, e); add_entity_use(ctx, e->identifier, e); } et->Enum.fields = fields; return et; } void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, Type *def) { GB_ASSERT(e->type == nullptr); DeclInfo *decl = decl_info_of_entity(e); bool is_distinct = is_type_distinct(init_expr); Ast *te = remove_type_alias_clutter(init_expr); e->type = t_invalid; String name = e->token.string; Type *named = alloc_type_named(name, nullptr, e); if (def != nullptr && def->kind == Type_Named) { def->Named.base = named; } e->type = named; check_type_path_push(ctx, e); Type *bt = check_type_expr(ctx, te, named); check_type_path_pop(ctx); Type *base = base_type(bt); if (is_distinct && bt->kind == Type_Named && base->kind == Type_Enum) { base = clone_enum_type(ctx, base, named); } named->Named.base = base; if (is_distinct && is_type_typeid(e->type)) { error(init_expr, "'distinct' cannot be applied to 'typeid'"); is_distinct = false; } if (is_distinct && is_type_any(e->type)) { error(init_expr, "'distinct' cannot be applied to 'any'"); is_distinct = false; } if (!is_distinct) { e->type = bt; named->Named.base = bt; e->TypeName.is_type_alias = true; } if (decl->type_expr != nullptr) { Type *t = check_type(ctx, decl->type_expr); if (t != nullptr && !is_type_typeid(t)) { Operand operand = {}; operand.mode = Addressing_Type; operand.type = e->type; operand.expr = init_expr; check_assignment(ctx, &operand, t, str_lit("constant declaration")); } } if (decl != nullptr) { AttributeContext ac = {}; 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"); } } } // using decl if (decl->is_using) { warning(init_expr, "'using' an enum declaration is not allowed, prefer using implicit selector expressions e.g. '.A'"); #if 1 // NOTE(bill): Must be an enum declaration if (te->kind == Ast_EnumType) { Scope *parent = e->scope; if (parent->flags&ScopeFlag_File) { // NOTE(bill): Use package scope parent = parent->parent; } Type *t = base_type(e->type); if (t->kind == Type_Enum) { for_array(i, t->Enum.fields) { Entity *f = t->Enum.fields[i]; if (f->kind != Entity_Constant) { continue; } String name = f->token.string; if (is_blank_ident(name)) { continue; } add_entity(ctx, parent, nullptr, f); } } } #endif } } void override_entity_in_scope(Entity *original_entity, Entity *new_entity) { // NOTE(bill): The original_entity's scope may not be same scope that it was inserted into // e.g. file entity inserted into its package scope String original_name = original_entity->token.string; Scope *found_scope = nullptr; Entity *found_entity = nullptr; scope_lookup_parent(original_entity->scope, original_name, &found_scope, &found_entity); if (found_scope == nullptr) { return; } mutex_lock(&found_scope->mutex); defer (mutex_unlock(&found_scope->mutex)); // IMPORTANT NOTE(bill, 2021-04-10): Overriding behaviour was flawed in that the // original entity was still used check checked, but the checking was only // relying on "constant" data such as the Entity.type and Entity.Constant.value // // Therefore two things can be done: the type can be assigned to state that it // has been "evaluated" and the variant data can be copied across string_map_set(&found_scope->elements, original_name, new_entity); original_entity->flags |= EntityFlag_Overridden; original_entity->type = new_entity->type; original_entity->aliased_of = new_entity; Ast *empty_ident = nullptr; original_entity->identifier.compare_exchange_strong(empty_ident, new_entity->identifier); if (original_entity->identifier.load() != nullptr && original_entity->identifier.load()->kind == Ast_Ident) { original_entity->identifier.load()->Ident.entity = new_entity; } // IMPORTANT NOTE(bill, 2021-04-10): copy only the variants // This is most likely NEVER required, but it does not at all hurt to keep isize offset = cast(u8 *)&original_entity->Dummy.start - cast(u8 *)original_entity; isize size = gb_size_of(*original_entity) - offset; gb_memmove(cast(u8 *)original_entity, cast(u8 *)new_entity, size); } void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init, Type *named_type) { GB_ASSERT(e->type == nullptr); GB_ASSERT(e->kind == Entity_Constant); init = unparen_expr(init); if (e->flags & EntityFlag_Visited) { e->type = t_invalid; return; } e->flags |= EntityFlag_Visited; if (type_expr) { e->type = check_type(ctx, type_expr); } Operand operand = {}; if (init != nullptr) { Entity *entity = check_entity_from_ident_or_selector(ctx, init, false); if (entity != nullptr && entity->kind == Entity_TypeName) { // @TypeAliasingProblem // NOTE(bill, 2022-02-03): This is used to solve the problem caused by type aliases // being "confused" as constants // // A :: B // C :: proc "c" (^A) // B :: struct {x: C} // // A gets evaluated first, and then checks B. // B then checks C. // C then tries to check A which is unresolved but thought to be a constant. // Therefore within C's check, A errs as "not a type". // // This is because a const declaration may or may not be a type and this cannot // be determined from a syntactical standpoint. // This check allows the compiler to override the entity to be checked as a type. // // There is no problem if B is prefixed with the `#type` helper enforcing at // both a syntax and semantic level that B must be a type. // // A :: #type B // // This approach is not fool proof and can fail in case such as: // // X :: type_of(x) // X :: Foo(int).Type // // Since even these kind of declarations may cause weird checking cycles. // For the time being, these are going to be treated as an unfortunate error // until there is a proper delaying system to try declaration again if they // have failed. e->kind = Entity_TypeName; check_type_decl(ctx, e, init, named_type); return; } entity = nullptr; if (init->kind == Ast_Ident) { entity = check_ident(ctx, &operand, init, nullptr, e->type, true); } else if (init->kind == Ast_SelectorExpr) { entity = check_selector(ctx, &operand, init, e->type); } else { check_expr_or_type(ctx, &operand, init, e->type); } switch (operand.mode) { case Addressing_Type: { if (e->type != nullptr && !is_type_typeid(e->type)) { check_assignment(ctx, &operand, e->type, str_lit("constant declaration")); } e->kind = Entity_TypeName; e->type = nullptr; if (entity != nullptr && entity->type != nullptr && is_type_polymorphic_record_unspecialized(entity->type)) { DeclInfo *decl = decl_info_of_entity(e); if (decl != nullptr) { if (decl->attributes.count > 0) { error(decl->attributes[0], "Constant alias declarations cannot have attributes"); } } override_entity_in_scope(e, entity); return; } check_type_decl(ctx, e, ctx->decl->init_expr, named_type); return; } // NOTE(bill): Check to see if the expression it to be aliases case Addressing_Builtin: if (e->type != nullptr) { error(type_expr, "A constant alias of a built-in procedure may not have a type initializer"); } e->kind = Entity_Builtin; e->Builtin.id = operand.builtin_id; e->type = t_invalid; return; case Addressing_ProcGroup: GB_ASSERT(operand.proc_group != nullptr); GB_ASSERT(operand.proc_group->kind == Entity_ProcGroup); // NOTE(bill, 2020-06-10): It is better to just clone the contents than overriding the entity in the scope // Thank goodness I made entities a tagged union to allow for this implace patching e->kind = Entity_ProcGroup; e->ProcGroup.entities = array_clone(heap_allocator(), operand.proc_group->ProcGroup.entities); return; } if (entity != nullptr) { if (e->type != nullptr) { Operand x = {}; x.type = entity->type; x.mode = Addressing_Variable; if (!check_is_assignable_to(ctx, &x, e->type)) { gbString expr_str = expr_to_string(init); gbString op_type_str = type_to_string(entity->type); gbString type_str = type_to_string(e->type); error(e->token, "Cannot assign '%s' of type '%s' to '%s'", expr_str, op_type_str, type_str); gb_string_free(type_str); gb_string_free(op_type_str); gb_string_free(expr_str); } } // NOTE(bill): Override aliased entity switch (entity->kind) { case Entity_ProcGroup: case Entity_Procedure: case Entity_LibraryName: case Entity_ImportName: { DeclInfo *decl = decl_info_of_entity(e); if (decl != nullptr) { if (decl->attributes.count > 0) { error(decl->attributes[0], "Constant alias declarations cannot have attributes"); } } override_entity_in_scope(e, entity); return; } } } } check_init_constant(ctx, e, &operand); if (operand.mode == Addressing_Invalid || base_type(operand.type) == t_invalid) { gbString str = expr_to_string(init); error(e->token, "Invalid declaration type '%s'", str); gb_string_free(str); } DeclInfo *decl = decl_info_of_entity(e); if (decl != nullptr) { check_decl_attributes(ctx, decl->attributes, const_decl_attribute, nullptr); } } typedef bool TypeCheckSig(Type *t); bool sig_compare(TypeCheckSig *a, Type *x, Type *y) { x = core_type(x); y = core_type(y); return (a(x) && a(y)); } bool sig_compare(TypeCheckSig *a, TypeCheckSig *b, Type *x, Type *y) { x = core_type(x); y = core_type(y); if (a == b) { return sig_compare(a, x, y); } return ((a(x) && b(y)) || (b(x) && a(y))); } bool signature_parameter_similar_enough(Type *x, Type *y) { if (sig_compare(is_type_pointer, x, y)) { return true; } if (sig_compare(is_type_multi_pointer, x, y)) { return true; } if (sig_compare(is_type_proc, x, y)) { return true; } if (sig_compare(is_type_integer, x, y)) { GB_ASSERT(core_type(x)->kind == Type_Basic); GB_ASSERT(core_type(y)->kind == Type_Basic); i64 sx = type_size_of(x); i64 sy = type_size_of(y); if (sx == sy) return true; } if (sig_compare(is_type_integer, is_type_boolean, x, y)) { GB_ASSERT(core_type(x)->kind == Type_Basic); GB_ASSERT(core_type(y)->kind == Type_Basic); i64 sx = type_size_of(x); i64 sy = type_size_of(y); if (sx == sy) return true; } if (sig_compare(is_type_cstring, is_type_u8_ptr, x, y)) { return true; } if (sig_compare(is_type_cstring, is_type_u8_multi_ptr, x, y)) { return true; } if (sig_compare(is_type_uintptr, is_type_rawptr, x, y)) { return true; } if (sig_compare(is_type_proc, is_type_pointer, x, y)) { return true; } if (sig_compare(is_type_pointer, is_type_multi_pointer, x, y)) { return true; } if (sig_compare(is_type_proc, is_type_multi_pointer, x, y)) { return true; } return are_types_identical(x, y); } bool are_signatures_similar_enough(Type *a_, Type *b_) { GB_ASSERT(a_->kind == Type_Proc); GB_ASSERT(b_->kind == Type_Proc); TypeProc *a = &a_->Proc; TypeProc *b = &b_->Proc; if (a->param_count != b->param_count) { return false; } if (a->result_count != b->result_count) { return false; } for (isize i = 0; i < a->param_count; i++) { Type *x = core_type(a->params->Tuple.variables[i]->type); Type *y = core_type(b->params->Tuple.variables[i]->type); if (!signature_parameter_similar_enough(x, y)) { return false; } } for (isize i = 0; i < a->result_count; i++) { Type *x = base_type(a->results->Tuple.variables[i]->type); Type *y = base_type(b->results->Tuple.variables[i]->type); if (!signature_parameter_similar_enough(x, y)) { return false; } } return true; } Entity *init_entity_foreign_library(CheckerContext *ctx, Entity *e) { Ast *ident = nullptr; Entity **foreign_library = nullptr; switch (e->kind) { case Entity_Procedure: ident = e->Procedure.foreign_library_ident; foreign_library = &e->Procedure.foreign_library; break; case Entity_Variable: ident = e->Variable.foreign_library_ident; foreign_library = &e->Variable.foreign_library; break; default: return nullptr; } if (ident == nullptr) { error(e->token, "foreign entiies must declare which library they are from"); } else if (ident->kind != Ast_Ident) { error(ident, "foreign library names must be an identifier"); } else { String name = ident->Ident.token.string; Entity *found = scope_lookup(ctx->scope, name); if (found == nullptr) { if (is_blank_ident(name)) { // NOTE(bill): link against nothing } else { error(ident, "Undeclared name: %.*s", LIT(name)); } } else if (found->kind != Entity_LibraryName) { error(ident, "'%.*s' cannot be used as a library name", LIT(name)); } else { // TODO(bill): Extra stuff to do with library names? *foreign_library = found; found->flags |= EntityFlag_Used; add_entity_use(ctx, ident, found); return found; } } return nullptr; } String handle_link_name(CheckerContext *ctx, Token token, String link_name, String link_prefix) { if (link_prefix.len > 0) { if (link_name.len > 0) { error(token, "'link_name' and 'link_prefix' cannot be used together"); } else { isize len = link_prefix.len + token.string.len; u8 *name = gb_alloc_array(permanent_allocator(), u8, len+1); gb_memmove(name, &link_prefix[0], link_prefix.len); gb_memmove(name+link_prefix.len, &token.string[0], token.string.len); name[len] = 0; link_name = make_string(name, len); } } return link_name; } void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { GB_ASSERT(e->type == nullptr); if (d->proc_lit->kind != Ast_ProcLit) { // TOOD(bill): Better error message error(d->proc_lit, "Expected a procedure to check"); return; } Type *proc_type = e->type; if (d->gen_proc_type != nullptr) { proc_type = d->gen_proc_type; } else { proc_type = alloc_type_proc(e->scope, nullptr, 0, nullptr, 0, false, default_calling_convention()); } e->type = proc_type; ast_node(pl, ProcLit, d->proc_lit); check_open_scope(ctx, pl->type); defer (check_close_scope(ctx)); ctx->scope->procedure_entity = e; Type *decl_type = nullptr; if (d->type_expr != nullptr) { decl_type = check_type(ctx, d->type_expr); if (!is_type_proc(decl_type)) { gbString str = type_to_string(decl_type); error(d->type_expr, "Expected a procedure type, got '%s'", str); gb_string_free(str); } } auto tmp_ctx = *ctx; tmp_ctx.allow_polymorphic_types = true; if (decl_type != nullptr) { tmp_ctx.type_hint = decl_type; } check_procedure_type(&tmp_ctx, proc_type, pl->type); if (decl_type != nullptr) { Operand x = {}; x.type = e->type; x.mode = Addressing_Variable; if (!check_is_assignable_to(ctx, &x, decl_type)) { gbString expr_str = expr_to_string(d->proc_lit); gbString op_type_str = type_to_string(e->type); gbString type_str = type_to_string(decl_type); error(e->token, "Cannot assign '%s' of type '%s' to '%s'", expr_str, op_type_str, type_str); gb_string_free(type_str); gb_string_free(op_type_str); gb_string_free(expr_str); } } TypeProc *pt = &proc_type->Proc; AttributeContext ac = make_attribute_context(e->Procedure.link_prefix); if (d != nullptr) { check_decl_attributes(ctx, d->attributes, proc_decl_attribute, &ac); } if (ac.test) { e->flags |= EntityFlag_Test; } if (ac.init) { e->flags |= EntityFlag_Init; } if (ac.set_cold) { e->flags |= EntityFlag_Cold; } 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}); } } } } } } switch (e->Procedure.optimization_mode) { case ProcedureOptimizationMode_None: case ProcedureOptimizationMode_Minimal: if (pl->inlining == ProcInlining_inline) { error(e->token, "#force_inline cannot be used in conjunction with the attribute 'optimization_mode' with neither \"none\" nor \"minimal\""); } break; } e->Procedure.is_export = ac.is_export; e->deprecated_message = ac.deprecated_message; e->warning_message = ac.warning_message; ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); if (ac.has_disabled_proc) { if (ac.disabled_proc) { e->flags |= EntityFlag_Disabled; } Type *t = base_type(e->type); GB_ASSERT(t->kind == Type_Proc); if (t->Proc.result_count != 0) { error(e->token, "Procedure with the 'disabled' attribute may not have any return values"); } } bool is_foreign = e->Procedure.is_foreign; bool is_export = e->Procedure.is_export; if (ac.linkage.len != 0) { if (ac.linkage == "internal") { e->flags |= EntityFlag_CustomLinkage_Internal; } else if (ac.linkage == "strong") { e->flags |= EntityFlag_CustomLinkage_Strong; } else if (ac.linkage == "weak") { e->flags |= EntityFlag_CustomLinkage_Weak; } else if (ac.linkage == "link_once") { e->flags |= EntityFlag_CustomLinkage_LinkOnce; } if (is_foreign && (e->flags & EntityFlag_CustomLinkage_Internal)) { error(e->token, "A foreign procedure may not have an \"internal\" linkage"); } } if (ac.require_declaration) { e->flags |= EntityFlag_Require; } if (e->pkg != nullptr && e->token.string == "main") { if (e->pkg->kind != Package_Runtime) { if (pt->param_count != 0 || pt->result_count != 0) { gbString str = type_to_string(proc_type); error(e->token, "Procedure type of 'main' was expected to be 'proc()', got %s", str); gb_string_free(str); } if (pt->calling_convention != default_calling_convention()) { error(e->token, "Procedure 'main' cannot have a custom calling convention"); } pt->calling_convention = default_calling_convention(); if (e->pkg->kind == Package_Init) { if (ctx->info->entry_point != nullptr) { error(e->token, "Redeclaration of the entry pointer procedure 'main'"); } else { ctx->info->entry_point = e; } } } } if (is_foreign && is_export) { error(pl->type, "A foreign procedure cannot have an 'export' tag"); } if (pt->is_polymorphic) { if (pl->body == nullptr) { error(e->token, "Polymorphic procedures must have a body"); } if (is_foreign) { error(e->token, "A foreign procedure cannot be a polymorphic"); return; } } if (pl->body != nullptr) { if (is_foreign) { error(pl->body, "A foreign procedure cannot have a body"); } if (proc_type->Proc.c_vararg) { error(pl->body, "A procedure with a '#c_vararg' field cannot have a body and must be foreign"); } d->scope = ctx->scope; GB_ASSERT(pl->body->kind == Ast_BlockStmt); if (!pt->is_polymorphic) { check_procedure_later(ctx, ctx->file, e->token, d, proc_type, pl->body, pl->tags); } } else if (!is_foreign) { if (e->Procedure.is_export) { error(e->token, "Foreign export procedures must have a body"); } else { error(e->token, "Only a foreign procedure cannot have a body"); } } if (pt->result_count == 0 && ac.require_results) { error(pl->type, "'require_results' is not needed on a procedure with no results"); } else { pt->require_results = ac.require_results; } if (ac.link_name.len > 0) { e->Procedure.link_name = ac.link_name; } if (ac.deferred_procedure.entity != nullptr) { e->Procedure.deferred_procedure = ac.deferred_procedure; mpmc_enqueue(&ctx->checker->procs_with_deferred_to_check, e); } if (is_foreign) { String name = e->token.string; if (e->Procedure.link_name.len > 0) { name = e->Procedure.link_name; } Entity *foreign_library = init_entity_foreign_library(ctx, e); if (is_arch_wasm()) { String module_name = str_lit("env"); if (foreign_library != nullptr) { GB_ASSERT (foreign_library->kind == Entity_LibraryName); if (foreign_library->LibraryName.paths.count != 1) { error(foreign_library->token, "'foreign import' for '%.*s' architecture may only have one path, got %td", LIT(target_arch_names[build_context.metrics.arch]), foreign_library->LibraryName.paths.count); } if (foreign_library->LibraryName.paths.count >= 1) { module_name = foreign_library->LibraryName.paths[0]; } } name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name); } e->Procedure.is_foreign = true; e->Procedure.link_name = name; mutex_lock(&ctx->info->foreign_mutex); auto *fp = &ctx->info->foreigns; StringHashKey key = string_hash_string(name); Entity **found = string_map_get(fp, key); if (found) { Entity *f = *found; TokenPos pos = f->token.pos; Type *this_type = base_type(e->type); Type *other_type = base_type(f->type); if (is_type_proc(this_type) && is_type_proc(other_type)) { if (!are_signatures_similar_enough(this_type, other_type)) { error(d->proc_lit, "Redeclaration of foreign procedure '%.*s' with different type signatures\n" "\tat %s", LIT(name), token_pos_to_string(pos)); } } else if (!are_types_identical(this_type, other_type)) { error(d->proc_lit, "Foreign entity '%.*s' previously declared elsewhere with a different type\n" "\tat %s", LIT(name), token_pos_to_string(pos)); } } else if (name == "main") { error(d->proc_lit, "The link name 'main' is reserved for internal use"); } else { string_map_set(fp, key, e); } mutex_unlock(&ctx->info->foreign_mutex); } else { String name = e->token.string; if (e->Procedure.link_name.len > 0) { name = e->Procedure.link_name; } if (e->Procedure.link_name.len > 0 || is_export) { mutex_lock(&ctx->info->foreign_mutex); auto *fp = &ctx->info->foreigns; StringHashKey key = string_hash_string(name); Entity **found = string_map_get(fp, key); if (found) { Entity *f = *found; TokenPos pos = f->token.pos; // TODO(bill): Better error message? error(d->proc_lit, "Non unique linking name for procedure '%.*s'\n" "\tother at %s", LIT(name), token_pos_to_string(pos)); } else if (name == "main") { if (d->entity->pkg->kind != Package_Runtime) { error(d->proc_lit, "The link name 'main' is reserved for internal use"); } } else { string_map_set(fp, key, e); } mutex_unlock(&ctx->info->foreign_mutex); } } if (e->Procedure.link_name.len > 0 ) { e->flags |= EntityFlag_CustomLinkName; } } void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr, Ast *init_expr) { GB_ASSERT(e->type == nullptr); GB_ASSERT(e->kind == Entity_Variable); if (e->flags & EntityFlag_Visited) { e->type = t_invalid; return; } e->flags |= EntityFlag_Visited; AttributeContext ac = make_attribute_context(e->Variable.link_prefix); ac.init_expr_list_count = init_expr != nullptr ? 1 : 0; DeclInfo *decl = decl_info_of_entity(e); GB_ASSERT(decl == ctx->decl); if (decl != nullptr) { check_decl_attributes(ctx, decl->attributes, var_decl_attribute, &ac); } if (ac.require_declaration) { e->flags |= EntityFlag_Require; mpmc_enqueue(&ctx->info->required_global_variable_queue, e); } e->Variable.thread_local_model = ac.thread_local_model; e->Variable.is_export = ac.is_export; e->flags &= ~EntityFlag_Static; if (ac.is_static) { error(e->token, "@(static) is not supported for global variables, nor required"); } ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); String context_name = str_lit("variable declaration"); if (type_expr != nullptr) { e->type = check_type(ctx, type_expr); } if (e->type != nullptr) { if (is_type_polymorphic(base_type(e->type))) { gbString str = type_to_string(e->type); defer (gb_string_free(str)); error(e->token, "Invalid use of a polymorphic type '%s' in %.*s", str, LIT(context_name)); e->type = t_invalid; } else if (is_type_empty_union(e->type)) { gbString str = type_to_string(e->type); defer (gb_string_free(str)); error(e->token, "An empty union '%s' cannot be instantiated in %.*s", str, LIT(context_name)); e->type = t_invalid; } } if (e->Variable.is_foreign) { if (init_expr != nullptr) { error(e->token, "A foreign variable declaration cannot have a default value"); } init_entity_foreign_library(ctx, e); if (is_arch_wasm()) { error(e->token, "A foreign variable declaration are not allowed for the '%.*s' architecture", LIT(target_arch_names[build_context.metrics.arch])); } } if (ac.link_name.len > 0) { e->Variable.link_name = ac.link_name; } if (ac.link_section.len > 0) { e->Variable.link_section = ac.link_section; } if (e->Variable.is_foreign || e->Variable.is_export) { String name = e->token.string; if (e->Variable.link_name.len > 0) { name = e->Variable.link_name; } auto *fp = &ctx->info->foreigns; StringHashKey key = string_hash_string(name); Entity **found = string_map_get(fp, key); if (found) { Entity *f = *found; TokenPos pos = f->token.pos; Type *this_type = base_type(e->type); Type *other_type = base_type(f->type); if (!are_types_identical(this_type, other_type)) { error(e->token, "Foreign entity '%.*s' previously declared elsewhere with a different type\n" "\tat %s", LIT(name), token_pos_to_string(pos)); } } else { string_map_set(fp, key, e); } } if (e->Variable.link_name.len > 0) { e->flags |= EntityFlag_CustomLinkName; } if (init_expr == nullptr) { if (type_expr == nullptr) { e->type = t_invalid; } return; } Operand o = {}; check_expr_with_type_hint(ctx, &o, init_expr, e->type); check_init_variable(ctx, e, &o, str_lit("variable declaration")); } 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; ast_node(pg, ProcGroup, d->init_expr); pge->entities = array_make(permanent_allocator(), 0, pg->args.count); // NOTE(bill): This must be set here to prevent cycles in checking if someone // places the entity within itself pg_entity->type = t_invalid; PtrSet entity_set = {}; ptr_set_init(&entity_set, heap_allocator(), 2*pg->args.count); for_array(i, pg->args) { Ast *arg = pg->args[i]; Entity *e = nullptr; Operand o = {}; if (arg->kind == Ast_Ident) { e = check_ident(ctx, &o, arg, nullptr, nullptr, true); } else if (arg->kind == Ast_SelectorExpr) { e = check_selector(ctx, &o, arg, nullptr); } if (e == nullptr) { error(arg, "Expected a valid entity name in procedure group, got %.*s", LIT(ast_strings[arg->kind])); continue; } if (e->kind == Entity_Variable) { if (!is_type_proc(e->type)) { gbString s = type_to_string(e->type); defer (gb_string_free(s)); error(arg, "Expected a procedure, got %s", s); continue; } } else if (e->kind != Entity_Procedure) { error(arg, "Expected a procedure entity"); continue; } if (ptr_set_update(&entity_set, e)) { error(arg, "Previous use of `%.*s` in procedure group", LIT(e->token.string)); continue; } array_add(&pge->entities, e); } ptr_set_destroy(&entity_set); for_array(j, pge->entities) { Entity *p = pge->entities[j]; if (p->type == t_invalid) { // NOTE(bill): This invalid overload has already been handled continue; } String name = p->token.string; for (isize k = j+1; k < pge->entities.count; k++) { Entity *q = pge->entities[k]; GB_ASSERT(p != q); bool is_invalid = false; TokenPos pos = q->token.pos; if (q->type == nullptr || q->type == t_invalid) { continue; } begin_error_block(); defer (end_error_block()); ProcTypeOverloadKind kind = are_proc_types_overload_safe(p->type, q->type); bool both_have_where_clauses = false; if (p->decl_info->proc_lit != nullptr && q->decl_info->proc_lit != nullptr) { GB_ASSERT(p->decl_info->proc_lit->kind == Ast_ProcLit); GB_ASSERT(q->decl_info->proc_lit->kind == Ast_ProcLit); auto pl = &p->decl_info->proc_lit->ProcLit; auto ql = &q->decl_info->proc_lit->ProcLit; // Allow collisions if the procedures both have 'where' clauses and are both polymorphic bool pw = pl->where_token.kind != Token_Invalid && is_type_polymorphic(p->type, true); bool qw = ql->where_token.kind != Token_Invalid && is_type_polymorphic(q->type, true); both_have_where_clauses = pw && qw; } if (!both_have_where_clauses) switch (kind) { case ProcOverload_Identical: error(p->token, "Overloaded procedure '%.*s' as the same type as another procedure in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); is_invalid = true; break; // case ProcOverload_CallingConvention: // error(p->token, "Overloaded procedure '%.*s' as the same type as another procedure in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); // is_invalid = true; // break; case ProcOverload_ParamVariadic: error(p->token, "Overloaded procedure '%.*s' as the same type as another procedure in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); is_invalid = true; break; case ProcOverload_ResultCount: case ProcOverload_ResultTypes: error(p->token, "Overloaded procedure '%.*s' as the same parameters but different results in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); is_invalid = true; break; case ProcOverload_Polymorphic: #if 0 error(p->token, "Overloaded procedure '%.*s' has a polymorphic counterpart in the procedure group '%.*s' which is not allowed", LIT(name), LIT(proc_group_name)); is_invalid = true; #endif break; case ProcOverload_ParamCount: case ProcOverload_ParamTypes: // This is okay :) break; } if (is_invalid) { error_line("\tprevious procedure at %s\n", token_pos_to_string(pos)); q->type = t_invalid; } } } } void check_entity_decl(CheckerContext *ctx, Entity *e, DeclInfo *d, Type *named_type) { if (e->state == EntityState_Resolved) { return; } if (e->flags & EntityFlag_Lazy) { mutex_lock(&ctx->info->lazy_mutex); } String name = e->token.string; if (e->type != nullptr || e->state != EntityState_Unresolved) { error(e->token, "Illegal declaration cycle of `%.*s`", LIT(name)); } else { GB_ASSERT(e->state == EntityState_Unresolved); if (d == nullptr) { d = decl_info_of_entity(e); if (d == nullptr) { // TODO(bill): Err here? e->type = t_invalid; e->state = EntityState_Resolved; set_base_type(named_type, t_invalid); goto end; } } CheckerContext c = *ctx; c.scope = d->scope; c.decl = d; c.type_level = 0; e->parent_proc_decl = c.curr_proc_decl; e->state = EntityState_InProgress; switch (e->kind) { case Entity_Variable: check_global_variable_decl(&c, e, d->type_expr, d->init_expr); break; case Entity_Constant: check_const_decl(&c, e, d->type_expr, d->init_expr, named_type); break; case Entity_TypeName: { check_type_decl(&c, e, d->init_expr, named_type); break; } case Entity_Procedure: check_proc_decl(&c, e, d); break; case Entity_ProcGroup: check_proc_group_decl(&c, e, d); break; } e->state = EntityState_Resolved; } end:; // NOTE(bill): Add it to the list of checked entities if (e->flags & EntityFlag_Lazy) { array_add(&ctx->info->entities, e); mutex_unlock(&ctx->info->lazy_mutex); } } struct ProcUsingVar { Entity *e; Entity *uvar; }; void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *type, Ast *body) { if (body == nullptr) { return; } GB_ASSERT(body->kind == Ast_BlockStmt); String proc_name = {}; if (token.kind == Token_Ident) { proc_name = token.string; } else { // TODO(bill): Better name proc_name = str_lit("(anonymous-procedure)"); } CheckerContext new_ctx = *ctx_; CheckerContext *ctx = &new_ctx; GB_ASSERT(type->kind == Type_Proc); ctx->scope = decl->scope; ctx->decl = decl; ctx->proc_name = proc_name; ctx->curr_proc_decl = decl; ctx->curr_proc_sig = type; ctx->curr_proc_calling_convention = type->Proc.calling_convention; if (ctx->pkg->name != "runtime") { switch (type->Proc.calling_convention) { case ProcCC_None: error(body, "Procedures with the calling convention \"none\" are not allowed a body"); break; } } ast_node(bs, BlockStmt, body); Array using_entities = {}; using_entities.allocator = heap_allocator(); defer (array_free(&using_entities)); { if (type->Proc.param_count > 0) { TypeTuple *params = &type->Proc.params->Tuple; for_array(i, params->variables) { Entity *e = params->variables[i]; if (e->kind != Entity_Variable) { continue; } if (!(e->flags & EntityFlag_Using)) { continue; } bool is_value = (e->flags & EntityFlag_Value) != 0 && !is_type_pointer(e->type); String name = e->token.string; Type *t = base_type(type_deref(e->type)); if (t->kind == Type_Struct) { Scope *scope = t->Struct.scope; GB_ASSERT(scope != nullptr); MUTEX_GUARD_BLOCK(scope->mutex) for_array(i, scope->elements.entries) { Entity *f = scope->elements.entries[i].value; if (f->kind == Entity_Variable) { Entity *uvar = alloc_entity_using_variable(e, f->token, f->type, nullptr); if (is_value) uvar->flags |= EntityFlag_Value; ProcUsingVar puv = {e, uvar}; array_add(&using_entities, puv); } } } else { error(e->token, "'using' can only be applied to variables of type struct"); break; } } } } MUTEX_GUARD_BLOCK(ctx->scope->mutex) for_array(i, using_entities) { Entity *e = using_entities[i].e; Entity *uvar = using_entities[i].uvar; Entity *prev = scope_insert(ctx->scope, uvar, false); if (prev != nullptr) { error(e->token, "Namespace collision while 'using' procedure argument '%.*s' of: %.*s", LIT(e->token.string), LIT(prev->token.string)); error_line("%.*s != %.*s\n", LIT(uvar->token.string), LIT(prev->token.string)); break; } } bool where_clause_ok = evaluate_where_clauses(ctx, nullptr, decl->scope, &decl->proc_lit->ProcLit.where_clauses, !decl->where_clauses_evaluated); if (!where_clause_ok) { // NOTE(bill, 2019-08-31): Don't check the body as the where clauses failed return; } check_open_scope(ctx, body); { for_array(i, using_entities) { Entity *uvar = using_entities[i].uvar; Entity *prev = scope_insert(ctx->scope, uvar); gb_unused(prev); // NOTE(bill): Don't err here } check_stmt_list(ctx, bs->stmts, Stmt_CheckScopeDecls); for_array(i, bs->stmts) { Ast *stmt = bs->stmts[i]; if (stmt->kind == Ast_ValueDecl) { ast_node(vd, ValueDecl, stmt); for_array(j, vd->names) { Ast *name = vd->names[j]; if (!is_blank_ident(name)) { if (name->kind == Ast_Ident) { GB_ASSERT(name->Ident.entity != nullptr); } } } } } if (type->Proc.result_count > 0) { if (!check_is_terminating(body, str_lit(""))) { if (token.kind == Token_Ident) { error(bs->close, "Missing return statement at the end of the procedure '%.*s'", LIT(token.string)); } else { // NOTE(bill): Anonymous procedure (lambda) error(bs->close, "Missing return statement at the end of the procedure"); } } } else if (type->Proc.diverging) { if (!check_is_terminating(body, str_lit(""))) { if (token.kind == Token_Ident) { error(bs->close, "Missing diverging call at the end of the procedure '%.*s'", LIT(token.string)); } else { // NOTE(bill): Anonymous procedure (lambda) error(bs->close, "Missing diverging call at the end of the procedure"); } } } } check_close_scope(ctx); check_scope_usage(ctx->checker, ctx->scope); if (decl->parent != nullptr) { Scope *ps = decl->parent->scope; if (ps->flags & (ScopeFlag_File & ScopeFlag_Pkg & ScopeFlag_Global)) { return; } else { mutex_lock(&ctx->info->deps_mutex); // NOTE(bill): Add the dependencies from the procedure literal (lambda) // But only at the procedure level for_array(i, decl->deps.entries) { Entity *e = decl->deps.entries[i].ptr; ptr_set_add(&decl->parent->deps, e); } for_array(i, decl->type_info_deps.entries) { Type *t = decl->type_info_deps.entries[i].ptr; ptr_set_add(&decl->parent->type_info_deps, t); } mutex_unlock(&ctx->info->deps_mutex); } } }