Skip to content

Commit 2f727d6

Browse files
committed
RequirementMachine: Opaque archetype support (sort of)
Complete support is behind a flag, because it can result in a non-convergent rewrite system if the opaque result type has a recursive conformance of its own (eg, `some View` for SwiftUI's View protocol). Without the flag, it's good enough for simple examples; you just can't have a requirement that mentions a nested type of a type parameter equated to the concrete type. Fixes rdar://problem/88135291, https://bugs.swift.org/browse/SR-15983.
1 parent 4d531ae commit 2f727d6

File tree

8 files changed

+175
-20
lines changed

8 files changed

+175
-20
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,11 @@ namespace swift {
546546
/// if you have a testcase which requires this, please submit a bug report.
547547
bool EnableRequirementMachineLoopNormalization = false;
548548

549+
/// Enable experimental, more correct support for opaque result types as
550+
/// concrete types. This will sometimes fail to produce a convergent
551+
/// rewrite system.
552+
bool EnableRequirementMachineOpaqueArchetypes = false;
553+
549554
/// Enables dumping type witness systems from associated type inference.
550555
bool DumpTypeWitnessSystems = false;
551556

include/swift/Option/FrontendOptions.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@ def disable_requirement_machine_concrete_contraction : Flag<["-"], "disable-requ
362362
def enable_requirement_machine_loop_normalization : Flag<["-"], "enable-requirement-machine-loop-normalization">,
363363
HelpText<"Enable stronger minimization algorithm, for debugging only">;
364364

365+
def enable_requirement_machine_opaque_archetypes : Flag<["-"], "enable-requirement-machine-opaque-archetypes">,
366+
HelpText<"Enable more correct opaque archetype support, which is off by default because it might fail to produce a convergent rewrite system">;
367+
365368
def dump_type_witness_systems : Flag<["-"], "dump-type-witness-systems">,
366369
HelpText<"Enables dumping type witness systems from associated type inference">;
367370

lib/AST/RequirementMachine/ConcreteTypeWitness.cpp

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -162,33 +162,44 @@ void PropertyMap::concretizeNestedTypesFromConcreteParent(
162162
continue;
163163
}
164164

165-
// FIXME: Maybe this can happen if the concrete type is an
166-
// opaque result type?
167-
assert(!conformance.isAbstract());
168-
169165
auto concreteConformanceSymbol = Symbol::forConcreteConformance(
170166
concreteType, substitutions, proto, Context);
171167

172168
recordConcreteConformanceRule(concreteRuleID, conformanceRuleID,
173169
requirementKind, concreteConformanceSymbol);
174170

171+
// This is disabled by default because we fail to produce a convergent
172+
// rewrite system if the opaque archetype has infinitely-recursive
173+
// nested types. Fixing this requires a better representation for
174+
// concrete conformances in the rewrite system.
175+
if (conformance.isAbstract() &&
176+
!Context.getASTContext().LangOpts.EnableRequirementMachineOpaqueArchetypes) {
177+
if (Debug.contains(DebugFlags::ConcretizeNestedTypes)) {
178+
llvm::dbgs() << "^^ " << "Skipping abstract conformance of "
179+
<< concreteType << " to " << proto->getName() << "\n";
180+
}
181+
182+
continue;
183+
}
184+
175185
for (auto *assocType : proto->getAssociatedTypeMembers()) {
176186
concretizeTypeWitnessInConformance(key, requirementKind,
177187
concreteConformanceSymbol,
178-
concrete, assocType);
188+
conformance, assocType);
179189
}
180190

181191
// We only infer conditional requirements in top-level generic signatures,
182192
// not in protocol requirement signatures.
183-
if (key.getRootProtocol() == nullptr)
184-
inferConditionalRequirements(concrete, substitutions);
193+
if (conformance.isConcrete() &&
194+
key.getRootProtocol() == nullptr)
195+
inferConditionalRequirements(conformance.getConcrete(), substitutions);
185196
}
186197
}
187198

