Skip to content

Commit 3549ec5

Browse files
committed
[QoI] make several improvements to the unused expression diagnostics, to go
along with recent policy changes: - For expression types that are not specifically handled, make sure to produce a general "unused value" warning, catching a bunch of unused values in the testsuite. - For unused operator results, diagnose them as uses of the operator instead of "calls". - For calls, mutter the type of the result for greater specificity. - For initializers, mutter the type of the initialized value. - Look through OpenExistentialExpr's so we can handle protocol member references propertly. - Look through several other expressions so we handle @discardableResult better.
1 parent 9c7692e commit 3549ec5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+219
-130
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2192,10 +2192,16 @@ ERROR(expression_unused_function,none,
21922192
"expression resolves to an unused function", ())
21932193
ERROR(expression_unused_lvalue,none,
21942194
"expression resolves to an unused l-value", ())
2195-
WARNING(expression_unused_result,none,
2195+
WARNING(expression_unused_result_call,none,
21962196
"result of call to %0 is unused", (DeclName))
2197+
WARNING(expression_unused_result_operator,none,
2198+
"result of operator %0 is unused", (DeclName))
2199+
WARNING(expression_unused_result_unknown, none,
2200+
"result of call is unused, but produces %0", (Type))
2201+
WARNING(expression_unused_result, none,
2202+
"expression of type %0 is unused", (Type))
21972203
WARNING(expression_unused_init_result,none,
2198-
"result of initializer is unused", ())
2204+
"result of %0 initializer is unused", (Type))
21992205
WARNING(expression_unused_optional_try,none,
22002206
"result of 'try?' is unused", ())
22012207
WARNING(expression_unused_selector_result, none,

lib/Sema/TypeCheckStmt.cpp

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,17 @@ void TypeChecker::checkIgnoredExpr(Expr *E) {
10151015
return;
10161016
}
10171017

1018+
// Drill through noop expressions we don't care about, like ParanExprs.
1019+
auto valueE = E;
1020+
while (1) {
1021+
valueE = valueE->getValueProvidingExpr();
1022+
1023+
if (auto *OEE = dyn_cast<OpenExistentialExpr>(valueE))
1024+
valueE = OEE->getSubExpr();
1025+
else
1026+
break;
1027+
}
1028+
10181029
// Complain about functions that aren't called.
10191030
// TODO: What about tuples which contain functions by-value that are
10201031
// dead?
@@ -1024,7 +1035,11 @@ void TypeChecker::checkIgnoredExpr(Expr *E) {
10241035
return;
10251036
}
10261037

1027-
auto valueE = E->getValueProvidingExpr();
1038+
// If the result of this expression is of type "()", then it is safe to
1039+
// ignore.
1040+
if (valueE->getType()->isVoid() ||
1041+
valueE->getType()->is<ErrorType>())
1042+
return;
10281043

10291044
// Complain about '#selector'.
10301045
if (auto *ObjCSE = dyn_cast<ObjCSelectorExpr>(valueE)) {
@@ -1047,15 +1062,20 @@ void TypeChecker::checkIgnoredExpr(Expr *E) {
10471062
if (auto *IIO = dyn_cast<InjectIntoOptionalExpr>(OEE->getSubExpr()))
10481063
return checkIgnoredExpr(IIO->getSubExpr());
10491064

1050-
// FIXME: Complain about literals
1051-
10521065
// Check if we have a call to a function not marked with
10531066
// '@discardableResult'.
10541067
if (auto call = dyn_cast<ApplyExpr>(valueE)) {
10551068
// Dig through all levels of calls.
1056-
Expr *fn = call->getFn()->getSemanticsProvidingExpr();
1057-
while (auto applyFn = dyn_cast<ApplyExpr>(fn)) {
1058-
fn = applyFn->getFn()->getSemanticsProvidingExpr();
1069+
Expr *fn = call->getFn();
1070+
while (true) {
1071+
fn = fn->getSemanticsProvidingExpr();
1072+
if (auto applyFn = dyn_cast<ApplyExpr>(fn)) {
1073+
fn = applyFn->getFn();
1074+
} else if (auto FVE = dyn_cast<ForceValueExpr>(fn)) {
1075+
fn = FVE->getSubExpr();
1076+
} else {
1077+
break;
1078+
}
10591079
}
10601080

10611081
// Find the callee.
@@ -1069,19 +1089,45 @@ void TypeChecker::checkIgnoredExpr(Expr *E) {
10691089
else if (auto dynMemberRef = dyn_cast<DynamicMemberRefExpr>(fn))
10701090
callee = dyn_cast<AbstractFunctionDecl>(
10711091
dynMemberRef->getMember().getDecl());
1072-
1073-
if (callee) {
1074-
if (!callee->getAttrs().getAttribute<DiscardableResultAttr>() &&
1075-
!valueE->getType()->isVoid()) {
1076-
diagnose(fn->getLoc(), diag::expression_unused_result,
1077-
callee->getFullName());
1078-
return;
1079-
}
1080-
if (isa<ConstructorDecl>(callee) && !call->isImplicit()) {
1081-
diagnose(fn->getLoc(), diag::expression_unused_init_result);
1082-
}
1092+
1093+
// If the callee explicitly allows its result to be ignored, then don't
1094+
// complain.
1095+
if (callee && callee->getAttrs().getAttribute<DiscardableResultAttr>())
1096+
return;
1097+
1098+
// Otherwise, complain. Start with more specific diagnostics.
1099+
if (callee && isa<ConstructorDecl>(callee) && !call->isImplicit()) {
1100+
diagnose(fn->getLoc(), diag::expression_unused_init_result,
1101+
callee->getDeclContext()->getDeclaredTypeOfContext())
1102+
.highlight(call->getArg()->getSourceRange());
1103+
return;
10831104
}
1105+
1106+
SourceRange SR1 = call->getArg()->getSourceRange(), SR2;
1107+
if (auto *BO = dyn_cast<BinaryExpr>(call)) {
1108+
SR1 = BO->getArg()->getElement(0)->getSourceRange();
1109+
SR2 = BO->getArg()->getElement(1)->getSourceRange();
1110+
}
1111+
1112+
// Otherwise, produce a generic diagnostic.
1113+
if (callee) {
1114+
auto diagID = diag::expression_unused_result_call;
1115+
if (callee->getFullName().isOperator())
1116+
diagID = diag::expression_unused_result_operator;
1117+
1118+
diagnose(fn->getLoc(), diagID, callee->getFullName())
1119+
.highlight(SR1).highlight(SR2);
1120+
} else
1121+
diagnose(fn->getLoc(), diag::expression_unused_result_unknown,
1122+
valueE->getType())
1123+
.highlight(SR1).highlight(SR2);
1124+
1125+
return;
10841126
}
1127+
1128+
// Produce a generic diagnostic.
1129+
diagnose(valueE->getLoc(), diag::expression_unused_result, valueE->getType())
1130+
.highlight(valueE->getSourceRange());
10851131
}
10861132

10871133
Stmt *StmtChecker::visitBraceStmt(BraceStmt *BS) {

test/ClangModules/AppKit_test.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ class MyDocument : NSDocument {
1515
}
1616

1717
func test(_ URL: NSURL, controller: NSDocumentController) {
18-
try! NSDocument(contentsOf: URL, ofType: "") // expected-warning{{unused}}
19-
try! MyDocument(contentsOf: URL, ofType: "")
18+
try! NSDocument(contentsOf: URL, ofType: "") // expected-warning{{result of 'NSDocument' initializer is unused}}
19+
try! MyDocument(contentsOf: URL, ofType: "") // expected-warning {{expression of type 'MyDocument' is unused}}
2020

2121
try! controller.makeDocument(withContentsOf: URL, ofType: "")
2222
}

test/ClangModules/adapter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Redeclaration
1515
let encoding: UInt = NSUTF8StringEncoding
1616

1717
let viaTypedef: Redeclaration.NSPoint = AppKit.NSPoint(x: 0, y: 0)
18-
Redeclaration.NSStringToNSString(AppKit.NSStringToNSString("abc"))
18+
Redeclaration.NSStringToNSString(AppKit.NSStringToNSString("abc")) // expected-warning {{result of call is unused}}
1919

2020
let viaStruct: Redeclaration.FooStruct1 = AppKit.FooStruct1()
2121
let forwardDecl: Redeclaration.Tribool = AppKit.Tribool() // expected-error {{no type named 'Tribool' in module 'Redeclaration'}}

test/ClangModules/availability.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ func test_unavailable_instance_method(_ x : NSObject) -> Bool {
1414
}
1515

1616
func test_unavailable_method_in_protocol(_ x : NSObjectProtocol) {
17+
// expected-warning @+1 {{expression of type 'NSObjectProtocol' is unused}}
1718
x.retain() // expected-error {{'retain()' is unavailable}}
1819
}
1920
func test_unavailable_method_in_protocol_use_class_instance(_ x : NSObject) {
@@ -34,8 +35,8 @@ func test_NSInvocation(_ x: NSInvocation, // expected-error {{'NSInvocat
3435

3536
func test_class_avail(_ x:NSObject, obj: AnyObject) {
3637
x.`class`() // expected-error {{'class()' is unavailable in Swift: use 'dynamicType' instead}} expected-warning {{result of call to 'class()' is unused}}
37-
NSObject.`class`() // expected-error {{'class()' is unavailable in Swift: use 'self' instead}} expected-warning {{result of call to 'class()' is unused}}
38-
obj.`class`!() // expected-error {{'class()' is unavailable in Swift: use 'dynamicType' instead}}
38+
_ = NSObject.`class`() // expected-error {{'class()' is unavailable in Swift: use 'self' instead}}
39+
_ = obj.`class`!() // expected-error {{'class()' is unavailable in Swift: use 'dynamicType' instead}}
3940
}
4041

4142
func test_unavailable_app_extension() {
@@ -100,7 +101,7 @@ func test_DistributedObjects(_ o: NSObject,
100101

101102
let ca = NSConnectionDidDieNotification // expected-error {{'NSConnectionDidDieNotification' is unavailable in Swift: Use NSXPCConnection instead}}
102103
let cc = NSConnectionReplyMode // expected-error {{'NSConnectionReplyMode' is unavailable in Swift: Use NSXPCConnection instead}}
103-
o.classForPortCoder // expected-error {{'classForPortCoder' is unavailable in Swift: Use NSXPCConnection instead}}
104+
_ = o.classForPortCoder // expected-error {{'classForPortCoder' is unavailable in Swift: Use NSXPCConnection instead}}
104105
}
105106

106107
func test_NSCalendarDate(_ o: NSCalendarDate) {} // expected-error {{'NSCalendarDate' is unavailable in Swift: Use NSCalendar and NSDateComponents and NSDateFormatter instead}}

test/ClangModules/foreign_errors.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func testNSErrorExhaustive() {
104104
do {
105105
try ErrorProne.fail()
106106
} catch let e as NSError {
107-
e
107+
e // expected-warning {{expression of type 'NSError' is unused}}
108108
}
109109
}
110110
}

test/ClangModules/objc_parse.swift

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func instanceMethods(_ b: B) {
4949
b.performAdd(1, 2, 3, 4) // expected-error{{missing argument labels 'withValue:withValue:withValue2:' in call}} {{19-19=withValue: }} {{22-22=withValue: }} {{25-25=withValue2: }}
5050

5151
// Both class and instance methods exist.
52-
b.description
52+
_ = b.description
5353
b.instanceTakesObjectClassTakesFloat(b)
5454
b.instanceTakesObjectClassTakesFloat(2.0) // expected-error{{cannot convert value of type 'Double' to expected argument type 'AnyObject!'}}
5555

@@ -86,21 +86,21 @@ func instanceMethodsInExtensions(_ b: B) {
8686
b.method(1, separateExtMethod:3.5)
8787

8888
let m1 = b.method(_:onCat1:)
89-
m1(1, onCat1: 2.5)
89+
_ = m1(1, onCat1: 2.5)
9090

9191
let m2 = b.method(_:onExtA:)
92-
m2(1, onExtA: 2.5)
92+
_ = m2(1, onExtA: 2.5)
9393

9494
let m3 = b.method(_:onExtB:)
95-
m3(1, onExtB: 2.5)
95+
_ = m3(1, onExtB: 2.5)
9696

9797
let m4 = b.method(_:separateExtMethod:)
98-
m4(1, separateExtMethod: 2.5)
98+
_ = m4(1, separateExtMethod: 2.5)
9999
}
100100

101101
func dynamicLookupMethod(_ b: AnyObject) {
102102
if let m5 = b.method(_:separateExtMethod:) {
103-
m5(1, separateExtMethod: 2.5)
103+
_ = m5(1, separateExtMethod: 2.5)
104104
}
105105
}
106106

@@ -204,15 +204,16 @@ func testProtocolMethods(_ b: B, p2m: P2.Type) {
204204
// Imported constructor.
205205
var b2 = B(viaP2: 3.14159, second: 3.14159)
206206

207-
p2m.init(viaP2:3.14159, second: 3.14159)
207+
_ = p2m.init(viaP2:3.14159, second: 3.14159)
208208
}
209209

210210
func testId(_ x: AnyObject) {
211211
x.perform!("foo:", with: x) // expected-warning{{no method declared with Objective-C selector 'foo:'}}
212+
// expected-warning @-1 {{result of call is unused, but produces 'Unmanaged<AnyObject>!'}}
212213

213-
x.performAdd(1, withValue: 2, withValue: 3, withValue2: 4)
214-
x.performAdd!(1, withValue: 2, withValue: 3, withValue2: 4)
215-
x.performAdd?(1, withValue: 2, withValue: 3, withValue2: 4)
214+
_ = x.performAdd(1, withValue: 2, withValue: 3, withValue2: 4)
215+
_ = x.performAdd!(1, withValue: 2, withValue: 3, withValue2: 4)
216+
_ = x.performAdd?(1, withValue: 2, withValue: 3, withValue2: 4)
216217
}
217218

218219
class MySubclass : B {
@@ -224,7 +225,7 @@ class MySubclass : B {
224225
}
225226

226227
func getDescription(_ array: NSArray) {
227-
array.description
228+
_ = array.description
228229
}
229230

230231
// Method overriding with unfortunate ordering.
@@ -257,7 +258,7 @@ func almostSubscriptableKeyMismatchInherited(_ roc: ReadOnlyCollectionChild,
257258

258259
// Use of 'Class' via dynamic lookup.
259260
func classAnyObject(_ obj: NSObject) {
260-
obj.myClass().description!()
261+
_ = obj.myClass().description!()
261262
}
262263

263264
// Protocol conformances
@@ -307,8 +308,8 @@ func customAccessors(_ hive: Hive, bee: Bee) {
307308
markUsed(hive.makingHoney()) // expected-error{{cannot call value of non-function type 'Bool'}}
308309
hive.setMakingHoney(true) // expected-error{{value of type 'Hive' has no member 'setMakingHoney'}}
309310

310-
hive.`guard`.description // okay
311-
hive.`guard`.description! // no-warning
311+
_ = hive.`guard`.description // okay
312+
_ = hive.`guard`.description! // no-warning
312313
hive.`guard` = bee // no-warning
313314
}
314315

@@ -339,7 +340,7 @@ func testDynamicSelf(_ queen: Bee, wobbler: NSWobbling) {
339340
}
340341

341342
func testRepeatedProtocolAdoption(_ w: NSWindow) {
342-
w.description
343+
_ = w.description
343344
}
344345

345346
class ProtocolAdopter1 : FooProto {

test/Constraints/construction.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ var z : Z = .none
4141
func acceptZ(_ z: Z) {}
4242
func acceptString(_ s: String) {}
4343

44-
Point(1, 2)
44+
Point(1, 2) // expected-warning {{expression of type '(x: Int, y: Int)' is unused}}
4545
var db : Base = d
4646
X(i: 1, j: 2) // expected-warning{{unused}}
4747
Y(1, 2, "hello") // expected-warning{{unused}}
@@ -60,7 +60,7 @@ _ = .none as Optional<Int>
6060
Optional(.none) // expected-error{{generic parameter 'T' could not be inferred}}
6161

6262
// Interpolation
63-
"\(hello), \(world) #\(i)!"
63+
_ = "\(hello), \(world) #\(i)!"
6464

6565
class File {
6666
init() {
@@ -86,21 +86,21 @@ extension Foo {
8686

8787
// Downcasting
8888
var b : Base
89-
b as! Derived
89+
_ = b as! Derived
9090

9191
// Construction doesn't permit conversion.
9292
// NOTE: Int and other integer-literal convertible types
9393
// are special cased in the library.
9494
Int(i) // expected-warning{{unused}}
95-
i as Int
95+
_ = i as Int
9696
Z(z) // expected-error{{cannot invoke initializer for type 'Z' with an argument list of type '(Z)'}}
9797
// expected-note @-1 {{overloads for 'Z' exist with these partially matching parameter lists: (UnicodeScalar), (String)}}
9898

9999
Z.init(z) // expected-error {{cannot invoke 'Z.Type.init' with an argument list of type '(Z)'}}
100100
// expected-note @-1 {{overloads for 'Z.Type.init' exist with these partially matching parameter lists: (UnicodeScalar), (String)}}
101101

102102

103-
z as Z
103+
_ = z as Z
104104

105105
// Construction from inouts.
106106
struct FooRef { }

test/Constraints/diagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ f7(1)(1.0) // expected-error {{missing argument label 'b:' in call}}
353353
f7(1)(b: 1.0) // expected-error {{cannot convert value of type 'Double' to expected argument type 'Int'}}
354354

355355
let f8 = f7(2)
356-
f8(b: 1)
356+
_ = f8(b: 1)
357357
f8(10) // expected-error {{missing argument label 'b:' in call}} {{4-4=b: }}
358358
f8(1.0) // expected-error {{missing argument label 'b:' in call}}
359359
f8(b: 1.0) // expected-error {{cannot convert value of type 'Double' to expected argument type 'Int'}}

test/Constraints/dynamic_lookup.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ obj.foo!(5)
117117
obj.foo!("hello")
118118
obj.wibble!()
119119
obj.wobble!() // expected-error{{value of type 'Id' (aka 'AnyObject') has no member 'wobble'}}
120-
obj.ext1!()
120+
obj.ext1!() // expected-warning {{result of call is unused}}
121121
obj.wonka!()
122122

123123
// Same as above but without the '!'
@@ -126,7 +126,7 @@ obj.foo(5)
126126
obj.foo("hello")
127127
obj.wibble()
128128
obj.wobble() // expected-error{{value of type 'Id' (aka 'AnyObject') has no member 'wobble'}}
129-
obj.ext1()
129+
obj.ext1() // expected-warning {{result of call is unused}}
130130
obj.wonka()
131131

132132
// Find class methods via dynamic method lookup.
@@ -144,7 +144,7 @@ var ovl1Result = obj.ovl1!()
144144
ovl1Result = A() // verify that we got an A, not a B
145145

146146
// Same as above but without the '!'
147-
obj.ovl1()
147+
obj.ovl1() // expected-warning {{result of call is unused}}
148148

149149
// Don't allow overload resolution between declarations from different
150150
// classes.
@@ -163,7 +163,7 @@ var ovl4Result = obj.ovl4!()
163163
var ovl5Result = obj.ovl5!() // expected-error{{ambiguous use of 'ovl5()'}}
164164

165165
// Same as above but without the '!'
166-
obj.ovl4()
166+
obj.ovl4() // expected-warning {{result of call is unused}}
167167

168168
// Generics
169169

0 commit comments

Comments
 (0)