NCGenerics: ext's might not infer invertible req's

If the extension adds conformance to an invertible protocol, it's
confusing for people to also infer conditional requirements on the
generic parameters for those invertible protocols. This came up in the
review of SE-427.
This commit is contained in:
Kavon Farvardin
2024-06-06 15:39:39 -07:00
parent 43ce060155
commit ec4a125f3e
20 changed files with 123 additions and 52 deletions

View File

@@ -2915,6 +2915,18 @@ void PrintAST::printExtendedTypeName(TypeLoc ExtendedTypeLoc) {
printTypeLoc(TypeLoc(ExtendedTypeLoc.getTypeRepr(), Ty));
}
/// If an extension adds a conformance for an invertible protocol, then we
/// should not print inverses for its generic signature, because no conditional
/// requirements are inferred by default for such an extension.
static bool isExtensionAddingInvertibleConformance(const ExtensionDecl *ext) {
auto conformances = ext->getLocalConformances();
for (auto *conf : conformances) {
if (conf->getProtocol()->getInvertibleProtocolKind()) {
return true;
}
}
return false;
}
void PrintAST::printSynthesizedExtension(Type ExtendedType,
ExtensionDecl *ExtDecl) {
@@ -3039,9 +3051,21 @@ void PrintAST::printExtension(ExtensionDecl *decl) {
auto baseGenericSig = decl->getExtendedNominal()->getGenericSignature();
assert(baseGenericSig &&
"an extension can't be generic if the base type isn't");
auto genSigFlags = defaultGenericRequirementFlags()
| IncludeOuterInverses;
// Disable printing inverses if the extension is adding a conformance
// for an invertible protocol itself, as we do not infer any requirements
// in such an extension. We need to print the whole signature:
// extension S: Copyable where T: Copyable
if (isExtensionAddingInvertibleConformance(decl)) {
genSigFlags &= ~PrintInverseRequirements;
genSigFlags &= ~IncludeOuterInverses;
}
printGenericSignature(genericSig,
defaultGenericRequirementFlags()
| IncludeOuterInverses,
genSigFlags,
[baseGenericSig](const Requirement &req) -> bool {
// Only include constraints that are not satisfied by the base type.
return !baseGenericSig->isRequirementSatisfied(req);

View File

@@ -782,7 +782,6 @@ InferredGenericSignatureRequest::evaluate(
unsigned numOuterParams = genericParams.size();
if (isExtension) {
assert(allowInverses);
numOuterParams = 0;
}

View File

@@ -684,6 +684,7 @@ GenericSignatureRequest::evaluate(Evaluator &evaluator,
SmallVector<TypeBase *, 2> inferenceSources;
SmallVector<Requirement, 2> extraReqs;
SourceLoc loc;
bool inferInvertibleReqs = true;
if (auto VD = dyn_cast<ValueDecl>(GC->getAsDecl())) {
loc = VD->getLoc();
@@ -781,6 +782,35 @@ GenericSignatureRequest::evaluate(Evaluator &evaluator,
} else if (auto *ext = dyn_cast<ExtensionDecl>(GC)) {
loc = ext->getLoc();
// The inherited entries influence the generic signature of the extension,
// because if it introduces conformance to invertible protocol IP, we do not
// we do not infer any requirements that the generic parameters to conform
// to invertible protocols. This forces people to write out the conditions.
const unsigned numEntries = ext->getInherited().size();
for (unsigned i = 0; i < numEntries; ++i) {
InheritedTypeRequest request{ext, i, TypeResolutionStage::Structural};
auto result = evaluateOrDefault(ctx.evaluator, request,
InheritedTypeResult::forDefault());
Type inheritedTy;
switch (result) {
case InheritedTypeResult::Inherited:
inheritedTy = result.getInheritedType();
break;
case InheritedTypeResult::Suppressed:
case InheritedTypeResult::Default:
continue;
}
if (inheritedTy) {
if (auto kp = inheritedTy->getKnownProtocol()) {
if (getInvertibleProtocolKind(*kp)) {
inferInvertibleReqs = false;
break;
}
}
}
}
collectAdditionalExtensionRequirements(ext->getExtendedType(), extraReqs);
auto *extendedNominal = ext->getExtendedNominal();
@@ -800,7 +830,7 @@ GenericSignatureRequest::evaluate(Evaluator &evaluator,
genericParams, WhereClauseOwner(GC),
extraReqs, inferenceSources, loc,
/*isExtension=*/isa<ExtensionDecl>(GC),
/*allowInverses=*/true};
/*allowInverses=*/inferInvertibleReqs};
return evaluateOrDefault(ctx.evaluator, request,
GenericSignatureWithError()).getPointer();
}

View File

@@ -132,7 +132,7 @@ public enum Optional<Wrapped: ~Copyable>: ~Copyable {
case some(Wrapped)
}
extension Optional: Copyable /* where Wrapped: Copyable */ {}
extension Optional: Copyable where Wrapped: Copyable {}
extension Optional: Sendable where Wrapped: ~Copyable & Sendable { }

View File

@@ -21,7 +21,7 @@ public enum Result<Success: ~Copyable, Failure: Error> {
case failure(Failure)
}
extension Result: Copyable /* where Success: Copyable */ {}
extension Result: Copyable where Success: Copyable {}
extension Result: Sendable where Success: Sendable & ~Copyable {}

View File

@@ -7,12 +7,15 @@ protocol Q {}
class DoggoClass {}
struct Blah<T: ~Copyable & ~Escapable>: ~Copyable, ~Escapable {}
extension Blah: Copyable {} // expected-error {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'T: Escapable'}}
extension Blah: Escapable {} // expected-error {{conditional conformance to suppressible protocol 'Escapable' cannot depend on 'T: Copyable'}}
extension Blah: Copyable where T: Copyable & Escapable {}
// expected-error@-1 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'T: Escapable'}}
struct Yuck<T: ~Copyable & ~Escapable>: ~Copyable, ~Escapable {}
extension Yuck: Copyable where T: ~Escapable {}
extension Yuck: Escapable where T: ~Copyable {}
extension Blah: Escapable where T: Copyable, T: Escapable {}
// expected-error@-1 {{conditional conformance to suppressible protocol 'Escapable' cannot depend on 'T: Copyable'}}
struct Fixed<T: ~Copyable & ~Escapable>: ~Copyable, ~Escapable {}
extension Fixed: Copyable where T: Copyable {}
extension Fixed: Escapable where T: Escapable {}
struct TryConformance<Whatever: ~Copyable>: ~Copyable {}
extension TryConformance: Copyable

View File

@@ -2,11 +2,26 @@
// RUN: -enable-experimental-feature NonescapableTypes \
// RUN: -enable-experimental-feature SuppressedAssociatedTypes
// expected-note@+1 {{'T' has '~Copyable' constraint preventing implicit 'Copyable' conformance}}
struct AttemptImplicitConditionalConformance<T: ~Copyable>: ~Copyable {
var t: T // expected-error {{stored property 't' of 'Copyable'-conforming generic struct 'AttemptImplicitConditionalConformance' has non-Copyable type 'T'}}
}
extension AttemptImplicitConditionalConformance: Copyable {}
// expected-error@-1 {{generic struct 'AttemptImplicitConditionalConformance' required to be 'Copyable' but is marked with '~Copyable'}}
enum Hello<T: ~Escapable & ~Copyable>: ~Escapable & ~Copyable {}
extension Hello: Escapable {} // expected-error {{generic enum 'Hello' required to be 'Escapable' but is marked with '~Escapable'}}
extension Hello: Copyable {} // expected-error {{generic enum 'Hello' required to be 'Copyable' but is marked with '~Copyable'}}
enum HelloExplicitlyFixed<T: ~Escapable & ~Copyable>: Escapable, Copyable {}
struct NoInverseBecauseNoDefault<T: ~Copyable & ~Escapable>: ~Copyable {}
extension NoInverseBecauseNoDefault: Copyable where T: Copyable, T: ~Escapable {}
// expected-error@-1 {{cannot suppress '~Escapable' on generic parameter 'T' defined in outer scope}}
// Check support for explicit conditional conformance
public struct ExplicitCond<T: ~Copyable>: ~Copyable {}
extension ExplicitCond: Copyable {}
extension ExplicitCond: Copyable where T: Copyable {}
// expected-note@-1 {{requirement from conditional conformance}}
// expected-note@-2 {{requirement from conditional conformance of 'ExplicitCondAlias<NC>' (aka 'ExplicitCond<NC>') to 'Copyable'}}
@@ -80,7 +95,7 @@ struct ConditionalContainment<T: ~Copyable>: ~Copyable {
var y: NC // expected-error {{stored property 'y' of 'Copyable'-conforming generic struct 'ConditionalContainment' has non-Copyable type 'NC'}}
}
extension ConditionalContainment: Copyable {}
extension ConditionalContainment: Copyable where T: Copyable {}
func chk(_ T: RequireCopyable<ConditionalContainment<Int>>) {}
@@ -126,7 +141,7 @@ enum Maybe<Wrapped: ~Copyable>: ~Copyable {
deinit {} // expected-error {{deinitializer cannot be declared in generic enum 'Maybe' that conforms to 'Copyable'}}
}
extension Maybe: Copyable {}
extension Maybe: Copyable where Wrapped: Copyable {}
// expected-note@+4{{requirement specified as 'NC' : 'Copyable'}}
// expected-note@+3{{requirement from conditional conformance of 'Maybe<NC>' to 'Copyable'}}
@@ -265,7 +280,7 @@ enum MaybeEscapes<T: ~Escapable>: ~Escapable { // expected-note {{generic enum '
case none
}
extension MaybeEscapes: Escapable {}
extension MaybeEscapes: Escapable where T: Escapable {}
struct Escapes { // expected-note {{consider adding '~Escapable' to struct 'Escapes'}}
let t: MaybeEscapes<NonescapingType> // expected-error {{stored property 't' of 'Escapable'-conforming struct 'Escapes' has non-Escapable type 'MaybeEscapes<NonescapingType>'}}

View File

@@ -21,7 +21,7 @@ public enum Optional<T: ~Copyable>: ~Copyable {
case none
}
extension Optional: Copyable {}
extension Optional: Copyable where T: Copyable {}
public func wrapping<T: ~Copyable>(_ t: consuming T) -> T? {
return .some(t)

View File

@@ -36,7 +36,7 @@ public enum Either<Success: ~Copyable, Failure: Error>: ~Copyable {
case failure(Failure)
}
extension Either: Copyable {}
extension Either: Copyable where Success: Copyable {}
extension Either where Failure == Swift.Error {
public init(catching body: () throws -> Success) {
@@ -77,7 +77,7 @@ public enum Maybe<Wrapped: ~Copyable>: ~Copyable {
case just(Wrapped)
case nothing
}
extension Maybe: Copyable {}
extension Maybe: Copyable where Wrapped: Copyable {}
extension Maybe: Show where Wrapped: Show & ~Copyable {
public borrowing func show() -> String {

View File

@@ -136,7 +136,7 @@ enum Maybe<Wrapped: ~Copyable>: ~Copyable {
case just(Wrapped)
case nothing
}
extension Maybe: Copyable {}
extension Maybe: Copyable where Wrapped: Copyable {}
extension Maybe: CustomDebugStringConvertible {
var debugDescription: String {
"cast succeeded"

View File

@@ -55,12 +55,12 @@ public struct _Toys {
public struct ExplicitHello<T: ~Copyable>: ~Copyable {
let thing: T
}
extension ExplicitHello: Copyable {}
extension ExplicitHello: Copyable where T: Copyable {}
public struct Hello<T: ~Copyable>: ~Copyable, ~Escapable where T: ~Escapable {}
extension Hello: Escapable where T: ~Copyable {}
extension Hello: Copyable where T: ~Escapable {}
extension Hello: Escapable where T: Escapable {}
extension Hello: Copyable where T: Copyable {}
public protocol TestAssocTypes {
associatedtype A: ~Copyable, _NoCopyP = Int
@@ -94,11 +94,11 @@ public struct Outer<A: ~Copyable>: ~Copyable {
public struct InnerVariation2<D: ~Escapable>: ~Copyable, ~Escapable {}
}
extension Outer: Copyable {}
extension Outer.InnerStruct: Copyable {}
extension Outer: Copyable where A: Copyable {}
extension Outer.InnerStruct: Copyable where C: Copyable, A: Copyable {}
extension Outer.InnerVariation1: Copyable {}
extension Outer.InnerVariation2: Escapable where A: ~Copyable {}
extension Outer.InnerVariation1: Copyable where A: Copyable, D: Copyable & Escapable {}
extension Outer.InnerVariation2: Escapable where A: Escapable, D: Escapable {}
extension Outer.InnerStruct {
public func hello<T: ~Escapable>(_ t: T) {}

View File

@@ -26,14 +26,14 @@ public protocol P: ~Copyable {
public struct X<T: ~Copyable>: ~Copyable { }
// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK: extension Test.X : Swift.Copyable {
// CHECK: extension Test.X : Swift.Copyable where T : Swift.Copyable {
// CHECK-NEXT: func f()
// CHECK: }
// CHECK: #else
// CHECK: extension Test.X {
// CHECK-NEXT: func f()
// CHECK: }
extension X: Copyable {
extension X: Copyable where T: Copyable {
public func f() { }
}

View File

@@ -77,18 +77,18 @@ import NoncopyableGenerics_Misc
// CHECK-MISC-NEXT: public struct ExplicitHello<T> : ~Swift.Copyable where T : ~Copyable {
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-MISC-NEXT: extension {{.*}}.ExplicitHello : Swift.Copyable {
// CHECK-MISC-NEXT: extension {{.*}}.ExplicitHello : Swift.Copyable where T : Swift.Copyable {
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-MISC-NEXT: public struct Hello<T> : ~Swift.Copyable, ~Swift.Escapable where T : ~Copyable, T : ~Escapable {
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-MISC-NEXT: extension NoncopyableGenerics_Misc.Hello : Swift.Escapable where T : ~Copyable {
// CHECK-MISC-NEXT: extension NoncopyableGenerics_Misc.Hello : Swift.Escapable where T : Swift.Escapable {
// CHECK-MISC-NEXT: }
// CHECK-MISC: #endif
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-MISC-NEXT: extension NoncopyableGenerics_Misc.Hello : Swift.Copyable where T : ~Escapable {
// CHECK-MISC-NEXT: extension NoncopyableGenerics_Misc.Hello : Swift.Copyable where T : Swift.Copyable {
// CHECK-MISC-NEXT: }
// CHECK-MISC: #endif
@@ -121,19 +121,19 @@ import NoncopyableGenerics_Misc
// CHECK-MISC: public struct InnerVariation2<D> : ~Swift.Copyable, ~Swift.Escapable where D : ~Escapable
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-MISC-NEXT: extension {{.*}}.Outer : Swift.Copyable {
// CHECK-MISC-NEXT: extension {{.*}}.Outer : Swift.Copyable where A : Swift.Copyable {
// CHECK-MISC: #endif
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-MISC-NEXT: extension {{.*}}.Outer.InnerStruct : Swift.Copyable {
// CHECK-MISC-NEXT: extension {{.*}}.Outer.InnerStruct : Swift.Copyable where A : Swift.Copyable, C : Swift.Copyable {
// CHECK-MISC: #endif
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-MISC-NEXT: extension {{.*}}.Outer.InnerVariation1 : Swift.Copyable {
// CHECK-MISC-NEXT: extension {{.*}}.Outer.InnerVariation1 : Swift.Copyable where A : Swift.Copyable, D : Swift.Copyable {
// CHECK-MISC: #endif
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-MISC-NEXT: extension {{.*}}.Outer.InnerVariation2 : Swift.Escapable where A : ~Copyable {
// CHECK-MISC-NEXT: extension {{.*}}.Outer.InnerVariation2 : Swift.Escapable where D : Swift.Escapable {
// CHECK-MISC: #endif
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics
@@ -250,7 +250,7 @@ import Swiftskell
// CHECK: #endif
// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-NEXT: extension Swiftskell.Pair : Swift.Copyable {
// CHECK-NEXT: extension Swiftskell.Pair : Swift.Copyable where L : Swift.Copyable, R : Swift.Copyable {
// CHECK: #endif
// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics
@@ -258,7 +258,7 @@ import Swiftskell
// CHECK: #endif
// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-NEXT: extension Swiftskell.Maybe : Swift.Copyable {
// CHECK-NEXT: extension Swiftskell.Maybe : Swift.Copyable where Wrapped : Swift.Copyable {
// CHECK: #endif
// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics

View File

@@ -145,7 +145,7 @@ public struct A<T: ~Copyable>: ~Copyable {
// CHECK: sil hidden [ossa] @$s4test1AVAARi_zrlEACyxGycfC : $@convention(method) <T where T : ~Copyable> (@thin A<T>.Type) -> @owned A<T> {
}
extension A: Copyable {}
extension A: Copyable where T: Copyable {}
// <T: ~Copyable>
extension A where T: ~Copyable {

View File

@@ -2,4 +2,4 @@
struct G<T: ~Copyable>: ~Copyable { }
extension G: Copyable {}
extension G: Copyable where T: Copyable {}

View File

@@ -15,14 +15,14 @@ enum RudeEnum<T: ~Copyable>: Copyable {
struct CondCopyableStruct<T: ~Copyable>: ~Copyable {}
extension CondCopyableStruct: Copyable {}
extension CondCopyableStruct: Copyable where T: Copyable {}
enum CondCopyableEnum<T: ~Copyable>: ~Copyable {
case some(T)
case none
}
extension CondCopyableEnum: Copyable {}
extension CondCopyableEnum: Copyable where T: Copyable {}
protocol NoEscapeP: ~Escapable {}
@@ -39,14 +39,14 @@ enum TooRudeEnum<T: ~Escapable>: Escapable {
struct CondEscapableStruct<T: ~Escapable>: ~Escapable {}
extension CondEscapableStruct: Escapable {}
extension CondEscapableStruct: Escapable where T: Escapable {}
enum CondEscapableEnum<T: ~Escapable>: ~Escapable {
case some(T)
case none
}
extension CondEscapableEnum: Escapable {}
extension CondEscapableEnum: Escapable where T: Escapable {}
// MARK: ensure certain conditionally Copyable types are treated as trivial (no ownership in func signature).
@@ -142,18 +142,18 @@ struct MyStruct<T: ~Copyable & ~Escapable>: ~Copyable & ~Escapable {
var x: T
}
extension MyStruct: Copyable where T: Copyable & ~Escapable {}
extension MyStruct: Copyable where T: Copyable {}
extension MyStruct: Escapable where T: Escapable & ~Copyable {}
extension MyStruct: Escapable where T: Escapable {}
enum MyEnum<T: ~Copyable & ~Escapable>: ~Copyable & ~Escapable {
case x(T)
case knoll
}
extension MyEnum: Copyable where T: Copyable & ~Escapable {}
extension MyEnum: Copyable where T: Copyable {}
extension MyEnum: Escapable where T: Escapable & ~Copyable {}
extension MyEnum: Escapable where T: Escapable {}
enum Trivial {
case a, b, c

View File

@@ -21,9 +21,9 @@ public enum Nillable<Wrapped: ~Copyable & ~Escapable>: ~Copyable & ~Escapable {
case some(Wrapped)
}
extension Nillable: Copyable where Wrapped: ~Escapable /* & Copyable */ {}
extension Nillable: Copyable where Wrapped: Copyable {}
extension Nillable: Escapable where Wrapped: ~Copyable /* & Escapable */ {}
extension Nillable: Escapable where Wrapped: Escapable {}
extension Nillable: Sendable where Wrapped: ~Copyable & ~Escapable & Sendable { }

View File

@@ -2,7 +2,7 @@
struct G<T: ~Copyable>: ~Copyable {}
extension G: Copyable {}
extension G: Copyable where T: Copyable {}
protocol Base {}
protocol Derived: Base {}

View File

@@ -25,7 +25,7 @@ public enum Maybe<Wrapped: ~Copyable>: ~Copyable {
case none
}
extension Maybe: Copyable {}
extension Maybe: Copyable where Wrapped: Copyable {}
public func ncIdentity<T: ~Copyable>(_ t: consuming T) -> T { return t }

View File

@@ -18,7 +18,7 @@
// CHECK-PRINT-DAG: protocol Generator<Value> {
// CHECK-PRINT-DAG: enum Maybe<Wrapped> : ~Copyable where Wrapped : ~Copyable {
// CHECK-PRINT-DAG: extension Maybe : Copyable {
// CHECK-PRINT-DAG: extension Maybe : Copyable where Wrapped : Copyable {
// CHECK-PRINT-DAG: func ncIdentity<T>(_ t: consuming T) -> T where T : ~Copyable
// CHECK-PRINT-DAG: protocol Either<Left, Right> : ~Copyable {
// CHECK-PRINT-DAG: associatedtype Left : ~Copyable