Skip to content

Commit f506376

Browse files
authored
Resolve rewrites in facet types, looking for duplicates (#5620)
Add a facet type rewrite constraint resolution step that is run every time a facet type is constructed, in line with the design here: https://docs.carbon-lang.dev/docs/design/generics/appendix-rewrite-constraints.html#rewrite-constraint-resolution The resolution has multiple steps, and this PR implements the first of them, finding and diagnosing any duplicate rewrites to the same associated constant. We already diagnosed this for impl construction, now we do so for all facet types, which includes the one used for impl construction, so this diagnostic is a superset of the previous.
1 parent 52727f9 commit f506376

File tree

7 files changed

+636
-1094
lines changed

7 files changed

+636
-1094
lines changed

toolchain/check/eval.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,8 @@ static auto GetConstantValue(EvalContext& eval_context,
619619
-> SemIR::FacetTypeId {
620620
SemIR::FacetTypeInfo info =
621621
GetConstantFacetTypeInfo(eval_context, facet_type_id, phase);
622-
info.Canonicalize();
622+
ResolveRewriteConstraintsAndCanonicalize(eval_context.context(),
623+
SemIR::LocId::None, info);
623624
// TODO: Return `facet_type_id` if we can detect nothing has changed.
624625
return eval_context.facet_types().Add(info);
625626
}
@@ -1538,7 +1539,7 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context,
15381539
auto info = SemIR::FacetTypeInfo::Combine(
15391540
context.facet_types().Get(lhs_facet_type_id),
15401541
context.facet_types().Get(rhs_facet_type_id));
1541-
info.Canonicalize();
1542+
ResolveRewriteConstraintsAndCanonicalize(context, loc_id, info);
15421543
return MakeFacetTypeResult(eval_context.context(), info, phase);
15431544
}
15441545

@@ -1971,8 +1972,8 @@ static auto IsPeriodSelf(EvalContext& eval_context, SemIR::ConstantId const_id)
19711972
// providing a `GetConstantValue` overload for a requirement block.
19721973
template <>
19731974
auto TryEvalTypedInst<SemIR::WhereExpr>(EvalContext& eval_context,
1974-
SemIR::InstId /*inst_id*/,
1975-
SemIR::Inst inst) -> SemIR::ConstantId {
1975+
SemIR::InstId inst_id, SemIR::Inst inst)
1976+
-> SemIR::ConstantId {
19761977
auto typed_inst = inst.As<SemIR::WhereExpr>();
19771978

19781979
Phase phase = Phase::Concrete;
@@ -2044,7 +2045,8 @@ auto TryEvalTypedInst<SemIR::WhereExpr>(EvalContext& eval_context,
20442045
}
20452046
}
20462047
}
2047-
info.Canonicalize();
2048+
ResolveRewriteConstraintsAndCanonicalize(eval_context.context(),
2049+
SemIR::LocId(inst_id), info);
20482050
return MakeFacetTypeResult(eval_context.context(), info, phase);
20492051
}
20502052

toolchain/check/facet_type.cpp

Lines changed: 105 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
#include "toolchain/check/facet_type.h"
66

77
#include "toolchain/check/convert.h"
8+
#include "toolchain/check/generic.h"
89
#include "toolchain/check/import_ref.h"
910
#include "toolchain/check/inst.h"
1011
#include "toolchain/check/interface.h"
1112
#include "toolchain/check/type.h"
1213
#include "toolchain/check/type_completion.h"
14+
#include "toolchain/sem_ir/ids.h"
1315
#include "toolchain/sem_ir/typed_insts.h"
1416

