Skip to content

Commit 9eba052

Browse files
authored
Merge pull request #74322 from kavon/se427-noimplicit-for-invertible
SE-427: Make conditional conformances to Copyable more explicit.
2 parents a83269e + a1e14ae commit 9eba052

25 files changed

+225
-77
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7728,8 +7728,8 @@ ERROR(inverse_conflicts_explicit_composition, none,
77287728
"composition cannot contain '~%0' when another member requires '%0'",
77297729
(StringRef))
77307730
ERROR(inverse_extension, none,
7731-
"cannot suppress %0 in extension",
7732-
(Type))
7731+
"cannot suppress '%0' in extension",
7732+
(StringRef))
77337733
ERROR(copyable_illegal_deinit, none,
77347734
"deinitializer cannot be declared in %kind0 that conforms to 'Copyable'",
77357735
(const ValueDecl *))
@@ -7799,6 +7799,9 @@ ERROR(suppress_nonsuppressable_protocol,none,
77997799
"conformance to %0 cannot be suppressed", (const ProtocolDecl *))
78007800
WARNING(suppress_already_suppressed_protocol,none,
78017801
"already suppressed conformance to %0", (const ProtocolDecl *))
7802+
ERROR(extension_conforms_to_invertible_and_others, none,
7803+
"conformance to '%0' must be declared in a separate extension",
7804+
(StringRef))
78027805

