Skip to content

Commit 8af4f2d

Browse files
authored
Merge pull request #29493 from LucianoPAlmeida/SR-11421-checked-cast-diag
[SR-11421][Diagnostics] Tailored diagnostic for checked downcast with literals
2 parents 1a9eb79 + 497d46f commit 8af4f2d

File tree

4 files changed

+63
-4
lines changed

4 files changed

+63
-4
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,11 @@ WARNING(conditional_downcast_coercion,none,
997997
"conditional cast from %0 to %1 always succeeds",
998998
(Type, Type))
999999

1000+
WARNING(literal_conditional_downcast_to_coercion,none,
1001+
"conditional downcast from literal to %0 always fails; "
1002+
"consider using 'as' coercion",
1003+
(Type))
1004+
10001005
WARNING(forced_downcast_noop,none,
10011006
"forced cast of %0 to same type has no effect", (Type))
10021007

lib/Sema/CSApply.cpp

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3686,9 +3686,9 @@ namespace {
36863686
auto sub = cs.coerceToRValue(expr->getSubExpr());
36873687
expr->setSubExpr(sub);
36883688

3689-
3689+
bool isSubExprLiteral = isa<LiteralExpr>(sub);
36903690
auto castContextKind =
3691-
(SuppressDiagnostics || isInsideIsExpr)
3691+
(SuppressDiagnostics || isInsideIsExpr || isSubExprLiteral)
36923692
? CheckedCastContextKind::None
36933693
: CheckedCastContextKind::ConditionalCast;
36943694

@@ -3699,13 +3699,35 @@ namespace {
36993699
switch (castKind) {
37003700
// Invalid cast.
37013701
case CheckedCastKind::Unresolved:
3702+
// FIXME: This literal diagnostics needs to be revisited by a proposal
3703+
// to unify casting semantics for literals.
3704+
// https://bugs.swift.org/browse/SR-12093
3705+
if (isSubExprLiteral) {
3706+
auto protocol = TypeChecker::getLiteralProtocol(ctx, sub);
3707+
// Special handle for literals conditional checked cast when they can
3708+
// be statically coerced to the cast type.
3709+
if (protocol && TypeChecker::conformsToProtocol(
3710+
toType, protocol, cs.DC,
3711+
ConformanceCheckFlags::InExpression)) {
3712+
ctx.Diags
3713+
.diagnose(expr->getLoc(),
3714+
diag::literal_conditional_downcast_to_coercion,
3715+
toType);
3716+
} else {
3717+
ctx.Diags
3718+
.diagnose(expr->getLoc(), diag::downcast_to_unrelated, fromType,
3719+
toType)
3720+
.highlight(sub->getSourceRange())
3721+
.highlight(expr->getCastTypeLoc().getSourceRange());
3722+
}
3723+
}
37023724
expr->setCastKind(CheckedCastKind::ValueCast);
37033725
break;
37043726

37053727
case CheckedCastKind::Coercion:
37063728
case CheckedCastKind::BridgingCoercion: {
37073729
ctx.Diags.diagnose(expr->getLoc(), diag::conditional_downcast_coercion,
3708-
cs.getType(sub), toType);
3730+
fromType, toType);
37093731
expr->setCastKind(castKind);
37103732
cs.setType(expr, OptionalType::get(toType));
37113733
return expr;
@@ -3719,7 +3741,7 @@ namespace {
37193741
expr->setCastKind(castKind);
37203742
break;
37213743
}
3722-
3744+
37233745
return handleOptionalBindingsForCast(expr, simplifyType(cs.getType(expr)),
37243746
OptionalBindingsCastKind::Conditional);
37253747
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
let ok = "A" as Character // OK
4+
let succeed = "A" as? String // expected-warning {{always succeeds}}
5+
let bad = "A" as? Character // expected-warning {{conditional downcast from literal to 'Character' always fails; consider using 'as' coercion}} {{none}}
6+
let bad2 = "Aa" as? Character // expected-warning {{cast from 'String' to unrelated type 'Character' always fails}}
7+
let bad1 = 1 as? Character // expected-warning {{cast from 'Int' to unrelated type 'Character' always fails}}
8+
9+
let okInt = 1 as Int // OK
10+
let badInt = 1 as? Int // expected-warning {{always succeeds}}
11+
let badInt1 = 1.0 as? Int // expected-warning {{cast from 'Double' to unrelated type 'Int' always fails}}
12+
let badInt2 = 1 as? Double // expected-warning {{conditional downcast from literal to 'Double' always fails; consider using 'as' coercion}} {{none}}
13+
14+
let okUInt = 1 as UInt // OK
15+
let badUInt = 1 as? UInt // expected-warning {{conditional downcast from literal to 'UInt' always fails; consider using 'as' coercion}} {{none}}
16+
let badUInt1 = 1.0 as? UInt // expected-warning {{cast from 'Double' to unrelated type 'UInt' always fails}}
17+
18+
// Custom protocol adoption
19+
struct S: ExpressibleByStringLiteral {
20+
typealias StringLiteralType = String
21+
init(stringLiteral value: Self.StringLiteralType) {}
22+
}
23+
24+
let a = "A" as? S // expected-warning {{conditional downcast from literal to 'S' always fails; consider using 'as' coercion}} {{none}}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// RUN: %target-typecheck-verify-swift -enable-objc-interop
2+
// REQUIRES: OS=macosx
3+
4+
import Foundation
5+
6+
// Can downcast by bridging
7+
let bridge = "A" as? NSString // expected-warning {{always succeeds}}
8+
let bridge1 = 1 as? NSNumber // expected-warning {{always succeeds}}

0 commit comments

Comments
 (0)