Skip to content

Commit 1d720dc

Browse files
zygoloidjonmeow
andauthored
Basic support for looking up impl members when naming an associated entity. (#3776)
When a member access names an interface member, perform impl lookup to find the impl and its corresponding member. --------- Co-authored-by: Jon Ross-Perkins <[email protected]>
1 parent 0217ec2 commit 1d720dc

34 files changed

+749
-83
lines changed

toolchain/check/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ cc_library(
199199
deps = [
200200
":context",
201201
"//common:check",
202+
"//toolchain/diagnostics:diagnostic_emitter",
202203
"//toolchain/sem_ir:file",
203204
"//toolchain/sem_ir:ids",
204205
"//toolchain/sem_ir:inst",

toolchain/check/context.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,7 @@ class TypeCompleter {
842842
case SemIR::InitializeFrom::Kind:
843843
case SemIR::InterfaceDecl::Kind:
844844
case SemIR::InterfaceWitness::Kind:
845+
case SemIR::InterfaceWitnessAccess::Kind:
845846
case SemIR::IntLiteral::Kind:
846847
case SemIR::NameRef::Kind:
847848
case SemIR::Namespace::Kind:

toolchain/check/eval.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
330330
&SemIR::BoundMethod::function_id);
331331
case SemIR::InterfaceWitness::Kind:
332332
return RebuildIfFieldsAreConstant(context, inst,
333-
&SemIR::InterfaceWitness::table_id);
333+
&SemIR::InterfaceWitness::elements_id);
334334
case SemIR::PointerType::Kind:
335335
return RebuildIfFieldsAreConstant(context, inst,
336336
&SemIR::PointerType::pointee_id);
@@ -417,6 +417,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
417417

418418
// The elements of a constant aggregate can be accessed.
419419
case SemIR::ClassElementAccess::Kind:
420+
case SemIR::InterfaceWitnessAccess::Kind:
420421
case SemIR::StructAccess::Kind:
421422
case SemIR::TupleAccess::Kind:
422423
return PerformAggregateAccess(context, inst);

toolchain/check/impl.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,7 @@ static auto BuildInterfaceWitness(
114114

115115
auto table_id = context.inst_blocks().Add(table);
116116
return context.AddInst(SemIR::InterfaceWitness{
117-
context.GetBuiltinType(SemIR::BuiltinKind::WitnessType), interface_id,
118-
table_id});
117+
context.GetBuiltinType(SemIR::BuiltinKind::WitnessType), table_id});
119118
}
120119

121120
auto BuildImplWitness(Context& context, SemIR::ImplId impl_id)

toolchain/check/member_access.cpp

Lines changed: 137 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,17 @@
55
#include "llvm/ADT/STLExtras.h"
66
#include "toolchain/check/context.h"
77
#include "toolchain/check/convert.h"
8+
#include "toolchain/diagnostics/diagnostic_emitter.h"
89
#include "toolchain/sem_ir/inst.h"
910
#include "toolchain/sem_ir/typed_insts.h"
1011

1112
namespace Carbon::Check {
1213

1314
// Returns the name scope corresponding to base_id, or nullopt if not a scope.
1415
// On invalid scopes, prints a diagnostic and still returns the scope.
15-
static auto GetAsNameScope(Context& context, SemIR::InstId base_id)
16+
static auto GetAsNameScope(Context& context, Parse::NodeId node_id,
17+
SemIR::ConstantId base_const_id)
1618
-> std::optional<SemIR::NameScopeId> {
17-
auto base_const_id = context.constant_values().Get(base_id);
18-
if (!base_const_id.is_constant()) {
19-
// A name scope must be a constant.
20-
return std::nullopt;
21-
}
2219
auto base = context.insts().Get(base_const_id.inst_id());
2320
if (auto base_as_namespace = base.TryAs<SemIR::Namespace>()) {
2421
return base_as_namespace->name_scope_id;
@@ -31,9 +28,9 @@ static auto GetAsNameScope(Context& context, SemIR::InstId base_id)
3128
CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error,
3229
"Member access into incomplete class `{0}`.",
3330
std::string);
34-
auto builder =
35-
context.emitter().Build(base_id, QualifiedExprInIncompleteClassScope,
36-
context.sem_ir().StringifyTypeExpr(base_id));
31+
auto builder = context.emitter().Build(
32+
node_id, QualifiedExprInIncompleteClassScope,
33+
context.sem_ir().StringifyTypeExpr(base_const_id.inst_id()));
3734
context.NoteIncompleteClass(base_as_class->class_id, builder);
3835
builder.Emit();
3936
}
@@ -47,8 +44,8 @@ static auto GetAsNameScope(Context& context, SemIR::InstId base_id)
4744
"Member access into undefined interface `{0}`.",
4845
std::string);
4946
auto builder = context.emitter().Build(
50-
base_id, QualifiedExprInUndefinedInterfaceScope,
51-
context.sem_ir().StringifyTypeExpr(base_id));
47+
node_id, QualifiedExprInUndefinedInterfaceScope,
48+
context.sem_ir().StringifyTypeExpr(base_const_id.inst_id()));
5249
context.NoteUndefinedInterface(base_as_interface->interface_id, builder);
5350
builder.Emit();
5451
}
@@ -93,12 +90,111 @@ static auto IsInstanceMethod(const SemIR::File& sem_ir,
9390
return false;
9491
}
9592