78037806
// -- older ones below --
78047807
ERROR(noncopyable_parameter_requires_ownership, none,

lib/AST/ASTPrinter.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2915,6 +2915,19 @@ void PrintAST::printExtendedTypeName(TypeLoc ExtendedTypeLoc) {
29152915
printTypeLoc(TypeLoc(ExtendedTypeLoc.getTypeRepr(), Ty));
29162916
}
29172917

2918+
/// If an extension adds a conformance for an invertible protocol, then we
2919+
/// should not print inverses for its generic signature, because no conditional
2920+
/// requirements are inferred by default for such an extension.
2921+
static bool isExtensionAddingInvertibleConformance(const ExtensionDecl *ext) {
2922+
auto conformances = ext->getLocalConformances();
2923+
for (auto *conf : conformances) {
2924+
if (conf->getProtocol()->getInvertibleProtocolKind()) {
2925+
assert(conformances.size() == 1 && "expected solo conformance");
2926+
return true;
2927+
}
2928+
}
2929+
return false;
2930+
}
29182931

29192932
void PrintAST::printSynthesizedExtension(Type ExtendedType,
29202933
ExtensionDecl *ExtDecl) {
@@ -3039,9 +3052,21 @@ void PrintAST::printExtension(ExtensionDecl *decl) {
30393052
auto baseGenericSig = decl->getExtendedNominal()->getGenericSignature();
30403053
assert(baseGenericSig &&
30413054
"an extension can't be generic if the base type isn't");
3055+
3056+
auto genSigFlags = defaultGenericRequirementFlags()
3057+
| IncludeOuterInverses;
3058+
3059+
// Disable printing inverses if the extension is adding a conformance
3060+
// for an invertible protocol itself, as we do not infer any requirements
3061+
// in such an extension. We need to print the whole signature:
3062+
// extension S: Copyable where T: Copyable
3063+
if (isExtensionAddingInvertibleConformance(decl)) {
3064+
genSigFlags &= ~PrintInverseRequirements;
3065+
genSigFlags &= ~IncludeOuterInverses;
3066+
}
3067+
30423068
printGenericSignature(genericSig,
3043-
defaultGenericRequirementFlags()
3044-
| IncludeOuterInverses,
3069+
genSigFlags,
30453070
[baseGenericSig](const Requirement &req) -> bool {
30463071
// Only include constraints that are not satisfied by the base type.
30473072
return !baseGenericSig->isRequirementSatisfied(req);

lib/AST/RequirementMachine/RequirementMachineRequests.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -782,7 +782,6 @@ InferredGenericSignatureRequest::evaluate(
782782

783783
unsigned numOuterParams = genericParams.size();
784784
if (isExtension) {
785-
assert(allowInverses);
786785
numOuterParams = 0;
787786
}
788787

lib/Frontend/ModuleInterfaceSupport.cpp

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ class InheritedProtocolCollector {
802802
}
803803

804804
/// If there were any conditional conformances that couldn't be printed,
805-
/// make a dummy extension that conforms to all of them, constrained by a
805+
/// make dummy extension(s) that conforms to all of them, constrained by a
806806
/// fake protocol.
807807
bool printInaccessibleConformanceExtensionIfNeeded(
808808
raw_ostream &out, const PrintOptions &printOptions,
@@ -811,20 +811,35 @@ class InheritedProtocolCollector {
811811
return false;
812812
assert(nominal->isGenericContext());
813813

814-
if (!printOptions.printPublicInterface())
815-
out << "@_spi(" << DummyProtocolName << ")\n";
816-
out << "@available(*, unavailable)\nextension ";
817-
nominal->getDeclaredType().print(out, printOptions);
818-
out << " : ";
819-
llvm::interleave(
820-
ConditionalConformanceProtocols,
821-
[&out, &printOptions](const ProtocolType *protoTy) {
822-
protoTy->print(out, printOptions);
823-
},
824-
[&out] { out << ", "; });
825-
out << " where "
826-
<< nominal->getGenericSignature().getGenericParams().front()->getName()
827-
<< " : " << DummyProtocolName << " {}\n";
814+
auto emitExtension =
815+
[&](ArrayRef<const ProtocolType *> conformanceProtos) {
816+
if (!printOptions.printPublicInterface())
817+
out << "@_spi(" << DummyProtocolName << ")\n";
818+
out << "@available(*, unavailable)\nextension ";
819+
nominal->getDeclaredType().print(out, printOptions);
820+
out << " : ";
821+
llvm::interleave(
822+
conformanceProtos,
823+
[&out, &printOptions](const ProtocolType *protoTy) {
824+
protoTy->print(out, printOptions);
825+
},
826+
[&out] { out << ", "; });
827+
out << " where "
828+
<< nominal->getGenericSignature().getGenericParams()[0]->getName()
829+
<< " : " << DummyProtocolName << " {}\n";
830+
};
831+
832+
// We have to print conformances for invertible protocols in separate
833+
// extensions, so do those first and save the rest for one extension.
834+
SmallVector<const ProtocolType *, 8> regulars;
835+
for (auto *proto : ConditionalConformanceProtocols) {
836+
if (proto->getDecl()->getInvertibleProtocolKind()) {
837+
emitExtension(proto);
838+
continue;
839+
}
840+
regulars.push_back(proto);
841+
}
842+
emitExtension(regulars);
828843
return true;
829844
}
830845

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,28 @@ class CheckRepressions {
154154
}
155155
};
156156

157+
/// If the extension adds a conformance to an invertible protocol, ensure that
158+
/// it does not add a conformance to any other protocol. So these are illegal:
159+
///
160+
/// extension S: Copyable & P {}
161+
/// extension S: Q, Copyable {}
162+
///
163+
/// This policy is in place because extensions adding a conformance to an
164+
/// invertible protocol do _not_ add default requirements on generic parameters,
165+
/// so it would be confusing to mix them together in the same extension.
166+
static void checkExtensionAddsSoloInvertibleProtocol(const ExtensionDecl *ext) {
167+
auto localConfs = ext->getLocalConformances();
168+
if (localConfs.size() <= 1)
169+
return;
170+
171+
for (auto *conf : localConfs) {
172+
if (auto ip = conf->getProtocol()->getInvertibleProtocolKind()) {
173+
ext->diagnose(diag::extension_conforms_to_invertible_and_others,
174+
getInvertibleProtocolKindName(*ip));
175+
}
176+
}
177+
}
178+
157179
/// Check the inheritance clause of a type declaration or extension thereof.
158180
///
159181
/// This routine performs detailed checking of the inheritance clause of the
@@ -295,10 +317,15 @@ static void checkInheritanceClause(
295317
auto layout = inheritedTy->getExistentialLayout();
296318

297319
// An inverse on an extension is an error.
298-
if (isa<ExtensionDecl>(decl))
299-
if (auto pct = inheritedTy->getAs<ProtocolCompositionType>())
300-
if (!pct->getInverses().empty())
301-
decl->diagnose(diag::inverse_extension, inheritedTy);
320+
if (isa<ExtensionDecl>(decl)) {
321+
auto canInheritedTy = inheritedTy->getCanonicalType();
322+
if (auto pct = canInheritedTy->getAs<ProtocolCompositionType>()) {
323+
for (auto inverse : pct->getInverses()) {
324+
decl->diagnose(diag::inverse_extension,
325+
getProtocolName(getKnownProtocolKind(inverse)));
326+
}
327+
}
328+
}
302329

303330
// Subclass existentials are not allowed except on classes and
304331
// non-@objc protocols.
@@ -3974,6 +4001,8 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
39744001
diagnoseExtensionOfMarkerProtocol(ED);
39754002

39764003
checkTupleExtension(ED);
4004+
4005+
checkExtensionAddsSoloInvertibleProtocol(ED);
39774006
}
39784007

39794008
void visitTopLevelCodeDecl(TopLevelCodeDecl *TLCD) {

lib/Sema/TypeCheckGeneric.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@ GenericSignatureRequest::evaluate(Evaluator &evaluator,
684684
SmallVector<TypeBase *, 2> inferenceSources;
685685
SmallVector<Requirement, 2> extraReqs;
686686
SourceLoc loc;
687+
bool inferInvertibleReqs = true;
687688

688689
if (auto VD = dyn_cast<ValueDecl>(GC->getAsDecl())) {
689690
loc = VD->getLoc();
@@ -781,6 +782,35 @@ GenericSignatureRequest::evaluate(Evaluator &evaluator,
781782
} else if (auto *ext = dyn_cast<ExtensionDecl>(GC)) {
782783
loc = ext->getLoc();
783784

785+
// The inherited entries influence the generic signature of the extension,
786+
// because if it introduces conformance to invertible protocol IP, we do not
787+
// we do not infer any requirements that the generic parameters to conform
788+
// to invertible protocols. This forces people to write out the conditions.
789+
const unsigned numEntries = ext->getInherited().size();
790+
for (unsigned i = 0; i < numEntries; ++i) {
791+
InheritedTypeRequest request{ext, i, TypeResolutionStage::Structural};
792+
auto result = evaluateOrDefault(ctx.evaluator, request,
793+
InheritedTypeResult::forDefault());
794+
Type inheritedTy;
795+
switch (result) {
796+
case InheritedTypeResult::Inherited:
797+
inheritedTy = result.getInheritedType();
798+
break;
799+
case InheritedTypeResult::Suppressed:
800+
case InheritedTypeResult::Default:
801+
continue;
802+
}
803+
804+
if (inheritedTy) {
805+
if (auto kp = inheritedTy->getKnownProtocol()) {
806+
if (getInvertibleProtocolKind(*kp)) {
807+
inferInvertibleReqs = false;
808+
break;
809+
}
810+
}
811+
}
812+
}
813+
784814
collectAdditionalExtensionRequirements(ext->getExtendedType(), extraReqs);
785815

786816
auto *extendedNominal = ext->getExtendedNominal();
@@ -800,7 +830,7 @@ GenericSignatureRequest::evaluate(Evaluator &evaluator,
800830
genericParams, WhereClauseOwner(GC),
801831
extraReqs, inferenceSources, loc,
802832
/*isExtension=*/isa<ExtensionDecl>(GC),
803-
/*allowInverses=*/true};
833+
/*allowInverses=*/inferInvertibleReqs};
804834
return evaluateOrDefault(ctx.evaluator, request,
805835
GenericSignatureWithError()).getPointer();
806836
}

stdlib/public/core/Optional.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public enum Optional<Wrapped: ~Copyable>: ~Copyable {
132132
case some(Wrapped)
133133
}
134134

135-
extension Optional: Copyable /* where Wrapped: Copyable */ {}
135+
extension Optional: Copyable where Wrapped: Copyable {}
136136

137137
extension Optional: Sendable where Wrapped: ~Copyable & Sendable { }
138138

stdlib/public/core/Result.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public enum Result<Success: ~Copyable, Failure: Error> {
2121
case failure(Failure)
2222
}
2323

24-
extension Result: Copyable /* where Success: Copyable */ {}
24+
extension Result: Copyable where Success: Copyable {}
2525

2626
extension Result: Sendable where Success: Sendable & ~Copyable {}
2727

test/Generics/inverse_conditional_conformance_restriction.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ protocol Q {}
77
class DoggoClass {}
88

99
struct Blah<T: ~Copyable & ~Escapable>: ~Copyable, ~Escapable {}
10-
extension Blah: Copyable {} // expected-error {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'T: Escapable'}}
11-
extension Blah: Escapable {} // expected-error {{conditional conformance to suppressible protocol 'Escapable' cannot depend on 'T: Copyable'}}
10+
extension Blah: Copyable where T: Copyable & Escapable {}
11+
// expected-error@-1 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'T: Escapable'}}
1212

13-
struct Yuck<T: ~Copyable & ~Escapable>: ~Copyable, ~Escapable {}
14-
extension Yuck: Copyable where T: ~Escapable {}
15-
extension Yuck: Escapable where T: ~Copyable {}
13+
extension Blah: Escapable where T: Copyable, T: Escapable {}
14+
// expected-error@-1 {{conditional conformance to suppressible protocol 'Escapable' cannot depend on 'T: Copyable'}}
15+
16+
struct Fixed<T: ~Copyable & ~Escapable>: ~Copyable, ~Escapable {}
17+
extension Fixed: Copyable where T: Copyable {}
18+
extension Fixed: Escapable where T: Escapable {}
1619

1720
struct TryConformance<Whatever: ~Copyable>: ~Copyable {}
1821
extension TryConformance: Copyable

test/Generics/inverse_generics.swift

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,26 @@
22
// RUN: -enable-experimental-feature NonescapableTypes \
33
// RUN: -enable-experimental-feature SuppressedAssociatedTypes
44

5+
// expected-note@+1 {{'T' has '~Copyable' constraint preventing implicit 'Copyable' conformance}}
6+
struct AttemptImplicitConditionalConformance<T: ~Copyable>: ~Copyable {
7+
var t: T // expected-error {{stored property 't' of 'Copyable'-conforming generic struct 'AttemptImplicitConditionalConformance' has non-Copyable type 'T'}}
8+
}
9+
extension AttemptImplicitConditionalConformance: Copyable {}
10+
// expected-error@-1 {{generic struct 'AttemptImplicitConditionalConformance' required to be 'Copyable' but is marked with '~Copyable'}}
11+
12+
enum Hello<T: ~Escapable & ~Copyable>: ~Escapable & ~Copyable {}
13+
extension Hello: Escapable {} // expected-error {{generic enum 'Hello' required to be 'Escapable' but is marked with '~Escapable'}}
14+
extension Hello: Copyable {} // expected-error {{generic enum 'Hello' required to be 'Copyable' but is marked with '~Copyable'}}
15+
16+
enum HelloExplicitlyFixed<T: ~Escapable & ~Copyable>: Escapable, Copyable {}
517

18+
struct NoInverseBecauseNoDefault<T: ~Copyable & ~Escapable>: ~Copyable {}
19+
extension NoInverseBecauseNoDefault: Copyable where T: Copyable, T: ~Escapable {}
20+
// expected-error@-1 {{cannot suppress '~Escapable' on generic parameter 'T' defined in outer scope}}
621

722
// Check support for explicit conditional conformance
823
public struct ExplicitCond<T: ~Copyable>: ~Copyable {}
9-
extension ExplicitCond: Copyable {}
24+
extension ExplicitCond: Copyable where T: Copyable {}
1025
// expected-note@-1 {{requirement from conditional conformance}}
1126
// expected-note@-2 {{requirement from conditional conformance of 'ExplicitCondAlias<NC>' (aka 'ExplicitCond<NC>') to 'Copyable'}}
1227

@@ -80,7 +95,7 @@ struct ConditionalContainment<T: ~Copyable>: ~Copyable {
8095
var y: NC // expected-error {{stored property 'y' of 'Copyable'-conforming generic struct 'ConditionalContainment' has non-Copyable type 'NC'}}
8196
}
8297

83-
extension ConditionalContainment: Copyable {}
98+
extension ConditionalContainment: Copyable where T: Copyable {}
8499

85100
func chk(_ T: RequireCopyable<ConditionalContainment<Int>>) {}
86101

@@ -126,7 +141,7 @@ enum Maybe<Wrapped: ~Copyable>: ~Copyable {
126141
deinit {} // expected-error {{deinitializer cannot be declared in generic enum 'Maybe' that conforms to 'Copyable'}}
127142
}
128143

129-
extension Maybe: Copyable {}
144+
extension Maybe: Copyable where Wrapped: Copyable {}
130145

131146
// expected-note@+4{{requirement specified as 'NC' : 'Copyable'}}
132147
// expected-note@+3{{requirement from conditional conformance of 'Maybe<NC>' to 'Copyable'}}
@@ -173,11 +188,11 @@ class NiceTry: ~Copyable, Copyable {} // expected-error {{classes cannot be '~Co
173188

174189

175190
struct Extendo: ~Copyable {}
176-
extension Extendo: Copyable, ~Copyable {} // expected-error {{cannot suppress '~Copyable' in extension}}
191+
extension Extendo: Copyable, ~Copyable {} // expected-error {{cannot suppress 'Copyable' in extension}}
177192
// expected-error@-1 {{struct 'Extendo' required to be 'Copyable' but is marked with '~Copyable'}}
178193

179194
enum EnumExtendo {}
180-
extension EnumExtendo: ~Copyable {} // expected-error {{cannot suppress '~Copyable' in extension}}
195+
extension EnumExtendo: ~Copyable {} // expected-error {{cannot suppress 'Copyable' in extension}}
181196

182197
extension NeedsCopyable where Self: ~Copyable {}
183198
// expected-error@-1 {{'Self' required to be 'Copyable' but is marked with '~Copyable'}}
@@ -265,7 +280,7 @@ enum MaybeEscapes<T: ~Escapable>: ~Escapable { // expected-note {{generic enum '
265280
case none
266281
}
267282

268-
extension MaybeEscapes: Escapable {}
283+
extension MaybeEscapes: Escapable where T: Escapable {}
269284

270285
struct Escapes { // expected-note {{consider adding '~Escapable' to struct 'Escapes'}}
271286
let t: MaybeEscapes<NonescapingType> // expected-error {{stored property 't' of 'Escapable'-conforming struct 'Escapes' has non-Escapable type 'MaybeEscapes<NonescapingType>'}}

0 commit comments

Comments
 (0)