Skip to content

Commit 392a1ff

Browse files
committed
Deprecation warnings for C-style for loops
Warns of deprecation, checks all the appropriate bits to see if we can do an automatic fix, and generates fix-its if that is valid. Also adds a note if the loop looks like it ought to be a simple for-each, but really isn’t because the loop var is modified inside the loop.
1 parent f7fd6d4 commit 392a1ff

File tree

8 files changed

+212
-44
lines changed

8 files changed

+212
-44
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2014,6 +2014,12 @@ NOTE(change_to_mutating,sema_tcs,none,
20142014
"mark %select{method|accessor}0 'mutating' to make 'self' mutable",
20152015
(bool))
20162016

2017+
// For Stmt
2018+
WARNING(deprecated_c_style_for_stmt,sema_tcs,none,
2019+
"C-style for statement is deprecated and will be removed in a future version of Swift", ())
2020+
NOTE(cant_fix_c_style_for_stmt,sema_tcs,none,
2021+
"C-style for statement can't be automatically fixed to for-in, because the loop variable is modified inside the loop", ())
2022+
20172023
// ForEach Stmt
20182024
ERROR(sequence_protocol_broken,sema_tcs,none,
20192025
"SequenceType protocol definition is broken", ())

include/swift/AST/Stmt.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,9 @@ class ForStmt : public LabeledStmt {
800800

801801
SourceLoc getStartLoc() const { return getLabelLocOrKeywordLoc(ForLoc); }
802802
SourceLoc getEndLoc() const { return Body->getEndLoc(); }
803+
804+
SourceLoc getFirstSemicolonLoc() const { return Semi1Loc; }
805+
SourceLoc getSecondSemicolonLoc() const { return Semi2Loc; }
803806

804807
NullablePtr<Expr> getInitializer() const { return Initializer; }
805808
void setInitializer(Expr *V) { Initializer = V; }

lib/Sema/MiscDiagnostics.cpp

Lines changed: 134 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,25 +1139,6 @@ static void diagAvailability(TypeChecker &TC, const Expr *E,
11391139
const_cast<Expr*>(E)->walk(walker);
11401140
}
11411141

1142-
//===--------------------------------------------------------------------===//
1143-
// High-level entry points.
1144-
//===--------------------------------------------------------------------===//
1145-
1146-
/// \brief Emit diagnostics for syntactic restrictions on a given expression.
1147-
void swift::performSyntacticExprDiagnostics(TypeChecker &TC, const Expr *E,
1148-
const DeclContext *DC,
1149-
bool isExprStmt) {
1150-
diagSelfAssignment(TC, E);
1151-
diagSyntacticUseRestrictions(TC, E, DC, isExprStmt);
1152-
diagRecursivePropertyAccess(TC, E, DC);
1153-
diagnoseImplicitSelfUseInClosure(TC, E, DC);
1154-
diagAvailability(TC, E, DC);
1155-
}
1156-
1157-
void swift::performStmtDiagnostics(TypeChecker &TC, const Stmt *S) {
1158-
TC.checkUnsupportedProtocolType(const_cast<Stmt *>(S));
1159-
}
1160-
11611142
//===--------------------------------------------------------------------===//
11621143
// Per func/init diagnostics
11631144
//===--------------------------------------------------------------------===//
@@ -1201,7 +1182,16 @@ class VarDeclUsageChecker : public ASTWalker {
12011182
VarDecls[VD] = 0;
12021183
});
12031184
}
1204-
1185+
1186+
VarDeclUsageChecker(TypeChecker &TC, VarDecl *VD) : TC(TC) {
1187+
// Track a specific VarDecl
1188+
VarDecls[VD] = 0;
1189+
}
1190+
1191+
void suppressDiagnostics() {
1192+
sawError = true; // set this flag so that no diagnostics will be emitted on delete.
1193+
}
1194+
12051195
// After we have scanned the entire region, diagnose variables that could be
12061196
// declared with a narrower usage kind.
12071197
~VarDeclUsageChecker();
@@ -1224,6 +1214,10 @@ class VarDeclUsageChecker : public ASTWalker {
12241214
}
12251215
return sawMutation;
12261216
}
1217+
1218+
bool isVarDeclEverWritten(VarDecl *VD) {
1219+
return (VarDecls[VD] & RK_Written) != 0;
1220+
}
12271221

