Skip to content

Commit 03506b5

Browse files
authored
Merge pull request swiftlang#74389 from kavon/6.0-se427-noimplicit-for-invertible
[6.0🍒] SE-427: Make conditional conformances to Copyable more explicit.
2 parents 9ce382d + 422743f commit 03506b5

25 files changed

+224
-78
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7681,8 +7681,8 @@ ERROR(inverse_conflicts_explicit_composition, none,
76817681
"composition cannot contain '~%0' when another member requires '%0'",
76827682
(StringRef))
76837683
ERROR(inverse_extension, none,
7684-
"cannot suppress %0 in extension",
7685-
(Type))
7684+
"cannot suppress '%0' in extension",
7685+
(StringRef))
76867686
ERROR(copyable_illegal_deinit, none,
76877687
"deinitializer cannot be declared in %kind0 that conforms to 'Copyable'",
76887688
(const ValueDecl *))
@@ -7750,6 +7750,9 @@ ERROR(suppress_nonsuppressable_protocol,none,
77507750
"conformance to %0 cannot be suppressed", (const ProtocolDecl *))
77517751
WARNING(suppress_already_suppressed_protocol,none,
77527752
"already suppressed conformance to %0", (const ProtocolDecl *))
7753+
ERROR(extension_conforms_to_invertible_and_others, none,
7754+
"conformance to '%0' must be declared in a separate extension",
7755+
(StringRef))
77537756

77547757
// -- older ones below --
77557758
ERROR(noncopyable_parameter_requires_ownership, none,

lib/AST/ASTPrinter.cpp

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

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

29172930
void PrintAST::printSynthesizedExtension(Type ExtendedType,
29182931
ExtensionDecl *ExtDecl) {
@@ -3037,9 +3050,21 @@ void PrintAST::printExtension(ExtensionDecl *decl) {
30373050
auto baseGenericSig = decl->getExtendedNominal()->getGenericSignature();
30383051
assert(baseGenericSig &&
30393052
"an extension can't be generic if the base type isn't");
3053+
3054+
auto genSigFlags = defaultGenericRequirementFlags()
3055+
| IncludeOuterInverses;
3056+
3057+
// Disable printing inverses if the extension is adding a conformance
3058+
// for an invertible protocol itself, as we do not infer any requirements
3059+
// in such an extension. We need to print the whole signature:
3060+
// extension S: Copyable where T: Copyable
3061+
if (isExtensionAddingInvertibleConformance(decl)) {
3062+
genSigFlags &= ~PrintInverseRequirements;
3063+
genSigFlags &= ~IncludeOuterInverses;
3064+
}
3065+
30403066
printGenericSignature(genericSig,
3041-
defaultGenericRequirementFlags()
3042-
| IncludeOuterInverses,
3067+
genSigFlags,
30433068
[baseGenericSig](const Requirement &req) -> bool {
30443069
// Only include constraints that are not satisfied by the base type.
30453070
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.
@@ -3971,6 +3998,8 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
39713998
diagnoseExtensionOfMarkerProtocol(ED);
39723999

39734000
checkTupleExtension(ED);
4001+
4002+
checkExtensionAddsSoloInvertibleProtocol(ED);
39744003
}
39754004

39764005
void visitTopLevelCodeDecl(TopLevelCodeDecl *TLCD) {

lib/Sema/TypeCheckGeneric.cpp

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

691692
if (auto VD = dyn_cast<ValueDecl>(GC->getAsDecl())) {
692693
loc = VD->getLoc();
@@ -784,6 +785,35 @@ GenericSignatureRequest::evaluate(Evaluator &evaluator,
784785
} else if (auto *ext = dyn_cast<ExtensionDecl>(GC)) {
785786
loc = ext->getLoc();
786787

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

789819
auto *extendedNominal = ext->getExtendedNominal();
@@ -815,7 +845,7 @@ GenericSignatureRequest::evaluate(Evaluator &evaluator,
815845
genericParams, WhereClauseOwner(GC),
816846
extraReqs, inferenceSources, loc,
817847
/*isExtension=*/isa<ExtensionDecl>(GC),
818-
/*allowInverses=*/true};
848+
/*allowInverses=*/inferInvertibleReqs};
819849
return evaluateOrDefault(ctx.evaluator, request,
820850
GenericSignatureWithError()).getPointer();
821851
}

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)