Skip to content

Commit c104452

Browse files
committed
[AST] Don't allow conditional conformances to imply others.
Many uses of conditional conformance to protocols with super-protocols are for wrappers, where the conformances to those super-protocols usually ends up using weaker bounds, such as: struct MyWrapper<Wrapped: Sequence> {} extension Wrapped: Sequence {} // Wrapped: Sequence extension Wrapped: Collection where Wrapped: Collection {} // * extension Wrapped: BidirectionalCollection where Wrapped: BidirectionalCollection {} If this code was instead written: struct MyWrapper<Wrapped: Sequence> {} extension Wrapped: Sequence {} // Wrapped: Sequence extension Wrapped: BidirectionalCollection where Wrapped: BidirectionalCollection {} Inferring a Collection conformance would have to mean extension Wrapped: Collection where Wrapped: BidirectionalCollection {} which is unnecessarily strong. It is a breaking change to change a protocol bound, and so we're thinking we'd prefer that the compiler didn't magic up that incorrect conformance. It also only is a small change (and the compiler even suggests it, with a fixit) to explicitly get the implying behaviour: declare the conformance explicitly. extension Wrapped: Collection, BidirectionalCollection where Wrapped: BidirectionalCollection {} Fixes rdar://problem/36499373.
1 parent 00c3269 commit c104452

File tree

5 files changed

+328
-32
lines changed

5 files changed

+328
-32
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,16 @@ ERROR(objc_generics_cannot_conditionally_conform,none,
14431443
"type %0 cannot conditionally conform to protocol %1 because "
14441444
"the type uses the Objective-C generics model",
14451445
(Type, Type))
1446+
ERROR(conditional_conformances_cannot_imply_conformances,none,
1447+
"conditional conformance of type %0 to protocol %1 does not imply conformance to "
1448+
"inherited protocol %2",
1449+
(Type, Type, Type))
1450+
NOTE(note_explicitly_state_conditional_conformance_different,none,
1451+
"did you mean to explicitly state the conformance with different bounds?", ())
1452+
NOTE(note_explicitly_state_conditional_conformance_relaxed,none,
1453+
"did you mean to explicitly state the conformance with relaxed bounds?", ())
1454+
NOTE(note_explicitly_state_conditional_conformance_same,none,
1455+
"did you mean to explicitly state the conformance with the same bounds?", ())
14461456
ERROR(protocol_has_missing_requirements,none,
14471457
"type %0 cannot conform to protocol %1 because it has requirements that "
14481458
"cannot be satisfied", (Type, Type))

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 154 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,118 @@ void MultiConformanceChecker::checkAllConformances() {
12631263
}
12641264
}
12651265

