Add -vet-when-shadowing; Resolve global whens and foreign blocks on demand via name placeholders

This commit is contained in:
gingerBill committed 2026-10-01 16:28:12 +01:00
1 parent 5f6e95a620
commit 2b90b616e1
8 files changed
+529 -261

No files matched your search

+4 -1
View File
@@ -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;
}
+36 -253
View File
@@ -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<Ast *> 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<Ast *> 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<Ast *> 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<Ast *> 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<Ast *> 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]) {
+1 -1
View File
@@ -564,6 +564,7 @@ struct Scope {
RwMutex mutex;
ScopeMap elements;
PtrSet<Scope *> imported;
PtrMap<u64, struct GlobalDeclSource *> *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;
+5 -5
View File
@@ -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.)",
};
+474
View File
@@ -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<GlobalDeclSource *> global_decl_sources;
gb_global Array<GlobalDeclSourceFrame> global_decl_source_stack;
gb_global Array<Scope *> 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<Ast *> 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<Ast *> 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<PtrMap<u64, GlobalDeclSource *>>();
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<GlobalDeclSource>();
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<Ast *> 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<Ast *> 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<Ast *> 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<Ast *> 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<u64, GlobalDeclSource *> *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<ImportGraphNode *> 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);
}
+8
View File
@@ -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<String> 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<String> 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) {
+1
View File
@@ -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;
}
}
-1
View File
@@ -101,7 +101,6 @@ enum AstFileFlag : u32 {
enum AstDelayQueueKind {
AstDelayQueue_Import,
AstDelayQueue_Expr,
AstDelayQueue_ForeignBlock,
AstDelayQueue_COUNT,
};