Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syntactic impl declaration matching updates #4762

Merged
merged 33 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bd77cfa
Checkpoint progress.
josh11b Dec 27, 2024
7502868
Checkpoint progress.
josh11b Dec 27, 2024
8bf2b71
DumpIfValid -> DumpNameIfValid
josh11b Dec 30, 2024
f307afb
Remove extra newline
josh11b Dec 30, 2024
8c7404a
Merge remote-tracking branch 'upstream/trunk' into dump
josh11b Jan 2, 2025
415c186
Failing tests
josh11b Jan 3, 2025
884821d
Debug
josh11b Jan 3, 2025
0fe446e
Merge branch 'dump' into assoc
josh11b Jan 3, 2025
edb5c9f
Roughly working
josh11b Jan 3, 2025
a987574
Update tests
josh11b Jan 4, 2025
9e0c236
Checkpoint progress.
josh11b Jan 4, 2025
f7bbc18
More diagnostics
josh11b Jan 4, 2025
2e0014a
Add TODOs about broken syntactic impl match
josh11b Jan 4, 2025
ab99a06
Exclude `where` from syntactic impl match
josh11b Jan 5, 2025
adfdfe4
`impl Self as` now matches `impl as`
josh11b Jan 5, 2025
aff650a
Checkpoint progress.
josh11b Jan 5, 2025
2285d25
Checkpoint progress.
josh11b Jan 5, 2025
11abc91
add impl_self_as tests
josh11b Jan 5, 2025
eed17cd
Comment
josh11b Jan 5, 2025
539474e
Checkpoint progress.
josh11b Jan 6, 2025
c7e2e18
Skip difference between `Self` and `Self as` in `CheckRedeclParamSynt…
josh11b Jan 6, 2025
a68b52d
TODO comments
josh11b Jan 6, 2025
ee6fa85
Checkpoint progress.
josh11b Jan 6, 2025
f25dda2
Merge remote-tracking branch 'upstream/trunk' into assoc
josh11b Jan 6, 2025
c7d82d6
Checkpoint progress.
josh11b Jan 6, 2025
e17d9f6
Checkpoint progress.
josh11b Jan 6, 2025
4d1155d
Test updates
josh11b Jan 6, 2025
95f98c1
Syntactic `impl` declaration matching updates
josh11b Jan 6, 2025
694204e
Checkpoint progress.
josh11b Jan 6, 2025
8afd387
Checkpoint progress.
josh11b Jan 6, 2025
1580edb
Checkpoint progress.
josh11b Jan 6, 2025
21c5af6
More tests
josh11b Jan 7, 2025
a320cbf
Implement suggestions
josh11b Jan 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions toolchain/check/handle_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,21 +212,45 @@ static auto PopImplIntroducerAndParamsAsNameComponent(

Parse::NodeId first_param_node_id =
context.node_stack().PopForSoloNodeId<Parse::NodeKind::ImplIntroducer>();

// Subtracting 1 since we don't want to include the final `{` or `;` of the
// declaration when performing syntactic match.
// TODO: Following proposal #3763, we should exclude any `where` clause, and
// add `Self` before `as` if needed, see:
auto end_node_kind = context.parse_tree().node_kind(end_of_decl_node_id);
CARBON_CHECK(end_node_kind == Parse::NodeKind::ImplDefinitionStart ||
end_node_kind == Parse::NodeKind::ImplDecl);
Parse::Tree::PostorderIterator last_param_iter(end_of_decl_node_id);
--last_param_iter;

// Following proposal #3763, exclude a final `where` clause, if present. See:
// https://github.com/carbon-language/carbon-lang/blob/trunk/proposals/p3763.md#redeclarations
auto node_kind = context.parse_tree().node_kind(end_of_decl_node_id);
CARBON_CHECK(node_kind == Parse::NodeKind::ImplDefinitionStart ||
node_kind == Parse::NodeKind::ImplDecl);
Parse::NodeId last_param_node_id(end_of_decl_node_id.index - 1);

// Caches the NodeKind for the current value of *last_param_iter so
if (context.parse_tree().node_kind(*last_param_iter) ==
Parse::NodeKind::WhereExpr) {
int where_operands_to_skip = 1;
--last_param_iter;
CARBON_CHECK(Parse::Tree::PostorderIterator(first_param_node_id) <
last_param_iter);
do {
auto node_kind = context.parse_tree().node_kind(*last_param_iter);
if (node_kind == Parse::NodeKind::WhereExpr) {
// If we have a nested `where`, we need to see another `WhereOperand`
// before we find the one that matches our original `WhereExpr` node.
++where_operands_to_skip;
} else if (node_kind == Parse::NodeKind::WhereOperand) {
--where_operands_to_skip;
}
--last_param_iter;
CARBON_CHECK(Parse::Tree::PostorderIterator(first_param_node_id) <
last_param_iter);
} while (where_operands_to_skip > 0);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to defer doing this until we build the DeclParams objects in MergeImplRedecl? That'd mean we don't need to do the scan here until we're about to compare two token sequences anyway, which would avoid doing this scan entirely in cases where the impl isn't in the same bucket as any other impl (which is probably a common case), and would likely increase locality given that we'd be doing two checks of nearby tokens at around the same time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonmeow What do you think?

Copy link
Contributor

@jonmeow jonmeow Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To try to offer a few upsides to the current approach:

  • When we're doing it here the nodes will have recently been loaded [for checking]. So from a cache locality perspective, these are the nodes we just checked and may still be in cache. If deferred, it may result in overlapping impls being reevaluated (I don't know how expensive that'd be).
  • This uses a reverse iteration instead of a forward iteration, whereas MergeImplRedecl uses a forward iteration, so may not cache well if the range turns out "large".
  • By doing it here, does that make it easier to save the results so that the scan won't be repeated?

But maybe I'd bring up a more radical option: in WhereExpr handling, if there's an impl on the stack and it hasn't reached a where clause, had you considered trying to store the where location? Maybe that could be annotated as part of decl_introducer_state_stack. Could that give the result you want without the scan of nodes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we could avoid the scan by doing saving something in the WhereExpr handling, but it seems really fragile. There are a lot of places where a where clause can occur in an impl declaration that we don't want to truncate at.

impl forall [T:! I where...] ...
impl DynPtr(Container where .ElementType = i32) as ...
impl ... as J(K where ...)
impl ... as (... where ...) where ...

I think the current code is easier to get right since it directly looks in the one place we want to operate on.

Copy link
Contributor

@jonmeow jonmeow Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In each of the cases you're showing, it seems like there'd be something on the NodeStack indicating the nesting:

  • [ at [T:!
  • ( at (Container
  • ( at (K
  • ( at (... where

Maybe it's worth thinking about further?

Copy link
Contributor

@jonmeow jonmeow Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put differently, is there a characteristic here where 2 nodes before the where must be something like forall or as?


return {
.name_loc_id = Parse::NodeId::Invalid,
.name_id = SemIR::NameId::Invalid,
.first_param_node_id = first_param_node_id,
.last_param_node_id = last_param_node_id,
.last_param_node_id = *last_param_iter,
.implicit_params_loc_id = implicit_params_loc_id,
.implicit_param_patterns_id =
implicit_param_patterns_id.value_or(SemIR::InstBlockId::Invalid),
Expand Down Expand Up @@ -297,6 +321,11 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
// TODO: Check that its constant value is a constraint.
auto [constraint_inst_id, constraint_type_id] =
ExprAsType(context, constraint_node, constraint_id);
// TODO: Do facet type resolution here.
// TODO: Determine `interface_id` and `specific_id` once and save it in the
// resolved facet type, instead of in multiple functions called below.
// TODO: Skip work below if facet type resolution fails, so we don't have a
// valid/non-error `interface_id` at all.

// Process modifiers.
// TODO: Should we somehow permit access specifiers on `impl`s?
Expand Down
53 changes: 42 additions & 11 deletions toolchain/check/merge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,18 +394,38 @@ static auto CheckRedeclParamSyntax(Context& context,
CARBON_CHECK(prev_last_param_node_id.is_valid(),
"prev_last_param_node_id.is_valid should match "
"prev_first_param_node_id.is_valid");

auto new_range = Parse::Tree::PostorderIterator::MakeRange(
new_first_param_node_id, new_last_param_node_id);
auto prev_range = Parse::Tree::PostorderIterator::MakeRange(
prev_first_param_node_id, prev_last_param_node_id);

// zip is using the shortest range. If they differ in length, there should be
// some difference inside the range because the range includes parameter
// brackets. As a consequence, we don't explicitly handle different range
// sizes here.
for (auto [new_node_id, prev_node_id] : llvm::zip(new_range, prev_range)) {
Parse::Tree::PostorderIterator new_iter(new_first_param_node_id);
Parse::Tree::PostorderIterator new_end(new_last_param_node_id);
Parse::Tree::PostorderIterator prev_iter(prev_first_param_node_id);
Parse::Tree::PostorderIterator prev_end(prev_last_param_node_id);
// Done when one past the last node to check.
++new_end;
++prev_end;

// Compare up to the shortest length.
for (; new_iter != new_end && prev_iter != prev_end;
++new_iter, ++prev_iter) {
auto new_node_id = *new_iter;
auto prev_node_id = *prev_iter;
if (!IsNodeSyntaxEqual(context, new_node_id, prev_node_id)) {
// Skip difference if it is `Self as` vs. `as` in an `impl` declaration.
// https://github.com/carbon-language/carbon-lang/blob/trunk/proposals/p3763.md#redeclarations
auto new_node_kind = context.parse_tree().node_kind(new_node_id);
auto prev_node_kind = context.parse_tree().node_kind(prev_node_id);
if (new_node_kind == Parse::NodeKind::DefaultSelfImplAs &&
prev_node_kind == Parse::NodeKind::SelfTypeNameExpr &&
context.parse_tree().node_kind(prev_iter[1]) ==
Parse::NodeKind::TypeImplAs) {
++prev_iter;
continue;
}
if (prev_node_kind == Parse::NodeKind::DefaultSelfImplAs &&
new_node_kind == Parse::NodeKind::SelfTypeNameExpr &&
context.parse_tree().node_kind(new_iter[1]) ==
Parse::NodeKind::TypeImplAs) {
++new_iter;
continue;
}
if (!diagnose) {
return false;
}
Expand All @@ -420,6 +440,17 @@ static auto CheckRedeclParamSyntax(Context& context,
return false;
}
}
// The prefixes are the same, but the lengths may still be different. This is
// only relevant for `impl` declarations where the final bracketing node is
// not included in the range of nodes being compared, and in those cases
// `diagnose` is false.
if (new_iter != new_end) {
CARBON_CHECK(!diagnose);
return false;
} else if (prev_iter != prev_end) {
CARBON_CHECK(!diagnose);
return false;
}

return true;
}
Expand Down
180 changes: 180 additions & 0 deletions toolchain/check/testdata/impl/no_prelude/impl_self_as.carbon
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// EXTRA-ARGS: --no-dump-sem-ir
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/no_prelude/impl_self_as.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/no_prelude/impl_self_as.carbon

// --- match.carbon
library "[[@TEST_NAME]]";

interface I1 {}
interface I2 {}
interface J1(T1:! type) {}
interface J2(T2:! type) {}

// `impl Self as` should match `impl as`, so these should not trigger impl
// declaration without definition diagnostics.

class C1 {
impl Self as I1;
impl as I1 {}

impl as I2;
impl Self as I2 {}

impl forall [U:! type] Self as J1(U);
impl forall [U:! type] as J1(U) {}

impl forall [V:! type] as J2(V);
impl forall [V:! type] Self as J2(V) {}
}

class C2(W:! type) {
impl Self as I1;
impl as I1 {}

impl as I2;
impl Self as I2 {}

impl forall [X:! type] Self as J1(X);
impl forall [X:! type] as J1(X) {}

impl forall [Y:! type] as J2(Y);
impl forall [Y:! type] Self as J2(Y) {}
}


// --- fail_no_match.carbon
library "[[@TEST_NAME]]";

interface I3 {}
interface I4 {}
interface I5 {}
interface I6 {}
interface J3(T3:! type) {}
interface J4(T4:! type) {}
interface J5(T5:! type) {}
interface J6(T6:! type) {}

// `impl C as` should not match `impl Self as` or `impl as`.

class C3 {
// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl C3 as I3;
// CHECK:STDERR: ^~~~~~~~~~~~~~
// CHECK:STDERR:
impl C3 as I3;
impl as I3 {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl C3 as I4;
// CHECK:STDERR: ^~~~~~~~~~~~~~
// CHECK:STDERR:
impl C3 as I4;
impl Self as I4 {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl as I5;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
impl as I5;
impl C3 as I5 {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl Self as I6;
// CHECK:STDERR: ^~~~~~~~~~~~~~~~
// CHECK:STDERR:
impl Self as I6;
impl C3 as I6 {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl forall [Z3:! type] C3 as J3(Z3);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
impl forall [Z3:! type] C3 as J3(Z3);
impl forall [Z3:! type] as J3(Z3) {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl forall [Z4:! type] C3 as J4(Z4);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
impl forall [Z4:! type] C3 as J4(Z4);
impl forall [Z4:! type] Self as J4(Z4) {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl forall [Z5:! type] as J5(Z5);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
impl forall [Z5:! type] as J5(Z5);
impl forall [Z5:! type] C3 as J5(Z5) {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl forall [Z6:! type] Self as J6(Z6);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
impl forall [Z6:! type] Self as J6(Z6);
impl forall [Z6:! type] C3 as J6(Z6) {}
}

class C4(A:! type) {
// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl C4(A) as I3;
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~
// CHECK:STDERR:
impl C4(A) as I3;
impl as I3 {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl C4(A) as I4;
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~
// CHECK:STDERR:
impl C4(A) as I4;
impl Self as I4 {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl as I5;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
impl as I5;
impl C4(A) as I5 {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl Self as I6;
// CHECK:STDERR: ^~~~~~~~~~~~~~~~
// CHECK:STDERR:
impl Self as I6;
impl C4(A) as I6 {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl forall [B3:! type] C4(A) as J3(B3);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
impl forall [B3:! type] C4(A) as J3(B3);
impl forall [B3:! type] as J3(B3) {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl forall [B4:! type] C4(A) as J4(B4);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
impl forall [B4:! type] C4(A) as J4(B4);
impl forall [B4:! type] Self as J4(B4) {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+4]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl forall [B5:! type] as J5(B5);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
impl forall [B5:! type] as J5(B5);
impl forall [B5:! type] C4(A) as J5(B5) {}

// CHECK:STDERR: fail_no_match.carbon:[[@LINE+3]]:3: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl forall [B6:! type] Self as J6(B6);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
impl forall [B6:! type] Self as J6(B6);
impl forall [B6:! type] C4(A) as J6(B6) {}
}
47 changes: 47 additions & 0 deletions toolchain/check/testdata/impl/no_prelude/impl_where_redecl.carbon
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// EXTRA-ARGS: --no-dump-sem-ir
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/no_prelude/impl_where_redecl.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/no_prelude/impl_where_redecl.carbon

// --- match.carbon
library "[[@TEST_NAME]]";

interface J {}

// `impl` matching ignores the `where` clause. It should not get confused by
// nested `where`, so the following should not trigger impl declaration without
// definition diagnostics.

impl () as J;
impl () as J where .Self impls type and .Self impls (type where .Self impls type) {}

impl {} as J where .Self impls type and .Self impls (type where .Self impls type);
impl {} as J {}

// --- parens_other_nesting.carbon
library "[[@TEST_NAME]]";

interface K {}

// `impl` matching only ignores a root-level `where` clause.

impl {} as (K where .Self impls type) where .Self impls type;
impl {} as (K where .Self impls type) {}

// --- fail_other_nesting.carbon
library "[[@TEST_NAME]]";

interface L {}

// CHECK:STDERR: fail_other_nesting.carbon:[[@LINE+3]]:1: error: impl declared but not defined [MissingImplDefinition]
// CHECK:STDERR: impl () as (L where .Self impls type) where .Self impls type;
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
impl () as (L where .Self impls type) where .Self impls type;
impl () as L {}
17 changes: 14 additions & 3 deletions toolchain/sem_ir/impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,22 @@ namespace Carbon::SemIR {

auto ImplStore::GetOrAddLookupBucket(const Impl& impl) -> LookupBucketRef {
auto self_id = sem_ir_.constant_values().GetConstantInstId(impl.self_id);
auto constraint_id =
sem_ir_.constant_values().GetConstantInstId(impl.constraint_id);
InterfaceId interface_id = InterfaceId::Invalid;
SpecificId specific_id = SpecificId::Invalid;
auto facet_type_id = TypeId::ForTypeConstant(
sem_ir_.constant_values().Get(impl.constraint_id));
if (auto facet_type =
sem_ir_.types().TryGetAs<SemIR::FacetType>(facet_type_id)) {
const SemIR::FacetTypeInfo& facet_type_info =
sem_ir_.facet_types().Get(facet_type->facet_type_id);
if (auto interface_type = facet_type_info.TryAsSingleInterface()) {
interface_id = interface_type->interface_id;
specific_id = interface_type->specific_id;
}
}
return LookupBucketRef(
*this, lookup_
.Insert(std::pair{self_id, constraint_id},
.Insert(std::tuple{self_id, interface_id, specific_id},
[] { return ImplOrLookupBucketId::Invalid; })
.value());
}
Expand Down
3 changes: 2 additions & 1 deletion toolchain/sem_ir/impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ class ImplStore {
private:
File& sem_ir_;
ValueStore<ImplId> values_;
Map<std::pair<InstId, InstId>, ImplOrLookupBucketId> lookup_;
Map<std::tuple<InstId, InterfaceId, SpecificId>, ImplOrLookupBucketId>
lookup_;
// Buckets with at least 2 entries, which will be rare; see LookupBucketRef.
llvm::SmallVector<llvm::SmallVector<ImplId, 2>> lookup_buckets_;
};
Expand Down
Loading