1266+
static void diagnoseConformanceImpliedByConditionalConformance(
1267+
DiagnosticEngine &Diags, NormalProtocolConformance *conformance,
1268+
NormalProtocolConformance *implyingConf, bool issueFixit) {
1269+
Type T = conformance->getType();
1270+
auto proto = conformance->getProtocol();
1271+
Type protoType = proto->getDeclaredType();
1272+
auto implyingProto = implyingConf->getProtocol()->getDeclaredType();
1273+
auto loc = implyingConf->getLoc();
1274+
Diags.diagnose(loc, diag::conditional_conformances_cannot_imply_conformances,
1275+
T, implyingProto, protoType);
1276+
1277+
if (!issueFixit)
1278+
return;
1279+
1280+
// Now we get down to business: constructing a few options for new
1281+
// extensions. They all look like:
1282+
//
1283+
// extension T: ProtoType where ... {
1284+
// <# witnesses #>
1285+
// }
1286+
//
1287+
// The options are:
1288+
//
1289+
// - if possible, the original bounds relaxed, when the requirements match the
1290+
// conforming protocol, e.g. 'X: Hashable where T: Hashable' often
1291+
// corresponds to 'X: Equatable where T: Equatable'. This fixit is included
1292+
// if all the requirements are conformance constraints to the protocol
1293+
// that implies the conformance.
1294+
// - the same bounds: ... is copied from the implying extension
1295+
// - new bounds: ... becomes a placeholder
1296+
//
1297+
// We could also suggest adding ", ProtoType" to the existing extension,
1298+
// but we don't think having multiple conformances in a single extension
1299+
// (especially conditional ones) is good Swift style, and so we don't
1300+
// want to encourage it.
1301+
1302+
auto ext = cast<ExtensionDecl>(implyingConf->getDeclContext());
1303+
1304+
auto &SM = ext->getASTContext().SourceMgr;
1305+
StringRef extraIndent;
1306+
StringRef indent = Lexer::getIndentationForLine(SM, loc, &extraIndent);
1307+
1308+
// First, the bits that aren't the requirements are the same for all the
1309+
// types.
1310+
llvm::SmallString<128> prefix;
1311+
llvm::SmallString<128> suffix;
1312+
{
1313+
llvm::raw_svector_ostream prefixStream(prefix);
1314+
llvm::raw_svector_ostream suffixStream(suffix);
1315+
1316+
prefixStream << "extension " << T << ": " << protoType << " ";
1317+
suffixStream << " {\n"
1318+
<< indent << extraIndent << "<#witnesses#>\n"
1319+
<< indent << "}\n\n"
1320+
<< indent;
1321+
}
1322+
1323+
// First, we do the fixit for "matching" requirements (i.e. X: P where T: P).
1324+
bool matchingIsValid = true;
1325+
llvm::SmallString<128> matchingFixit = prefix;
1326+
{
1327+
llvm::raw_svector_ostream matchingStream(matchingFixit);
1328+
matchingStream << "where ";
1329+
bool first = true;
1330+
for (const auto &req : implyingConf->getConditionalRequirements()) {
1331+
auto firstType = req.getFirstType();
1332+
// T: ImplyingProto => T: Proto
1333+
if (req.getKind() == RequirementKind::Conformance &&
1334+
req.getSecondType()->isEqual(implyingProto)) {
1335+
auto comma = first ? "" : ", ";
1336+
matchingStream << comma << firstType << ": " << protoType;
1337+
first = false;
1338+
continue;
1339+
}
1340+
// something didn't work out, so give up on this fixit.
1341+
matchingIsValid = false;
1342+
break;
1343+
}
1344+
}
1345+
1346+
if (matchingIsValid) {
1347+
matchingFixit += suffix;
1348+
Diags
1349+
.diagnose(loc,
1350+
diag::note_explicitly_state_conditional_conformance_relaxed)
1351+
.fixItInsert(loc, matchingFixit);
1352+
}
1353+
1354+
// Next, do the fixit for using the same requirements, but be resilient to a
1355+
// missing `where` clause: this is one of a few fixits that get emitted here,
1356+
// and so is a very low priority diagnostic, and so shouldn't crash.
1357+
if (auto TWC = ext->getTrailingWhereClause()) {
1358+
llvm::SmallString<128> sameFixit = prefix;
1359+
auto CSR =
1360+
Lexer::getCharSourceRangeFromSourceRange(SM, TWC->getSourceRange());
1361+
sameFixit += SM.extractText(CSR);
1362+
sameFixit += suffix;
1363+
Diags
1364+
.diagnose(loc, diag::note_explicitly_state_conditional_conformance_same)
1365+
.fixItInsert(loc, sameFixit);
1366+
}
1367+
1368+
// And finally, just the generic new-requirements one:
1369+
llvm::SmallString<128> differentFixit = prefix;
1370+
differentFixit += "where <#requirements#>";
1371+
differentFixit += suffix;
1372+
Diags
1373+
.diagnose(loc,
1374+
diag::note_explicitly_state_conditional_conformance_different)
1375+
.fixItInsert(loc, differentFixit);
1376+
}
1377+
12661378
/// \brief Determine whether the type \c T conforms to the protocol \c Proto,
12671379
/// recording the complete witness table if it does.
12681380
ProtocolConformance *MultiConformanceChecker::
@@ -1300,6 +1412,7 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
13001412
auto canT = T->getCanonicalType();
13011413
DeclContext *DC = conformance->getDeclContext();
13021414
auto Proto = conformance->getProtocol();
1415+
auto ProtoType = Proto->getDeclaredType();
13031416
SourceLoc ComplainLoc = conformance->getLoc();
13041417

