Skip to content

Commit d6be206

Browse files
authored
Use earlier require decls inside a named constraint to provide witnesses for Self (#6915)
Performing a lookup against `Self` inside the definition of the named constraint leads to cycles, as described in the document [Self contradictions in Named Constraints](https://docs.google.com/document/d/17rn2XmME8o2MM4OJqatSVuMa1iYZ1PAgcNrf0PXR9Q4/edit?tab=t.0). To prevent those cycles, this change introduces a large refactoring of impl lookup. The impl lookup done inside eval is reduced to only performing monomorphization. That is it: - Only looks for an provides final witnesses. - Is not allowed to identify the facet type of the query self. - Returns either a final witness or None (or an error) The paths for finding non-final witnesses are now done outside of eval, directly in the initial `LookupImplWitness()` function. If no final witness it found through eval, the resulting non-final `LookupImplWitness` instruction witness is returned. It does not produce cycles to identify the facet type of query self outside of eval, since that does not result in repeating the identification when resolving specifics of the named constraint or require decl. Move the ArrayStack for Context::require_impls_stack into a new class which tracks a NamedConstraintId (or InterfaceId) for each frame of RequireImplsIds, so that in type completion we always can find the correct frame for a given named constraint which is still being defined, in order to find the RequireImplsIds in the in-progress definition.
1 parent 4a0c1dd commit d6be206

File tree

66 files changed

+1428
-887
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1428
-887
lines changed

toolchain/check/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ cc_library(
6666
"pattern.cpp",
6767
"pattern_match.cpp",
6868
"pointer_dereference.cpp",
69+
"require_impls_stack.cpp",
6970
"return.cpp",
7071
"scope_stack.cpp",
7172
"subst.cpp",
@@ -134,6 +135,7 @@ cc_library(
134135
"pending_block.h",
135136
"pointer_dereference.h",
136137
"region_stack.h",
138+
"require_impls_stack.h",
137139
"return.h",
138140
"scope_index.h",
139141
"scope_stack.h",

toolchain/check/check_unit.cpp

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -530,20 +530,21 @@ auto CheckUnit::CheckPoisonedConcreteImplLookupQueries() -> void {
530530
auto poisoned_queries =
531531
std::exchange(context_.poisoned_concrete_impl_lookup_queries(), {});
532532
for (const auto& poison : poisoned_queries) {
533-
auto witness_result = EvalLookupSingleImplWitness(
534-
context_, poison.loc_id, poison.query, poison.query.query_self_inst_id,
533+
auto found_witness_id = EvalLookupSingleFinalWitness(
534+
context_, poison.loc_id, poison.query, SemIR::InstId::None,
535535
EvalImplLookupMode::RecheckPoisonedLookup);
536-
CARBON_CHECK(witness_result.has_final_value());
537-
auto found_witness_id = witness_result.final_witness();
538-
if (found_witness_id == SemIR::ErrorInst::InstId) {
536+
CARBON_CHECK(found_witness_id.has_value());
537+
if (found_witness_id == SemIR::ErrorInst::ConstantId) {
539538
// Errors may have been diagnosed with the impl used in the poisoned query
540539
// in the meantime (such as a missing definition).
541540
continue;
542541
}
543-
if (found_witness_id != poison.impl_witness) {
544-
auto witness_to_impl_id = [&](SemIR::InstId witness_id) {
542+
if (found_witness_id != poison.witness_id) {
543+
auto witness_to_impl_id = [&](SemIR::ConstantId witness_id) {
544+
// TODO: Add and use constant_values().GetAs<SemIR::FacetType>().
545+
auto inst_id = context_.constant_values().GetInstId(witness_id);
545546
auto table_id = context_.insts()
546-
.GetAs<SemIR::ImplWitness>(witness_id)
547+
.GetAs<SemIR::ImplWitness>(inst_id)
547548
.witness_table_id;
548549
return context_.insts()
549550
.GetAs<SemIR::ImplWitnessTable>(table_id)
@@ -555,7 +556,7 @@ auto CheckUnit::CheckPoisonedConcreteImplLookupQueries() -> void {
555556
auto bad_impl_id = witness_to_impl_id(found_witness_id);
556557
const auto& bad_impl = context_.impls().Get(bad_impl_id);
557558

558-
auto prev_impl_id = witness_to_impl_id(poison.impl_witness);
559+
auto prev_impl_id = witness_to_impl_id(poison.witness_id);
559560
const auto& prev_impl = context_.impls().Get(prev_impl_id);
560561

561562
CARBON_DIAGNOSTIC(

toolchain/check/context.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "toolchain/check/node_stack.h"
2626
#include "toolchain/check/param_and_arg_refs_stack.h"
2727
#include "toolchain/check/region_stack.h"
28+
#include "toolchain/check/require_impls_stack.h"
2829
#include "toolchain/check/scope_stack.h"
2930
#include "toolchain/diagnostics/emitter.h"
3031
#include "toolchain/parse/node_ids.h"
@@ -125,7 +126,7 @@ class Context {
125126
return field_decls_stack_;
126127
}
127128

128-
auto require_impls_stack() -> ArrayStack<SemIR::RequireImplsId>& {
129+
auto require_impls_stack() -> RequireImplsStack& {
129130
return require_impls_stack_;
130131
}
131132

@@ -221,6 +222,9 @@ class Context {
221222
SemIR::ConstantId query_facet_type_const_id;
222223
// The location of the impl being looked at for the stack entry.
223224
SemIR::InstId impl_loc = SemIR::InstId::None;
225+
// TODO: If we make a proper stack class, we only need one `diagnosed_cycle`
226+
// field, which resets to false when the stack becomes empty.
227+
bool diagnosed_cycle = false;
224228
};
225229
auto impl_lookup_stack() -> llvm::SmallVector<ImplLookupStackEntry>& {
226230
return impl_lookup_stack_;
@@ -229,7 +233,7 @@ class Context {
229233
// A map from a (self, interface) pair to a final witness.
230234
using ImplLookupCacheKey =
231235
std::pair<SemIR::ConstantId, SemIR::SpecificInterfaceId>;
232-
using ImplLookupCacheMap = Map<ImplLookupCacheKey, SemIR::InstId>;
236+
using ImplLookupCacheMap = Map<ImplLookupCacheKey, SemIR::ConstantId>;
233237
auto impl_lookup_cache() -> ImplLookupCacheMap& { return impl_lookup_cache_; }
234238

235239
// An impl lookup query that resulted in a concrete witness from finding an
@@ -240,8 +244,8 @@ class Context {
240244
SemIR::LocId loc_id;
241245
// The query for a witness of an impl for an interface.
242246
SemIR::LookupImplWitness query;
243-
// The resulting ImplWitness.
244-
SemIR::InstId impl_witness;
247+
// The resulting final witness.
248+
SemIR::ConstantId witness_id;
245249
};
246250
auto poisoned_concrete_impl_lookup_queries()
247251
-> llvm::SmallVector<PoisonedConcreteImplLookupQuery>& {
@@ -425,7 +429,7 @@ class Context {
425429

426430
// The stack of RequireImpls for in-progress Interface and Constraint
427431
// definitions.
428-
ArrayStack<SemIR::RequireImplsId> require_impls_stack_;
432+
RequireImplsStack require_impls_stack_;
429433

430434
// The stack used for qualified declaration name construction.
431435
DeclNameStack decl_name_stack_;

toolchain/check/convert.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1512,7 +1512,7 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
15121512
// by finding impl witnesses for the target FacetType.
15131513
auto lookup_result = LookupImplWitness(
15141514
context, loc_id, sem_ir.constant_values().Get(type_inst_id),
1515-
sem_ir.types().GetConstantId(target.type_id));
1515+
sem_ir.types().GetConstantId(target.type_id), target.diagnose);
15161516
if (lookup_result.has_value()) {
15171517
if (lookup_result.has_error_value()) {
15181518
return SemIR::ErrorInst::InstId;

toolchain/check/custom_witness.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ static auto TypeCanDestroy(Context& context,
377377
static auto MakeDestroyWitness(
378378
Context& context, SemIR::LocId loc_id,
379379
SemIR::ConstantId query_self_const_id,
380-
SemIR::SpecificInterfaceId query_specific_interface_id)
380+
SemIR::SpecificInterfaceId query_specific_interface_id, bool build_witness)
381381
-> std::optional<SemIR::InstId> {
382382
auto query_specific_interface =
383383
context.specific_interfaces().Get(query_specific_interface_id);
@@ -387,6 +387,10 @@ static auto MakeDestroyWitness(
387387
return std::nullopt;
388388
}
389389

390+
if (!build_witness) {
391+
return SemIR::InstId::None;
392+
}
393+
390394
if (query_self_const_id.is_symbolic()) {
391395
return SemIR::InstId::None;
392396
}
@@ -407,7 +411,7 @@ static auto MakeDestroyWitness(
407411
static auto MakeIntFitsInWitness(
408412
Context& context, SemIR::LocId loc_id,
409413
SemIR::ConstantId query_self_const_id,
410-
SemIR::SpecificInterfaceId query_specific_interface_id)
414+
SemIR::SpecificInterfaceId query_specific_interface_id, bool build_witness)
411415
-> std::optional<SemIR::InstId> {
412416
auto query_specific_interface =
413417
context.specific_interfaces().Get(query_specific_interface_id);
@@ -470,22 +474,26 @@ static auto MakeIntFitsInWitness(
470474
return std::nullopt;
471475
}
472476

477+
if (!build_witness) {
478+
return SemIR::InstId::None;
479+
}
480+
473481
return BuildCustomWitness(context, loc_id, query_self_const_id,
474482
query_specific_interface_id, {});
475483
}
476484

477485
auto LookupCustomWitness(Context& context, SemIR::LocId loc_id,
478486
CoreInterface core_interface,
479487
SemIR::ConstantId query_self_const_id,
480-
SemIR::SpecificInterfaceId query_specific_interface_id)
481-
-> std::optional<SemIR::InstId> {
488+
SemIR::SpecificInterfaceId query_specific_interface_id,
489+
bool build_witness) -> std::optional<SemIR::InstId> {
482490
switch (core_interface) {
483491
case CoreInterface::Destroy:
484492
return MakeDestroyWitness(context, loc_id, query_self_const_id,
485-
query_specific_interface_id);
493+
query_specific_interface_id, build_witness);
486494
case CoreInterface::IntFitsIn:
487495
return MakeIntFitsInWitness(context, loc_id, query_self_const_id,
488-
query_specific_interface_id);
496+
query_specific_interface_id, build_witness);
489497
case CoreInterface::Copy:
490498
case CoreInterface::CppUnsafeDeref:
491499
case CoreInterface::Default:

toolchain/check/custom_witness.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,15 @@ auto GetCoreInterface(Context& context, SemIR::InterfaceId interface_id)
3939
// Returns a witness for a `CoreInterface` `CustomWitness`. A return value of
4040
// `None` indicates a non-final witness should be produced, while `std::nullopt`
4141
// indicates the query is final and no witness can be produced.
42+
//
43+
// If `build_witness` is false, this function always returns `None` as the
44+
// witness, whether it would be final or not. It is used to indicate the
45+
// presence of such a witness without adding instructions for it.
4246
auto LookupCustomWitness(Context& context, SemIR::LocId loc_id,
4347
CoreInterface core_interface,
4448
SemIR::ConstantId query_self_const_id,
45-
SemIR::SpecificInterfaceId query_specific_interface_id)
46-
-> std::optional<SemIR::InstId>;
49+
SemIR::SpecificInterfaceId query_specific_interface_id,
50+
bool build_witness) -> std::optional<SemIR::InstId>;
4751

4852
} // namespace Carbon::Check
4953

toolchain/check/eval_inst.cpp

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -281,48 +281,35 @@ auto EvalConstantInst(Context& /*context*/, SemIR::FunctionDecl inst)
281281

282282
auto EvalConstantInst(Context& context, SemIR::InstId inst_id,
283283
SemIR::LookupImplWitness inst) -> ConstantEvalResult {
284+
// If the monomorphized query self is a FacetValue, we may get a witness from
285+
// it under limited circumstances. If no final witness is found though, we
286+
// don't need to preserve it for future evaluations, so we strip it from the
287+
// LookupImplWitness instruction to reduce the number of distinct constant
288+
// values.
289+
auto self_facet_value_inst_id = SemIR::InstId::None;
290+
if (auto facet_value = context.insts().TryGetAs<SemIR::FacetValue>(
291+
inst.query_self_inst_id)) {
292+
self_facet_value_inst_id =
293+
std::exchange(inst.query_self_inst_id, facet_value->type_inst_id);
294+
}
295+
284296
// The self value is canonicalized in order to produce a canonical
285297
// LookupImplWitness instruction, avoiding multiple constant values for
286298
// `<facet value>` and `<facet value>` as type, which always have the same
287299
// lookup result.
288-
auto self_facet_value_inst_id =
300+
inst.query_self_inst_id =
289301
GetCanonicalFacetOrTypeValue(context, inst.query_self_inst_id);
290302

291-
// When we look for a witness in the (facet) type of self, we may get a
292-
// concrete witness from a `FacetValue` (which is `self_facet_value_inst_id`)
293-
// in which case this instruction evaluates to that witness.
294-
//
295-
// If we only get a symbolic witness result though, then this instruction
296-
// evaluates to a `LookupImplWitness`. Since there was no concrete result in
297-
// the `FacetValue`, we don't need to preserve it. By looking through the
298-
// `FacetValue` at the type value it wraps to generate a more canonical value
299-
// for a symbolic `LookupImplWitness`. This makes us produce the same constant
300-
// value for symbolic lookups in `FacetValue(T)` and `T`, since they will
301-
// always have the same lookup result later, when `T` is replaced in a
302-
// specific by something that can provide a concrete witness.
303-
if (auto facet_value = context.insts().TryGetAs<SemIR::FacetValue>(
304-
self_facet_value_inst_id)) {
305-
inst.query_self_inst_id =
306-
GetCanonicalFacetOrTypeValue(context, facet_value->type_inst_id);
307-
} else {
308-
inst.query_self_inst_id = self_facet_value_inst_id;
309-
}
310-
311-
auto result = EvalLookupSingleImplWitness(context, SemIR::LocId(inst_id),
312-
inst, self_facet_value_inst_id,
313-
EvalImplLookupMode::Normal);
314-
if (!result.has_value()) {
315-
// We use NotConstant to communicate back to impl lookup that the lookup
316-
// failed. This can not happen for a deferred symbolic lookup in a generic
317-
// eval block, since we only add the deferred lookup instruction (being
318-
// evaluated here) to the SemIR if the lookup succeeds.
319-
return ConstantEvalResult::NotConstant;
303+
auto witness_id = EvalLookupSingleFinalWitness(context, SemIR::LocId(inst_id),
304+
inst, self_facet_value_inst_id,
305+
EvalImplLookupMode::Normal);
306+
if (witness_id == SemIR::ErrorInst::ConstantId) {
307+
return ConstantEvalResult::Error;
320308
}
321-
if (result.has_final_value()) {
322-
return ConstantEvalResult::Existing(
323-
context.constant_values().Get(result.final_witness()));
309+
if (witness_id.has_value()) {
310+
return ConstantEvalResult::Existing(witness_id);
324311
}
325-
312+
// Try again when the query is modified by a specific.
326313
return ConstantEvalResult::NewSamePhase(inst);
327314
}
328315

toolchain/check/generic.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,21 @@ class RebuildGenericConstantInEvalBlockCallbacks : public SubstInstCallbacks {
147147

148148
auto ReuseUnchanged(SemIR::InstId orig_inst_id) -> SemIR::InstId override {
149149
auto inst = context().insts().Get(orig_inst_id);
150+
151+
auto const_id = context().constant_values().Get(orig_inst_id);
152+
const auto& symbolic =
153+
context().constant_values().GetSymbolicConstant(const_id);
154+
// Template actions are inserted into the eval block directly, instead of
155+
// adding a new instruction, in `AddTemplateActionToEvalBlock`. This means
156+
// any instruction that is symbolic because it contains a template action as
157+
// an operand would Rebuild that operand with the same instruction. Then the
158+
// dependent instruction would be reused unchanged.
159+
bool is_template =
160+
symbolic.dependence == SemIR::ConstantDependence::Template;
161+
150162
CARBON_CHECK(
151-
(inst.IsOneOf<SemIR::SymbolicBinding, SemIR::SymbolicBindingPattern>()),
163+
is_template || (inst.IsOneOf<SemIR::SymbolicBinding,
164+
SemIR::SymbolicBindingPattern>()),
152165
"Instruction {0} has symbolic constant value but no symbolic operands",
153166
inst);
154167

toolchain/check/handle_interface.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ auto HandleParseNode(Context& context,
184184
context.inst_block_stack().PeekOrAdd();
185185

186186
context.inst_block_stack().Push();
187-
context.require_impls_stack().PushArray();
187+
context.require_impls_stack().Push(interface_id);
188188
// We use the arg stack to build the witness table type.
189189
context.args_type_info_stack().Push();
190190

@@ -213,8 +213,8 @@ auto HandleParseNode(Context& context, Parse::InterfaceDefinitionId /*node_id*/)
213213
auto associated_entities_id = context.args_type_info_stack().Pop();
214214

215215
auto require_impls_block_id = context.require_impls_blocks().Add(
216-
context.require_impls_stack().PeekArray());
217-
context.require_impls_stack().PopArray();
216+
context.require_impls_stack().PeekTop());
217+
context.require_impls_stack().Pop();
218218

219219
auto& interface_info = context.interfaces().Get(interface_id);
220220
if (!interface_info.associated_entities_id.has_value()) {

toolchain/check/handle_named_constraint.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ auto HandleParseNode(Context& context,
184184
constraint_info.body_block_with_self_id =
185185
context.inst_block_stack().PeekOrAdd();
186186

187-
context.require_impls_stack().PushArray();
187+
context.require_impls_stack().Push(named_constraint_id);
188188

189189
context.node_stack().Push(node_id, named_constraint_id);
190190
return true;
@@ -199,8 +199,8 @@ auto HandleParseNode(Context& context,
199199
context.inst_block_stack().Pop();
200200

201201
auto require_impls_block_id = context.require_impls_blocks().Add(
202-
context.require_impls_stack().PeekArray());
203-
context.require_impls_stack().PopArray();
202+
context.require_impls_stack().PeekTop());
203+
context.require_impls_stack().Pop();
204204

205205
auto& constraint_info = context.named_constraints().Get(named_constraint_id);
206206
if (!constraint_info.complete) {

0 commit comments

Comments
 (0)