@@ -1263,6 +1263,118 @@ void MultiConformanceChecker::checkAllConformances() {
1263
1263
}
1264
1264
}
1265
1265
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
+
1266
1378
// / \brief Determine whether the type \c T conforms to the protocol \c Proto,
1267
1379
// / recording the complete witness table if it does.
1268
1380
ProtocolConformance *MultiConformanceChecker::
@@ -1300,6 +1412,7 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
1300
1412
auto canT = T->getCanonicalType ();
1301
1413
DeclContext *DC = conformance->getDeclContext ();
1302
1414
auto Proto = conformance->getProtocol ();
1415
+ auto ProtoType = Proto->getDeclaredType ();
1303
1416
SourceLoc ComplainLoc = conformance->getLoc ();
1304
1417
1305
1418
// Note that we are checking this conformance now.
@@ -1317,7 +1430,7 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
1317
1430
// If the protocol requires a class, non-classes are a non-starter.
1318
1431
if (Proto->requiresClass () && !canT->getClassOrBoundGenericClass ()) {
1319
1432
TC.diagnose (ComplainLoc, diag::non_class_cannot_conform_to_class_protocol,
1320
- T, Proto-> getDeclaredType () );
1433
+ T, ProtoType );
1321
1434
conformance->setInvalid ();
1322
1435
return conformance;
1323
1436
}
@@ -1338,8 +1451,7 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
1338
1451
break ;
1339
1452
}
1340
1453
if (diagKind) {
1341
- TC.diagnose (ComplainLoc, diagKind.getValue (),
1342
- T, Proto->getDeclaredType ());
1454
+ TC.diagnose (ComplainLoc, diagKind.getValue (), T, ProtoType);
1343
1455
conformance->setInvalid ();
1344
1456
return conformance;
1345
1457
}
@@ -1357,7 +1469,7 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
1357
1469
if (clas->usesObjCGenericsModel ()) {
1358
1470
TC.diagnose (ComplainLoc,
1359
1471
diag::objc_generics_cannot_conditionally_conform, T,
1360
- Proto-> getDeclaredType () );
1472
+ ProtoType );
1361
1473
conformance->setInvalid ();
1362
1474
return conformance;
1363
1475
}
@@ -1376,21 +1488,49 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
1376
1488
if (serialized->getLanguageVersionBuiltWith () !=
1377
1489
TC.getLangOpts ().EffectiveLanguageVersion ) {
1378
1490
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 (),
1382
1493
TC.getLangOpts ().EffectiveLanguageVersion );
1383
1494
hasDiagnosed = true ;
1384
1495
}
1385
1496
}
1386
1497
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 );
1389
1500
}
1390
1501
conformance->setInvalid ();
1391
1502
return conformance;
1392
1503
}
1393
1504
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
+
1394
1534
// Check that T conforms to all inherited protocols.
1395
1535
for (auto InheritedProto : Proto->getInheritedProtocols ()) {
1396
1536
auto InheritedConformance =
@@ -1420,9 +1560,11 @@ checkIndividualConformance(NormalProtocolConformance *conformance,
1420
1560
AllUsedCheckers.emplace_back (TC, conformance, MissingWitnesses);
1421
1561
MissingWitnesses.insert (revivedMissingWitnesses.begin (),
1422
1562
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);
1426
1568
return conformance;
1427
1569
}
1428
1570
0 commit comments