Skip to content

Commit 74b00a8

Browse files
committed
[CS] Diagnose misuse of CheckedCastExpr with ~=
Using an unwrap operator with 'as' or the wrong keyword (i.e. `is`) when already checking a cast via ~= results in error: 'pattern variable binding cannot appear in an expression'. Add a diagnostic that provides more guidance and a fix-it for the removal of ?/! or replacement of 'is' with 'as'.
1 parent 6b57a9f commit 74b00a8

File tree

7 files changed

+115
-18
lines changed

7 files changed

+115
-18
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,15 @@ ERROR(optional_chain_isnt_chaining,none,
14191419
())
14201420
ERROR(pattern_in_expr,none,
14211421
"%0 cannot appear in an expression", (DescriptivePatternKind))
1422+
ERROR(conditional_cast_in_type_casting_pattern,none,
1423+
"cannot conditionally downcast in a type-casting pattern",
1424+
())
1425+
ERROR(force_cast_in_type_casting_pattern,none,
1426+
"cannot force downcast in a type-casting pattern",
1427+
())
1428+
ERROR(cannot_bind_value_with_is,none,
1429+
"use 'as' keyword to bind a matched value",
1430+
())
14221431
NOTE(note_call_to_operator,none,
14231432
"in call to operator %0", (const ValueDecl *))
14241433
NOTE(note_call_to_func,none,

include/swift/Sema/CSFix.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3094,11 +3094,12 @@ class IgnoreUnresolvedPatternVar final : public ConstraintFix {
30943094

30953095
class IgnoreInvalidPatternInExpr final : public ConstraintFix {
30963096
Pattern *P;
3097+
bool IsInvalidCast;
30973098

30983099
IgnoreInvalidPatternInExpr(ConstraintSystem &cs, Pattern *pattern,
3099-
ConstraintLocator *locator)
3100+
bool isInvalidCast, ConstraintLocator *locator)
31003101
: ConstraintFix(cs, FixKind::IgnoreInvalidPatternInExpr, locator),
3101-
P(pattern) {}
3102+
P(pattern), IsInvalidCast(isInvalidCast) {}
31023103

31033104
public:
31043105
std::string getName() const override {
@@ -3111,8 +3112,10 @@ class IgnoreInvalidPatternInExpr final : public ConstraintFix {
31113112
return diagnose(*commonFixes.front().first);
31123113
}
31133114

3114-
static IgnoreInvalidPatternInExpr *
3115-
create(ConstraintSystem &cs, Pattern *pattern, ConstraintLocator *locator);
3115+
static IgnoreInvalidPatternInExpr *create(ConstraintSystem &cs,
3116+
Pattern *pattern,
3117+
bool isInvalidCast,
3118+
ConstraintLocator *locator);
31163119

31173120
static bool classof(const ConstraintFix *fix) {
31183121
return fix->getKind() == FixKind::IgnoreInvalidPatternInExpr;

lib/Sema/CSDiagnostics.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8699,8 +8699,33 @@ bool InvalidPatternInExprFailure::diagnoseAsError() {
86998699
E = parent;
87008700
}
87018701
}
8702-
emitDiagnostic(diag::pattern_in_expr, P->getDescriptiveKind());
8703-
return true;
8702+
if (IsInvalidCast) {
8703+
return diagnoseInvalidCheckedCast();
8704+
} else {
8705+
emitDiagnostic(diag::pattern_in_expr, P->getDescriptiveKind());
8706+
return true;
8707+
}
8708+
}
8709+
8710+
bool InvalidPatternInExprFailure::diagnoseInvalidCheckedCast() const {
8711+
auto *castExpr = cast<CheckedCastExpr>(getAsExpr(getAnchor()));
8712+
// Emit the appropriate diagnostic based on the cast kind.
8713+
if (auto *forced = dyn_cast<ForcedCheckedCastExpr>(castExpr)) {
8714+
emitDiagnostic(diag::force_cast_in_type_casting_pattern)
8715+
.fixItRemove(forced->getExclaimLoc());
8716+
return true;
8717+
}
8718+
if (auto *conditional = dyn_cast<ConditionalCheckedCastExpr>(castExpr)) {
8719+
emitDiagnostic(diag::conditional_cast_in_type_casting_pattern)
8720+
.fixItRemove(conditional->getQuestionLoc());
8721+
return true;
8722+
}
8723+
if (auto *isExpr = dyn_cast<IsExpr>(castExpr)) {
8724+
emitDiagnostic(diag::cannot_bind_value_with_is)
8725+
.fixItReplace(isExpr->getAsLoc(), "as");
8726+
return true;
8727+
}
8728+
return false;
87048729
}
87058730

87068731
bool MissingContextualTypeForNil::diagnoseAsError() {

lib/Sema/CSDiagnostics.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2597,13 +2597,27 @@ class InvalidEmptyKeyPathFailure final : public FailureDiagnostic {
25972597
/// \endcode
25982598
class InvalidPatternInExprFailure final : public FailureDiagnostic {
25992599
Pattern *P;
2600+
bool IsInvalidCast;
26002601

26012602
public:
26022603
InvalidPatternInExprFailure(const Solution &solution, Pattern *pattern,
2603-
ConstraintLocator *locator)
2604-
: FailureDiagnostic(solution, locator), P(pattern) {}
2604+
bool isInvalidCast, ConstraintLocator *locator)
2605+
: FailureDiagnostic(solution, locator), P(pattern),
2606+
IsInvalidCast(isInvalidCast) {}
26052607

26062608
bool diagnoseAsError() override;
2609+
2610+
private:
2611+
/// Diagnose situations where a type-casting pattern that binds a value
2612+
/// expects 'as' but is given 'as!', 'as?' or 'is' instead
2613+
/// e.g:
2614+
///
2615+
/// \code
2616+
/// case let x as? Int = y
2617+
/// case let x as! Int = y
2618+
/// case let x is Int = y
2619+
/// \endcode
2620+
bool diagnoseInvalidCheckedCast() const;
26072621
};
26082622

26092623
/// Diagnose situations where there is no context to determine a

lib/Sema/CSFix.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2124,15 +2124,16 @@ IgnoreInvalidASTNode *IgnoreInvalidASTNode::create(ConstraintSystem &cs,
21242124

21252125
bool IgnoreInvalidPatternInExpr::diagnose(const Solution &solution,
21262126
bool asNote) const {
2127-
InvalidPatternInExprFailure failure(solution, P, getLocator());
2127+
InvalidPatternInExprFailure failure(solution, P, IsInvalidCast, getLocator());
21282128
return failure.diagnose(asNote);
21292129
}
21302130

21312131
IgnoreInvalidPatternInExpr *
21322132
IgnoreInvalidPatternInExpr::create(ConstraintSystem &cs, Pattern *pattern,
2133+
bool isInvalidCast,
21332134
ConstraintLocator *locator) {
21342135
return new (cs.getAllocator())
2135-
IgnoreInvalidPatternInExpr(cs, pattern, locator);
2136+
IgnoreInvalidPatternInExpr(cs, pattern, isInvalidCast, locator);
21362137
}
21372138

21382139
bool SpecifyContextualTypeForNil::diagnose(const Solution &solution,

lib/Sema/CSGen.cpp

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3543,16 +3543,36 @@ namespace {
35433543

35443544
Type visitUnresolvedPatternExpr(UnresolvedPatternExpr *expr) {
35453545
// Encountering an UnresolvedPatternExpr here means we have an invalid
3546-
// ExprPattern with a Pattern node like 'let x' nested in it. Record a
3547-
// fix, and assign ErrorTypes to any VarDecls bound.
3548-
auto *locator = CS.getConstraintLocator(expr);
3546+
// ExprPattern with a Pattern node like 'let x' nested in it. Assign
3547+
// ErrorTypes to any VarDecls bound, and record a fix after finding the
3548+
// correct anchor.
35493549
auto *P = expr->getSubPattern();
3550-
CS.recordFix(IgnoreInvalidPatternInExpr::create(CS, P, locator));
3551-
35523550
P->forEachVariable([&](VarDecl *VD) {
35533551
CS.setType(VD, ErrorType::get(CS.getASTContext()));
35543552
});
3555-
3553+
Expr *anchor = expr;
3554+
bool isInvalidCast = false;
3555+
// If we are in a CheckedCastExpr, it's possible that is the problem.
3556+
// E.g: `if case let x as? Int = y {}`. In which case, we want to anchor
3557+
// the fix there and set `isInvalidCast` = true.
3558+
auto *E = CS.getParentExpr(expr);
3559+
while (E && !isa<CheckedCastExpr>(E))
3560+
E = CS.getParentExpr(E);
3561+
auto *castExpr = cast_or_null<CheckedCastExpr>(E);
3562+
// Check if we are in an argument of `~=` that is _not_ inside a CallExpr,
3563+
// avoiding cases like: `case let f(x as? Int) = y {}`,
3564+
// where resolving the CheckedCastExpr would not be a fix.
3565+
auto *parent = castExpr ? CS.getParentExpr(castExpr) : nullptr;
3566+
while (parent && !isa<BinaryExpr>(parent))
3567+
parent = isa<CallExpr>(parent) ? nullptr : CS.getParentExpr(parent);
3568+
auto *BE = cast_or_null<BinaryExpr>(parent);
3569+
if (BE && isPatternMatchingOperator(BE->getFn())) {
3570+
anchor = castExpr;
3571+
isInvalidCast = true;
3572+
}
3573+
auto *locator = CS.getConstraintLocator(anchor);
3574+
CS.recordFix(
3575+
IgnoreInvalidPatternInExpr::create(CS, P, isInvalidCast, locator));
35563576
return CS.createTypeVariable(locator, TVO_CanBindToHole);
35573577
}
35583578

test/Constraints/rdar106598067.swift

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,35 @@
33
enum E: Error { case e }
44

55
// rdar://106598067 – Make sure we don't crash.
6-
// FIXME: We ought to have a tailored diagnostic to change to 'as' instead of 'as?'
76
let fn = {
87
do {} catch let x as? E {}
9-
// expected-error@-1 {{pattern variable binding cannot appear in an expression}}
8+
// expected-error@-1 {{cannot conditionally downcast in a type-casting pattern}}{{23-24=}}
109
// expected-error@-2 {{expression pattern of type 'E?' cannot match values of type 'any Error'}}
1110
// expected-warning@-3 {{'catch' block is unreachable because no errors are thrown in 'do' block}}
1211
}
12+
13+
// https://github.com/swiftlang/swift/issues/44631
14+
let maybeInt: Any = 1
15+
switch maybeInt {
16+
case let intValue as? Int: _ = intValue
17+
// expected-error@-1 {{cannot conditionally downcast in a type-casting pattern}}{{21-22=}}
18+
// expected-error@-2 {{expression pattern of type 'Int?' cannot match values of type 'Any'}}
19+
case let intValue as! Int: _ = intValue
20+
// expected-error@-1 {{cannot force downcast in a type-casting pattern}}{{21-22=}}
21+
case let intValue is Int: _ = intValue
22+
// expected-error@-1 {{use 'as' keyword to bind a matched value}}{{19-21=as}}
23+
default: break
24+
}
25+
26+
let maybeIntTuple: (Any, (Any, Any)) = (1, (2, 3))
27+
switch maybeIntTuple {
28+
case let (intValue, (intValue2, intValue3)) as? Int: _ = intValue; _ = intValue2; _ = intValue3
29+
// expected-error@-1 {{cannot conditionally downcast in a type-casting pattern}}{{47-48=}}
30+
// expected-error@-2 {{expression pattern of type 'Int?' cannot match values of type '(Any, (Any, Any))'}}
31+
case let (intValue, (intValue2, intValue3)) as! Int: _ = intValue; _ = intValue2; _ = intValue3
32+
// expected-error@-1 {{cannot force downcast in a type-casting pattern}}{{47-48=}}
33+
// expected-error@-2 {{expression pattern of type 'Int' cannot match values of type '(Any, (Any, Any))'}}
34+
case let (intValue, (intValue2, intValue3)) is Int: _ = intValue; _ = intValue2; _ = intValue3
35+
// expected-error@-1 {{use 'as' keyword to bind a matched value}}{{45-47=as}}
36+
// expected-error@-2 {{expression pattern of type 'Bool' cannot match values of type '(Any, (Any, Any))'}}
37+
}

0 commit comments

Comments
 (0)