188199
void PropertyMap::concretizeTypeWitnessInConformance(
189200
Term key, RequirementKind requirementKind,
190201
Symbol concreteConformanceSymbol,
191-
ProtocolConformance *concrete,
202+
ProtocolConformanceRef conformance,
192203
AssociatedTypeDecl *assocType) const {
193204
auto concreteType = concreteConformanceSymbol.getConcreteType();
194205
auto substitutions = concreteConformanceSymbol.getSubstitutions();
@@ -202,17 +213,35 @@ void PropertyMap::concretizeTypeWitnessInConformance(
202213
<< " on " << concreteType << "\n";
203214
}
204215

205-
auto t = concrete->getTypeWitness(assocType);
206-
if (!t) {
207-
if (Debug.contains(DebugFlags::ConcretizeNestedTypes)) {
208-
llvm::dbgs() << "^^ " << "Type witness for " << assocType->getName()
209-
<< " of " << concreteType << " could not be inferred\n";
216+
CanType typeWitness;
217+
if (conformance.isConcrete()) {
218+
auto t = conformance.getConcrete()->getTypeWitness(assocType);
219+
220+
if (!t) {
221+
if (Debug.contains(DebugFlags::ConcretizeNestedTypes)) {
222+
llvm::dbgs() << "^^ " << "Type witness for " << assocType->getName()
223+
<< " of " << concreteType << " could not be inferred\n";
224+
}
225+
226+
t = ErrorType::get(concreteType);
210227
}
211228

212-
t = ErrorType::get(concreteType);
213-
}
229+
typeWitness = t->getCanonicalType();
230+
} else if (conformance.isAbstract()) {
231+
auto archetype = concreteType->getAs<OpaqueTypeArchetypeType>();
232+
if (archetype == nullptr) {
233+
llvm::errs() << "Should only have an abstract conformance with an "
234+
<< "opaque archetype type\n";
235+
llvm::errs() << "Symbol: " << concreteConformanceSymbol << "\n";
236+
llvm::errs() << "Term: " << key << "\n";
237+
dump(llvm::errs());
238+
abort();
239+
}
214240

215-
auto typeWitness = t->getCanonicalType();
241+
typeWitness = archetype->getNestedType(assocType)->getCanonicalType();
242+
} else if (conformance.isInvalid()) {
243+
typeWitness = CanType(ErrorType::get(Context.getASTContext()));
244+
}
216245

217246
if (Debug.contains(DebugFlags::ConcretizeNestedTypes)) {
218247
llvm::dbgs() << "^^ " << "Type witness for " << assocType->getName()

lib/AST/RequirementMachine/PropertyMap.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ class PropertyMap {
285285
void concretizeTypeWitnessInConformance(
286286
Term key, RequirementKind requirementKind,
287287
Symbol concreteConformanceSymbol,
288-
ProtocolConformance *concrete,
288+
ProtocolConformanceRef conformance,
289289
AssociatedTypeDecl *assocType) const;
290290

291291
void inferConditionalRequirements(

lib/Frontend/CompilerInvocation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,9 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
10041004
if (Args.hasArg(OPT_enable_requirement_machine_loop_normalization))
10051005
Opts.EnableRequirementMachineLoopNormalization = true;
10061006

1007+
if (Args.hasArg(OPT_enable_requirement_machine_opaque_archetypes))
1008+
Opts.EnableRequirementMachineOpaqueArchetypes = true;
1009+
10071010
Opts.DumpTypeWitnessSystems = Args.hasArg(OPT_dump_type_witness_systems);
10081011

10091012
return HadError || UnsupportedOS || UnsupportedArch;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s -disable-availability-checking -debug-generic-signatures -requirement-machine-inferred-signatures=on -enable-requirement-machine-opaque-archetypes 2>&1 | %FileCheck %s
2+
3+
protocol P1 {
4+
associatedtype T : P2
5+
associatedtype U
6+
}
7+
8+
struct S_P1 : P1 {
9+
typealias T = S_P2
10+
typealias U = Int
11+
}
12+
13+
protocol P2 {}
14+
15+
struct S_P2 : P2 {}
16+
17+
protocol P {
18+
associatedtype T
19+
20+
var t: T { get }
21+
}
22+
23+
struct DefinesOpaqueP1 : P {
24+
var t: some P1 {
25+
return S_P1()
26+
}
27+
}
28+
29+
struct ConcreteHasP<T : P1, TT : P2, TU> {}
30+
31+
// CHECK-LABEL: ExtensionDecl line={{.*}} base=ConcreteHasP
32+
// CHECK-NEXT: Generic signature: <T, TT, TU where T == some P1, TT == (some P1).T, TU == (some P1).U>
33+
extension ConcreteHasP where T == DefinesOpaqueP1.T, TT == T.T, TU == T.U {
34+
func checkSameType1(_ t: TT) -> DefinesOpaqueP1.T.T { return t }
35+
func checkSameType2(_ u: TU) -> DefinesOpaqueP1.T.U { return u }
36+
37+
func checkSameType3(_ t: T.T) -> DefinesOpaqueP1.T.T { return t }
38+
func checkSameType4(_ u: T.U) -> DefinesOpaqueP1.T.U { return u }
39+
}
40+
41+
struct G<T> {}
42+
43+
protocol HasP {
44+
associatedtype T : P1
45+
associatedtype U
46+
}
47+
48+
// CHECK-LABEL: ExtensionDecl line={{.*}} base=HasP
49+
// CHECK-NEXT: Generic signature: <Self where Self : HasP, Self.[HasP]T == some P1, Self.[HasP]U == G<(some P1).T>>
50+
extension HasP where T == DefinesOpaqueP1.T, U == G<T.T> {
51+
func checkSameType1(_ t: T.T) -> DefinesOpaqueP1.T.T { return t }
52+
func checkSameType2(_ u: T.U) -> DefinesOpaqueP1.T.U { return u }
53+
}
54+
55+
// FIXME: This does not work with -enable-requirement-machine-opaque-archetypes.
56+
// See opaque_archetype_concrete_requirement_recursive.swift for a demonstration
57+
// that it works without the flag (but more involved examples like the above
58+
// won't work).
59+
60+
protocol RecursiveP {
61+
associatedtype T : RecursiveP
62+
}
63+
64+
struct S_RecursiveP : RecursiveP {
65+
typealias T = S_RecursiveP
66+
}
67+
68+
struct DefinesRecursiveP : P {
69+
var t: some RecursiveP {
70+
return S_RecursiveP()
71+
}
72+
}
73+
74+
protocol HasRecursiveP {
75+
associatedtype T : RecursiveP
76+
}
77+
78+
extension HasRecursiveP where T == DefinesRecursiveP.T {}
79+
// expected-error@-1 {{cannot build rewrite system for generic signature; rule length limit exceeded}}
80+
// expected-note@-2 {{failed rewrite rule is τ_0_0.[HasRecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[concrete: ((((((((((some RecursiveP).T).T).T).T).T).T).T).T).T).T] => τ_0_0.[HasRecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T].[RecursiveP:T]}}
81+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s -disable-availability-checking -debug-generic-signatures -requirement-machine-inferred-signatures=on 2>&1 | %FileCheck %s
2+
3+
protocol P {
4+
associatedtype T
5+
6+
var t: T { get }
7+
}
8+
9+
// FIXME: This does not work with -enable-requirement-machine-opaque-archetypes.
10+
// See opaque_archetype_concrete_requirement.swift for a demonstration that it
11+
// fails with the flag.
12+
13+
protocol RecursiveP {
14+
associatedtype T : RecursiveP
15+
}
16+
17+
struct S_RecursiveP : RecursiveP {
18+
typealias T = S_RecursiveP
19+
}
20+
21+
struct DefinesRecursiveP : P {
22+
var t: some RecursiveP {
23+
return S_RecursiveP()
24+
}
25+
}
26+
27+
protocol HasRecursiveP {
28+
associatedtype T : RecursiveP
29+
}
30+
31+
// CHECK-LABEL: ExtensionDecl line={{.*}} base=HasRecursiveP
32+
// CHECK-NEXT: Generic signature: <Self where Self : HasRecursiveP, Self.[HasRecursiveP]T == some RecursiveP>
33+
extension HasRecursiveP where T == DefinesRecursiveP.T {
34+
func checkSameType1(_ t: T) -> DefinesRecursiveP.T { return t }
35+
func checkSameType2(_ t: T.T) -> DefinesRecursiveP.T.T { return t }
36+
}

validation-test/compiler_crashers_2/unsupported_recursive_opaque_conformance.swift renamed to validation-test/compiler_crashers_2_fixed/unsupported_recursive_opaque_conformance.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
// RUN: not --crash %target-swift-frontend -disable-availability-checking -emit-ir -enable-parameterized-protocol-types %s
2-
3-
// REQUIRES: asserts
1+
// RUN: %target-swift-frontend -disable-availability-checking -emit-ir -enable-parameterized-protocol-types %s
42

53
protocol P<X: P, Y: P> {
64
var x: X { get }

0 commit comments

Comments
 (0)