mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
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:
@@ -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;
|
||||
|
||||
|
||||
@@ -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">;
|
||||
|
||||
|
||||
@@ -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,17 +213,35 @@ void PropertyMap::concretizeTypeWitnessInConformance(
|
||||
<< " on " << concreteType << "\n";
|
||||
}
|
||||
|
||||
auto t = concrete->getTypeWitness(assocType);
|
||||
if (!t) {
|
||||
if (Debug.contains(DebugFlags::ConcretizeNestedTypes)) {
|
||||
llvm::dbgs() << "^^ " << "Type witness for " << assocType->getName()
|
||||
<< " of " << concreteType << " could not be inferred\n";
|
||||
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()
|
||||
<< " of " << concreteType << " could not be inferred\n";
|
||||
}
|
||||
|
||||
t = ErrorType::get(concreteType);
|
||||
}
|
||||
|
||||
t = ErrorType::get(concreteType);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
auto typeWitness = t->getCanonicalType();
|
||||
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()
|
||||
|
||||
@@ -285,7 +285,7 @@ private:
|
||||
void concretizeTypeWitnessInConformance(
|
||||
Term key, RequirementKind requirementKind,
|
||||
Symbol concreteConformanceSymbol,
|
||||
ProtocolConformance *concrete,
|
||||
ProtocolConformanceRef conformance,
|
||||
AssociatedTypeDecl *assocType) const;
|
||||
|
||||
void inferConditionalRequirements(
|
||||
|
||||
@@ -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;
|
||||
|
||||
81
test/Generics/opaque_archetype_concrete_requirement.swift
Normal file
81
test/Generics/opaque_archetype_concrete_requirement.swift
Normal 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]}}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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 }
|
||||
Reference in New Issue
Block a user