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.
This commit is contained in:
Slava Pestov
2022-03-16 13:31:08 -04:00
parent 4d531aea9c
commit 2f727d6b47
8 changed files with 175 additions and 20 deletions

View File

@@ -546,6 +546,11 @@ namespace swift {
/// if you have a testcase which requires this, please submit a bug report.
bool EnableRequirementMachineLoopNormalization = false;
/// Enable experimental, more correct support for opaque result types as
/// concrete types. This will sometimes fail to produce a convergent
/// rewrite system.
bool EnableRequirementMachineOpaqueArchetypes = false;
/// Enables dumping type witness systems from associated type inference.
bool DumpTypeWitnessSystems = false;

View File

@@ -362,6 +362,9 @@ def disable_requirement_machine_concrete_contraction : Flag<["-"], "disable-requ
def enable_requirement_machine_loop_normalization : Flag<["-"], "enable-requirement-machine-loop-normalization">,
HelpText<"Enable stronger minimization algorithm, for debugging only">;
def enable_requirement_machine_opaque_archetypes : Flag<["-"], "enable-requirement-machine-opaque-archetypes">,
HelpText<"Enable more correct opaque archetype support, which is off by default because it might fail to produce a convergent rewrite system">;
def dump_type_witness_systems : Flag<["-"], "dump-type-witness-systems">,
HelpText<"Enables dumping type witness systems from associated type inference">;

View File

@@ -162,33 +162,44 @@ void PropertyMap::concretizeNestedTypesFromConcreteParent(
continue;
}
// FIXME: Maybe this can happen if the concrete type is an
// opaque result type?
assert(!conformance.isAbstract());
auto concreteConformanceSymbol = Symbol::forConcreteConformance(
concreteType, substitutions, proto, Context);
recordConcreteConformanceRule(concreteRuleID, conformanceRuleID,
requirementKind, concreteConformanceSymbol);
// This is disabled by default because we fail to produce a convergent
// rewrite system if the opaque archetype has infinitely-recursive
// nested types. Fixing this requires a better representation for
// concrete conformances in the rewrite system.
if (conformance.isAbstract() &&
!Context.getASTContext().LangOpts.EnableRequirementMachineOpaqueArchetypes) {
if (Debug.contains(DebugFlags::ConcretizeNestedTypes)) {
llvm::dbgs() << "^^ " << "Skipping abstract conformance of "
<< concreteType << " to " << proto->getName() << "\n";
}
continue;
}
for (auto *assocType : proto->getAssociatedTypeMembers()) {
concretizeTypeWitnessInConformance(key, requirementKind,
concreteConformanceSymbol,
concrete, assocType);
conformance, assocType);
}
// We only infer conditional requirements in top-level generic signatures,
// not in protocol requirement signatures.
if (key.getRootProtocol() == nullptr)
inferConditionalRequirements(concrete, substitutions);
if (conformance.isConcrete() &&
key.getRootProtocol() == nullptr)
inferConditionalRequirements(conformance.getConcrete(), substitutions);
}
}
void PropertyMap::concretizeTypeWitnessInConformance(
Term key, RequirementKind requirementKind,
Symbol concreteConformanceSymbol,
ProtocolConformance *concrete,
ProtocolConformanceRef conformance,
AssociatedTypeDecl *assocType) const {
auto concreteType = concreteConformanceSymbol.getConcreteType();
auto substitutions = concreteConformanceSymbol.getSubstitutions();
@@ -202,7 +213,10 @@ void PropertyMap::concretizeTypeWitnessInConformance(
<< " on " << concreteType << "\n";
}
auto t = concrete->getTypeWitness(assocType);
CanType typeWitness;
if (conformance.isConcrete()) {
auto t = conformance.getConcrete()->getTypeWitness(assocType);
if (!t) {
if (Debug.contains(DebugFlags::ConcretizeNestedTypes)) {
llvm::dbgs() << "^^ " << "Type witness for " << assocType->getName()
@@ -212,7 +226,22 @@ void PropertyMap::concretizeTypeWitnessInConformance(
t = ErrorType::get(concreteType);
}
auto typeWitness = t->getCanonicalType();
typeWitness = t->getCanonicalType();
} else if (conformance.isAbstract()) {
auto archetype = concreteType->getAs<OpaqueTypeArchetypeType>();
if (archetype == nullptr) {
llvm::errs() << "Should only have an abstract conformance with an "
<< "opaque archetype type\n";
llvm::errs() << "Symbol: " << concreteConformanceSymbol << "\n";
llvm::errs() << "Term: " << key << "\n";
dump(llvm::errs());
abort();
}
typeWitness = archetype->getNestedType(assocType)->getCanonicalType();
} else if (conformance.isInvalid()) {
typeWitness = CanType(ErrorType::get(Context.getASTContext()));
}
if (Debug.contains(DebugFlags::ConcretizeNestedTypes)) {
llvm::dbgs() << "^^ " << "Type witness for " << assocType->getName()

View File

@@ -285,7 +285,7 @@ private:
void concretizeTypeWitnessInConformance(
Term key, RequirementKind requirementKind,
Symbol concreteConformanceSymbol,
ProtocolConformance *concrete,
ProtocolConformanceRef conformance,
AssociatedTypeDecl *assocType) const;
void inferConditionalRequirements(

View File

@@ -1004,6 +1004,9 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
if (Args.hasArg(OPT_enable_requirement_machine_loop_normalization))
Opts.EnableRequirementMachineLoopNormalization = true;
if (Args.hasArg(OPT_enable_requirement_machine_opaque_archetypes))
Opts.EnableRequirementMachineOpaqueArchetypes = true;
Opts.DumpTypeWitnessSystems = Args.hasArg(OPT_dump_type_witness_systems);
return HadError || UnsupportedOS || UnsupportedArch;

View File

@@ -0,0 +1,81 @@
// 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
protocol P1 {
associatedtype T : P2
associatedtype U
}
struct S_P1 : P1 {
typealias T = S_P2
typealias U = Int
}
protocol P2 {}
struct S_P2 : P2 {}
protocol P {
associatedtype T
var t: T { get }
}
struct DefinesOpaqueP1 : P {
var t: some P1 {
return S_P1()
}
}
struct ConcreteHasP<T : P1, TT : P2, TU> {}
// CHECK-LABEL: ExtensionDecl line={{.*}} base=ConcreteHasP
// CHECK-NEXT: Generic signature: <T, TT, TU where T == some P1, TT == (some P1).T, TU == (some P1).U>
extension ConcreteHasP where T == DefinesOpaqueP1.T, TT == T.T, TU == T.U {
func checkSameType1(_ t: TT) -> DefinesOpaqueP1.T.T { return t }
func checkSameType2(_ u: TU) -> DefinesOpaqueP1.T.U { return u }
func checkSameType3(_ t: T.T) -> DefinesOpaqueP1.T.T { return t }
func checkSameType4(_ u: T.U) -> DefinesOpaqueP1.T.U { return u }
}
struct G<T> {}
protocol HasP {
associatedtype T : P1
associatedtype U
}
// CHECK-LABEL: ExtensionDecl line={{.*}} base=HasP
// CHECK-NEXT: Generic signature: <Self where Self : HasP, Self.[HasP]T == some P1, Self.[HasP]U == G<(some P1).T>>
extension HasP where T == DefinesOpaqueP1.T, U == G<T.T> {
func checkSameType1(_ t: T.T) -> DefinesOpaqueP1.T.T { return t }
func checkSameType2(_ u: T.U) -> DefinesOpaqueP1.T.U { return u }
}
// FIXME: This does not work with -enable-requirement-machine-opaque-archetypes.
// See opaque_archetype_concrete_requirement_recursive.swift for a demonstration
// that it works without the flag (but more involved examples like the above
// won't work).
protocol RecursiveP {
associatedtype T : RecursiveP
}
struct S_RecursiveP : RecursiveP {
typealias T = S_RecursiveP
}
struct DefinesRecursiveP : P {
var t: some RecursiveP {
return S_RecursiveP()
}
}
protocol HasRecursiveP {
associatedtype T : RecursiveP
}
extension HasRecursiveP where T == DefinesRecursiveP.T {}
// expected-error@-1 {{cannot build rewrite system for generic signature; rule length limit exceeded}}
// 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]}}

View File

@@ -0,0 +1,36 @@
// RUN: %target-swift-frontend -typecheck -verify %s -disable-availability-checking -debug-generic-signatures -requirement-machine-inferred-signatures=on 2>&1 | %FileCheck %s
protocol P {
associatedtype T
var t: T { get }
}
// FIXME: This does not work with -enable-requirement-machine-opaque-archetypes.
// See opaque_archetype_concrete_requirement.swift for a demonstration that it
// fails with the flag.
protocol RecursiveP {
associatedtype T : RecursiveP
}
struct S_RecursiveP : RecursiveP {
typealias T = S_RecursiveP
}
struct DefinesRecursiveP : P {
var t: some RecursiveP {
return S_RecursiveP()
}
}
protocol HasRecursiveP {
associatedtype T : RecursiveP
}
// CHECK-LABEL: ExtensionDecl line={{.*}} base=HasRecursiveP
// CHECK-NEXT: Generic signature: <Self where Self : HasRecursiveP, Self.[HasRecursiveP]T == some RecursiveP>
extension HasRecursiveP where T == DefinesRecursiveP.T {
func checkSameType1(_ t: T) -> DefinesRecursiveP.T { return t }
func checkSameType2(_ t: T.T) -> DefinesRecursiveP.T.T { return t }
}

View File

@@ -1,6 +1,4 @@
// RUN: not --crash %target-swift-frontend -disable-availability-checking -emit-ir -enable-parameterized-protocol-types %s
// REQUIRES: asserts
// RUN: %target-swift-frontend -disable-availability-checking -emit-ir -enable-parameterized-protocol-types %s
protocol P<X: P, Y: P> {
var x: X { get }