Skip to content

Commit 6bc36b0

Browse files
authored
Rearrange name poisoning logic to do a little less work. (#4766)
Insert the poison at the same time we do the name lookup to avoid doing two hash table lookups into each scope. This adds a bit of complication because import logic now needs to cope with importing a name that is already poisoned, but the complexity seems worthwhile to reduce the number of name lookups performed. This incidentally fixes a bug where we wouldn't poison any name scopes if we found the name in an enclosing lexical scope, leading to one extra diagnostic in existing tests. Part of #4622
1 parent 9dc450e commit 6bc36b0

File tree

9 files changed

+251
-77
lines changed

9 files changed

+251
-77
lines changed

toolchain/check/context.cpp

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,8 @@ auto Context::LookupNameInDecl(SemIR::LocId loc_id, SemIR::NameId name_id,
365365
// // Error, no `F` in `B`.
366366
// fn B.F() {}
367367
auto result = LookupNameInExactScope(loc_id, name_id, scope_id,
368-
name_scopes().Get(scope_id));
368+
name_scopes().Get(scope_id),
369+
/*is_being_declared=*/true);
369370
return {result.inst_id, result.is_poisoned};
370371
}
371372
}
@@ -381,26 +382,15 @@ auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
381382
scope_stack().LookupInLexicalScopes(name_id);
382383

383384
// Walk the non-lexical scopes and perform lookups into each of them.
384-
// Collect scopes to poison this name when it's found.
385-
llvm::SmallVector<LookupScope> scopes_to_poison;
386385
for (auto [index, lookup_scope_id, specific_id] :
387386
llvm::reverse(non_lexical_scopes)) {
388387
if (auto non_lexical_result =
389388
LookupQualifiedName(node_id, name_id,
390389
LookupScope{.name_scope_id = lookup_scope_id,
391390
.specific_id = specific_id},
392391
/*required=*/false);
393-
!non_lexical_result.is_poisoned) {
394-
if (non_lexical_result.inst_id.is_valid()) {
395-
// Poison the scopes for this name.
396-
for (const auto [scope_id, specific_id] : scopes_to_poison) {
397-
name_scopes().Get(scope_id).AddPoison(name_id);
398-
}
399-
400-
return non_lexical_result;
401-
}
402-
scopes_to_poison.push_back(
403-
{.name_scope_id = lookup_scope_id, .specific_id = specific_id});
392+
non_lexical_result.inst_id.is_valid()) {
393+
return non_lexical_result;
404394
}
405395
}
406396

@@ -423,12 +413,16 @@ auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
423413

424414
auto Context::LookupNameInExactScope(SemIRLoc loc, SemIR::NameId name_id,
425415
SemIR::NameScopeId scope_id,
426-
const SemIR::NameScope& scope)
416+
SemIR::NameScope& scope,
417+
bool is_being_declared)
427418
-> LookupNameInExactScopeResult {
428-
if (auto entry_id = scope.Lookup(name_id)) {
419+
if (auto entry_id = is_being_declared ? scope.Lookup(name_id)
420+
: scope.LookupOrPoison(name_id)) {
429421
auto entry = scope.GetEntry(*entry_id);
430422
if (!entry.is_poisoned) {
431423
LoadImportRef(*this, entry.inst_id);
424+
} else if (is_being_declared) {
425+
entry.inst_id = SemIR::InstId::Invalid;
432426
}
433427
return {entry.inst_id, entry.access_kind, entry.is_poisoned};
434428
}
@@ -593,7 +587,7 @@ auto Context::LookupQualifiedName(SemIR::LocId loc_id, SemIR::NameId name_id,
593587
has_error = true;
594588
continue;
595589
}
596-
const auto& name_scope = name_scopes().Get(scope_id);
590+
auto& name_scope = name_scopes().Get(scope_id);
597591
has_error |= name_scope.has_error();
598592

599593
auto [scope_result_id, access_kind, is_poisoned] =
@@ -612,7 +606,7 @@ auto Context::LookupQualifiedName(SemIR::LocId loc_id, SemIR::NameId name_id,
612606
});
613607
}
614608

