Skip to content

Commit a6bcd80

Browse files
authored
Merge pull request #40694 from calda/cal--if-let-shorthand
[SE-0345] Support `if let foo {` optional binding conditions
2 parents 15c2192 + cc85edf commit a6bcd80

File tree

4 files changed

+83
-9
lines changed

4 files changed

+83
-9
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,8 @@ ERROR(conditional_var_initializer_required,none,
10861086
ERROR(wrong_condition_case_location,none,
10871087
"pattern matching binding is spelled with 'case %0', not '%0 case'",
10881088
(StringRef))
1089+
ERROR(conditional_var_valid_identifiers_only,none,
1090+
"unwrap condition requires a valid identifier", ())
10891091

10901092
// If Statement
10911093
ERROR(expected_condition_if,PointsToFirstBadToken,

lib/Parse/ParseStmt.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1618,12 +1618,32 @@ Parser::parseStmtConditionElement(SmallVectorImpl<StmtConditionElement> &result,
16181618
ThePattern = makeParserResult(AP);
16191619
}
16201620

1621-
// Conditional bindings must have an initializer.
1621+
// Conditional bindings can have the format:
1622+
// `let newBinding = <expr>`, or
1623+
// `let newBinding`, which is shorthand for `let newBinding = newBinding`
16221624
ParserResult<Expr> Init;
16231625
if (Tok.is(tok::equal)) {
16241626
SyntaxParsingContext InitCtxt(SyntaxContext, SyntaxKind::InitializerClause);
16251627
consumeToken();
16261628
Init = parseExprBasic(diag::expected_expr_conditional_var);
1629+
} else if (!ThePattern.getPtrOrNull()->getBoundName().empty()) {
1630+
auto bindingName = DeclNameRef(ThePattern.getPtrOrNull()->getBoundName());
1631+
auto loc = DeclNameLoc(ThePattern.getPtrOrNull()->getEndLoc());
1632+
auto declRefExpr = new (Context) UnresolvedDeclRefExpr(bindingName,
1633+
DeclRefKind::Ordinary,
1634+
loc);
1635+
1636+
declRefExpr->setImplicit();
1637+
Init = makeParserResult(declRefExpr);
1638+
} else if (BindingKindStr != "case") {
1639+
// If the pattern is present but isn't an identifier, the user wrote
1640+
// something invalid like `let foo.bar`. Emit a special diagnostic for this,
1641+
// with a fix-it prepending "<#identifier#> = "
1642+
// - We don't emit this fix-it if the user wrote `case let` (etc),
1643+
// since the shorthand syntax isn't available for pattern matching
1644+
auto diagLoc = ThePattern.get()->getSemanticsProvidingPattern()->getStartLoc();
1645+
diagnose(diagLoc, diag::conditional_var_valid_identifiers_only)
1646+
.fixItInsert(diagLoc, "<#identifier#> = ");
16271647
} else {
16281648
diagnose(Tok, diag::conditional_var_initializer_required);
16291649
}

test/stmt/if_while_var.swift

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// RUN: %target-typecheck-verify-swift
22

3-
struct NonOptionalStruct {}
3+
struct NonOptionalStruct { let property: Any }
44
enum NonOptionalEnum { case foo }
55

66
func foo() -> Int? { return .none }
@@ -21,23 +21,52 @@ if var x = foo() {
2121
modify(&x)
2222
}
2323

24+
if let x { // expected-error{{cannot find 'x' in scope}}
25+
use(x)
26+
}
27+
28+
if var x { // expected-error{{cannot find 'x' in scope}}
29+
use(x)
30+
}
31+
2432
use(x) // expected-error{{cannot find 'x' in scope}}
2533

34+
let nonOptional = nonOptionalStruct()
35+
2636
if let x = nonOptionalStruct() { _ = x} // expected-error{{initializer for conditional binding must have Optional type, not 'NonOptionalStruct'}}
2737
if let x = nonOptionalEnum() { _ = x} // expected-error{{initializer for conditional binding must have Optional type, not 'NonOptionalEnum'}}
2838

39+
if let nonOptional { _ = nonOptional } // expected-error{{initializer for conditional binding must have Optional type, not 'NonOptionalStruct'}}
40+
if var nonOptional { nonOptional = nonOptionalStruct(); _ = nonOptional } // expected-error{{initializer for conditional binding must have Optional type, not 'NonOptionalStruct'}}
41+
42+
guard let nonOptional else { _ = nonOptional; fatalError() } // expected-error{{initializer for conditional binding must have Optional type, not 'NonOptionalStruct'}}
43+
guard var nonOptional else { _ = nonOptional; fatalError() } // expected-error{{initializer for conditional binding must have Optional type, not 'NonOptionalStruct'}}
44+
45+
if let nonOptional.property { } // expected-error{{unwrap condition requires a valid identifier}} expected-error{{pattern matching in a condition requires the 'case' keyword}}
46+
if var nonOptional.property { } // expected-error{{unwrap condition requires a valid identifier}} expected-error{{pattern matching in a condition requires the 'case' keyword}}
47+
2948
guard let _ = nonOptionalStruct() else { fatalError() } // expected-error{{initializer for conditional binding must have Optional type, not 'NonOptionalStruct'}}
3049
guard let _ = nonOptionalEnum() else { fatalError() } // expected-error{{initializer for conditional binding must have Optional type, not 'NonOptionalEnum'}}
3150

51+
let optional: String? = nil
52+
if case let optional? { _ = optional } // expected-error{{variable binding in a condition requires an initializer}}
53+
if case let .some(optional) { _ = optional } // expected-error{{variable binding in a condition requires an initializer}}
54+
if case .some(let optional) { _ = optional } // expected-error{{variable binding in a condition requires an initializer}}
55+
3256
if case let x? = nonOptionalStruct() { _ = x } // expected-error{{'?' pattern cannot match values of type 'NonOptionalStruct'}}
3357
if case let x? = nonOptionalEnum() { _ = x } // expected-error{{'?' pattern cannot match values of type 'NonOptionalEnum'}}
3458

59+
if let x { _ = x } // expected-error{{cannot find 'x' in scope}}
60+
61+
if let optional: String { _ = optional }
62+
if let optional: Int { _ = optional } // expected-error{{cannot convert value of type 'String?' to specified type 'Int?'}}
63+
3564
class B {} // expected-note * {{did you mean 'B'?}}
3665
class D : B {}// expected-note * {{did you mean 'D'?}}
3766

3867
// TODO poor recovery in these cases
39-
if let {} // expected-error {{expected '{' after 'if' condition}} expected-error {{pattern matching in a condition requires the 'case' keyword}}
40-
if let x = { } // expected-error{{'{' after 'if'}} expected-error {{variable binding in a condition requires an initializer}} expected-error{{initializer for conditional binding must have Optional type, not '() -> ()'}}
68+
if let {} // expected-error {{expected '{' after 'if' condition}} expected-error {{pattern matching in a condition requires the 'case' keyword}} expected-error {{unwrap condition requires a valid identifier}}
69+
if let x = { } // expected-error{{'{' after 'if'}} expected-error{{initializer for conditional binding must have Optional type, not '() -> ()'}}
4170
// expected-warning@-1{{value 'x' was defined but never used}}
4271

4372
if let x = foo() {
@@ -59,6 +88,16 @@ if let x = foo() {
5988

6089
var opt: Int? = .none
6190

91+
if let opt {
92+
use(opt)
93+
opt = 10 // expected-error{{cannot assign to value: 'opt' is a 'let' constant}}
94+
}
95+
96+
if var opt {
97+
use(opt)
98+
opt = 10
99+
}
100+
62101
if let x = opt {} // expected-warning {{value 'x' was defined but never used; consider replacing with boolean test}} {{4-12=}} {{15-15= != nil}}
63102
if var x = opt {} // expected-warning {{value 'x' was defined but never used; consider replacing with boolean test}} {{4-12=}} {{15-15= != nil}}
64103

@@ -137,13 +176,26 @@ func testShadowing(_ a: Int?, b: Int?, c: Int?, d: Int?) {
137176
}
138177
}
139178

179+
func testShadowingWithShorthand(_ a: Int?, b: Int?, c: Int?, d: Int?) {
180+
guard let a, let b = a > 0 ? b : nil else { return }
181+
_ = b
182+
183+
if let c, let d = c > 0 ? d : nil {
184+
_ = d
185+
}
186+
}
187+
140188
func useInt(_ x: Int) {}
141189

142-
func testWhileScoping(_ a: Int?) {// expected-note {{did you mean 'a'?}}
190+
func testWhileScoping(_ a: Int?) {
143191
while let x = a { }
144192
useInt(x) // expected-error{{cannot find 'x' in scope}}
145193
}
146194

195+
func testWhileShorthand(_ b: Int?) {
196+
while let b { _ = b }
197+
}
198+
147199
// Matching a case with a single, labeled associated value.
148200
public enum SomeParseResult<T> {
149201
case error(length: Int)

test/stmt/statements.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ func testMyEnumWithCaseLabels(_ a : MyEnumWithCaseLabels) {
357357
}
358358

359359

360-
func test_guard(_ x : Int, y : Int??, cond : Bool) {
360+
func test_guard(_ x : Int, y : Int??, z: Int?, cond : Bool) {
361361

362362
// These are all ok.
363363
guard let a = y else {}
@@ -366,9 +366,9 @@ func test_guard(_ x : Int, y : Int??, cond : Bool) {
366366
guard case let c = x, cond else {}
367367
guard case let Optional.some(d) = y else {}
368368
guard x != 4, case _ = x else { }
369-
370-
371-
guard let e, cond else {} // expected-error {{variable binding in a condition requires an initializer}}
369+
guard let z, cond else {}
370+
371+
372372
guard case let f? : Int?, cond else {} // expected-error {{variable binding in a condition requires an initializer}}
373373

374374
// FIXME: Bring back the tailored diagnostic

0 commit comments

Comments
 (0)