13051418
// Note that we are checking this conformance now.
@@ -1317,7 +1430,7 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
13171430
// If the protocol requires a class, non-classes are a non-starter.
13181431
if (Proto->requiresClass() && !canT->getClassOrBoundGenericClass()) {
13191432
TC.diagnose(ComplainLoc, diag::non_class_cannot_conform_to_class_protocol,
1320-
T, Proto->getDeclaredType());
1433+
T, ProtoType);
13211434
conformance->setInvalid();
13221435
return conformance;
13231436
}
@@ -1338,8 +1451,7 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
13381451
break;
13391452
}
13401453
if (diagKind) {
1341-
TC.diagnose(ComplainLoc, diagKind.getValue(),
1342-
T, Proto->getDeclaredType());
1454+
TC.diagnose(ComplainLoc, diagKind.getValue(), T, ProtoType);
13431455
conformance->setInvalid();
13441456
return conformance;
13451457
}
@@ -1357,7 +1469,7 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
13571469
if (clas->usesObjCGenericsModel()) {
13581470
TC.diagnose(ComplainLoc,
13591471
diag::objc_generics_cannot_conditionally_conform, T,
1360-
Proto->getDeclaredType());
1472+
ProtoType);
13611473
conformance->setInvalid();
13621474
return conformance;
13631475
}
@@ -1376,21 +1488,49 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
13761488
if (serialized->getLanguageVersionBuiltWith() !=
13771489
TC.getLangOpts().EffectiveLanguageVersion) {
13781490
TC.diagnose(ComplainLoc,
1379-
diag::protocol_has_missing_requirements_versioned,
1380-
T, Proto->getDeclaredType(),
1381-
serialized->getLanguageVersionBuiltWith(),
1491+
diag::protocol_has_missing_requirements_versioned, T,
1492+
ProtoType, serialized->getLanguageVersionBuiltWith(),
13821493
TC.getLangOpts().EffectiveLanguageVersion);
13831494
hasDiagnosed = true;
13841495
}
13851496
}
13861497
if (!hasDiagnosed) {
1387-
TC.diagnose(ComplainLoc, diag::protocol_has_missing_requirements,
1388-
T, Proto->getDeclaredType());
1498+
TC.diagnose(ComplainLoc, diag::protocol_has_missing_requirements, T,
1499+
ProtoType);
13891500
}
13901501
conformance->setInvalid();
13911502
return conformance;
13921503
}
13931504

1505+
bool impliedDisablesMissingWitnessFixits = false;
1506+
if (conformance->getSourceKind() == ConformanceEntryKind::Implied) {
1507+
// We've got something like:
1508+
//
1509+
// protocol Foo : Proto {}
1510+
// extension SomeType : Foo {}
1511+
//
1512+
// We don't want to allow this when the SomeType : Foo conformance is
1513+
// conditional
1514+
auto implyingConf = conformance->getImplyingConformance();
1515+
// There might be a long chain of implications, e.g. protocol Foo: Proto {}
1516+
// protocol Bar: Foo {} extension SomeType: Bar {}, so keep looking all the
1517+
// way up.
1518+
while (implyingConf->getSourceKind() == ConformanceEntryKind::Implied) {
1519+
implyingConf = implyingConf->getImplyingConformance();
1520+
}
1521+
if (!implyingConf->getConditionalRequirements().empty()) {
1522+
// We shouldn't suggest including witnesses for the conformance, because
1523+
// those suggestions will go in the current DeclContext, but really they
1524+
// should go into the new extension we (might) suggest here.
1525+
impliedDisablesMissingWitnessFixits = true;
1526+
1527+
diagnoseConformanceImpliedByConditionalConformance(
1528+
TC.Diags, conformance, implyingConf, issueFixit);
1529+
1530+
conformance->setInvalid();
1531+
}
1532+
}
1533+
13941534
// Check that T conforms to all inherited protocols.
13951535
for (auto InheritedProto : Proto->getInheritedProtocols()) {
13961536
auto InheritedConformance =
@@ -1420,9 +1560,11 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
14201560
AllUsedCheckers.emplace_back(TC, conformance, MissingWitnesses);
14211561
MissingWitnesses.insert(revivedMissingWitnesses.begin(),
14221562
revivedMissingWitnesses.end());
1423-
AllUsedCheckers.back().checkConformance(issueFixit ?
1424-
MissingWitnessDiagnosisKind::ErrorFixIt :
1425-
MissingWitnessDiagnosisKind::ErrorOnly);
1563+
1564+
auto missingWitnessFixits = issueFixit && !impliedDisablesMissingWitnessFixits;
1565+
AllUsedCheckers.back().checkConformance(
1566+
missingWitnessFixits ? MissingWitnessDiagnosisKind::ErrorFixIt
1567+
: MissingWitnessDiagnosisKind::ErrorOnly);
14261568
return conformance;
14271569
}
14281570

