Skip to content

[Diagnostics] Offer 'is' replacement for unused 'if let' expression with optional operand #37897

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2888,23 +2888,41 @@ VarDeclUsageChecker::~VarDeclUsageChecker() {

// If the subexpr is an "as?" cast, we can rewrite it to
// be an "is" test.
bool isIsTest = false;
if (isa<ConditionalCheckedCastExpr>(initExpr) &&
!initExpr->isImplicit()) {
noParens = isIsTest = true;
ConditionalCheckedCastExpr *CCE = nullptr;

// initExpr can be wrapped inside parens or try expressions.
if (auto ccExpr = dyn_cast<ConditionalCheckedCastExpr>(
initExpr->getValueProvidingExpr())) {
if (!ccExpr->isImplicit()) {
CCE = ccExpr;
noParens = true;
}
}


// In cases where the value is optional, the cast expr is
// wrapped inside OptionalEvaluationExpr. Unwrap it to get
// ConditionalCheckedCastExpr.
if (auto oeExpr = dyn_cast<OptionalEvaluationExpr>(
initExpr->getValueProvidingExpr())) {
if (auto ccExpr = dyn_cast<ConditionalCheckedCastExpr>(
oeExpr->getSubExpr()->getValueProvidingExpr())) {
if (!ccExpr->isImplicit()) {
CCE = ccExpr;
noParens = true;
}
}
}

auto diagIF = Diags.diagnose(var->getLoc(),
diag::pbd_never_used_stmtcond,
var->getName());
auto introducerLoc = SC->getCond()[0].getIntroducerLoc();
diagIF.fixItReplaceChars(introducerLoc,
initExpr->getStartLoc(),
&"("[noParens]);
if (isIsTest) {

if (CCE) {
// If this was an "x as? T" check, rewrite it to "x is T".
auto CCE = cast<ConditionalCheckedCastExpr>(initExpr);
diagIF.fixItReplace(SourceRange(CCE->getLoc(),
CCE->getQuestionLoc()),
"is");
Expand Down
46 changes: 44 additions & 2 deletions test/decl/var/usage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,44 @@ func test(_ a : Int?, b : Any) {
if let x = b as? Int { // expected-warning {{value 'x' was defined but never used; consider replacing with boolean test}} {{6-14=}} {{16-19=is}}
}

// SR-1112
// SR-14646. Special case, turn this into an 'is' test with optional value.
let bb: Any? = 3
if let bbb = bb as? Int {} // expected-warning {{value 'bbb' was defined but never used; consider replacing with boolean test}} {{6-16=}} {{19-22=is}}
if let bbb = (bb) as? Int {} // expected-warning {{value 'bbb' was defined but never used; consider replacing with boolean test}} {{6-16=}} {{21-24=is}}

func aa() -> Any? { return 1 }
if let aaa = aa() as? Int {} // expected-warning {{value 'aaa' was defined but never used; consider replacing with boolean test}} {{6-16=}} {{21-24=is}}
if let aaa = (aa()) as? Int {} // expected-warning {{value 'aaa' was defined but never used; consider replacing with boolean test}} {{6-16=}} {{23-26=is}}

func bb() -> Any { return 1 }
if let aaa = aa() as? Int {} // expected-warning {{value 'aaa' was defined but never used; consider replacing with boolean test}} {{6-16=}} {{21-24=is}}
if let aaa = (aa()) as? Int {} // expected-warning {{value 'aaa' was defined but never used; consider replacing with boolean test}} {{6-16=}} {{23-26=is}}


func throwingAA() throws -> Any? { return 1 }
do {
if let aaa = try! throwingAA() as? Int {} // expected-warning {{value 'aaa' was defined but never used; consider replacing with boolean test}} {{8-18=}} {{36-39=is}}
if let aaa = (try! throwingAA()) as? Int {} // expected-warning {{value 'aaa' was defined but never used; consider replacing with boolean test}} {{8-18=}} {{38-41=is}}
if let aaa = try throwingAA() as? Int {} // expected-warning {{value 'aaa' was defined but never used; consider replacing with boolean test}} {{8-18=}} {{35-38=is}}
if let aaa = (try throwingAA()) as? Int {} // expected-warning {{value 'aaa' was defined but never used; consider replacing with boolean test}} {{8-18=}} {{37-40=is}}
} catch { }
if let aaa = try? throwingAA() as? Int {} // expected-warning {{value 'aaa' was defined but never used; consider replacing with boolean test}} {{6-16=(}} {{41-41=) != nil}}
if let aaa = (try? throwingAA()) as? Int {} // expected-warning {{value 'aaa' was defined but never used; consider replacing with boolean test}} {{6-16=}} {{36-39=is}}

func throwingBB() throws -> Any { return 1 }
do {
if let bbb = try! throwingBB() as? Int {} // expected-warning {{value 'bbb' was defined but never used; consider replacing with boolean test}} {{8-18=}} {{36-39=is}}
if let bbb = (try! throwingBB()) as? Int {} // expected-warning {{value 'bbb' was defined but never used; consider replacing with boolean test}} {{8-18=}} {{38-41=is}}
if let bbb = try throwingBB() as? Int {} // expected-warning {{value 'bbb' was defined but never used; consider replacing with boolean test}} {{8-18=}} {{35-38=is}}
if let bbb = (try throwingBB()) as? Int {} // expected-warning {{value 'bbb' was defined but never used; consider replacing with boolean test}} {{8-18=}} {{37-40=is}}
} catch { }
if let bbb = try? throwingBB() as? Int {} // expected-warning {{value 'bbb' was defined but never used; consider replacing with boolean test}} {{6-16=(}} {{41-41=) != nil}}
if let bbb = (try? throwingBB()) as? Int {} // expected-warning {{value 'bbb' was defined but never used; consider replacing with boolean test}} {{6-16=}} {{36-39=is}}

let cc: (Any?, Any) = (1, 2)
if let (cc1, cc2) = cc as? (Int, Int) {} // expected-warning {{immutable value 'cc1' was never used; consider replacing with '_' or removing it}} expected-warning {{immutable value 'cc2' was never used; consider replacing with '_' or removing it}}

// SR-1112
let xxx: Int? = 0

if let yyy = xxx { } // expected-warning{{with boolean test}} {{6-16=}} {{19-19= != nil}}
Expand All @@ -356,7 +392,7 @@ let optionalString: String? = "check"
if let string = optionalString {} // expected-warning {{value 'string' was defined but never used; consider replacing with boolean test}} {{4-17=}} {{31-31= != nil}}

let optionalAny: Any? = "check"
if let string = optionalAny as? String {} // expected-warning {{value 'string' was defined but never used; consider replacing with boolean test}} {{4-17=(}} {{39-39=) != nil}}
if let string = optionalAny as? String {} // expected-warning {{value 'string' was defined but never used; consider replacing with boolean test}} {{4-17=}} {{29-32=is}}

// Due to the complexities of global variable tracing, these will not generate warnings
let unusedVariable = ""
Expand Down Expand Up @@ -504,3 +540,9 @@ func testVariablesBoundInPatterns() {
break
}
}
// Tests fix to SR-14646
func testUselessCastWithInvalidParam(foo: Any?) -> Int {
class Foo { }
if let bar = foo as? Foo { return 42 } // expected-warning {{value 'bar' was defined but never used; consider replacing with boolean test}} {{6-16=}} {{20-23=is}}
else { return 54 }
}
4 changes: 2 additions & 2 deletions test/stmt/if_while_var.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ if let x = foo() {

var opt: Int? = .none

if let x = opt {} // expected-warning {{value 'x' was defined but never used; consider replacing with boolean test}}
if var x = opt {} // expected-warning {{value 'x' was defined but never used; consider replacing with boolean test}}
if let x = opt {} // expected-warning {{value 'x' was defined but never used; consider replacing with boolean test}} {{4-12=}} {{15-15= != nil}}
if var x = opt {} // expected-warning {{value 'x' was defined but never used; consider replacing with boolean test}} {{4-12=}} {{15-15= != nil}}

// <rdar://problem/20800015> Fix error message for invalid if-let
let someInteger = 1
Expand Down