12281222
bool shouldTrackVarDecl(VarDecl *VD) {
12291223
// If the variable is implicit, ignore it.
@@ -1689,8 +1683,128 @@ void swift::performAbstractFuncDeclDiagnostics(TypeChecker &TC,
16891683
AFD->getBody()->walk(VarDeclUsageChecker(TC, AFD));
16901684
}
16911685

1686+
/// Diagnose C style for loops.
1687+
1688+
static Expr *endConditionValueForConvertingCStyleForLoop(const ForStmt *FS, VarDecl *loopVar) {
1689+
auto *Cond = FS->getCond().getPtrOrNull();
1690+
if (!Cond)
1691+
return nullptr;
1692+
auto callExpr = dyn_cast<CallExpr>(Cond);
1693+
if (!callExpr)
1694+
return nullptr;
1695+
auto dotSyntaxExpr = dyn_cast<DotSyntaxCallExpr>(callExpr->getFn());
1696+
if (!dotSyntaxExpr)
1697+
return nullptr;
1698+
auto binaryExpr = dyn_cast<BinaryExpr>(dotSyntaxExpr->getBase());
1699+
if (!binaryExpr)
1700+
return nullptr;
1701+
auto binaryFuncExpr = dyn_cast<DeclRefExpr>(binaryExpr->getFn());
1702+
if (!binaryFuncExpr)
1703+
return nullptr;
1704+
1705+
// Verify that the condition is a simple != or < comparison to the loop variable.
1706+
auto comparisonOpName = binaryFuncExpr->getDecl()->getNameStr();
1707+
if (comparisonOpName != "!=" && comparisonOpName != "<")
1708+
return nullptr;
1709+
auto args = binaryExpr->getArg()->getElements();
1710+
auto loadExpr = dyn_cast<LoadExpr>(args[0]);
1711+
if (!loadExpr)
1712+
return nullptr;
1713+
auto declRefExpr = dyn_cast<DeclRefExpr>(loadExpr->getSubExpr());
1714+
if (!declRefExpr)
1715+
return nullptr;
1716+
if (declRefExpr->getDecl() != loopVar)
1717+
return nullptr;
1718+
return args[1];
1719+
}
16921720

1721+
static bool simpleIncrementForConvertingCStyleForLoop(const ForStmt *FS, VarDecl *loopVar) {
1722+
auto *Increment = FS->getIncrement().getPtrOrNull();
1723+
if (!Increment)
1724+
return false;
1725+
ApplyExpr *unaryExpr = dyn_cast<PrefixUnaryExpr>(Increment);
1726+
if (!unaryExpr)
1727+
unaryExpr = dyn_cast<PostfixUnaryExpr>(Increment);
1728+
if (!unaryExpr)
1729+
return false;
1730+
auto inoutExpr = dyn_cast<InOutExpr>(unaryExpr->getArg());
1731+
if (!inoutExpr)
1732+
return false;
1733+
auto incrementDeclRefExpr = dyn_cast<DeclRefExpr>(inoutExpr->getSubExpr());
1734+
if (!incrementDeclRefExpr)
1735+
return false;
1736+
auto unaryFuncExpr = dyn_cast<DeclRefExpr>(unaryExpr->getFn());
1737+
if (!unaryFuncExpr)
1738+
return false;
1739+
if (unaryFuncExpr->getDecl()->getNameStr() != "++")
1740+
return false;
1741+
return incrementDeclRefExpr->getDecl() == loopVar;
1742+
}
16931743

1744+
static void checkCStyleForLoop(TypeChecker &TC, const ForStmt *FS) {
1745+
// If we're missing semi-colons we'll already be erroring out, and this may not even have been intended as C-style.
1746+
if (FS->getFirstSemicolonLoc().isInvalid() || FS->getSecondSemicolonLoc().isInvalid())
1747+
return;
1748+
1749+
InFlightDiagnostic diagnostic = TC.diagnose(FS->getStartLoc(), diag::deprecated_c_style_for_stmt);
1750+
1751+
// Try to construct a fix it using for-each:
1752+
1753+
// Verify that there is only one loop variable, and it is declared here.
1754+
auto initializers = FS->getInitializerVarDecls();
1755+
PatternBindingDecl *loopVarDecl = initializers.size() == 2 ? dyn_cast<PatternBindingDecl>(initializers[0]) : nullptr;
1756+
if (!loopVarDecl || loopVarDecl->getNumPatternEntries() != 1)
1757+
return;
1758+
1759+
VarDecl *loopVar = dyn_cast<VarDecl>(initializers[1]);
1760+
Expr *startValue = loopVarDecl->getInit(0);
1761+
Expr *endValue = endConditionValueForConvertingCStyleForLoop(FS, loopVar);
1762+
bool strideByOne = simpleIncrementForConvertingCStyleForLoop(FS, loopVar);
1763+
1764+
if (!loopVar || !startValue || !endValue || !strideByOne)
1765+
return;
1766+
1767+
// Verify that the loop variable is invariant inside the body.
1768+
VarDeclUsageChecker checker(TC, loopVar);
1769+
checker.suppressDiagnostics();
1770+
FS->getBody()->walk(checker);
1771+
1772+
if (checker.isVarDeclEverWritten(loopVar)) {
1773+
diagnostic.flush();
1774+
TC.diagnose(FS->getStartLoc(), diag::cant_fix_c_style_for_stmt);
1775+
return;
1776+
}
1777+
1778+
SourceLoc loopPatternEnd = Lexer::getLocForEndOfToken(TC.Context.SourceMgr, loopVarDecl->getPattern(0)->getEndLoc());
1779+
SourceLoc endOfIncrementLoc = Lexer::getLocForEndOfToken(TC.Context.SourceMgr, FS->getIncrement().getPtrOrNull()->getEndLoc());
1780+
1781+
diagnostic
1782+
.fixItReplaceChars(loopPatternEnd, startValue->getStartLoc(), " in ")
1783+
.fixItReplaceChars(FS->getFirstSemicolonLoc(), endValue->getStartLoc(), " ..< ")
1784+
.fixItRemoveChars(FS->getSecondSemicolonLoc(), endOfIncrementLoc);
1785+
}
1786+
1787+
//===--------------------------------------------------------------------===//
1788+
// High-level entry points.
1789+
//===--------------------------------------------------------------------===//
1790+
1791+
/// \brief Emit diagnostics for syntactic restrictions on a given expression.
1792+
void swift::performSyntacticExprDiagnostics(TypeChecker &TC, const Expr *E,
1793+
const DeclContext *DC,
1794+
bool isExprStmt) {
1795+
diagSelfAssignment(TC, E);
1796+
diagSyntacticUseRestrictions(TC, E, DC, isExprStmt);
1797+
diagRecursivePropertyAccess(TC, E, DC);
1798+
diagnoseImplicitSelfUseInClosure(TC, E, DC);
1799+
diagAvailability(TC, E, DC);
1800+
}
1801+
1802+
void swift::performStmtDiagnostics(TypeChecker &TC, const Stmt *S) {
1803+
TC.checkUnsupportedProtocolType(const_cast<Stmt *>(S));
1804+
1805+
if (auto forStmt = dyn_cast<ForStmt>(S))
1806+
checkCStyleForLoop(TC, forStmt);
1807+
}
16941808

16951809
//===--------------------------------------------------------------------===//
16961810
// Utility functions

test/Parse/recovery.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,16 +162,16 @@ func missingControllingExprInFor() {
162162
}
163163

164164
// Ensure that we don't do recovery in the following cases.
165-
for ; ; {
165+
for ; ; { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
166166
}
167167

168-
for { true }(); ; {
168+
for { true }(); ; { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
169169
}
170170

171-
for ; { true }() ; {
171+
for ; { true }() ; { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
172172
}
173173

174-
for acceptsClosure { 42 }; ; {
174+
for acceptsClosure { 42 }; ; { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
175175
}
176176

177177
// A trailing closure is not accepted for the condition.

test/SILGen/statements.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,15 @@ func for_loops1(x: Int, c: Bool) {
161161
markUsed(i)
162162
}
163163

164-
for ; x < 40; {
164+
for ; x < 40; { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
165165
markUsed(x)
166166
x += 1
167167
}
168168

169-
for var i = 0; i < 100; ++i {
169+
for var i = 0; i < 100; ++i { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
170170
}
171171

172-
for let i = 0; i < 100; i {
172+
for let i = 0; i < 100; i { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
173173
}
174174
}
175175

test/SILGen/unreachable_code.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func testUnreachableAfterIfReturn(a: Bool) -> Int {
1616
}
1717

1818
func testUnreachableForAfterContinue(b: Bool) {
19-
for (var i:Int = 0; i<10; i++) {
19+
for (var i:Int = 0; i<10; i++) { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
2020
var y: Int = 300;
2121
y++;
2222
if b {
@@ -45,7 +45,7 @@ func testUnreachableWhileAfterContinue(b: Bool) {
4545
func testBreakAndContinue() {
4646
var i = 0;
4747
var m = 0;
48-
for (i = 0; i < 10; ++i) {
48+
for (i = 0; i < 10; ++i) { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
4949
m += 1
5050
if m == 15 {
5151
break

test/Sema/diag_c_style_for.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// RUN: %target-parse-verify-swift
2+
3+
for var a = 0; a < 10; a++ { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}} {{10-13= in }} {{14-20= ..< }} {{22-27=}}
4+
}
5+
6+
for var b = 0; b < 10; ++b { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}} {{10-13= in }} {{14-20= ..< }} {{22-27=}}
7+
}
8+
9+
for var c=1;c != 5 ;++c { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}} {{10-11= in }} {{12-18= ..< }} {{20-24=}}
10+
}
11+
12+
for var d=100;d<5;d++ { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}} {{10-11= in }} {{14-17= ..< }} {{18-22=}}
13+
}
14+
15+
// next three aren't auto-fixable
16+
for var e = 3; e > 4; e++ { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
17+
}
18+
19+
for var f = 3; f < 4; f-- { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
20+
}
21+
22+
let start = Int8(4)
23+
let count = Int8(10)
24+
var other = Int8(2)
25+
26+
for ; other<count; other++ { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
27+
}
28+
29+
// this should be fixable, and keep the type
30+
for (var number : Int8 = start; number < count; number++) { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}} {{23-26= in }} {{31-42= ..< }} {{47-57=}}
31+
print(number)
32+
}
33+
34+
// should produce extra note
35+
for (var m : Int8 = start; m < count; ++m) { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}} expected-note {{C-style for statement can't be automatically fixed to for-in, because the loop variable is modified inside the loop}}
36+
m += 3
37+
}
38+
39+
// could theoretically fix this (and more like it if we auto-suggested "stride:")
40+
for var o = 2; o < 888; o += 1 { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
41+
}
42+
43+
// could theoretically fix this with "..."
44+
for var p = 2; p <= 8; p++ { // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
45+
}

test/stmt/statements.swift

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,23 +120,23 @@ SomeGeneric<Int>
120120

121121
func for_loop() {
122122
var x = 0
123-
for ;; { }
124-
for x = 1; x != 42; ++x { }
125-
for infloopbooltest(); x != 12; infloopbooltest() {}
123+
for ;; { } // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
124+
for x = 1; x != 42; ++x { } // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
125+
for infloopbooltest(); x != 12; infloopbooltest() {} // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
126126

127127
for ; { } // expected-error {{expected ';' in 'for' statement}}
128128

129-
for var y = 1; y != 42; ++y {}
130-
for (var y = 1; y != 42; ++y) {}
129+
for var y = 1; y != 42; ++y {} // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
130+
for (var y = 1; y != 42; ++y) {} // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
131131
var z = 10
132-
for (; z != 0; --z) {}
133-
for (z = 10; z != 0; --z) {}
134-
for var (a,b) = (0,12); a != b; --b {++a}
135-
for (var (a,b) = (0,12); a != b; --b) {++a}
132+
for (; z != 0; --z) {} // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
133+
for (z = 10; z != 0; --z) {} // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
134+
for var (a,b) = (0,12); a != b; --b {++a} // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
135+
for (var (a,b) = (0,12); a != b; --b) {++a} // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
136136
var j, k : Int
137-
for ((j,k) = (0,10); j != k; --k) {}
138-
for var i = 0, j = 0; i * j < 10; i++, j++ {}
139-
for j = 0, k = 52; j < k; ++j, --k { }
137+
for ((j,k) = (0,10); j != k; --k) {} // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
138+
for var i = 0, j = 0; i * j < 10; i++, j++ {} // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
139+
for j = 0, k = 52; j < k; ++j, --k { } // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
140140
// rdar://19540536
141141
// expected-error@+4{{expected var declaration in a 'for' statement}}
142142
// expected-error@+3{{expression resolves to an unused function}}
@@ -145,7 +145,7 @@ func for_loop() {
145145
for @ {}
146146

147147
// <rdar://problem/17462274> Is increment in for loop optional?
148-
for (let i = 0; i < 10; ) {}
148+
for (let i = 0; i < 10; ) {} // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
149149
}
150150

151151
break // expected-error {{'break' is only allowed inside a loop, if, do, or switch}}
@@ -426,13 +426,13 @@ func testThrowNil() throws {
426426
// <rdar://problem/16650625>
427427
func for_ignored_lvalue_init() {
428428
var i = 0
429-
for i; // expected-error {{expression resolves to an unused l-value}}
429+
for i; // expected-error {{expression resolves to an unused l-value}} expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
430430
i < 10; ++i {}
431431
}
432432

433433
// rdar://problem/18643692
434434
func for_loop_multi_iter() {
435-
for (var i = 0, x = 0; i < 10; i++,
435+
for (var i = 0, x = 0; i < 10; i++, // expected-warning {{C-style for statement is deprecated and will be removed in a future version of Swift}}
436436
x) { // expected-error {{expression resolves to an unused l-value}}
437437
x -= 1
438438
}

0 commit comments

Comments
 (0)