96-
// Performs a member name lookup into the specified scope. If the scope is
97-
// invalid, assume an error has already been diagnosed, and return BuiltinError.
93+
// Returns whether `name_scope_id` is a scope for which impl lookup should be
94+
// performed if we find an associated entity.
95+
static auto ScopeNeedsImplLookup(Context& context,
96+
SemIR::NameScopeId name_scope_id) -> bool {
97+
auto inst_id = context.name_scopes().GetInstIdIfValid(name_scope_id);
98+
if (!inst_id.is_valid()) {
99+
return false;
100+
}
101+
102+
auto inst = context.insts().Get(inst_id);
103+
if (inst.Is<SemIR::InterfaceDecl>()) {
104+
// Don't perform impl lookup if an associated entity is named as a member of
105+
// a facet type.
106+
return false;
107+
}
108+
if (inst.Is<SemIR::Namespace>()) {
109+
// Don't perform impl lookup if an associated entity is named as a namespace
110+
// member.
111+
// TODO: This case is not yet listed in the design.
112+
return false;
113+
}
114+
// Any other kind of scope is assumed to be a type that implements the
115+
// interface containing the associated entity, and impl lookup is performed.
116+
return true;
117+
}
118+
119+
// Given a type and an interface, searches for an impl that describes how that
120+
// type implements that interface, and returns the corresponding witness.
121+
// Returns an invalid InstId if no matching impl is found.
122+
static auto LookupInterfaceWitness(Context& context,
123+
SemIR::ConstantId type_const_id,
124+
SemIR::InterfaceId interface_id)
125+
-> SemIR::InstId {
126+
// TODO: Add a better impl lookup system. At the very least, we should only be
127+
// considering impls that are for the same interface we're querying. We can
128+
// also skip impls that mention any types that aren't part of our impl query.
129+
for (const auto& impl : context.impls().array_ref()) {
130+
if (context.types().GetInstId(impl.self_id) != type_const_id.inst_id()) {
131+
continue;
132+
}
133+
auto interface_type =
134+
context.types().TryGetAs<SemIR::InterfaceType>(impl.constraint_id);
135+
if (!interface_type) {
136+
// TODO: An impl of a constraint type should be treated as implementing
137+
// the constraint's interfaces.
138+
continue;
139+
}
140+
if (interface_type->interface_id != interface_id) {
141+
continue;
142+
}
143+
// TODO: Diagnose if the impl isn't defined yet?
144+
return impl.witness_id;
145+
}
146+
return SemIR::InstId::Invalid;
147+
}
148+
149+
// Performs impl lookup for a member name expression. This finds the relevant
150+
// impl witness and extracts the corresponding impl member.
151+
static auto PerformImplLookup(Context& context, SemIR::ConstantId type_const_id,
152+
SemIR::AssociatedEntityType assoc_type,
153+
SemIR::InstId member_id) -> SemIR::InstId {
154+
auto witness_id =
155+
LookupInterfaceWitness(context, type_const_id, assoc_type.interface_id);
156+
if (!witness_id.is_valid()) {
157+
CARBON_DIAGNOSTIC(MissingImplInMemberAccess, Error,
158+
"Cannot access member of interface {0} in type {1} "
159+
"that does not implement that interface.",
160+
SemIR::NameId, std::string);
161+
context.emitter().Emit(
162+
member_id, MissingImplInMemberAccess,
163+
context.interfaces().Get(assoc_type.interface_id).name_id,
164+
context.sem_ir().StringifyTypeExpr(type_const_id.inst_id()));
165+
return SemIR::InstId::BuiltinError;
166+
}
167+
168+
auto const_id = context.constant_values().Get(member_id);
169+
if (!const_id.is_constant()) {
170+
if (const_id != SemIR::ConstantId::Error) {
171+
context.TODO(context.insts().GetNodeId(member_id),
172+
"non-constant associated entity");
173+
}
174+
return SemIR::InstId::BuiltinError;
175+
}
176+
177+
auto assoc_entity =
178+
context.insts().TryGetAs<SemIR::AssociatedEntity>(const_id.inst_id());
179+
if (!assoc_entity) {
180+
context.TODO(context.insts().GetNodeId(member_id),
181+
"unexpected value for associated entity");
182+
return SemIR::InstId::BuiltinError;
183+
}
184+
185+
// TODO: Substitute interface arguments and `Self` into `entity_type_id`.
186+
return context.AddInst(SemIR::InterfaceWitnessAccess{
187+
assoc_type.entity_type_id, witness_id, assoc_entity->index});
188+
}
189+
190+
// Performs a member name lookup into the specified scope, including performing
191+
// impl lookup if necessary. If the scope is invalid, assume an error has
192+
// already been diagnosed, and return BuiltinError.
98193
static auto LookupMemberNameInScope(Context& context,
99194
Parse::MemberAccessExprId node_id,
100195
SemIR::InstId /*base_id*/,
101196
SemIR::NameId name_id,
197+
SemIR::ConstantId name_scope_const_id,
102198
SemIR::NameScopeId name_scope_id)
103199
-> SemIR::InstId {
104200
auto inst_id = name_scope_id.is_valid() ? context.LookupQualifiedName(
@@ -107,8 +203,24 @@ static auto LookupMemberNameInScope(Context& context,
107203
auto inst = context.insts().Get(inst_id);
108204
// TODO: Use a different kind of instruction that also references the
109205
// `base_id` so that `SemIR` consumers can find it.
110-
return context.AddInst(
206+
auto member_id = context.AddInst(
111207
{node_id, SemIR::NameRef{inst.type_id(), name_id, inst_id}});
208+
209+
// If member name lookup finds an associated entity name, and the scope is not
210+
// a facet type, perform impl lookup.
211+
//
212+
// TODO: We need to do this as part of searching extended scopes, because a
213+
// lookup that finds an associated entity and also finds the corresponding
214+
// impl member is not supposed to be treated as ambiguous.
215+
if (auto assoc_type = context.types().TryGetAs<SemIR::AssociatedEntityType>(
216+
inst.type_id())) {
217+
if (ScopeNeedsImplLookup(context, name_scope_id)) {
218+
member_id = PerformImplLookup(context, name_scope_const_id, *assoc_type,
219+
member_id);
220+
}
221+
}
222+
223+
return member_id;
112224
}
113225

114226
// Performs the instance binding step in member access. If the found member is a
@@ -180,9 +292,12 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id,
180292
-> SemIR::InstId {
181293
// If the base is a name scope, such as a class or namespace, perform lookup
182294
// into that scope.
183-
if (auto name_scope_id = GetAsNameScope(context, base_id)) {
184-
return LookupMemberNameInScope(context, node_id, base_id, name_id,
185-
*name_scope_id);
295+
if (auto base_const_id = context.constant_values().Get(base_id);
296+
base_const_id.is_constant()) {
297+
if (auto name_scope_id = GetAsNameScope(context, node_id, base_const_id)) {
298+
return LookupMemberNameInScope(context, node_id, base_id, name_id,
299+
base_const_id, *name_scope_id);
300+
}
186301
}
187302

188303
// If the base isn't a scope, it must have a complete type.
@@ -200,14 +315,14 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id,
200315
// Materialize a temporary for the base expression if necessary.
201316
base_id = ConvertToValueOrRefExpr(context, base_id);
202317
base_type_id = context.insts().Get(base_id).type_id();
203-
auto base_type_inst_id = context.types().GetInstId(base_type_id);
318+
auto base_type_const_id = context.types().GetConstantId(base_type_id);
204319

205320
// Find the scope corresponding to the base type.
206-
auto name_scope_id = GetAsNameScope(context, base_type_inst_id);
321+
auto name_scope_id = GetAsNameScope(context, node_id, base_type_const_id);
207322
if (!name_scope_id) {
208323
// The base type is not a name scope. Try some fallback options.
209-
if (auto struct_type =
210-
context.insts().TryGetAs<SemIR::StructType>(base_type_inst_id)) {
324+
if (auto struct_type = context.insts().TryGetAs<SemIR::StructType>(
325+
base_type_const_id.inst_id())) {
211326
// TODO: Do we need to optimize this with a lookup table for O(1)?
212327
for (auto [i, ref_id] :
213328
llvm::enumerate(context.inst_blocks().Get(struct_type->fields_id))) {
@@ -239,7 +354,7 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id,
239354

240355
// Perform lookup into the base type.
241356
auto member_id = LookupMemberNameInScope(context, node_id, base_id, name_id,
242-
*name_scope_id);
357+
base_type_const_id, *name_scope_id);
243358

244359
// Perform instance binding if we found an instance member.
245360
member_id = PerformInstanceBinding(context, node_id, base_id, member_id);

toolchain/check/testdata/class/fail_incomplete.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ fn Class.Function() {}
1717
fn CallClassFunction() {
1818
// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:3: ERROR: Member access into incomplete class `Class`.
1919
// CHECK:STDERR: Class.Function();
20-
// CHECK:STDERR: ^~~~~
20+
// CHECK:STDERR: ^~~~~~~~~~~~~~
2121
// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-14]]:1: Class was forward declared here.
2222
// CHECK:STDERR: class Class;
2323
// CHECK:STDERR: ^~~~~~~~~~~~

toolchain/check/testdata/class/fail_reorder.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Class {
1010
// later.
1111
// CHECK:STDERR: fail_reorder.carbon:[[@LINE+9]]:12: ERROR: Member access into incomplete class `Class`.
1212
// CHECK:STDERR: return Class.F();
13-
// CHECK:STDERR: ^~~~~
13+
// CHECK:STDERR: ^~~~~~~
1414
// CHECK:STDERR: fail_reorder.carbon:[[@LINE-7]]:1: Class is incomplete within its definition.
1515
// CHECK:STDERR: class Class {
1616
// CHECK:STDERR: ^~~~~~~~~~~~~

toolchain/check/testdata/impl/basic.carbon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ impl i32 as Simple {
1818
// CHECK:STDOUT: %.1: type = interface_type @Simple [template]
1919
// CHECK:STDOUT: %.2: type = assoc_entity_type @Simple, <function> [template]
2020
// CHECK:STDOUT: %.3: <associated <function> in Simple> = assoc_entity element0, @Simple.%F [template]
21-
// CHECK:STDOUT: %.4: <witness> = interface_witness @Simple, (@impl.%F) [template]
21+
// CHECK:STDOUT: %.4: <witness> = interface_witness (@impl.%F) [template]
2222
// CHECK:STDOUT: }
2323
// CHECK:STDOUT:
2424
// CHECK:STDOUT: file {
@@ -44,7 +44,7 @@ impl i32 as Simple {
4444
// CHECK:STDOUT:
4545
// CHECK:STDOUT: impl @impl: i32 as Simple {
4646
// CHECK:STDOUT: %F: <function> = fn_decl @F.2 [template] {}
47-
// CHECK:STDOUT: %.1: <witness> = interface_witness @Simple, (%F) [template = constants.%.4]
47+
// CHECK:STDOUT: %.1: <witness> = interface_witness (%F) [template = constants.%.4]
4848
// CHECK:STDOUT:
4949
// CHECK:STDOUT: !members:
5050
// CHECK:STDOUT: .F = %F

toolchain/check/testdata/impl/empty.carbon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ impl i32 as Empty {
1414
// CHECK:STDOUT:
1515
// CHECK:STDOUT: constants {
1616
// CHECK:STDOUT: %.1: type = interface_type @Empty [template]
17-
// CHECK:STDOUT: %.2: <witness> = interface_witness @Empty, () [template]
17+
// CHECK:STDOUT: %.2: <witness> = interface_witness () [template]
1818
// CHECK:STDOUT: }
1919
// CHECK:STDOUT:
2020
// CHECK:STDOUT: file {
@@ -36,7 +36,7 @@ impl i32 as Empty {
3636
// CHECK:STDOUT: }
3737
// CHECK:STDOUT:
3838
// CHECK:STDOUT: impl @impl: i32 as Empty {
39-
// CHECK:STDOUT: %.1: <witness> = interface_witness @Empty, () [template = constants.%.2]
39+
// CHECK:STDOUT: %.1: <witness> = interface_witness () [template = constants.%.2]
4040
// CHECK:STDOUT:
4141
// CHECK:STDOUT: !members:
4242
// CHECK:STDOUT: witness = %.1

toolchain/check/testdata/impl/fail_todo_extend_impl.carbon renamed to toolchain/check/testdata/impl/extend_impl.carbon

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,18 @@ class C {
1515
}
1616

1717
fn G(c: C) {
18-
// TODO: These should do impl lookup. Because that's not implemented yet, they
19-
// refer to the interface members rather than to the impl members.
20-
// CHECK:STDERR: fail_todo_extend_impl.carbon:[[@LINE+3]]:3: ERROR: Value of type `<associated <function> in HasF>` is not callable.
21-
// CHECK:STDERR: C.F();
22-
// CHECK:STDERR: ^~~~
2318
C.F();
24-
// CHECK:STDERR: fail_todo_extend_impl.carbon:[[@LINE+3]]:3: ERROR: Value of type `<associated <function> in HasF>` is not callable.
25-
// CHECK:STDERR: c.F();
26-
// CHECK:STDERR: ^~~~
2719
c.F();
2820
}
2921

30-
// CHECK:STDOUT: --- fail_todo_extend_impl.carbon
22+
// CHECK:STDOUT: --- extend_impl.carbon
3123
// CHECK:STDOUT:
3224
// CHECK:STDOUT: constants {
3325
// CHECK:STDOUT: %.1: type = interface_type @HasF [template]
3426
// CHECK:STDOUT: %.2: type = assoc_entity_type @HasF, <function> [template]
3527
// CHECK:STDOUT: %.3: <associated <function> in HasF> = assoc_entity element0, @HasF.%F [template]
3628
// CHECK:STDOUT: %C: type = class_type @C [template]
37-
// CHECK:STDOUT: %.4: <witness> = interface_witness @HasF, (@impl.%F) [template]
29+
// CHECK:STDOUT: %.4: <witness> = interface_witness (@impl.%F) [template]
3830
// CHECK:STDOUT: %.5: type = struct_type {} [template]
3931
// CHECK:STDOUT: %.6: type = tuple_type () [template]
4032
// CHECK:STDOUT: %.7: type = ptr_type {} [template]
@@ -68,7 +60,7 @@ fn G(c: C) {
6860
// CHECK:STDOUT:
6961
// CHECK:STDOUT: impl @impl: C as HasF {
7062
// CHECK:STDOUT: %F: <function> = fn_decl @F.2 [template] {}
71-
// CHECK:STDOUT: %.1: <witness> = interface_witness @HasF, (%F) [template = constants.%.4]
63+
// CHECK:STDOUT: %.1: <witness> = interface_witness (%F) [template = constants.%.4]
7264
// CHECK:STDOUT:
7365
// CHECK:STDOUT: !members:
7466
// CHECK:STDOUT: .F = %F
@@ -95,9 +87,13 @@ fn G(c: C) {
9587
// CHECK:STDOUT: fn @G(%c: C) {
9688
// CHECK:STDOUT: !entry:
9789
// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
98-
// CHECK:STDOUT: %F.ref.loc23: <associated <function> in HasF> = name_ref F, @HasF.%.loc8 [template = constants.%.3]
90+
// CHECK:STDOUT: %F.ref.loc18: <associated <function> in HasF> = name_ref F, @HasF.%.loc8 [template = constants.%.3]
91+
// CHECK:STDOUT: %.1: <function> = interface_witness_access @impl.%.1, element0 [template = @impl.%F]
92+
// CHECK:STDOUT: %.loc18: init () = call %.1()
9993
// CHECK:STDOUT: %c.ref: C = name_ref c, %c
100-
// CHECK:STDOUT: %F.ref.loc27: <associated <function> in HasF> = name_ref F, @HasF.%.loc8 [template = constants.%.3]
94+
// CHECK:STDOUT: %F.ref.loc19: <associated <function> in HasF> = name_ref F, @HasF.%.loc8 [template = constants.%.3]
95+
// CHECK:STDOUT: %.2: <function> = interface_witness_access @impl.%.1, element0 [template = @impl.%F]
96+
// CHECK:STDOUT: %.loc19: init () = call %.2()
10197
// CHECK:STDOUT: return
10298
// CHECK:STDOUT: }
10399
// CHECK:STDOUT:

0 commit comments

Comments
 (0)