test/Generics/conditional_conformances.swift

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-typecheck-verify-swift -typecheck %s -verify
1+
// RUN: %target-typecheck-verify-swift -typecheck -verify
22
// RUN: %target-typecheck-verify-swift -typecheck -debug-generic-signatures %s > %t.dump 2>&1
33
// RUN: %FileCheck %s < %t.dump
44

@@ -7,10 +7,7 @@ protocol P2 {}
77
protocol P3 {}
88
protocol P4: P1 {}
99
protocol P5: P2 {}
10-
// expected-note@-1{{type 'InheritImplicitGood<T>' does not conform to inherited protocol 'P2'}}
11-
// expected-note@-2{{type 'InheritImplicitBad<T>' does not conform to inherited protocol 'P2'}}
1210
protocol P6: P2 {}
13-
// expected-note@-1{{type 'InheritImplicitBad<T>' does not conform to inherited protocol 'P2'}}
1411

1512
protocol Assoc { associatedtype AT }
1613

@@ -276,35 +273,64 @@ func inheritequal_bad_bad<U>(_: U) {
276273
// expected-note@-3{{requirement from conditional conformance of 'InheritMore<U>' to 'P5'}}
277274
}
278275

279-
struct InheritImplicitGood<T> {}
280-
// FIXME: per SE-0143, this should result in an implicit conformance
281-
// InheritImplicitGood: P2.
282-
extension InheritImplicitGood: P5 where T: P1 {}
283-
// expected-error@-1{{type 'InheritImplicitGood<T>' does not conform to protocol 'P2'}}
276+
struct InheritImplicitOne<T> {}
277+
// This shouldn't give anything implicit since we disallow implication for
278+
// conditional conformances (in many cases, the implied bounds are
279+
// incorrect/insufficiently general).
280+
extension InheritImplicitOne: P5 where T: P1 {}
281+
// expected-error@-1{{conditional conformance of type 'InheritImplicitOne<T>' to protocol 'P5' does not imply conformance to inherited protocol 'P2'}}
282+
// expected-note@-2{{did you mean to explicitly state the conformance with the same bounds?}}
283+
// expected-note@-3{{did you mean to explicitly state the conformance with different bounds?}}
284+
285+
struct InheritImplicitTwo<T> {}
286+
// Even if we relax the rule about implication, this double-up should still be
287+
// an error, because either conformance could imply InheritImplicitTwo: P2.
288+
extension InheritImplicitTwo: P5 where T: P1 {}
289+
// expected-error@-1{{conditional conformance of type 'InheritImplicitTwo<T>' to protocol 'P5' does not imply conformance to inherited protocol 'P2'}}
290+
// expected-note@-2{{did you mean to explicitly state the conformance with the same bounds?}}
291+
// expected-note@-3{{did you mean to explicitly state the conformance with different bounds?}}
292+
extension InheritImplicitTwo: P6 where T: P1 {}
293+
294+
// However, if there's a non-conditional conformance that implies something, we
295+
// can imply from that one.
296+
struct InheritImplicitGood1<T> {}
297+
extension InheritImplicitGood1: P5 {}
298+
extension InheritImplicitGood1: P6 where T: P1 {}
299+
300+
func inheritimplicitgood1<T>(_ : T) {
301+
takes_P2(InheritImplicitGood1<T>()) // unconstrained!
302+
takes_P2(InheritImplicitGood1<Int>())
303+
}
304+
struct InheritImplicitGood2<T> {}
305+
extension InheritImplicitGood2: P6 where T: P1 {}
306+
extension InheritImplicitGood2: P5 {}
284307

285-
struct InheritImplicitBad<T> {}
286-
// This shouldn't give anything implicit since either conformance could imply
287-
// InheritImplicitBad: P2.
288-
extension InheritImplicitBad: P5 where T: P1 {}
289-
// expected-error@-1{{type 'InheritImplicitBad<T>' does not conform to protocol 'P2'}}
290-
extension InheritImplicitBad: P6 where T: P1 {}
291-
// expected-error@-1{{type 'InheritImplicitBad<T>' does not conform to protocol 'P2'}}
308+
func inheritimplicitgood2<T>(_: T) {
309+
takes_P2(InheritImplicitGood2<T>()) // unconstrained!
310+
takes_P2(InheritImplicitGood2<Int>())
292311

312+
}
313+
struct InheritImplicitGood3<T>: P5 {}
314+
extension InheritImplicitGood3: P6 where T: P1 {}
293315

