5
5
#include " llvm/ADT/STLExtras.h"
6
6
#include " toolchain/check/context.h"
7
7
#include " toolchain/check/convert.h"
8
+ #include " toolchain/diagnostics/diagnostic_emitter.h"
8
9
#include " toolchain/sem_ir/inst.h"
9
10
#include " toolchain/sem_ir/typed_insts.h"
10
11
11
12
namespace Carbon ::Check {
12
13
13
14
// Returns the name scope corresponding to base_id, or nullopt if not a scope.
14
15
// 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)
16
18
-> 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
- }
22
19
auto base = context.insts ().Get (base_const_id.inst_id ());
23
20
if (auto base_as_namespace = base.TryAs <SemIR::Namespace>()) {
24
21
return base_as_namespace->name_scope_id ;
@@ -31,9 +28,9 @@ static auto GetAsNameScope(Context& context, SemIR::InstId base_id)
31
28
CARBON_DIAGNOSTIC (QualifiedExprInIncompleteClassScope, Error,
32
29
" Member access into incomplete class `{0}`." ,
33
30
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 () ));
37
34
context.NoteIncompleteClass (base_as_class->class_id , builder);
38
35
builder.Emit ();
39
36
}
@@ -47,8 +44,8 @@ static auto GetAsNameScope(Context& context, SemIR::InstId base_id)
47
44
" Member access into undefined interface `{0}`." ,
48
45
std::string);
49
46
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 () ));
52
49
context.NoteUndefinedInterface (base_as_interface->interface_id , builder);
53
50
builder.Emit ();
54
51
}
@@ -93,12 +90,111 @@ static auto IsInstanceMethod(const SemIR::File& sem_ir,
93
90
return false ;
94
91
}
95
92
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.
98
193
static auto LookupMemberNameInScope (Context& context,
99
194
Parse::MemberAccessExprId node_id,
100
195
SemIR::InstId /* base_id*/ ,
101
196
SemIR::NameId name_id,
197
+ SemIR::ConstantId name_scope_const_id,
102
198
SemIR::NameScopeId name_scope_id)
103
199
-> SemIR::InstId {
104
200
auto inst_id = name_scope_id.is_valid () ? context.LookupQualifiedName (
@@ -107,8 +203,24 @@ static auto LookupMemberNameInScope(Context& context,
107
203
auto inst = context.insts ().Get (inst_id);
108
204
// TODO: Use a different kind of instruction that also references the
109
205
// `base_id` so that `SemIR` consumers can find it.
110
- return context.AddInst (
206
+ auto member_id = context.AddInst (
111
207
{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;
112
224
}
113
225
114
226
// 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,
180
292
-> SemIR::InstId {
181
293
// If the base is a name scope, such as a class or namespace, perform lookup
182
294
// 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
+ }
186
301
}
187
302
188
303
// 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,
200
315
// Materialize a temporary for the base expression if necessary.
201
316
base_id = ConvertToValueOrRefExpr (context, base_id);
202
317
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);
204
319
205
320
// 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 );
207
322
if (!name_scope_id) {
208
323
// 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 () )) {
211
326
// TODO: Do we need to optimize this with a lookup table for O(1)?
212
327
for (auto [i, ref_id] :
213
328
llvm::enumerate (context.inst_blocks ().Get (struct_type->fields_id ))) {
@@ -239,7 +354,7 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id,
239
354
240
355
// Perform lookup into the base type.
241
356
auto member_id = LookupMemberNameInScope (context, node_id, base_id, name_id,
242
- *name_scope_id);
357
+ base_type_const_id, *name_scope_id);
243
358
244
359
// Perform instance binding if we found an instance member.
245
360
member_id = PerformInstanceBinding (context, node_id, base_id, member_id);
0 commit comments