// Global 'when's and 'foreign' blocks: every name one may declare is a placeholder in its scope, and the // first lookup of a placeholder resolves them, so the order of files and declarations does not matter struct GlobalDeclSource { Ast * node; // WhenStmt or ForeignBlockDecl AstFile * file; GlobalDeclSource *parent; bool in_else; // within the else branch of `parent` bool reachable; bool reported_cycle; EntityState state; ForeignContext foreign_context; // of a resolved 'foreign' block }; struct GlobalDeclSourceFrame { GlobalDeclSource *source; InternedString needs; // the placeholder being resolved for it }; gb_global Array global_decl_sources; gb_global Array global_decl_source_stack; gb_global Array global_placeholder_scopes; gb_global CheckerContext global_decl_source_export_ctx; gb_global UntypedExprInfoMap global_decl_source_export_untyped; enum : u8 { PlaceholderScope_File = 1<<0, PlaceholderScope_Pkg = 1<<1, }; // -1 when 'private' has a value that is not a string literal gb_internal i32 syntactic_visibility(Array const &attributes) { for (Ast *attr : attributes) { if (attr->kind != Ast_Attribute) { continue; } for (Ast *elem : attr->Attribute.elems) { if (elem->kind == Ast_Ident && elem->Ident.token.string == "private") { return EntityVisiblity_PrivateToPackage; } if (elem->kind == Ast_FieldValue && elem->FieldValue.field->kind == Ast_Ident && elem->FieldValue.field->Ident.token.string == "private") { Ast *value = elem->FieldValue.value; if (value != nullptr && value->tav.value.kind == ExactValue_String) { return value->tav.value.value_string == "file" ? EntityVisiblity_PrivateToFile : EntityVisiblity_PrivateToPackage; } return -1; } } } return EntityVisiblity_Public; } gb_internal bool has_syntactic_attribute(Array const &attributes, String const &name) { for (Ast *attr : attributes) { if (attr->kind != Ast_Attribute) { continue; } for (Ast *elem : attr->Attribute.elems) { Ast *field = elem->kind == Ast_FieldValue ? elem->FieldValue.field : elem; if (field->kind == Ast_Ident && field->Ident.token.string == name) { return true; } } } return false; } gb_internal void add_placeholder(Scope *s, InternedString name, GlobalDeclSource *src) { if (name.value == 0 || name.is_blank()) { return; } if (s->placeholders == nullptr) { s->placeholders = permanent_alloc_item>(); map_init(s->placeholders); array_add(&global_placeholder_scopes, s); } u64 key = name.value; for (auto *e = multi_map_find_first(s->placeholders, key); e != nullptr; e = multi_map_find_next(s->placeholders, e)) { if (e->value == src) { return; } } multi_map_insert(s->placeholders, key, src); } gb_internal void add_placeholders(AstFile *f, u8 scopes, InternedString name, GlobalDeclSource *src) { if (scopes & PlaceholderScope_File) { add_placeholder(f->scope, name, src); } if (scopes & PlaceholderScope_Pkg) { add_placeholder(f->pkg->scope, name, src); } } gb_internal GlobalDeclSource *add_global_decl_source(Ast *node, AstFile *f, GlobalDeclSource *parent, bool in_else) { GlobalDeclSource *src = permanent_alloc_item(); src->node = node; src->file = f; src->parent = parent; src->in_else = in_else; src->reachable = true; src->state = EntityState_Unresolved; array_add(&global_decl_sources, src); return src; } gb_internal void scan_global_decl_sources(AstFile *f, Slice const &stmts, GlobalDeclSource *owner, bool in_else, i32 foreign_visibility); gb_internal void scan_global_when_stmt(AstFile *f, Ast *node, GlobalDeclSource *parent, bool in_else, i32 foreign_visibility) { ast_node(ws, WhenStmt, node); GlobalDeclSource *src = add_global_decl_source(node, f, parent, in_else); if (ws->body != nullptr && ws->body->kind == Ast_BlockStmt) { scan_global_decl_sources(f, ws->body->BlockStmt.stmts, src, false, foreign_visibility); } if (ws->else_stmt != nullptr) { switch (ws->else_stmt->kind) { case Ast_BlockStmt: scan_global_decl_sources(f, ws->else_stmt->BlockStmt.stmts, src, true, foreign_visibility); break; case Ast_WhenStmt: scan_global_when_stmt(f, ws->else_stmt, src, true, foreign_visibility); break; } } } gb_internal void scan_global_decl_sources(AstFile *f, Slice const &stmts, GlobalDeclSource *owner, bool in_else, i32 foreign_visibility) { // NOTE(bill): `owner == nullptr` is the file scope itself, whose other declarations are already collected for (Ast *decl : stmts) { switch (decl->kind) { case_ast_node(vd, ValueDecl, decl); if (owner == nullptr) { break; } i32 visibility = syntactic_visibility(vd->attributes); if (visibility == EntityVisiblity_Public) { visibility = foreign_visibility; } if (visibility == EntityVisiblity_Public && (f->flags & AstFile_IsPrivateFile)) { visibility = EntityVisiblity_PrivateToFile; } u8 scopes = PlaceholderScope_Pkg; if (visibility == EntityVisiblity_PrivateToFile) { scopes = PlaceholderScope_File; } else if (visibility < 0) { scopes = PlaceholderScope_File|PlaceholderScope_Pkg; } for (Ast *name : vd->names) { if (name->kind == Ast_Ident) { add_placeholders(f, scopes, name->Ident.interned, owner); } } case_end; case_ast_node(fl, ForeignImportDecl, decl); if (owner == nullptr) { break; } String library_name = fl->library_name.string; if (library_name.len == 0 && fl->fullpaths.count != 0) { library_name = path_to_entity_name(fl->library_name.string, fl->fullpaths[0]); } if (library_name.len != 0) { u8 scopes = has_syntactic_attribute(fl->attributes, str_lit("export")) ? PlaceholderScope_Pkg : PlaceholderScope_File; add_placeholders(f, scopes, string_interner_insert(library_name), owner); } case_end; case_ast_node(fb, ForeignBlockDecl, decl); GlobalDeclSource *src = add_global_decl_source(decl, f, owner, in_else); if (fb->body != nullptr && fb->body->kind == Ast_BlockStmt) { scan_global_decl_sources(f, fb->body->BlockStmt.stmts, src, false, syntactic_visibility(fb->attributes)); } case_end; case_ast_node(ws, WhenStmt, decl); scan_global_when_stmt(f, decl, owner, in_else, foreign_visibility); case_end; case_ast_node(es, ExprStmt, decl); if (owner == nullptr && es->expr->kind == Ast_CallExpr && es->expr->CallExpr.proc->kind == Ast_BasicDirective && (decl->state_flags & StateFlag_BeenHandled) == 0) { decl->state_flags |= StateFlag_BeenHandled; array_add(&f->delayed_decls_queues[AstDelayQueue_Expr], es->expr); } case_end; } } } gb_internal bool is_global_decl_source_in_when(GlobalDeclSource *src) { for (; src != nullptr; src = src->parent) { if (src->node->kind == Ast_WhenStmt) { return true; } } return false; } gb_internal Slice global_decl_source_taken_stmts(GlobalDeclSource *src) { if (src->node->kind == Ast_ForeignBlockDecl) { Ast *body = src->node->ForeignBlockDecl.body; if (body != nullptr && body->kind == Ast_BlockStmt) { return body->BlockStmt.stmts; } return {}; } ast_node(ws, WhenStmt, src->node); if (ws->determined_cond) { if (ws->body != nullptr && ws->body->kind == Ast_BlockStmt) { return ws->body->BlockStmt.stmts; } } else if (ws->else_stmt != nullptr && ws->else_stmt->kind == Ast_BlockStmt) { return ws->else_stmt->BlockStmt.stmts; } return {}; } gb_internal void collect_global_decl_source_stmts(CheckerContext *ctx, Slice const &stmts) { AstFile *f = ctx->file; for (Ast *decl : stmts) { if (decl->kind == Ast_ValueDecl) { check_collect_value_decl(ctx, decl); } } check_export_entities_in_pkg(&global_decl_source_export_ctx, f->pkg, &global_decl_source_export_untyped); // NOTE(bill): after the value declarations, as their attributes are evaluated for (Ast *decl : stmts) { switch (decl->kind) { case_ast_node(fl, ForeignImportDecl, decl); check_add_foreign_import_decl(ctx, decl); case_end; case_ast_node(es, ExprStmt, decl); if (es->expr->kind == Ast_CallExpr && es->expr->CallExpr.proc->kind == Ast_BasicDirective && (decl->state_flags & StateFlag_BeenHandled) == 0) { decl->state_flags |= StateFlag_BeenHandled; array_add(&f->delayed_decls_queues[AstDelayQueue_Expr], es->expr); } case_end; } } } gb_internal Token global_decl_source_token(GlobalDeclSource *src) { if (src->node->kind == Ast_WhenStmt) { return src->node->WhenStmt.token; } return src->node->ForeignBlockDecl.token; } gb_internal void report_global_decl_source_cycle(GlobalDeclSource *src, InternedString needed) { if (src->reported_cycle) { return; } src->reported_cycle = true; isize start = 0; for (isize i = global_decl_source_stack.count-1; i >= 0; i--) { if (global_decl_source_stack[i].source == src) { start = i; break; } } ERROR_BLOCK(); Token token = global_decl_source_token(src); error(token, "Cyclic dependency between global '%.*s' declarations", LIT(token.string)); for (isize i = start; i < global_decl_source_stack.count; i++) { Token t = global_decl_source_token(global_decl_source_stack[i].source); InternedString name = i+1 < global_decl_source_stack.count ? global_decl_source_stack[i].needs : needed; error_line("\t'%.*s' at %s needs '%s', which may be declared by\n", LIT(t.string), token_pos_to_string(t.pos), name.cstring()); } error_line("\t'%.*s' at %s\n", LIT(token.string), token_pos_to_string(token.pos)); } gb_internal void resolve_global_decl_source(GlobalDeclSource *src, InternedString needed) { if (src->state == EntityState_Resolved) { return; } if (src->state == EntityState_InProgress) { report_global_decl_source_cycle(src, needed); return; } GlobalDeclSource *foreign_block = nullptr; if (src->parent != nullptr) { GlobalDeclSource *parent = src->parent; resolve_global_decl_source(parent, needed); if (parent->state != EntityState_Resolved) { return; } bool reachable = parent->reachable; if (parent->node->kind == Ast_WhenStmt) { reachable = reachable && parent->node->WhenStmt.determined_cond != src->in_else; } if (!reachable) { src->reachable = false; src->state = EntityState_Resolved; return; } for (GlobalDeclSource *p = parent; p != nullptr; p = p->parent) { if (p->node->kind == Ast_ForeignBlockDecl) { foreign_block = p; break; } } } src->state = EntityState_InProgress; array_add(&global_decl_source_stack, GlobalDeclSourceFrame{src, {}}); CheckerContext ctx = {}; init_checker_context(&ctx, global_checker_ptr.load(std::memory_order_relaxed)); UntypedExprInfoMap untyped = {}; reset_checker_context(&ctx, src->file, &untyped); if (foreign_block != nullptr) { ctx.foreign_context = foreign_block->foreign_context; } if (src->node->kind == Ast_WhenStmt) { ast_node(ws, WhenStmt, src->node); Operand operand = {Addressing_Invalid}; check_expr(&ctx, &operand, ws->cond); if (operand.mode != Addressing_Invalid && !is_type_boolean(operand.type)) { error(ws->cond, "Non-boolean condition in 'when' statement"); } if (operand.mode != Addressing_Constant) { error(ws->cond, "Non-constant condition in 'when' statement"); } ws->is_cond_determined = true; ws->determined_cond = operand.value.kind == ExactValue_Bool && operand.value.value_bool; if (ws->body == nullptr || ws->body->kind != Ast_BlockStmt) { error(ws->cond, "Invalid body for 'when' statement"); } else if (ws->else_stmt != nullptr && ws->else_stmt->kind != Ast_BlockStmt && ws->else_stmt->kind != Ast_WhenStmt) { error(ws->else_stmt, "Invalid 'else' statement in 'when' statement"); } } else { ast_node(fb, ForeignBlockDecl, src->node); if (fb->foreign_library->kind == Ast_Ident) { ctx.foreign_context.curr_library = fb->foreign_library; } else { error(fb->foreign_library, "Foreign block name must be an identifier or 'export'"); ctx.foreign_context.curr_library = nullptr; } check_decl_attributes(&ctx, fb->attributes, foreign_block_decl_attribute, nullptr); src->foreign_context = ctx.foreign_context; } // NOTE: resolved before its declarations are collected, which evaluates the attributes of 'foreign import's src->state = EntityState_Resolved; array_pop(&global_decl_source_stack); collect_global_decl_source_stmts(&ctx, global_decl_source_taken_stmts(src)); add_untyped_expressions(ctx.info, &untyped); map_destroy(&untyped); destroy_checker_context(&ctx); } gb_internal Entity *force_scope_placeholders(Scope *s, InternedString name, u32 hash) { PtrMap *m = s->placeholders; bool forced = false; for (auto *e = multi_map_find_first(m, cast(u64)name.value); e != nullptr; e = multi_map_find_next(m, e)) { GlobalDeclSource *src = e->value; if (src->state != EntityState_Resolved) { if (global_decl_source_stack.count > 0) { global_decl_source_stack[global_decl_source_stack.count-1].needs = name; } resolve_global_decl_source(src, name); forced = true; } } if (!forced) { return nullptr; } rw_mutex_shared_lock(&s->mutex); Entity *found = scope_map_get(&s->elements, name, hash); rw_mutex_shared_unlock(&s->mutex); return found; } gb_internal void check_vet_when_shadowing_entity(Entity *e) { if (e == nullptr || e->scope == nullptr || (e->scope->flags & ScopeFlag_File) == 0) { return; } InternedString name = entity_interned_name(e); u32 hash = e->interned_name_hash.load(std::memory_order_relaxed); Scope *outer = e->scope->parent; if (scope_map_get(&e->scope->elements, name, hash) != e) { outer = outer->parent; // in the package scope } if (outer == nullptr) { return; } Entity *shadowed = scope_lookup(outer, name, hash); if (shadowed == nullptr || shadowed == e) { return; } if (shadowed->scope == builtin_pkg->scope) { error(e->token, "Declaration of '%.*s' within a global 'when' shadows the builtin '%.*s'", LIT(e->token.string), LIT(e->token.string)); } else { error(e->token, "Declaration of '%.*s' within a global 'when' shadows the declaration at %s", LIT(e->token.string), token_pos_to_string(shadowed->token.pos)); } } gb_internal void check_vet_when_shadowing(void) { for (GlobalDeclSource *src : global_decl_sources) { if (!src->reachable || src->state != EntityState_Resolved) { continue; } if ((ast_file_vet_flags(src->file) & VetFlag_WhenShadowing) == 0 || !is_global_decl_source_in_when(src)) { continue; } for (Ast *decl : global_decl_source_taken_stmts(src)) { if (decl->kind == Ast_ValueDecl) { for (Ast *name : decl->ValueDecl.names) { if (name->kind == Ast_Ident) { check_vet_when_shadowing_entity(name->Ident.entity.load()); } } } else if (decl->kind == Ast_ForeignImportDecl) { Token token = decl->ForeignImportDecl.library_name; InternedString name = string_interner_insert(token.string); for (Scope *s = src->file->scope; s != nullptr && s != builtin_pkg->scope; s = s->parent) { Entity *e = scope_map_get(&s->elements, name, name.hash()); if (e != nullptr && e->kind == Entity_LibraryName && e->LibraryName.decl == decl) { check_vet_when_shadowing_entity(e); break; } } } } } } // Placeholders for every file, then every source resolved in package, file and source order, which // only matters for which errors are reported gb_internal void resolve_global_decl_sources(Checker *c, Array const &package_order) { array_init(&global_decl_sources, heap_allocator()); array_init(&global_decl_source_stack, heap_allocator()); array_init(&global_placeholder_scopes, heap_allocator()); init_checker_context(&global_decl_source_export_ctx, c); defer (destroy_checker_context(&global_decl_source_export_ctx)); u64 stage_start = global_import_stage_begin(); for (ImportGraphNode *node : package_order) { for (AstFile *f : node->pkg->files) { scan_global_decl_sources(f, f->decls, nullptr, false, EntityVisiblity_Public); } } global_import_stage_end(GlobalImportStage_Placeholders, stage_start); stage_start = global_import_stage_begin(); for (GlobalDeclSource *src : global_decl_sources) { resolve_global_decl_source(src, {}); } GB_ASSERT(global_decl_source_stack.count == 0); for (Scope *s : global_placeholder_scopes) { map_destroy(s->placeholders); s->placeholders = nullptr; } array_clear(&global_placeholder_scopes); check_vet_when_shadowing(); global_import_stage_end(GlobalImportStage_DeclSources, stage_start); map_destroy(&global_decl_source_export_untyped); }