615-
if (!is_poisoned && (!scope_result_id.is_valid() || is_access_prohibited)) {
609+
if (!scope_result_id.is_valid() || is_access_prohibited) {
616610
// If nothing is found in this scope or if we encountered an invalid
617611
// access, look in its extended scopes.
618612
const auto& extended = name_scope.extended_scopes();
@@ -657,7 +651,7 @@ auto Context::LookupQualifiedName(SemIR::LocId loc_id, SemIR::NameId name_id,
657651
result.is_poisoned = is_poisoned;
658652
}
659653

660-
if (required && (!result.inst_id.is_valid() || result.is_poisoned)) {
654+
if (required && !result.inst_id.is_valid()) {
661655
if (!has_error) {
662656
if (prohibited_accesses.empty()) {
663657
DiagnoseMemberNameNotFound(loc_id, name_id, lookup_scopes);

toolchain/check/context.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,11 +242,17 @@ class Context {
242242

243243
// Performs a name lookup in a specified scope, returning the referenced
244244
// instruction. Does not look into extended scopes. Returns an invalid
245-
// instruction if the name is poisoned or not found.
246-
// TODO: Return the poisoning instruction if poisoned.
245+
// instruction if the name is not found.
246+
//
247+
// If `is_being_declared` is false, then this is a regular name lookup, and
248+
// the name will be poisoned if not found so that later lookups will fail; a
249+
// poisoned name will be treated as if it is not declared. Otherwise, this is
250+
// a lookup for a name being declared, so the name will not be poisoned, but
251+
// poison will be returned if it's already been looked up.
247252
auto LookupNameInExactScope(SemIRLoc loc, SemIR::NameId name_id,
248253
SemIR::NameScopeId scope_id,
249-
const SemIR::NameScope& scope)
254+
SemIR::NameScope& scope,
255+
bool is_being_declared = false)
250256
-> LookupNameInExactScopeResult;
251257

252258
// Appends the lookup scopes corresponding to `base_const_id` to `*scopes`.

toolchain/check/import.cpp

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,17 @@ static auto AddNamespace(Context& context, SemIR::TypeId namespace_type_id,
110110
SemIR::InstId::Invalid, SemIR::AccessKind::Public);
111111
if (!inserted) {
112112
const auto& prev_entry = parent_scope->GetEntry(entry_id);
113-
CARBON_CHECK(!prev_entry.is_poisoned);
114-
auto prev_inst_id = prev_entry.inst_id;
115-
if (auto namespace_inst =
116-
context.insts().TryGetAs<SemIR::Namespace>(prev_inst_id)) {
117-
if (diagnose_duplicate_namespace) {
118-
auto import_id = make_import_id();
119-
CARBON_CHECK(import_id.is_valid());
120-
context.DiagnoseDuplicateName(import_id, prev_inst_id);
113+
if (!prev_entry.is_poisoned) {
114+
auto prev_inst_id = prev_entry.inst_id;
115+
if (auto namespace_inst =
116+
context.insts().TryGetAs<SemIR::Namespace>(prev_inst_id)) {
117+
if (diagnose_duplicate_namespace) {
118+
auto import_id = make_import_id();
119+
CARBON_CHECK(import_id.is_valid());
120+
context.DiagnoseDuplicateName(import_id, prev_inst_id);
121+
}
122+
return {namespace_inst->name_scope_id, prev_inst_id, true};
121123
}
122-
return {namespace_inst->name_scope_id, prev_inst_id, true};
123124
}
124125
}
125126

@@ -148,9 +149,11 @@ static auto AddNamespace(Context& context, SemIR::TypeId namespace_type_id,
148149
parent_scope = &context.name_scopes().Get(parent_scope_id);
149150

150151
// Diagnose if there's a name conflict, but still produce the namespace to
151-
// supersede the name conflict in order to avoid repeat diagnostics.
152+
// supersede the name conflict in order to avoid repeat diagnostics. Names are
153+
// poisoned optimistically by name lookup before checking for imports, so we
154+
// may be overwriting a poisoned entry here.
152155
auto& entry = parent_scope->GetEntry(entry_id);
153-
if (!inserted) {
156+
if (!inserted && !entry.is_poisoned) {
154157
context.DiagnoseDuplicateName(namespace_id, entry.inst_id);
155158
entry.access_kind = SemIR::AccessKind::Public;
156159
}

toolchain/check/testdata/function/declaration/no_prelude/name_poisoning.carbon

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -293,21 +293,49 @@ class X {
293293
}
294294
}
295295

296-
// --- fail_no_poison_when_lookup_fails.carbon
296+
// --- fail_poison_when_lookup_fails.carbon
297297

298298
library "[[@TEST_NAME]]";
299299

300300
namespace N;
301-
// Here we fail to find C so we don't poison anything.
302-
// CHECK:STDERR: fail_no_poison_when_lookup_fails.carbon:[[@LINE+3]]:11: error: name `C` not found [NameNotFound]
301+
// CHECK:STDERR: fail_poison_when_lookup_fails.carbon:[[@LINE+5]]:11: error: name `C` not found [NameNotFound]
303302
// CHECK:STDERR: fn N.F(x: C);
304303
// CHECK:STDERR: ^
304+
// CHECK:STDERR:
305+
// CHECK:STDERR: fail_poison_when_lookup_fails.carbon: error: name used before it was declared [NameUseBeforeDecl]
305306
fn N.F(x: C);
306307

307-
// No failures below because nothing was poisoned.
308+
// TODO: We should ideally only produce one diagnostic here.
309+
// CHECK:STDERR: fail_poison_when_lookup_fails.carbon:[[@LINE+5]]:1: note: declared here [NameUseBeforeDeclNote]
310+
// CHECK:STDERR: class C {}
311+
// CHECK:STDERR: ^~~~~~~~~
312+
// CHECK:STDERR:
313+
// CHECK:STDERR: fail_poison_when_lookup_fails.carbon: error: name used before it was declared [NameUseBeforeDecl]
308314
class C {}
315+
// CHECK:STDERR: fail_poison_when_lookup_fails.carbon:[[@LINE+4]]:1: note: declared here [NameUseBeforeDeclNote]
316+
// CHECK:STDERR: class N.C {}
317+
// CHECK:STDERR: ^~~~~~~~~~~
318+
// CHECK:STDERR:
309319
class N.C {}
310320

321+
// --- fail_poison_with_lexical_result.carbon
322+
// CHECK:STDERR: fail_poison_with_lexical_result.carbon: error: name used before it was declared [NameUseBeforeDecl]
323+
324+
library "[[@TEST_NAME]]";
325+
326+
fn F() {
327+
class A {}
328+
329+
class B {
330+
var v: A;
331+
332+
// CHECK:STDERR: fail_poison_with_lexical_result.carbon:[[@LINE+3]]:5: note: declared here [NameUseBeforeDeclNote]
333+
// CHECK:STDERR: class A {}
334+
// CHECK:STDERR: ^~~~~~~~~
335+
class A {}
336+
}
337+
}
338+
311339
// CHECK:STDOUT: --- no_poison.carbon
312340
// CHECK:STDOUT:
313341
// CHECK:STDOUT: constants {
@@ -1158,25 +1186,23 @@ class N.C {}
11581186
// CHECK:STDOUT:
11591187
// CHECK:STDOUT: specific @B(constants.%Self) {}
11601188
// CHECK:STDOUT:
1161-
// CHECK:STDOUT: --- fail_no_poison_when_lookup_fails.carbon
1189+
// CHECK:STDOUT: --- fail_poison_when_lookup_fails.carbon
11621190
// CHECK:STDOUT:
11631191
// CHECK:STDOUT: constants {
11641192
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
11651193
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
1166-
// CHECK:STDOUT: %C.f79: type = class_type @C.1 [template]
1194+
// CHECK:STDOUT: %.a95: type = class_type @.1 [template]
11671195
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template]
11681196
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
1169-
// CHECK:STDOUT: %C.9f4: type = class_type @C.2 [template]
1197+
// CHECK:STDOUT: %.fb7: type = class_type @.2 [template]
11701198
// CHECK:STDOUT: }
11711199
// CHECK:STDOUT:
11721200
// CHECK:STDOUT: file {
11731201
// CHECK:STDOUT: package: <namespace> = namespace [template] {
11741202
// CHECK:STDOUT: .N = %N
1175-
// CHECK:STDOUT: .C = %C.decl.loc12
11761203
// CHECK:STDOUT: }
11771204
// CHECK:STDOUT: %N: <namespace> = namespace [template] {
11781205
// CHECK:STDOUT: .F = %F.decl
1179-
// CHECK:STDOUT: .C = %C.decl.loc13
11801206
// CHECK:STDOUT: }
11811207
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {
11821208
// CHECK:STDOUT: %x.patt: <error> = binding_pattern x
@@ -1186,25 +1212,81 @@ class N.C {}
11861212
// CHECK:STDOUT: %C.ref: <error> = name_ref C, <error> [template = <error>]
11871213
// CHECK:STDOUT: %x: <error> = bind_name x, %x.param
11881214
// CHECK:STDOUT: }
1189-
// CHECK:STDOUT: %C.decl.loc12: type = class_decl @C.1 [template = constants.%C.f79] {} {}
1190-
// CHECK:STDOUT: %C.decl.loc13: type = class_decl @C.2 [template = constants.%C.9f4] {} {}
1215+
// CHECK:STDOUT: %.decl.loc18: type = class_decl @.1 [template = constants.%.a95] {} {}
1216+
// CHECK:STDOUT: %.decl.loc23: type = class_decl @.2 [template = constants.%.fb7] {} {}
11911217
// CHECK:STDOUT: }
11921218
// CHECK:STDOUT:
1193-
// CHECK:STDOUT: class @C.1 {
1219+
// CHECK:STDOUT: class @.1 {
11941220
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
11951221
// CHECK:STDOUT:
11961222
// CHECK:STDOUT: !members:
1197-
// CHECK:STDOUT: .Self = constants.%C.f79
1223+
// CHECK:STDOUT: .Self = constants.%.a95
11981224
// CHECK:STDOUT: complete_type_witness = %complete_type
11991225
// CHECK:STDOUT: }
12001226
// CHECK:STDOUT:
1201-
// CHECK:STDOUT: class @C.2 {
1227+
// CHECK:STDOUT: class @.2 {
12021228
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
12031229
// CHECK:STDOUT:
12041230
// CHECK:STDOUT: !members:
1205-
// CHECK:STDOUT: .Self = constants.%C.9f4
1231+
// CHECK:STDOUT: .Self = constants.%.fb7
12061232
// CHECK:STDOUT: complete_type_witness = %complete_type
12071233
// CHECK:STDOUT: }
12081234
// CHECK:STDOUT:
12091235
// CHECK:STDOUT: fn @F(%x.param_patt: <error>);
12101236
// CHECK:STDOUT:
1237+
// CHECK:STDOUT: --- fail_poison_with_lexical_result.carbon
1238+
// CHECK:STDOUT:
1239+
// CHECK:STDOUT: constants {
1240+
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
1241+
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
1242+
// CHECK:STDOUT: %A: type = class_type @A [template]
1243+
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template]
1244+
// CHECK:STDOUT: %complete_type.357: <witness> = complete_type_witness %empty_struct_type [template]
1245+
// CHECK:STDOUT: %B: type = class_type @B [template]
1246+
// CHECK:STDOUT: %B.elem: type = unbound_element_type %B, %A [template]
1247+
// CHECK:STDOUT: %.96d: type = class_type @.1 [template]
1248+
// CHECK:STDOUT: %struct_type.v: type = struct_type {.v: %A} [template]
1249+
// CHECK:STDOUT: %complete_type.57e: <witness> = complete_type_witness %struct_type.v [template]
1250+
// CHECK:STDOUT: }
1251+
// CHECK:STDOUT:
1252+
// CHECK:STDOUT: file {
1253+
// CHECK:STDOUT: package: <namespace> = namespace [template] {
1254+
// CHECK:STDOUT: .F = %F.decl
1255+
// CHECK:STDOUT: }
1256+
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
1257+
// CHECK:STDOUT: }
1258+
// CHECK:STDOUT:
1259+
// CHECK:STDOUT: class @A {
1260+
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type.357]
1261+
// CHECK:STDOUT:
1262+
// CHECK:STDOUT: !members:
1263+
// CHECK:STDOUT: .Self = constants.%A
1264+
// CHECK:STDOUT: complete_type_witness = %complete_type
1265+
// CHECK:STDOUT: }
1266+
// CHECK:STDOUT:
1267+
// CHECK:STDOUT: class @B {
1268+
// CHECK:STDOUT: %.loc9: %B.elem = field_decl v, element0 [template]
1269+
// CHECK:STDOUT: %.decl: type = class_decl @.1 [template = constants.%.96d] {} {}
1270+
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %struct_type.v [template = constants.%complete_type.57e]
1271+
// CHECK:STDOUT:
1272+
// CHECK:STDOUT: !members:
1273+
// CHECK:STDOUT: .Self = constants.%B
1274+
// CHECK:STDOUT: .v = %.loc9
1275+
// CHECK:STDOUT: complete_type_witness = %complete_type
1276+
// CHECK:STDOUT: }
1277+
// CHECK:STDOUT:
1278+
// CHECK:STDOUT: class @.1 {
1279+
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type.357]
1280+
// CHECK:STDOUT:
1281+
// CHECK:STDOUT: !members:
1282+
// CHECK:STDOUT: .Self = constants.%.96d
1283+
// CHECK:STDOUT: complete_type_witness = %complete_type
1284+
// CHECK:STDOUT: }
1285+
// CHECK:STDOUT:
1286+
// CHECK:STDOUT: fn @F() {
1287+
// CHECK:STDOUT: !entry:
1288+
// CHECK:STDOUT: %A.decl: type = class_decl @A [template = constants.%A] {} {}
1289+
// CHECK:STDOUT: %B.decl: type = class_decl @B [template = constants.%B] {} {}
1290+
// CHECK:STDOUT: return
1291+
// CHECK:STDOUT: }
1292+
// CHECK:STDOUT:

toolchain/check/testdata/interface/no_prelude/import_access.carbon

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,17 @@ fn F(i: Test.ForwardWithDef) {}
101101

102102
impl package Test library "[[@TEST_NAME]]";
103103

104-
// CHECK:STDERR: fail_todo_forward.impl.carbon:[[@LINE+4]]:9: error: name `Forward` not found [NameNotFound]
104+
// CHECK:STDERR: fail_todo_forward.impl.carbon:[[@LINE+5]]:9: error: name `Forward` not found [NameNotFound]
105105
// CHECK:STDERR: fn F(i: Forward*) {}
106106
// CHECK:STDERR: ^~~~~~~
107107
// CHECK:STDERR:
108+
// CHECK:STDERR: fail_todo_forward.impl.carbon: error: name used before it was declared [NameUseBeforeDecl]
108109
fn F(i: Forward*) {}
109110

111+
// CHECK:STDERR: fail_todo_forward.impl.carbon:[[@LINE+4]]:1: note: declared here [NameUseBeforeDeclNote]
112+
// CHECK:STDERR: interface Forward {}
113+
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~
114+
// CHECK:STDERR:
110115
interface Forward {}
111116

112117
// --- fail_local_forward.carbon
@@ -406,14 +411,13 @@ private interface Redecl {}
406411
// CHECK:STDOUT: constants {
407412
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
408413
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
409-
// CHECK:STDOUT: %Forward.type: type = facet_type <@Forward> [template]
410-
// CHECK:STDOUT: %Self: %Forward.type = bind_symbolic_name Self, 0 [symbolic]
414+
// CHECK:STDOUT: %.type: type = facet_type <@.1> [template]
415+
// CHECK:STDOUT: %Self: %.type = bind_symbolic_name Self, 0 [symbolic]
411416
// CHECK:STDOUT: }
412417
// CHECK:STDOUT:
413418
// CHECK:STDOUT: file {
414419
// CHECK:STDOUT: package: <namespace> = namespace [template] {
415420
// CHECK:STDOUT: .F = %F.decl
416-
// CHECK:STDOUT: .Forward = %Forward.decl
417421
// CHECK:STDOUT: }
418422
// CHECK:STDOUT: %Test.import = import Test
419423
// CHECK:STDOUT: %default.import = import <invalid>
@@ -422,17 +426,17 @@ private interface Redecl {}
422426
// CHECK:STDOUT: %i.param_patt: <error> = value_param_pattern %i.patt, runtime_param0
423427
// CHECK:STDOUT: } {
424428
// CHECK:STDOUT: %i.param: <error> = value_param runtime_param0
425-
// CHECK:STDOUT: %.loc8: type = splice_block %ptr [template = <error>] {
429+
// CHECK:STDOUT: %.loc9: type = splice_block %ptr [template = <error>] {
426430
// CHECK:STDOUT: %Forward.ref: <error> = name_ref Forward, <error> [template = <error>]
427431
// CHECK:STDOUT: %ptr: type = ptr_type <error> [template = <error>]
428432
// CHECK:STDOUT: }
429433
// CHECK:STDOUT: %i: <error> = bind_name i, %i.param
430434
// CHECK:STDOUT: }
431-
// CHECK:STDOUT: %Forward.decl: type = interface_decl @Forward [template = constants.%Forward.type] {} {}
435+
// CHECK:STDOUT: %.decl: type = interface_decl @.1 [template = constants.%.type] {} {}
432436
// CHECK:STDOUT: }
433437
// CHECK:STDOUT:
434-
// CHECK:STDOUT: interface @Forward {
435-
// CHECK:STDOUT: %Self: %Forward.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
438+
// CHECK:STDOUT: interface @.1 {
439+
// CHECK:STDOUT: %Self: %.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
436440
// CHECK:STDOUT:
437441
// CHECK:STDOUT: !members:
438442
// CHECK:STDOUT: .Self = %Self

toolchain/check/testdata/namespace/merging_with_indirections.carbon

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ fn Run() {
168168
// CHECK:STDOUT: }
169169
// CHECK:STDOUT: %Other: <namespace> = namespace file.%Other.import, [template] {
170170
// CHECK:STDOUT: .F = %import_ref.f04
171-
// CHECK:STDOUT: .NS1 = %NS1
172171
// CHECK:STDOUT: import Other//b
173172
// CHECK:STDOUT: import Other//a
174173
// CHECK:STDOUT: }

0 commit comments

Comments
 (0)