@@ -1216,6 +1216,11 @@ class VarDeclUsageChecker : public ASTWalker {
1216
1216
// / This is a mapping from an OpaqueValue to the expression that initialized
1217
1217
// / it.
1218
1218
llvm::SmallDenseMap<OpaqueValueExpr*, Expr*> OpaqueValueMap;
1219
+
1220
+ // / This is a mapping from VarDecls to the if/while/guard statement that they
1221
+ // / occur in, when they are in a pattern in a StmtCondition.
1222
+ llvm::SmallDenseMap<VarDecl*, LabeledConditionalStmt*> StmtConditionForVD;
1223
+
1219
1224
bool sawError = false ;
1220
1225
1221
1226
VarDeclUsageChecker (const VarDeclUsageChecker &) = delete ;
@@ -1340,6 +1345,18 @@ class VarDeclUsageChecker : public ASTWalker {
1340
1345
// for them.
1341
1346
if (auto *ICS = dyn_cast<IfConfigStmt>(S))
1342
1347
handleIfConfig (ICS);
1348
+
1349
+ // Keep track of an association between vardecls and the StmtCondition that
1350
+ // they are bound in for IfStmt, GuardStmt, WhileStmt, etc.
1351
+ if (auto LCS = dyn_cast<LabeledConditionalStmt>(S)) {
1352
+ for (auto &cond : LCS->getCond ())
1353
+ if (auto pat = cond.getPatternOrNull ()) {
1354
+ pat->forEachVariable ([&](VarDecl *VD) {
1355
+ StmtConditionForVD[VD] = LCS;
1356
+ });
1357
+ }
1358
+ }
1359
+
1343
1360
return { true , S };
1344
1361
}
1345
1362
@@ -1409,6 +1426,60 @@ VarDeclUsageChecker::~VarDeclUsageChecker() {
1409
1426
continue ;
1410
1427
}
1411
1428
1429
+ // If the variable is defined in a pattern in an if/while/guard statement,
1430
+ // see if we can produce a tuned fixit. When we have something like:
1431
+ //
1432
+ // if let x = <expr> {
1433
+ //
1434
+ // we prefer to rewrite it to:
1435
+ //
1436
+ // if <expr> != nil {
1437
+ //
1438
+ if (auto SC = StmtConditionForVD[var]) {
1439
+ // We only handle the "if let" case right now, since it is vastly the
1440
+ // most common situation that people run into.
1441
+ if (SC->getCond ().size () == 1 ) {
1442
+ auto pattern = SC->getCond ()[0 ].getPattern ();
1443
+ if (auto OSP = dyn_cast<OptionalSomePattern>(pattern))
1444
+ if (auto LP = dyn_cast<VarPattern>(OSP->getSubPattern ()))
1445
+ if (isa<NamedPattern>(LP->getSubPattern ())) {
1446
+ auto initExpr = SC->getCond ()[0 ].getInitializer ();
1447
+ auto beforeExprLoc =
1448
+ initExpr->getStartLoc ().getAdvancedLocOrInvalid (-1 );
1449
+ if (beforeExprLoc.isValid ()) {
1450
+ unsigned noParens = initExpr->canAppendCallParentheses ();
1451
+
1452
+ // If the subexpr is an "as?" cast, we can rewrite it to
1453
+ // be an "is" test.
1454
+ bool isIsTest = false ;
1455
+ if (isa<ConditionalCheckedCastExpr>(initExpr) &&
1456
+ !initExpr->isImplicit ()) {
1457
+ noParens = isIsTest = true ;
1458
+ }
1459
+
1460
+ auto diagIF = TC.diagnose (var->getLoc (),
1461
+ diag::pbd_never_used_stmtcond,
1462
+ var->getName ());
1463
+ auto introducerLoc = SC->getCond ()[0 ].getIntroducerLoc ();
1464
+ diagIF.fixItReplace (SourceRange (introducerLoc, beforeExprLoc),
1465
+ &" (" [noParens]);
1466
+
1467
+ if (isIsTest) {
1468
+ // If this was an "x as? T" check, rewrite it to "x is T".
1469
+ auto CCE = cast<ConditionalCheckedCastExpr>(initExpr);
1470
+ diagIF.fixItReplace (SourceRange (CCE->getLoc (),
1471
+ CCE->getQuestionLoc ()),
1472
+ " is" );
1473
+ } else {
1474
+ diagIF.fixItInsertAfter (initExpr->getEndLoc (),
1475
+ &" ) != nil" [noParens]);
1476
+ }
1477
+ continue ;
1478
+ }
1479
+ }
1480
+ }
1481
+ }
1482
+
1412
1483
// Otherwise, this is something more complex, perhaps
1413
1484
// let (a,b) = foo()
1414
1485
// Just rewrite the one variable with a _.
0 commit comments