1517
namespace Carbon::Check {
@@ -164,24 +166,10 @@ auto InitialFacetTypeImplWitness(
164166
continue;
165167
}
166168

167-
if (table_entry != SemIR::ImplWitnessTablePlaceholder::TypeInstId) {
168-
if (table_entry != rewrite_inst_id) {
169-
// TODO: Figure out how to print the two different values
170-
// `const_id` & `rewrite_inst_id` in the diagnostic
171-
// message.
172-
CARBON_DIAGNOSTIC(
173-
AssociatedConstantWithDifferentValues, Error,
174-
"associated constant {0} given two different values {1} and {2}",
175-
SemIR::NameId, InstIdAsConstant, InstIdAsConstant);
176-
auto& assoc_const = context.associated_constants().Get(
177-
assoc_constant_decl->assoc_const_id);
178-
context.emitter().Emit(
179-
facet_type_inst_id, AssociatedConstantWithDifferentValues,
180-
assoc_const.name_id, table_entry, rewrite_inst_id);
181-
}
182-
table_entry = SemIR::ErrorInst::InstId;
183-
continue;
184-
}
169+
// FacetTypes resolution disallows two rewrites to the same associated
170+
// constant, so we won't ever have a facet write twice to the same position
171+
// in the witness table.
172+
CARBON_CHECK(table_entry == SemIR::ImplWitnessTablePlaceholder::TypeInstId);
185173

186174
// If the associated constant has a symbolic type, convert the rewrite
187175
// value to that type now we know the value of `Self`.
@@ -263,4 +251,103 @@ auto AllocateFacetTypeImplWitness(Context& context,
263251
context.inst_blocks().ReplacePlaceholder(witness_id, empty_table);
264252
}
265253

254+
auto IsPeriodSelf(Context& context, SemIR::ConstantId const_id) -> bool {
255+
// This also rejects the singleton Error value as it's concrete.
256+
if (!const_id.is_symbolic()) {
257+
return false;
258+
}
259+
const auto& symbolic =
260+
context.constant_values().GetSymbolicConstant(const_id);
261+
// Fast early reject before doing more expensive operations.
262+
if (symbolic.dependence != SemIR::ConstantDependence::PeriodSelf) {
263+
return false;
264+
}
265+
return IsPeriodSelf(context, symbolic.inst_id);
266+
}
267+
268+
auto IsPeriodSelf(Context& context, SemIR::InstId inst_id) -> bool {
269+
// Unwrap the `FacetAccessType` instruction, which we get when the `.Self` is
270+
// converted to `type`.
271+
if (auto facet_access_type =
272+
context.insts().TryGetAs<SemIR::FacetAccessType>(inst_id)) {
273+
inst_id = facet_access_type->facet_value_inst_id;
274+
}
275+
if (auto bind_symbolic_name =
276+
context.insts().TryGetAs<SemIR::BindSymbolicName>(inst_id)) {
277+
const auto& bind_name =
278+
context.entity_names().Get(bind_symbolic_name->entity_name_id);
279+
return bind_name.name_id == SemIR::NameId::PeriodSelf;
280+
}
281+
return false;
282+
}
283+
284+
auto ResolveRewriteConstraintsAndCanonicalize(Context& context,
285+
SemIR::LocId loc_id,
286+
SemIR::FacetTypeInfo& facet_type)
287+
-> void {
288+
// This operation sorts and dedupes the rewrite constraints. They are sorted
289+
// primarily by the `lhs_id`, then by the `rhs_id`.
290+
facet_type.Canonicalize();
291+
292+
if (facet_type.rewrite_constraints.empty()) {
293+
return;
294+
}
295+
296+
for (size_t i = 0; i < facet_type.rewrite_constraints.size() - 1; ++i) {
297+
auto& constraint = facet_type.rewrite_constraints[i];
298+
if (constraint.lhs_id == SemIR::ErrorInst::InstId ||
299+
constraint.rhs_id == SemIR::ErrorInst::InstId) {
300+
continue;
301+
}
302+
303+
auto lhs_access =
304+
context.insts().TryGetAs<SemIR::ImplWitnessAccess>(constraint.lhs_id);
305+
if (!lhs_access) {
306+
continue;
307+
}
308+
auto lhs_lookup = context.insts().TryGetAs<SemIR::LookupImplWitness>(
309+
lhs_access->witness_id);
310+
if (!lhs_lookup) {
311+
continue;
312+
}
313+
if (!IsPeriodSelf(context, lhs_lookup->query_self_inst_id)) {
314+
continue;
315+
}
316+
317+
// This loop moves `i` to the last position with the same LHS value, so that
318+
// we don't diagnose more than once within the same contiguous range of
319+
// assignments to a single LHS value.
320+
for (; i < facet_type.rewrite_constraints.size() - 1; ++i) {
321+
auto& next = facet_type.rewrite_constraints[i + 1];
322+
if (constraint.lhs_id != next.lhs_id) {
323+
break;
324+
}
325+
// `constraint.lhs_id == next.lhs_id` so only check for `ErrorInst` in the
326+
// RHS. On the first error, `constraint.rhs_id` is set to `ErrorInst`
327+
// which prevents further diagnostics for the same LHS value due to this
328+
// condition.
329+
if (constraint.rhs_id != SemIR::ErrorInst::InstId &&
330+
next.rhs_id != SemIR::ErrorInst::InstId) {
331+
CARBON_DIAGNOSTIC(
332+
AssociatedConstantWithDifferentValues, Error,
333+
"associated constant {0} given two different values {1} and {2}",
334+
InstIdAsConstant, InstIdAsConstant, InstIdAsConstant);
335+
// TODO: It would be nice to note the places where the values are
336+
// assigned but rewrite constraint instructions are from canonical
337+
// constant values, and have no locations. We'd need to store a location
338+
// along with them in the rewrite constraints.
339+
context.emitter().Emit(loc_id, AssociatedConstantWithDifferentValues,
340+
constraint.lhs_id, constraint.rhs_id,
341+
next.rhs_id);
342+
}
343+
constraint.rhs_id = SemIR::ErrorInst::InstId;
344+
next.rhs_id = SemIR::ErrorInst::InstId;
345+
}
346+
}
347+
348+
// Canonicalize again, as we may have inserted errors into the rewrite
349+
// constraints, and these could change sorting order.
350+
facet_type.Canonicalize();
351+
}
352+
266353
} // namespace Carbon::Check

toolchain/check/facet_type.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,21 @@ auto AllocateFacetTypeImplWitness(Context& context,
5858
SemIR::InterfaceId interface_id,
5959
SemIR::InstBlockId witness_id) -> void;
6060

61+
// Returns whether the constant or instruction is a `BindSymbolicName` referring
62+
// to `.Self`.
63+
//
64+
// Working with a `ConstantId` can be more efficient for this operation than
65+
// unconditionally unwrapping to the `InstId`.
66+
auto IsPeriodSelf(Context& context, SemIR::ConstantId const_id) -> bool;
67+
auto IsPeriodSelf(Context& context, SemIR::InstId inst_id) -> bool;
68+
69+
// Perform rewrite constraint resolution for a facet type and canonicalize the
70+
// FacetTypeInfo. The FacetTypeInfo must not be modified after this operation.
71+
auto ResolveRewriteConstraintsAndCanonicalize(Context& context,
72+
SemIR::LocId loc_id,
73+
SemIR::FacetTypeInfo& facet_type)
74+
-> void;
75+
6176
} // namespace Carbon::Check
6277

6378
#endif // CARBON_TOOLCHAIN_CHECK_FACET_TYPE_H_

0 commit comments

Comments
 (0)