Skip to content

Commit 24c0d19

Browse files
committed
Basic name poisoning support
carbon-language#4622 When using an unqualified name disallow using that name in all scopes that would make it ambiguous in retrospect. Doesn't include support for poisoning when importing (see new test for that with TODO). Implemented by introduce `InstId::PoisonedName` and entries with it to `NameScope`.
1 parent bd0f620 commit 24c0d19

17 files changed

+590
-74
lines changed

toolchain/check/context.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ auto Context::DiagnoseDuplicateName(SemIRLoc dup_def, SemIRLoc prev_def)
216216
.Emit();
217217
}
218218

219+
auto Context::DiagnosePoisonedName(SemIRLoc dup_def) -> void {
220+
CARBON_DIAGNOSTIC(
221+
NameDeclPoisoned, Error,
222+
"cannot declare this name in this scope since is was already used "
223+
"without qualification in the context of this scope");
224+
emitter_->Build(dup_def, NameDeclPoisoned).Emit();
225+
}
226+
219227
auto Context::DiagnoseNameNotFound(SemIRLoc loc, SemIR::NameId name_id)
220228
-> void {
221229
CARBON_DIAGNOSTIC(NameNotFound, Error, "name `{0}` not found", SemIR::NameId);
@@ -326,16 +334,26 @@ auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
326334
scope_stack().LookupInLexicalScopes(name_id);
327335

328336
// Walk the non-lexical scopes and perform lookups into each of them.
337+
// Collect scopes to poison this name when it's found.
338+
std::vector<LookupScope> scopes_to_poison;
329339
for (auto [index, lookup_scope_id, specific_id] :
330340
llvm::reverse(non_lexical_scopes)) {
331341
if (auto non_lexical_result =
332342
LookupQualifiedName(node_id, name_id,
333343
LookupScope{.name_scope_id = lookup_scope_id,
334344
.specific_id = specific_id},
335345
/*required=*/false);
336-
non_lexical_result.inst_id.is_valid()) {
346+
non_lexical_result.inst_id.is_valid() &&
347+
!non_lexical_result.inst_id.is_poisoned()) {
348+
// Poison the scopes for this name.
349+
for (const auto [scope_id, specific_id] : scopes_to_poison) {
350+
name_scopes().Get(scope_id).AddPoison(name_id);
351+
}
352+
337353
return non_lexical_result;
338354
}
355+
scopes_to_poison.push_back(
356+
{.name_scope_id = lookup_scope_id, .specific_id = specific_id});
339357
}
340358

341359
if (lexical_result.is_valid()) {
@@ -587,7 +605,8 @@ auto Context::LookupQualifiedName(SemIRLoc loc, SemIR::NameId name_id,
587605
result.specific_id = specific_id;
588606
}
589607

590-
if (required && !result.inst_id.is_valid()) {
608+
if (required &&
609+
(!result.inst_id.is_valid() || result.inst_id.is_poisoned())) {
591610
if (!has_error) {
592611
if (prohibited_accesses.empty()) {
593612
DiagnoseNameNotFound(loc, name_id);

toolchain/check/context.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,9 @@ class Context {
245245
// Prints a diagnostic for a duplicate name.
246246
auto DiagnoseDuplicateName(SemIRLoc dup_def, SemIRLoc prev_def) -> void;
247247

248+
// Prints a diagnostic for a poisoned name.
249+
auto DiagnosePoisonedName(SemIRLoc dup_def) -> void;
250+
248251
// Prints a diagnostic for a missing name.
249252
auto DiagnoseNameNotFound(SemIRLoc loc, SemIR::NameId name_id) -> void;
250253

toolchain/check/decl_name_stack.cpp

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ auto DeclNameStack::NameContext::prev_inst_id() -> SemIR::InstId {
3333
case NameContext::State::Unresolved:
3434
return SemIR::InstId::Invalid;
3535

36+
case NameContext::State::Poisoned:
37+
return SemIR::InstId::PoisonedName;
38+
3639
case NameContext::State::Finished:
3740
CARBON_FATAL("Finished state should only be used internally");
3841
}
@@ -166,12 +169,15 @@ auto DeclNameStack::AddName(NameContext name_context, SemIR::InstId target_id,
166169
}
167170
}
168171

169-
auto DeclNameStack::AddNameOrDiagnoseDuplicate(NameContext name_context,
170-
SemIR::InstId target_id,
171-
SemIR::AccessKind access_kind)
172-
-> void {
172+
auto DeclNameStack::AddNameOrDiagnose(NameContext name_context,
173+
SemIR::InstId target_id,
174+
SemIR::AccessKind access_kind) -> void {
173175
if (auto id = name_context.prev_inst_id(); id.is_valid()) {
174-
context_->DiagnoseDuplicateName(target_id, id);
176+
if (id.is_poisoned()) {
177+
context_->DiagnosePoisonedName(target_id);
178+
} else {
179+
context_->DiagnoseDuplicateName(target_id, id);
180+
}
175181
} else {
176182
AddName(name_context, target_id, access_kind);
177183
}
@@ -259,6 +265,9 @@ auto DeclNameStack::ApplyAndLookupName(NameContext& name_context,
259265
// Invalid indicates an unresolved name. Store it and return.
260266
name_context.unresolved_name_id = name_id;
261267
name_context.state = NameContext::State::Unresolved;
268+
} else if (resolved_inst_id.is_poisoned()) {
269+
name_context.unresolved_name_id = name_id;
270+
name_context.state = NameContext::State::Poisoned;
262271
} else {
263272
// Store the resolved instruction and continue for the target scope
264273
// update.
@@ -276,9 +285,15 @@ static auto CheckQualifierIsResolved(
276285
CARBON_FATAL("No qualifier to resolve");
277286

278287
case DeclNameStack::NameContext::State::Resolved:
288+
if (name_context.resolved_inst_id.is_poisoned()) {
289+
context.DiagnoseNameNotFound(name_context.loc_id,
290+
name_context.unresolved_name_id);
291+
return false;
292+
}
279293
return true;
280294

281295
case DeclNameStack::NameContext::State::Unresolved:
296+
case DeclNameStack::NameContext::State::Poisoned:
282297
// Because more qualifiers were found, we diagnose that the earlier
283298
// qualifier failed to resolve.
284299
context.DiagnoseNameNotFound(name_context.loc_id,
@@ -366,6 +381,10 @@ auto DeclNameStack::ResolveAsScope(const NameContext& name_context,
366381
return InvalidResult;
367382
}
368383

384+
if (name_context.resolved_inst_id.is_poisoned()) {
385+
return InvalidResult;
386+
}
387+
369388
auto new_params = DeclParams(
370389
name.name_loc_id, name.first_param_node_id, name.last_param_node_id,
371390
name.implicit_param_patterns_id, name.param_patterns_id);

toolchain/check/decl_name_stack.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ class DeclNameStack {
7878
// An identifier didn't resolve.
7979
Unresolved,
8080

81+
// An identifier was poisoned in this scope.
82+
Poisoned,
83+
8184
// The name has already been finished. This is not set in the name
8285
// returned by `FinishName`, but is used internally to track that
8386
// `FinishName` has already been called.
@@ -231,9 +234,8 @@ class DeclNameStack {
231234
SemIR::AccessKind access_kind) -> void;
232235

233236
// Adds a name to name lookup. Prints a diagnostic for name conflicts.
234-
auto AddNameOrDiagnoseDuplicate(NameContext name_context,
235-
SemIR::InstId target_id,
236-
SemIR::AccessKind access_kind) -> void;
237+
auto AddNameOrDiagnose(NameContext name_context, SemIR::InstId target_id,
238+
SemIR::AccessKind access_kind) -> void;
237239

238240
// Adds a name to name lookup, or returns the existing instruction if this
239241
// name has already been declared in this scope.

toolchain/check/handle_alias.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ auto HandleParseNode(Context& context, Parse::AliasId /*node_id*/) -> bool {
7171

7272
// Add the name of the binding to the current scope.
7373
context.decl_name_stack().PopScope();
74-
context.decl_name_stack().AddNameOrDiagnoseDuplicate(
74+
context.decl_name_stack().AddNameOrDiagnose(
7575
name_context, alias_id, introducer.modifier_set.GetAccessKind());
7676
return true;
7777
}

toolchain/check/handle_class.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ static auto MergeOrAddName(Context& context, Parse::AnyClassDeclId node_id,
113113
return;
114114
}
115115

116+
if (prev_id.is_poisoned()) {
117+
// This is declaration of a poisoned name.
118+
context.DiagnosePoisonedName(class_decl_id);
119+
return;
120+
}
121+
116122
auto prev_class_id = SemIR::ClassId::Invalid;
117123
auto prev_import_ir_id = SemIR::ImportIRId::Invalid;
118124
auto prev = context.insts().Get(prev_id);
@@ -546,7 +552,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
546552
}
547553

548554
// Bind the name `base` in the class to the base field.
549-
context.decl_name_stack().AddNameOrDiagnoseDuplicate(
555+
context.decl_name_stack().AddNameOrDiagnose(
550556
context.decl_name_stack().MakeUnqualifiedName(node_id,
551557
SemIR::NameId::Base),
552558
class_info.base_id, introducer.modifier_set.GetAccessKind());

toolchain/check/handle_let_and_var.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ static auto BuildAssociatedConstantDecl(Context& context,
9494
auto assoc_id = BuildAssociatedEntity(context, interface_id, decl_id);
9595
auto name_context =
9696
context.decl_name_stack().MakeUnqualifiedName(pattern.loc_id, name_id);
97-
context.decl_name_stack().AddNameOrDiagnoseDuplicate(name_context, assoc_id,
98-
access_kind);
97+
context.decl_name_stack().AddNameOrDiagnose(name_context, assoc_id,
98+
access_kind);
9999
}
100100

101101
// Adds name bindings. Returns the resulting ID for the references.
@@ -109,16 +109,16 @@ static auto HandleNameBinding(Context& context, SemIR::InstId pattern_id,
109109
auto name_context = context.decl_name_stack().MakeUnqualifiedName(
110110
context.insts().GetLocId(pattern_id),
111111
context.entity_names().Get(bind_name->entity_name_id).name_id);
112-
context.decl_name_stack().AddNameOrDiagnoseDuplicate(
113-
name_context, pattern_id, access_kind);
112+
context.decl_name_stack().AddNameOrDiagnose(name_context, pattern_id,
113+
access_kind);
114114
return bind_name->value_id;
115115
} else if (auto field_decl =
116116
context.insts().TryGetAs<SemIR::FieldDecl>(pattern_id)) {
117117
// Introduce the field name into the class.
118118
auto name_context = context.decl_name_stack().MakeUnqualifiedName(
119119
context.insts().GetLocId(pattern_id), field_decl->name_id);
120-
context.decl_name_stack().AddNameOrDiagnoseDuplicate(
121-
name_context, pattern_id, access_kind);
120+
context.decl_name_stack().AddNameOrDiagnose(name_context, pattern_id,
121+
access_kind);
122122
return pattern_id;
123123
} else {
124124
// TODO: Handle other kinds of pattern.

toolchain/check/import.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ static auto AddNamespace(Context& context, SemIR::TypeId namespace_type_id,
110110
SemIR::InstId::Invalid, SemIR::AccessKind::Public);
111111
if (!inserted) {
112112
auto prev_inst_id = parent_scope->GetEntry(entry_id).inst_id;
113+
CARBON_CHECK(!prev_inst_id.is_poisoned());
113114
if (auto namespace_inst =
114115
context.insts().TryGetAs<SemIR::Namespace>(prev_inst_id)) {
115116
if (diagnose_duplicate_namespace) {

toolchain/check/import_ref.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,9 @@ static auto AddNameScopeImportRefs(ImportContext& context,
12051205
const SemIR::NameScope& import_scope,
12061206
SemIR::NameScope& new_scope) -> void {
12071207
for (auto entry : import_scope.entries()) {
1208+
if (entry.inst_id.is_poisoned()) {
1209+
continue;
1210+
}
12081211
auto ref_id = AddImportRef(context, entry.inst_id);
12091212
new_scope.AddRequired({.name_id = GetLocalNameId(context, entry.name_id),
12101213
.inst_id = ref_id,
@@ -2790,6 +2793,9 @@ static auto GetInstForLoad(Context& context,
27902793
}
27912794

27922795
auto LoadImportRef(Context& context, SemIR::InstId inst_id) -> void {
2796+
if (inst_id.is_poisoned()) {
2797+
return;
2798+
}
27932799
auto inst = context.insts().TryGetAs<SemIR::ImportRefUnloaded>(inst_id);
27942800
if (!inst) {
27952801
return;

0 commit comments

Comments
 (0)