316+
func inheritimplicitgood3<T>(_: T) {
317+
takes_P2(InheritImplicitGood3<T>()) // unconstrained!
318+
takes_P2(InheritImplicitGood3<Int>())
319+
}
294320

295321
// "Multiple conformances" from SE0143
296322

297323
struct TwoConformances<T> {}
298324
extension TwoConformances: P2 where T: P1 {}
299-
// expected-error@-1{{redundant conformance of 'TwoConformances<T>' to protocol 'P2'}}
300-
extension TwoConformances: P2 where T: P3 {}
301325
// expected-note@-1{{'TwoConformances<T>' declares conformance to protocol 'P2' here}}
326+
extension TwoConformances: P2 where T: P3 {}
327+
// expected-error@-1{{redundant conformance of 'TwoConformances<T>' to protocol 'P2'}}
302328

303329
struct TwoDisjointConformances<T> {}
304330
extension TwoDisjointConformances: P2 where T == Int {}
305-
// expected-error@-1{{redundant conformance of 'TwoDisjointConformances<T>' to protocol 'P2'}}
306-
extension TwoDisjointConformances: P2 where T == String {}
307331
// expected-note@-1{{'TwoDisjointConformances<T>' declares conformance to protocol 'P2' here}}
332+
extension TwoDisjointConformances: P2 where T == String {}
333+
// expected-error@-1{{redundant conformance of 'TwoDisjointConformances<T>' to protocol 'P2'}}
308334

309335

310336
// FIXME: these cases should be equivalent (and both with the same output as the
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// RUN: %target-typecheck-verify-swift -emit-fixits-path %t.remap -fixit-all
2+
// RUN: c-arcmt-test %t.remap | arcmt-test -verify-transformed-files %s.result
3+
4+
protocol P1 {}
5+
protocol P2: P1 {}
6+
7+
8+
struct S1<T> {}
9+
10+
extension S1: P2 where T: P1 {}
11+
// expected-error@-1 {{conditional conformance of type 'S1<T>' to protocol 'P2' does not imply conformance to inherited protocol 'P1'}}
12+
// expected-note@-2 {{did you mean to explicitly state the conformance with the same bounds?}}
13+
// expected-note@-3 {{did you mean to explicitly state the conformance with different bounds?}}
14+
15+
protocol P3 {
16+
associatedtype X
17+
}
18+
struct S2<T, U, V: P3> {}
19+
20+
extension S2: P2 where T: P2, U: P2, V.X: P2 {}
21+
// expected-error@-1 {{conditional conformance of type 'S2<T, U, V>' to protocol 'P2' does not imply conformance to inherited protocol 'P1'}}
22+
// expected-note@-2 {{did you mean to explicitly state the conformance with relaxed bounds?}}
23+
// expected-note@-3 {{did you mean to explicitly state the conformance with the same bounds?}}
24+
// expected-note@-4 {{did you mean to explicitly state the conformance with different bounds?}}
25+
26+
27+
struct S3<T, U, V: P3> {}
28+
29+
extension S3: P2 where T: P2, U: P2, V.X == Int {}
30+
// expected-error@-1 {{conditional conformance of type 'S3<T, U, V>' to protocol 'P2' does not imply conformance to inherited protocol 'P1'}}
31+
// expected-note@-2 {{did you mean to explicitly state the conformance with the same bounds?}}
32+
// expected-note@-3 {{did you mean to explicitly state the conformance with different bounds?}}
33+
34+
35+
struct S4<T, U, V: P3> {}
36+
37+
extension S4: P2 where T: P2, U: P3, V.X: P2 {}
38+
// expected-error@-1 {{conditional conformance of type 'S4<T, U, V>' to protocol 'P2' does not imply conformance to inherited protocol 'P1'}}
39+
// expected-note@-2 {{did you mean to explicitly state the conformance with the same bounds?}}
40+
// expected-note@-3 {{did you mean to explicitly state the conformance with different bounds?}}
41+

0 commit comments

Comments
 (0)