From 2b90b616e19068c23bb92ca7cf1ccfbd600a70be Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 1 Oct 2026 16:24:01 +0100 Subject: [PATCH] Add `-vet-when-shadowing`; Resolve global `when`s and `foreign` blocks on demand via name placeholders --- src/build_settings.cpp | 5 +- src/checker.cpp | 289 +++------------------ src/checker.hpp | 2 +- src/checker_global_graph.cpp | 10 +- src/checker_global_when.cpp | 474 +++++++++++++++++++++++++++++++++++ src/main.cpp | 8 + src/parser.cpp | 1 + src/parser.hpp | 1 - 8 files changed, 529 insertions(+), 261 deletions(-) create mode 100644 src/checker_global_when.cpp diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 041622a30..7f4da5140 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -317,10 +317,11 @@ enum VetFlags : u64 { VetFlag_Tabs = 1u<<9, VetFlag_UnusedProcedures = 1u<<10, VetFlag_ExplicitAllocators = 1u<<11, + VetFlag_WhenShadowing = 1u<<12, VetFlag_Unused = VetFlag_UnusedVariables|VetFlag_UnusedImports, - VetFlag_All = VetFlag_Unused|VetFlag_Shadowing|VetFlag_UsingStmt|VetFlag_Deprecated|VetFlag_Cast, + VetFlag_All = VetFlag_Unused|VetFlag_Shadowing|VetFlag_UsingStmt|VetFlag_Deprecated|VetFlag_Cast|VetFlag_WhenShadowing, VetFlag_Using = VetFlag_UsingStmt|VetFlag_UsingParam, }; @@ -352,6 +353,8 @@ u64 get_vet_flag_from_name(String const &name) { return VetFlag_UnusedProcedures; } else if (name == "explicit-allocators") { return VetFlag_ExplicitAllocators; + } else if (name == "when-shadowing") { + return VetFlag_WhenShadowing; } return VetFlag_NONE; } diff --git a/src/checker.cpp b/src/checker.cpp index e87c2ad59..27ef4b147 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -400,6 +400,8 @@ gb_internal void check_close_scope(CheckerContext *c) { } +gb_internal Entity *force_scope_placeholders(Scope *s, InternedString name, u32 hash); + gb_internal Entity *scope_lookup_current(Scope *s, InternedString name, u32 hash) { // Entity **found = string_map_get(&s->elements, name); if (hash == 0) { @@ -409,6 +411,9 @@ gb_internal Entity *scope_lookup_current(Scope *s, InternedString name, u32 hash if (found) { return found; } + if (s->placeholders != nullptr) { + return force_scope_placeholders(s, name, hash); + } return nullptr; } @@ -428,6 +433,9 @@ gb_internal void scope_lookup_parent(Scope *scope, InternedString name, Scope ** if (!is_single_threaded) rw_mutex_shared_lock(&s->mutex); found = scope_map_get(&s->elements, name, hash); if (!is_single_threaded) rw_mutex_shared_unlock(&s->mutex); + if (found == nullptr && s->placeholders != nullptr) { + found = force_scope_placeholders(s, name, hash); + } if (found) { Entity *e = found; if (gone_thru_proc) { @@ -5127,9 +5135,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { } } -gb_internal bool collect_file_decls(CheckerContext *ctx, Slice const &decls); - -gb_internal bool check_add_foreign_block_decl(CheckerContext *ctx, Ast *decl) { +gb_internal void check_add_foreign_block_decl(CheckerContext *ctx, Ast *decl) { ast_node(fb, ForeignBlockDecl, decl); Ast *foreign_library = fb->foreign_library; @@ -5144,11 +5150,7 @@ gb_internal bool check_add_foreign_block_decl(CheckerContext *ctx, Ast *decl) { check_decl_attributes(&c, fb->attributes, foreign_block_decl_attribute, nullptr); ast_node(block, BlockStmt, fb->body); - if (c.collect_delayed_decls && (c.scope->flags&ScopeFlag_File) != 0) { - return collect_file_decls(&c, block->stmts); - } check_collect_entities(&c, block->stmts); - return false; } gb_internal bool correct_single_type_alias(CheckerContext *c, Entity *e) { @@ -5156,7 +5158,11 @@ gb_internal bool correct_single_type_alias(CheckerContext *c, Entity *e) { DeclInfo *d = e->decl_info; if (d != nullptr && d->init_expr != nullptr) { Ast *init = d->init_expr; + // NOTE: in the scope of its own file, as a package's files may bind a name differently + Scope *prev_scope = c->scope; + c->scope = d->scope; Entity *alias_of = check_entity_from_ident_or_selector(c, init, true); + c->scope = prev_scope; if (alias_of != nullptr && alias_of->kind == Entity_TypeName) { e->kind = Entity_TypeName; return true; @@ -5189,7 +5195,7 @@ gb_internal bool correct_type_alias_in_scope_forwards(CheckerContext *c, Scope * } -gb_internal void correct_type_aliases_in_scope(CheckerContext *c, Scope *s) { +gb_internal void correct_type_aliases_in_package(CheckerContext *c, AstPackage *pkg) { // NOTE(bill, 2022-02-04): This is used to solve the problem caused by type aliases // of type aliases being "confused" as constants // @@ -5200,8 +5206,12 @@ gb_internal void correct_type_aliases_in_scope(CheckerContext *c, Scope *s) { // See @TypeAliasingProblem for more information for (;;) { bool corrections = false; - corrections |= correct_type_alias_in_scope_backwards(c, s); - corrections |= correct_type_alias_in_scope_forwards(c, s); + corrections |= correct_type_alias_in_scope_backwards(c, pkg->scope); + corrections |= correct_type_alias_in_scope_forwards(c, pkg->scope); + for (AstFile *f : pkg->files) { + corrections |= correct_type_alias_in_scope_backwards(c, f->scope); + corrections |= correct_type_alias_in_scope_forwards(c, f->scope); + } if (!corrections) { return; } @@ -5220,17 +5230,7 @@ gb_internal void check_collect_entities(CheckerContext *c, Slice const &n for_array(decl_index, nodes) { Ast *decl = nodes[decl_index]; if (!is_ast_decl(decl) && !is_ast_when_stmt(decl)) { - if (curr_file && decl->kind == Ast_ExprStmt) { - Ast *expr = decl->ExprStmt.expr; - if (expr->kind == Ast_CallExpr && expr->CallExpr.proc->kind == Ast_BasicDirective) { - if (c->collect_delayed_decls) { - if (decl->state_flags & StateFlag_BeenHandled) return; - decl->state_flags |= StateFlag_BeenHandled; - array_add(&curr_file->delayed_decls_queues[AstDelayQueue_Expr], expr); - } - continue; - } - } + // NOTE: global directives such as '#assert' are queued by `scan_global_decl_sources` continue; } @@ -5266,9 +5266,7 @@ gb_internal void check_collect_entities(CheckerContext *c, Slice const &n case_end; case_ast_node(fb, ForeignBlockDecl, decl); - if (curr_file != nullptr) { - array_add(&curr_file->delayed_decls_queues[AstDelayQueue_ForeignBlock], decl); - } + // NOTE: global ones are resolved like global 'when's, see `resolve_global_decl_sources` case_end; default: @@ -5785,7 +5783,6 @@ gb_internal void check_foreign_import_fullpaths(Checker *c) { AstFile *f = decl->file(); reset_checker_context(&ctx, f, &untyped); - ctx.collect_delayed_decls = false; GB_ASSERT(ctx.scope == e->scope); @@ -5935,165 +5932,6 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { } -// Returns true if a new package is present -gb_internal bool collect_file_decls(CheckerContext *ctx, Slice const &decls); -gb_internal bool collect_file_decls_from_when_stmt(CheckerContext *ctx, AstWhenStmt *ws); - -gb_internal bool collect_when_stmt_from_file(CheckerContext *ctx, AstWhenStmt *ws) { - Operand operand = {Addressing_Invalid}; - if (!ws->is_cond_determined) { - 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->determined_cond) { - check_collect_entities(ctx, ws->body->BlockStmt.stmts); - return true; - } else if (ws->else_stmt) { - switch (ws->else_stmt->kind) { - case Ast_BlockStmt: - check_collect_entities(ctx, ws->else_stmt->BlockStmt.stmts); - return true; - case Ast_WhenStmt: - collect_when_stmt_from_file(ctx, &ws->else_stmt->WhenStmt); - return true; - default: - error(ws->else_stmt, "Invalid 'else' statement in 'when' statement"); - break; - } - } - } - - return false; -} - -gb_internal bool collect_file_decls_from_when_stmt(CheckerContext *ctx, AstWhenStmt *ws) { - Operand operand = {Addressing_Invalid}; - if (!ws->is_cond_determined) { - 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->determined_cond) { - return collect_file_decls(ctx, ws->body->BlockStmt.stmts); - } else if (ws->else_stmt) { - switch (ws->else_stmt->kind) { - case Ast_BlockStmt: - return collect_file_decls(ctx, ws->else_stmt->BlockStmt.stmts); - case Ast_WhenStmt: - return collect_file_decls_from_when_stmt(ctx, &ws->else_stmt->WhenStmt); - default: - error(ws->else_stmt, "Invalid 'else' statement in 'when' statement"); - break; - } - } - } - - return false; -} - - -gb_internal bool collect_file_decl(CheckerContext *ctx, Ast *decl) { - GB_ASSERT(ctx->scope->flags&ScopeFlag_File); - - AstFile *curr_file = ctx->scope->file; - GB_ASSERT(curr_file != nullptr); - - if (decl->state_flags & StateFlag_BeenHandled) { - return false; - } - - switch (decl->kind) { - case_ast_node(vd, ValueDecl, decl); - check_collect_value_decl(ctx, decl); - case_end; - - case_ast_node(id, ImportDecl, decl); - check_add_import_decl(ctx, decl); - case_end; - - case_ast_node(fl, ForeignImportDecl, decl); - check_add_foreign_import_decl(ctx, decl); - case_end; - - case_ast_node(fb, ForeignBlockDecl, decl); - GB_ASSERT(ctx->collect_delayed_decls); - decl->state_flags |= StateFlag_BeenHandled; - array_add(&curr_file->delayed_decls_queues[AstDelayQueue_ForeignBlock], decl); - case_end; - - case_ast_node(ws, WhenStmt, decl); - if (!ws->is_cond_determined) { - if (collect_when_stmt_from_file(ctx, ws)) { - return true; - } - - CheckerContext nctx = *ctx; - nctx.collect_delayed_decls = true; - - if (collect_file_decls_from_when_stmt(&nctx, ws)) { - return true; - } - } else { - CheckerContext nctx = *ctx; - nctx.collect_delayed_decls = true; - - if (collect_file_decls_from_when_stmt(&nctx, ws)) { - return true; - } - } - case_end; - - case_ast_node(es, ExprStmt, decl); - GB_ASSERT(ctx->collect_delayed_decls); - decl->state_flags |= StateFlag_BeenHandled; - if (es->expr->kind == Ast_CallExpr) { - ast_node(ce, CallExpr, es->expr); - if (ce->proc->kind == Ast_BasicDirective) { - array_add(&curr_file->delayed_decls_queues[AstDelayQueue_Expr], es->expr); - } - } - case_end; - } - - return false; -} - -gb_internal bool collect_file_decls(CheckerContext *ctx, Slice const &decls) { - GB_ASSERT(ctx->scope->flags&ScopeFlag_File); - - for_array(i, decls) { - if (collect_file_decl(ctx, decls[i])) { - correct_type_aliases_in_scope(ctx, ctx->scope); - return true; - } - } - correct_type_aliases_in_scope(ctx, ctx->scope); - return false; -} - gb_internal GB_COMPARE_PROC(sort_file_by_name) { AstFile const *x = *cast(AstFile const **)a; AstFile const *y = *cast(AstFile const **)b; @@ -6140,7 +5978,6 @@ gb_internal WORKER_TASK_PROC(check_collect_entities_all_worker_proc) { reset_checker_context(ctx, f, untyped); check_collect_entities(ctx, f->decls); - GB_ASSERT(ctx->collect_delayed_decls == false); add_untyped_expressions(&c->info, ctx->untyped); @@ -6205,6 +6042,8 @@ gb_internal void check_export_entities(Checker *c) { thread_pool_wait(); } +#include "checker_global_when.cpp" + gb_internal void check_import_entities(Checker *c) { TEMPORARY_ALLOCATOR_GUARD(); @@ -6261,101 +6100,45 @@ gb_internal void check_import_entities(Checker *c) { array_add(&package_order, n); } - TIME_SECTION("check_import_entities - collect file decls"); CheckerContext ctx = {}; init_checker_context(&ctx, c); + defer (destroy_checker_context(&ctx)); UntypedExprInfoMap untyped = {}; defer (map_destroy(&untyped)); - isize min_pkg_index = 0; + TIME_SECTION("check_import_entities - imports"); + // NOTE(bill): every import first, as resolving a 'when' may check declarations in any package u64 stage_start = global_import_stage_begin(); for (isize pkg_index = 0; pkg_index < package_order.count; pkg_index++) { - ImportGraphNode *node = package_order[pkg_index]; - AstPackage *pkg = node->pkg; + AstPackage *pkg = package_order[pkg_index]->pkg; pkg->order = 1+pkg_index; - for_array(i, pkg->files) { - AstFile *f = pkg->files[i]; - + for (AstFile *f : pkg->files) { reset_checker_context(&ctx, f, &untyped); - ctx.collect_delayed_decls = true; - - // Check import declarations first to simplify things for (Ast *decl : f->delayed_decls_queues[AstDelayQueue_Import]) { check_add_import_decl(&ctx, decl); } array_clear(&f->delayed_decls_queues[AstDelayQueue_Import]); - - if (collect_file_decls(&ctx, f->decls)) { - check_export_entities_in_pkg(&ctx, pkg, &untyped); - pkg_index = min_pkg_index-1; - break; - } - add_untyped_expressions(ctx.info, &untyped); } - if (pkg_index < 0) { - continue; - } - min_pkg_index = pkg_index; } - global_import_stage_end(GlobalImportStage_CollectFileDecls, stage_start); + global_import_stage_end(GlobalImportStage_Imports, stage_start); + + TIME_SECTION("check_import_entities - resolve 'when' and 'foreign' blocks"); + resolve_global_decl_sources(c, package_order); TIME_SECTION("check_import_entities - check delayed entities"); - for (isize pkg_index = 0; pkg_index < package_order.count; pkg_index++) { - ImportGraphNode *node = package_order[pkg_index]; + for (ImportGraphNode *node : package_order) { GB_ASSERT(node->scope->flags&ScopeFlag_Pkg); AstPackage *pkg = node->scope->pkg; stage_start = global_import_stage_begin(); - for_array(i, pkg->files) { - AstFile *f = pkg->files[i]; - reset_checker_context(&ctx, f, &untyped); - - for (Ast *decl : f->delayed_decls_queues[AstDelayQueue_Import]) { - check_add_import_decl(&ctx, decl); - } - array_clear(&f->delayed_decls_queues[AstDelayQueue_Import]); - add_untyped_expressions(ctx.info, &untyped); - } - global_import_stage_end(GlobalImportStage_Imports, stage_start); - - stage_start = global_import_stage_begin(); - for_array(i, pkg->files) { - AstFile *f = pkg->files[i]; - reset_checker_context(&ctx, f, &untyped); - correct_type_aliases_in_scope(&ctx, pkg->scope); - } + correct_type_aliases_in_package(&ctx, pkg); global_import_stage_end(GlobalImportStage_TypeAliases, stage_start); stage_start = global_import_stage_begin(); - for_array(i, pkg->files) { - AstFile *f = pkg->files[i]; - reset_checker_context(&ctx, f, &untyped); - - ctx.collect_delayed_decls = true; - - bool will_recheck_foreign_block = false; - for (Ast *decl : f->delayed_decls_queues[AstDelayQueue_ForeignBlock]) { - if (check_add_foreign_block_decl(&ctx, decl)) { - pkg_index -= 1; // Re-check package - will_recheck_foreign_block = true; - break; - } - } - - if (will_recheck_foreign_block) { - break; - } - - array_clear(&f->delayed_decls_queues[AstDelayQueue_ForeignBlock]); - } - global_import_stage_end(GlobalImportStage_ForeignBlocks, stage_start); - - stage_start = global_import_stage_begin(); - for_array(i, pkg->files) { - AstFile *f = pkg->files[i]; + for (AstFile *f : pkg->files) { reset_checker_context(&ctx, f, &untyped); for (Ast *expr : f->delayed_decls_queues[AstDelayQueue_Expr]) { diff --git a/src/checker.hpp b/src/checker.hpp index e51b48da3..9643fa683 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -564,6 +564,7 @@ struct Scope { RwMutex mutex; ScopeMap elements; PtrSet imported; + PtrMap *placeholders; // multi-map; names a global 'when' or 'foreign' block may declare, until all are resolved DeclInfo *decl_info; @@ -838,7 +839,6 @@ struct CheckerContext { u32 stmt_flags; bool in_enum_type; bool in_proc_group; - bool collect_delayed_decls; bool allow_polymorphic_types; bool disallow_polymorphic_return_types; // NOTE(zen3ger): no poly type decl in return types bool no_polymorphic_errors; diff --git a/src/checker_global_graph.cpp b/src/checker_global_graph.cpp index d639297ac..67a2c7c07 100644 --- a/src/checker_global_graph.cpp +++ b/src/checker_global_graph.cpp @@ -13,20 +13,20 @@ struct GlobalEntityTimingFrame { }; enum GlobalImportStagePart { - GlobalImportStage_CollectFileDecls, GlobalImportStage_Imports, + GlobalImportStage_Placeholders, + GlobalImportStage_DeclSources, GlobalImportStage_TypeAliases, - GlobalImportStage_ForeignBlocks, GlobalImportStage_DelayedExprs, GlobalImportStage_COUNT, }; gb_global char const *global_import_stage_names[GlobalImportStage_COUNT] = { - "collect file decls ('when')", - "delayed imports", + "imports", + "'when' and 'foreign' placeholders", + "resolve 'when' and 'foreign' blocks", "type alias correction", - "foreign blocks", "delayed expressions (#assert etc.)", }; diff --git a/src/checker_global_when.cpp b/src/checker_global_when.cpp new file mode 100644 index 000000000..2b4187b68 --- /dev/null +++ b/src/checker_global_when.cpp @@ -0,0 +1,474 @@ +// 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); +} diff --git a/src/main.cpp b/src/main.cpp index 6f0308cfb..5a3f4e3da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -460,6 +460,7 @@ enum BuildFlagKind { BuildFlag_VetSemicolon, BuildFlag_VetCast, BuildFlag_VetTabs, + BuildFlag_VetWhenShadowing, BuildFlag_VetPackages, BuildFlag_CustomAttribute, @@ -725,6 +726,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_VetSemicolon, str_lit("vet-semicolon"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetCast, str_lit("vet-cast"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetTabs, str_lit("vet-tabs"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_VetWhenShadowing, str_lit("vet-when-shadowing"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetPackages, str_lit("vet-packages"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_CustomAttribute, str_lit("custom-attribute"), BuildFlagParam_String, Command__does_check, true); @@ -1493,6 +1495,7 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_VetSemicolon: build_context.vet_flags |= VetFlag_Semicolon; break; case BuildFlag_VetCast: build_context.vet_flags |= VetFlag_Cast; break; case BuildFlag_VetTabs: build_context.vet_flags |= VetFlag_Tabs; break; + case BuildFlag_VetWhenShadowing: build_context.vet_flags |= VetFlag_WhenShadowing; break; case BuildFlag_VetUnusedProcedures: build_context.vet_flags |= VetFlag_UnusedProcedures; break; case BuildFlag_VetPackages: @@ -3408,6 +3411,7 @@ gb_internal int print_show_help(String const arg0, String command, String option print_usage_line(3, "-vet-unused-imports"); print_usage_line(3, "-vet-shadowing"); print_usage_line(3, "-vet-using-stmt"); + print_usage_line(3, "-vet-when-shadowing"); } if (print_flag("-vet-cast")) { @@ -3467,6 +3471,10 @@ gb_internal int print_show_help(String const arg0, String command, String option print_usage_line(2, "Checks for the use of 'using' as a statement."); print_usage_line(2, "'using' is considered bad practice outside of immediate refactoring."); } + + if (print_flag("-vet-when-shadowing")) { + print_usage_line(2, "Checks for declarations within a global 'when' that shadow a builtin or package-level name."); + } } if (check) { diff --git a/src/parser.cpp b/src/parser.cpp index 8f4cc99ec..3f6578ab4 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -7243,6 +7243,7 @@ gb_internal u64 parse_vet_tag(Token token_for_pos, String s, u64 base_vet_flags) error_line("\tcast\n"); error_line("\ttabs\n"); error_line("\texplicit-allocators\n"); + error_line("\twhen-shadowing\n"); return vet_flags; } } diff --git a/src/parser.hpp b/src/parser.hpp index 08811c37a..f9d8da6af 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -101,7 +101,6 @@ enum AstFileFlag : u32 { enum AstDelayQueueKind { AstDelayQueue_Import, AstDelayQueue_Expr, - AstDelayQueue_ForeignBlock, AstDelayQueue_COUNT, };