forked from carbon-language/carbon-lang
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathimpl_scope.cpp
445 lines (412 loc) · 18.3 KB
/
impl_scope.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
// 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
#include "explorer/interpreter/impl_scope.h"
#include "explorer/ast/value.h"
#include "explorer/interpreter/type_checker.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Casting.h"
using llvm::cast;
using llvm::dyn_cast;
namespace Carbon {
void ImplScope::Add(Nonnull<const Value*> iface, Nonnull<const Value*> type,
Nonnull<const Witness*> witness,
const TypeChecker& type_checker) {
Add(iface, {}, type, {}, witness, type_checker);
}
void ImplScope::Add(Nonnull<const Value*> iface,
llvm::ArrayRef<Nonnull<const GenericBinding*>> deduced,
Nonnull<const Value*> type,
llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
Nonnull<const Witness*> witness,
const TypeChecker& type_checker,
std::optional<TypeStructureSortKey> sort_key) {
if (const auto* constraint = dyn_cast<ConstraintType>(iface)) {
CARBON_CHECK(!sort_key)
<< "should only be given a sort key for an impl of an interface";
// The caller should have substituted `.Self` for `type` already.
Add(constraint->impls_constraints(), deduced, impl_bindings, witness,
type_checker);
// A parameterized impl declaration doesn't contribute any equality
// constraints to the scope. Instead, we'll resolve the equality
// constraints by resolving a witness when needed.
if (deduced.empty()) {
for (const auto& equality_constraint :
constraint->equality_constraints()) {
equalities_.push_back(&equality_constraint);
}
}
return;
}
ImplFact new_impl = {.interface = cast<InterfaceType>(iface),
.deduced = deduced,
.type = type,
.impl_bindings = impl_bindings,
.witness = witness,
.sort_key = std::move(sort_key)};
// Find the first impl that's more specific than this one, and place this
// impl right before it. This keeps the impls with the same type structure
// sorted in lexical order, which is important for `match_first` semantics.
auto insert_pos =
std::upper_bound(impl_facts_.begin(), impl_facts_.end(), new_impl,
[](const ImplFact& a, const ImplFact& b) {
return a.sort_key < b.sort_key;
});
impl_facts_.insert(insert_pos, std::move(new_impl));
}
void ImplScope::Add(llvm::ArrayRef<ImplsConstraint> impls_constraints,
llvm::ArrayRef<Nonnull<const GenericBinding*>> deduced,
llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
Nonnull<const Witness*> witness,
const TypeChecker& type_checker) {
for (size_t i = 0; i != impls_constraints.size(); ++i) {
ImplsConstraint impl = impls_constraints[i];
Add(impl.interface, deduced, impl.type, impl_bindings,
type_checker.MakeConstraintWitnessAccess(witness, i), type_checker);
}
}
// Diagnose that `a_evaluated != b_evaluated` for the purpose of an equality
// constraint.
static auto DiagnoseUnequalValues(SourceLocation source_loc,
Nonnull<const Value*> a_written,
Nonnull<const Value*> a_evaluated,
Nonnull<const Value*> b_written,
Nonnull<const Value*> b_evaluated,
Nonnull<const EqualityContext*> equality_ctx)
-> Error {
CARBON_CHECK(!ValueEqual(a_evaluated, b_evaluated, equality_ctx))
<< "expected unequal values";
auto error = ProgramError(source_loc);
error << "constraint requires that " << *a_written;
if (!ValueEqual(a_written, a_evaluated, std::nullopt)) {
error << " (with value " << *a_evaluated << ")";
}
error << " == " << *b_written;
if (!ValueEqual(b_written, b_evaluated, std::nullopt)) {
error << " (with value " << *b_evaluated << ")";
}
error << ", which is not known to be true";
return std::move(error);
}
auto ImplScope::Resolve(Nonnull<const Value*> constraint_type,
Nonnull<const Value*> impl_type,
SourceLocation source_loc,
const TypeChecker& type_checker,
const Bindings& bindings) const
-> ErrorOr<Nonnull<const Witness*>> {
CARBON_ASSIGN_OR_RETURN(
std::optional<Nonnull<const Witness*>> witness,
TryResolve(constraint_type, impl_type, source_loc, type_checker, bindings,
/*diagnose_missing_impl=*/true));
CARBON_CHECK(witness) << "should have diagnosed missing impl";
return *witness;
}
auto ImplScope::TryResolve(Nonnull<const Value*> constraint_type,
Nonnull<const Value*> impl_type,
SourceLocation source_loc,
const TypeChecker& type_checker,
const Bindings& bindings,
bool diagnose_missing_impl) const
-> ErrorOr<std::optional<Nonnull<const Witness*>>> {
if (const auto* iface_type = dyn_cast<InterfaceType>(constraint_type)) {
CARBON_ASSIGN_OR_RETURN(
iface_type,
type_checker.SubstituteCast<InterfaceType>(bindings, iface_type));
return TryResolveInterface(iface_type, impl_type, source_loc, type_checker,
diagnose_missing_impl);
}
if (const auto* constraint = dyn_cast<ConstraintType>(constraint_type)) {
std::vector<Nonnull<const Witness*>> witnesses;
for (auto impl : constraint->impls_constraints()) {
// Note that later impls constraints can refer to earlier impls
// constraints via impl bindings. For example, in
// `C where .Self.AssocType impls D`,
// ... the `.Self.AssocType impls D` constraint refers to the
// `.Self impls C` constraint when naming `AssocType`. So incrementally
// build up a partial constraint witness as we go.
std::optional<Nonnull<const Witness*>> witness;
if (constraint->self_binding()->impl_binding()) {
// Note, this is a partial impl binding covering only the impl
// constraints that we've already seen. Earlier impls constraints should
// not be able to refer to impl bindings for later impls constraints.
witness = type_checker.MakeConstraintWitness(witnesses);
}
Bindings local_bindings = bindings;
local_bindings.Add(constraint->self_binding(), impl_type, witness);
CARBON_ASSIGN_OR_RETURN(const auto* subst_interface,
type_checker.SubstituteCast<InterfaceType>(
local_bindings, impl.interface));
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> subst_type,
type_checker.Substitute(local_bindings, impl.type));
CARBON_ASSIGN_OR_RETURN(
std::optional<Nonnull<const Witness*>> result,
TryResolveInterface(subst_interface, subst_type, source_loc,
type_checker, diagnose_missing_impl));
if (!result) {
return {std::nullopt};
}
witnesses.push_back(*result);
}
// Check that all intrinsic, equality, and rewrite constraints
// are satisfied in this scope.
llvm::ArrayRef<IntrinsicConstraint> intrinsics =
constraint->intrinsic_constraints();
llvm::ArrayRef<EqualityConstraint> equals =
constraint->equality_constraints();
llvm::ArrayRef<RewriteConstraint> rewrites =
constraint->rewrite_constraints();
if (!intrinsics.empty() || !equals.empty() || !rewrites.empty()) {
std::optional<Nonnull<const Witness*>> witness;
if (constraint->self_binding()->impl_binding()) {
witness = type_checker.MakeConstraintWitness(witnesses);
}
Bindings local_bindings = bindings;
local_bindings.Add(constraint->self_binding(), impl_type, witness);
SingleStepEqualityContext equality_ctx(this);
for (const auto& intrinsic : intrinsics) {
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> type,
type_checker.Substitute(local_bindings, intrinsic.type));
IntrinsicConstraint converted(type, intrinsic.kind, {});
converted.arguments.reserve(intrinsic.arguments.size());
for (Nonnull<const Value*> argument : intrinsic.arguments) {
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> subst_arg,
type_checker.Substitute(local_bindings, argument));
converted.arguments.push_back(subst_arg);
}
CARBON_ASSIGN_OR_RETURN(bool intrinsic_satisfied,
type_checker.IsIntrinsicConstraintSatisfied(
source_loc, converted, *this));
if (!intrinsic_satisfied) {
if (!diagnose_missing_impl) {
return {std::nullopt};
}
return ProgramError(source_loc)
<< "constraint requires that " << converted;
}
}
for (const auto& equal : equals) {
auto it = equal.values.begin();
CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> first,
type_checker.Substitute(local_bindings, *it++));
for (; it != equal.values.end(); ++it) {
CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> current,
type_checker.Substitute(local_bindings, *it));
if (!ValueEqual(first, current, &equality_ctx)) {
if (!diagnose_missing_impl) {
return {std::nullopt};
}
return DiagnoseUnequalValues(source_loc, equal.values.front(),
first, *it, current, &equality_ctx);
}
}
}
for (const auto& rewrite : rewrites) {
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> constant,
type_checker.Substitute(local_bindings, rewrite.constant));
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> value,
type_checker.Substitute(local_bindings,
rewrite.converted_replacement));
if (!ValueEqual(constant, value, &equality_ctx)) {
if (!diagnose_missing_impl) {
return {std::nullopt};
}
return DiagnoseUnequalValues(source_loc, rewrite.constant, constant,
rewrite.converted_replacement, value,
&equality_ctx);
}
}
}
return {type_checker.MakeConstraintWitness(std::move(witnesses))};
}
CARBON_FATAL() << "expected a constraint, not " << *constraint_type;
}
auto ImplScope::VisitEqualValues(
Nonnull<const Value*> value,
llvm::function_ref<bool(Nonnull<const Value*>)> visitor) const -> bool {
for (Nonnull<const EqualityConstraint*> eq : equalities_) {
if (!eq->VisitEqualValues(value, visitor)) {
return false;
}
}
return !parent_scope_ || (*parent_scope_)->VisitEqualValues(value, visitor);
}
auto ImplScope::TryResolveInterface(Nonnull<const InterfaceType*> iface_type,
Nonnull<const Value*> type,
SourceLocation source_loc,
const TypeChecker& type_checker,
bool diagnose_missing_impl) const
-> ErrorOr<std::optional<Nonnull<const Witness*>>> {
CARBON_ASSIGN_OR_RETURN(
std::optional<ResolveResult> result,
TryResolveInterfaceRecursively(iface_type, type, source_loc, *this,
type_checker));
if (!result.has_value() && diagnose_missing_impl) {
return ProgramError(source_loc) << "could not find implementation of "
<< *iface_type << " for " << *type;
}
return result ? std::optional(result->witness) : std::nullopt;
}
// Do these two witnesses refer to `impl` declarations in the same
// `match_first` block?
static auto InSameMatchFirst(Nonnull<const Witness*> a,
Nonnull<const Witness*> b) -> bool {
const auto* impl_a = dyn_cast<ImplWitness>(a);
const auto* impl_b = dyn_cast<ImplWitness>(b);
if (!impl_a || !impl_b) {
return false;
}
// TODO: Once we support an impl being declared more than once, we will need
// to check this more carefully.
return impl_a->declaration().match_first() &&
impl_a->declaration().match_first() ==
impl_b->declaration().match_first();
}
// Determine whether this result is definitely right -- that there can be no
// specialization that would give a better match.
static auto IsEffectivelyFinal(ImplScope::ResolveResult result) -> bool {
// TODO: Once we support 'final', check whether this is a final impl
// declaration if it's parameterized.
return result.impl->deduced.empty();
}
// Combines the results of two impl lookups. In the event of a tie, arbitrarily
// prefer `a` over `b`.
static auto CombineResults(Nonnull<const InterfaceType*> iface_type,
Nonnull<const Value*> type,
SourceLocation source_loc,
std::optional<ImplScope::ResolveResult> a,
std::optional<ImplScope::ResolveResult> b)
-> ErrorOr<std::optional<ImplScope::ResolveResult>> {
// If only one lookup succeeded, return that.
if (!b) {
return a;
}
if (!a) {
return b;
}
// If exactly one of them is effectively final, prefer that result.
bool a_is_final = IsEffectivelyFinal(*a);
bool b_is_final = IsEffectivelyFinal(*b);
if (a_is_final && !b_is_final) {
return a;
} else if (b_is_final && !a_is_final) {
return b;
}
const auto* impl_a = dyn_cast<ImplWitness>(a->witness);
const auto* impl_b = dyn_cast<ImplWitness>(b->witness);
// If both are effectively final, prefer an impl declaration over a
// symbolic ImplBinding, because we get more information from the impl
// declaration. If they're both symbolic, arbitrarily pick a.
if (a_is_final && b_is_final) {
if (!impl_b) {
return a;
}
if (!impl_a) {
return b;
}
}
CARBON_CHECK(impl_a && impl_b) << "non-final impl should not be symbolic";
// At this point, we're comparing two `impl` declarations, and either they're
// both final or neither of them is.
// TODO: We should reject the case where both are final when checking their
// declarations, but we don't do so yet, so for now we report it as an
// ambiguity.
//
// If they refer to the same `impl` declaration, it doesn't matter which one
// we pick, so we pick `a`.
// TODO: Compare the identities of the `impl`s, not the declarations.
if (&impl_a->declaration() == &impl_b->declaration()) {
return a;
}
return ProgramError(source_loc)
<< "ambiguous implementations of " << *iface_type << " for " << *type;
}
auto ImplScope::TryResolveInterfaceRecursively(
Nonnull<const InterfaceType*> iface_type, Nonnull<const Value*> type,
SourceLocation source_loc, const ImplScope& original_scope,
const TypeChecker& type_checker) const
-> ErrorOr<std::optional<ResolveResult>> {
CARBON_ASSIGN_OR_RETURN(
std::optional<ResolveResult> result,
TryResolveInterfaceHere(iface_type, type, source_loc, original_scope,
type_checker));
if (parent_scope_) {
CARBON_ASSIGN_OR_RETURN(
std::optional<ResolveResult> parent_result,
(*parent_scope_)
->TryResolveInterfaceRecursively(iface_type, type, source_loc,
original_scope, type_checker));
CARBON_ASSIGN_OR_RETURN(result, CombineResults(iface_type, type, source_loc,
result, parent_result));
}
return result;
}
auto ImplScope::TryResolveInterfaceHere(
Nonnull<const InterfaceType*> iface_type, Nonnull<const Value*> impl_type,
SourceLocation source_loc, const ImplScope& original_scope,
const TypeChecker& type_checker) const
-> ErrorOr<std::optional<ResolveResult>> {
std::optional<ResolveResult> result = std::nullopt;
for (const ImplFact& impl : impl_facts_) {
// If we've passed the final impl with a sort key matching our best impl,
// all further are worse and don't need to be checked.
if (result && result->impl->sort_key < impl.sort_key) {
break;
}
// If this impl appears later in the same match_first block as our best
// result, we should not consider it.
//
// TODO: This should apply transitively: if we have
// match_first { impl a; impl b; }
// match_first { impl b; impl c; }
// then we should not consider c once we match a. For now, because each
// impl is only declared once, this is not a problem.
if (result && InSameMatchFirst(result->impl->witness, impl.witness)) {
continue;
}
// Try matching this impl against our query.
CARBON_ASSIGN_OR_RETURN(std::optional<Nonnull<const Witness*>> witness,
type_checker.MatchImpl(*iface_type, impl_type, impl,
original_scope, source_loc));
if (witness) {
CARBON_ASSIGN_OR_RETURN(
result,
CombineResults(iface_type, impl_type, source_loc, result,
ResolveResult{.impl = &impl, .witness = *witness}));
}
}
return result;
}
// TODO: Add indentation when printing the parents.
void ImplScope::Print(llvm::raw_ostream& out) const {
llvm::ListSeparator sep(",\n ");
out << " "
<< "[";
for (const ImplFact& impl : impl_facts_) {
out << sep << "`" << *(impl.type) << "` as `" << *(impl.interface) << "`";
if (impl.sort_key) {
out << " " << *impl.sort_key;
}
}
for (Nonnull<const EqualityConstraint*> eq : equalities_) {
out << sep;
llvm::ListSeparator equal(" == ");
for (Nonnull<const Value*> value : eq->values) {
out << equal << "`" << *value << "`";
}
}
out << "]\n";
if (parent_scope_) {
out << **parent_scope_;
}
}
auto SingleStepEqualityContext::VisitEqualValues(
Nonnull<const Value*> value,
llvm::function_ref<bool(Nonnull<const Value*>)> visitor) const -> bool {
return impl_scope_->VisitEqualValues(value, visitor);
}
} // namespace Carbon