Skip to content

Commit 981a782

Browse files
committed
Emit a warning when optionals are coerced to Any.
Emit a warning for optionals that are implicitly converted to Any, and add fixits giving options to: - Add '??' with a default value after - Force-unwrap the optional with '!' - Explicitly cast to 'as Any' to silence the warning This covers diagnostics aspect of SE-0140. rdar://problem/28196843
1 parent 1f11ef2 commit 981a782

File tree

7 files changed

+131
-7
lines changed

7 files changed

+131
-7
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2328,7 +2328,14 @@ WARNING(optional_check_promotion,none,
23282328
WARNING(optional_pattern_match_promotion,none,
23292329
"pattern match introduces an implicit promotion from %0 to %1",
23302330
(Type, Type))
2331-
2331+
WARNING(optional_to_any_coercion,none,
2332+
"expression implicitly coerced from %0 to Any", (Type))
2333+
NOTE(default_optional_to_any,none,
2334+
"provide a default value to avoid the warning", ())
2335+
NOTE(force_optional_to_any,none,
2336+
"force-unwrap the value to avoid the warning", ())
2337+
NOTE(silence_optional_to_any,none,
2338+
"explicitly cast to Any with 'as Any' to silence the warning", ())
23322339

23332340
ERROR(invalid_noescape_use,none,
23342341
"non-escaping %select{value|parameter}1 %0 may only be called",

lib/Sema/MiscDiagnostics.cpp

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3593,7 +3593,56 @@ checkImplicitPromotionsInCondition(const StmtConditionElement &cond,
35933593
.highlight(subExpr->getSourceRange());
35943594
}
35953595
}
3596-
3596+
3597+
static void diagnoseOptionalToAnyCoercion(TypeChecker &TC, const Expr *E,
3598+
const DeclContext *DC) {
3599+
if (E == nullptr || isa<ErrorExpr>(E) || !E->getType())
3600+
return;
3601+
3602+
class OptionalToAnyCoercionWalker : public ASTWalker {
3603+
TypeChecker &TC;
3604+
SmallPtrSet<Expr *, 4> ErasureCoercedToAny;
3605+
3606+
virtual std::pair<bool, Expr *> walkToExprPre(Expr *E) {
3607+
if (!E || isa<ErrorExpr>(E) || !E->getType())
3608+
return { false, E };
3609+
3610+
if (auto *coercion = dyn_cast<CoerceExpr>(E)) {
3611+
if (E->getType()->getDesugaredType()->isAny() &&
3612+
isa<ErasureExpr>(coercion->getSubExpr()))
3613+
ErasureCoercedToAny.insert(coercion->getSubExpr());
3614+
} else if (isa<ErasureExpr>(E) && !ErasureCoercedToAny.count(E) &&
3615+
E->getType()->getDesugaredType()->isAny()) {
3616+
auto subExpr = cast<ErasureExpr>(E)->getSubExpr();
3617+
auto erasedTy = subExpr->getType()->getDesugaredType();
3618+
if (erasedTy->getOptionalObjectType()) {
3619+
TC.diagnose(subExpr->getStartLoc(), diag::optional_to_any_coercion,
3620+
erasedTy)
3621+
.highlight(subExpr->getSourceRange());
3622+
3623+
TC.diagnose(subExpr->getLoc(), diag::default_optional_to_any)
3624+
.highlight(subExpr->getSourceRange())
3625+
.fixItInsertAfter(subExpr->getEndLoc(), " ?? <#default value#>");
3626+
TC.diagnose(subExpr->getLoc(), diag::force_optional_to_any)
3627+
.highlight(subExpr->getSourceRange())
3628+
.fixItInsertAfter(subExpr->getEndLoc(), "!");
3629+
TC.diagnose(subExpr->getLoc(), diag::silence_optional_to_any)
3630+
.highlight(subExpr->getSourceRange())
3631+
.fixItInsertAfter(subExpr->getEndLoc(), " as Any");
3632+
}
3633+
}
3634+
3635+
return { true, E };
3636+
}
3637+
3638+
public:
3639+
OptionalToAnyCoercionWalker(TypeChecker &tc) : TC(tc) { }
3640+
};
3641+
3642+
OptionalToAnyCoercionWalker Walker(TC);
3643+
const_cast<Expr *>(E)->walk(Walker);
3644+
}
3645+
35973646
//===----------------------------------------------------------------------===//
35983647
// High-level entry points.
35993648
//===----------------------------------------------------------------------===//
@@ -3606,6 +3655,7 @@ void swift::performSyntacticExprDiagnostics(TypeChecker &TC, const Expr *E,
36063655
diagSyntacticUseRestrictions(TC, E, DC, isExprStmt);
36073656
diagRecursivePropertyAccess(TC, E, DC);
36083657
diagnoseImplicitSelfUseInClosure(TC, E, DC);
3658+
diagnoseOptionalToAnyCoercion(TC, E, DC);
36093659
if (!TC.getLangOpts().DisableAvailabilityChecking)
36103660
diagAvailability(TC, E, const_cast<DeclContext*>(DC));
36113661
if (TC.Context.LangOpts.EnableObjCInterop)

test/ClangModules/accessibility_framework.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ class AA: NSView {
2626
}
2727

2828
let a = A()
29-
print(a.accessibilityLabel(), terminator: "")
29+
print(a.accessibilityLabel() as Any, terminator: "")

test/Constraints/bridging.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func dictionaryToNSDictionary() {
171171

172172
// <rdar://problem/17134986>
173173
var bcOpt: BridgedClass?
174-
nsd = [BridgedStruct() : bcOpt]
174+
nsd = [BridgedStruct() : bcOpt as Any]
175175
bcOpt = nil
176176
_ = nsd
177177
}

test/Constraints/patterns.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ func SR2066(x: Int?) {
146146
// Test x???? patterns.
147147
switch (nil as Int???) {
148148
case let x???: print(x, terminator: "")
149-
case let x??: print(x, terminator: "")
150-
case let x?: print(x, terminator: "")
149+
case let x??: print(x as Any, terminator: "")
150+
case let x?: print(x as Any, terminator: "")
151151
case 4???: break
152152
case nil??: break
153153
case nil?: break

test/SILOptimizer/definite_init_diagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ func test22436880() {
11211121

11221122
// sr-184
11231123
let x: String? // expected-note 2 {{constant defined here}}
1124-
print(x?.characters.count) // expected-error {{constant 'x' used before being initialized}}
1124+
print(x?.characters.count as Any) // expected-error {{constant 'x' used before being initialized}}
11251125
print(x!) // expected-error {{constant 'x' used before being initialized}}
11261126

11271127

test/Sema/diag_optional_to_any.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// RUN: %target-parse-verify-swift
2+
3+
func takeAny(_ left: Any, _ right: Any) -> Int? {
4+
return left as? Int
5+
}
6+
7+
func throwing() throws -> Int? {}
8+
9+
func warnOptionalToAnyCoercion(value x: Int?) -> Any {
10+
let a: Any = x // expected-warning {{expression implicitly coerced from 'Optional<Int>' to Any}}
11+
// expected-note@-1 {{provide a default value to avoid the warning}}
12+
// expected-note@-2 {{force-unwrap the value to avoid the warning}}
13+
// expected-note@-3 {{explicitly cast to Any with 'as Any' to silence}}
14+
15+
let b: Any = x as Any
16+
17+
let c: Any = takeAny(a, b) // expected-warning {{expression implicitly coerced from 'Optional<Int>' to Any}}
18+
// expected-note@-1 {{provide a default value to avoid the warning}}
19+
// expected-note@-2 {{force-unwrap the value to avoid the warning}}
20+
// expected-note@-3 {{explicitly cast to Any with 'as Any' to silence}}
21+
22+
let d: Any = takeAny(c, c) as Any
23+
24+
let e: Any = (x) // expected-warning {{expression implicitly coerced from 'Optional<Int>' to Any}}
25+
// expected-note@-1 {{provide a default value to avoid the warning}}
26+
// expected-note@-2 {{force-unwrap the value to avoid the warning}}
27+
// expected-note@-3 {{explicitly cast to Any with 'as Any' to silence}}
28+
29+
_ = takeAny(d, e)
30+
31+
let f: Any = (x as Any)
32+
let g: Any = (x) as (Any)
33+
34+
_ = takeAny(f as? Int, g) // expected-warning {{expression implicitly coerced from 'Optional<Int>' to Any}}
35+
// expected-note@-1 {{provide a default value to avoid the warning}}
36+
// expected-note@-2 {{force-unwrap the value to avoid the warning}}
37+
// expected-note@-3 {{explicitly cast to Any with 'as Any' to silence}}
38+
39+
let h: Any = takeAny(f as? Int, g) as Any // expected-warning {{expression implicitly coerced from 'Optional<Int>' to Any}}
40+
// expected-note@-1 {{provide a default value to avoid the warning}}
41+
// expected-note@-2 {{force-unwrap the value to avoid the warning}}
42+
// expected-note@-3 {{explicitly cast to Any with 'as Any' to silence}}
43+
44+
let i: Any = takeAny(f as? Int as Any, g) as Any
45+
46+
_ = takeAny(h, i)
47+
48+
let j: Any = x! == x! ? 1 : x // expected-warning {{expression implicitly coerced from 'Optional<Int>' to Any}}
49+
// expected-note@-1 {{provide a default value to avoid the warning}}
50+
// expected-note@-2 {{force-unwrap the value to avoid the warning}}
51+
// expected-note@-3 {{explicitly cast to Any with 'as Any' to silence}}
52+
53+
let k: Any
54+
do {
55+
k = try throwing() // expected-warning {{expression implicitly coerced from 'Optional<Int>' to Any}}
56+
// expected-note@-1 {{provide a default value to avoid the warning}}
57+
// expected-note@-2 {{force-unwrap the value to avoid the warning}}
58+
// expected-note@-3 {{explicitly cast to Any with 'as Any' to silence}}
59+
} catch {}
60+
61+
_ = takeAny(j, k)
62+
63+
return x // expected-warning {{expression implicitly coerced from 'Optional<Int>' to Any}}
64+
// expected-note@-1 {{provide a default value to avoid the warning}}
65+
// expected-note@-2 {{force-unwrap the value to avoid the warning}}
66+
// expected-note@-3 {{explicitly cast to Any with 'as Any' to silence}}
67+
}

0 commit comments

Comments
 (0)