Skip to content

Commit a97879d

Browse files
Merge pull request #42254 from LucianoPAlmeida/refactor-diag-checked-cast
[Sema] Improvements on typeCheckCheckedCast and checked cast diagnostics
2 parents c36716e + c2ce16d commit a97879d

File tree

11 files changed

+278
-163
lines changed

11 files changed

+278
-163
lines changed

include/swift/Sema/CSFix.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,11 +316,19 @@ enum class FixKind : uint8_t {
316316
/// succeed.
317317
AllowNoopCheckedCast,
318318

319+
/// Warn about special runtime case where statically known
320+
/// checked cast from existentials to CFType always succeed.
321+
AllowNoopExistentialToCFTypeCheckedCast,
322+
319323
/// Allow a runtime checked cast where at compile time the from is
320324
/// convertible, but runtime does not support such convertions. e.g.
321325
/// function type casts.
322326
AllowUnsupportedRuntimeCheckedCast,
323327

328+
/// Allow a runtime checked cast where it is known at compile time
329+
/// always fails.
330+
AllowCheckedCastToUnrelated,
331+
324332
/// Allow reference to a static member on a protocol metatype
325333
/// even though result type of the reference doesn't conform
326334
/// to an expected protocol.
@@ -2789,6 +2797,31 @@ class AllowNoopCheckedCast final : public CheckedCastContextualMismatchWarning {
27892797
}
27902798
};
27912799

2800+
class AllowNoopExistentialToCFTypeCheckedCast final
2801+
: public CheckedCastContextualMismatchWarning {
2802+
AllowNoopExistentialToCFTypeCheckedCast(ConstraintSystem &cs, Type fromType,
2803+
Type toType, CheckedCastKind kind,
2804+
ConstraintLocator *locator)
2805+
: CheckedCastContextualMismatchWarning(
2806+
cs, FixKind::AllowNoopExistentialToCFTypeCheckedCast, fromType,
2807+
toType, kind, locator) {}
2808+
2809+
public:
2810+
std::string getName() const override {
2811+
return "checked cast from existential to CFType always succeeds";
2812+
}
2813+
2814+
bool diagnose(const Solution &solution, bool asNote = false) const override;
2815+
2816+
static AllowNoopExistentialToCFTypeCheckedCast *
2817+
attempt(ConstraintSystem &cs, Type fromType, Type toType,
2818+
CheckedCastKind kind, ConstraintLocator *locator);
2819+
2820+
static bool classof(ConstraintFix *fix) {
2821+
return fix->getKind() == FixKind::AllowNoopExistentialToCFTypeCheckedCast;
2822+
}
2823+
};
2824+
27922825
class AllowUnsupportedRuntimeCheckedCast final
27932826
: public CheckedCastContextualMismatchWarning {
27942827
AllowUnsupportedRuntimeCheckedCast(ConstraintSystem &cs, Type fromType,
@@ -2816,6 +2849,29 @@ class AllowUnsupportedRuntimeCheckedCast final
28162849
}
28172850
};
28182851

2852+
class AllowCheckedCastToUnrelated final
2853+
: public CheckedCastContextualMismatchWarning {
2854+
AllowCheckedCastToUnrelated(ConstraintSystem &cs, Type fromType, Type toType,
2855+
CheckedCastKind kind, ConstraintLocator *locator)
2856+
: CheckedCastContextualMismatchWarning(
2857+
cs, FixKind::AllowCheckedCastToUnrelated, fromType, toType, kind,
2858+
locator) {}
2859+
2860+
public:
2861+
std::string getName() const override { return "checked cast always fails"; }
2862+
2863+
bool diagnose(const Solution &solution, bool asNote = false) const override;
2864+
2865+
static AllowCheckedCastToUnrelated *attempt(ConstraintSystem &cs,
2866+
Type fromType, Type toType,
2867+
CheckedCastKind kind,
2868+
ConstraintLocator *locator);
2869+
2870+
static bool classof(ConstraintFix *fix) {
2871+
return fix->getKind() == FixKind::AllowCheckedCastToUnrelated;
2872+
}
2873+
};
2874+
28192875
class AllowInvalidStaticMemberRefOnProtocolMetatype final
28202876
: public ConstraintFix {
28212877
AllowInvalidStaticMemberRefOnProtocolMetatype(ConstraintSystem &cs,

lib/Sema/CSApply.cpp

Lines changed: 11 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3726,12 +3726,8 @@ namespace {
37263726
expr->setCastType(toType);
37273727
cs.setType(castTypeRepr, toType);
37283728

3729-
auto castContextKind =
3730-
SuppressDiagnostics ? CheckedCastContextKind::None
3731-
: CheckedCastContextKind::IsExpr;
37323729
auto castKind = TypeChecker::typeCheckCheckedCast(
3733-
fromType, toType, castContextKind, dc, expr->getLoc(), sub,
3734-
castTypeRepr->getSourceRange());
3730+
fromType, toType, CheckedCastContextKind::IsExpr, dc);
37353731

37363732
switch (castKind) {
37373733
case CheckedCastKind::Unresolved:
@@ -3740,19 +3736,7 @@ namespace {
37403736

37413737
case CheckedCastKind::Coercion:
37423738
case CheckedCastKind::BridgingCoercion:
3743-
expr->setCastKind(castKind);
3744-
break;
37453739
case CheckedCastKind::ValueCast:
3746-
// Check the cast target is a non-foreign type
3747-
if (auto cls = toType->getAs<ClassType>()) {
3748-
if (cls->getDecl()->getForeignClassKind() ==
3749-
ClassDecl::ForeignKind::CFType) {
3750-
ctx.Diags.diagnose(expr->getLoc(), diag::isa_is_foreign_check,
3751-
toType);
3752-
}
3753-
}
3754-
expr->setCastKind(castKind);
3755-
break;
37563740
case CheckedCastKind::ArrayDowncast:
37573741
case CheckedCastKind::DictionaryDowncast:
37583742
case CheckedCastKind::SetDowncast:
@@ -4183,17 +4167,16 @@ namespace {
41834167
if (hasForcedOptionalResult(expr))
41844168
toType = toType->getOptionalObjectType();
41854169

4186-
auto castContextKind = SuppressDiagnostics || expr->isImplicit()
4187-
? CheckedCastContextKind::None
4188-
: CheckedCastContextKind::ForcedCast;
4189-
41904170
const auto castKind = TypeChecker::typeCheckCheckedCast(
4191-
fromType, toType, castContextKind, dc, expr->getLoc(), sub,
4192-
castTypeRange);
4171+
fromType, toType, CheckedCastContextKind::ForcedCast, dc);
41934172
switch (castKind) {
41944173
/// Invalid cast.
41954174
case CheckedCastKind::Unresolved:
4196-
return nullptr;
4175+
if (expr->isImplicit())
4176+
return nullptr;
4177+
4178+
expr->setCastKind(CheckedCastKind::ValueCast);
4179+
break;
41974180
case CheckedCastKind::Coercion:
41984181
case CheckedCastKind::BridgingCoercion: {
41994182
expr->setCastKind(castKind);
@@ -4254,47 +4237,18 @@ namespace {
42544237
"cast requires TypeRepr; implicit casts are superfluous");
42554238

42564239
// The subexpression is always an rvalue.
4257-
auto &ctx = cs.getASTContext();
42584240
auto sub = cs.coerceToRValue(expr->getSubExpr());
42594241
expr->setSubExpr(sub);
42604242

42614243
// Simplify and update the type we're casting to.
42624244
const auto fromType = cs.getType(sub);
42634245
const auto toType = expr->getCastType();
42644246

4265-
bool isSubExprLiteral = isa<LiteralExpr>(sub);
4266-
auto castContextKind =
4267-
(SuppressDiagnostics || expr->isImplicit() || isSubExprLiteral)
4268-
? CheckedCastContextKind::None
4269-
: CheckedCastContextKind::ConditionalCast;
4270-
42714247
auto castKind = TypeChecker::typeCheckCheckedCast(
4272-
fromType, toType, castContextKind, dc, expr->getLoc(), sub,
4273-
castTypeRepr->getSourceRange());
4248+
fromType, toType, CheckedCastContextKind::ConditionalCast, dc);
42744249
switch (castKind) {
42754250
// Invalid cast.
42764251
case CheckedCastKind::Unresolved:
4277-
// FIXME: This literal diagnostics needs to be revisited by a proposal
4278-
// to unify casting semantics for literals.
4279-
// https://bugs.swift.org/browse/SR-12093
4280-
if (isSubExprLiteral) {
4281-
auto protocol = TypeChecker::getLiteralProtocol(ctx, sub);
4282-
// Special handle for literals conditional checked cast when they can
4283-
// be statically coerced to the cast type.
4284-
if (protocol && TypeChecker::conformsToProtocol(
4285-
toType, protocol, dc->getParentModule())) {
4286-
ctx.Diags
4287-
.diagnose(expr->getLoc(),
4288-
diag::literal_conditional_downcast_to_coercion,
4289-
toType);
4290-
} else {
4291-
ctx.Diags
4292-
.diagnose(expr->getLoc(), diag::downcast_to_unrelated, fromType,
4293-
toType)
4294-
.highlight(sub->getSourceRange())
4295-
.highlight(castTypeRepr->getSourceRange());
4296-
}
4297-
}
42984252
expr->setCastKind(CheckedCastKind::ValueCast);
42994253
break;
43004254

@@ -6162,12 +6116,9 @@ static Expr *buildElementConversion(ExprRewriter &rewriter,
61626116
Type destType, bool bridged,
61636117
ConstraintLocatorBuilder locator,
61646118
Expr *element) {
6165-
if (bridged &&
6166-
TypeChecker::typeCheckCheckedCast(srcType, destType,
6167-
CheckedCastContextKind::None,
6168-
rewriter.dc,
6169-
SourceLoc(), nullptr, SourceRange())
6170-
!= CheckedCastKind::Coercion) {
6119+
if (bridged && TypeChecker::typeCheckCheckedCast(
6120+
srcType, destType, CheckedCastContextKind::None,
6121+
rewriter.dc) != CheckedCastKind::Coercion) {
61716122
if (auto conversion =
61726123
rewriter.buildObjCBridgeExpr(element, destType, locator))
61736124
return conversion;

lib/Sema/CSDiagnostics.cpp

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3035,10 +3035,8 @@ bool ContextualFailure::tryTypeCoercionFixIt(
30353035
}
30363036
}
30373037

3038-
CheckedCastKind Kind =
3039-
TypeChecker::typeCheckCheckedCast(fromType, toType,
3040-
CheckedCastContextKind::None, getDC(),
3041-
SourceLoc(), nullptr, SourceRange());
3038+
CheckedCastKind Kind = TypeChecker::typeCheckCheckedCast(
3039+
fromType, toType, CheckedCastContextKind::None, getDC());
30423040

30433041
if (Kind != CheckedCastKind::Unresolved) {
30443042
bool canUseAs = Kind == CheckedCastKind::Coercion ||
@@ -7982,7 +7980,7 @@ bool CoercibleOptionalCheckedCastFailure::diagnoseConditionalCastExpr() const {
79827980
return true;
79837981
}
79847982

7985-
bool NoopCheckedCast::diagnoseIfExpr() const {
7983+
bool NoopCheckedCast::diagnoseIsExpr() const {
79867984
auto *expr = getAsExpr<IsExpr>(CastExpr);
79877985
if (!expr)
79887986
return false;
@@ -8028,7 +8026,7 @@ bool NoopCheckedCast::diagnoseForcedCastExpr() const {
80288026
}
80298027

80308028
bool NoopCheckedCast::diagnoseAsError() {
8031-
if (diagnoseIfExpr())
8029+
if (diagnoseIsExpr())
80328030
return true;
80338031

80348032
if (diagnoseForcedCastExpr())
@@ -8040,6 +8038,11 @@ bool NoopCheckedCast::diagnoseAsError() {
80408038
llvm_unreachable("Shouldn't reach here");
80418039
}
80428040

8041+
bool NoopExistentialToCFTypeCheckedCast::diagnoseAsError() {
8042+
emitDiagnostic(diag::isa_is_foreign_check, getToType());
8043+
return true;
8044+
}
8045+
80438046
bool CoercibleOptionalCheckedCastFailure::diagnoseAsError() {
80448047
if (diagnoseIfExpr())
80458048
return true;
@@ -8062,6 +8065,42 @@ bool UnsupportedRuntimeCheckedCastFailure::diagnoseAsError() {
80628065
return true;
80638066
}
80648067

8068+
bool CheckedCastToUnrelatedFailure::diagnoseAsError() {
8069+
const auto toType = getToType();
8070+
auto *sub = CastExpr->getSubExpr()->getSemanticsProvidingExpr();
8071+
// FIXME: This literal diagnostics needs to be revisited by a proposal
8072+
// to unify casting semantics for literals.
8073+
// https://bugs.swift.org/browse/SR-12093
8074+
auto &ctx = getASTContext();
8075+
auto *dc = getDC();
8076+
if (isa<LiteralExpr>(sub)) {
8077+
auto *protocol = TypeChecker::getLiteralProtocol(ctx, sub);
8078+
// Special handle for literals conditional checked cast when they can
8079+
// be statically coerced to the cast type.
8080+
if (protocol && TypeChecker::conformsToProtocol(toType, protocol,
8081+
dc->getParentModule())) {
8082+
emitDiagnostic(diag::literal_conditional_downcast_to_coercion, toType);
8083+
return true;
8084+
}
8085+
}
8086+
8087+
emitDiagnostic(diag::downcast_to_unrelated, getFromType(), toType)
8088+
.highlight(getFromRange())
8089+
.highlight(getToRange());
8090+
// If we're referring to a function with a return value (not Void) then
8091+
// emit a fix-it suggesting to add `()` to call the function
8092+
if (auto DRE = dyn_cast<DeclRefExpr>(sub)) {
8093+
if (auto FD = dyn_cast<FuncDecl>(DRE->getDecl())) {
8094+
if (!FD->getResultInterfaceType()->isVoid()) {
8095+
emitDiagnostic(diag::downcast_to_unrelated_fixit,
8096+
FD->getBaseIdentifier())
8097+
.fixItInsertAfter(sub->getEndLoc(), "()");
8098+
}
8099+
}
8100+
}
8101+
return true;
8102+
}
8103+
80658104
bool InvalidWeakAttributeUse::diagnoseAsError() {
80668105
auto *pattern =
80678106
dyn_cast_or_null<NamedPattern>(getAnchor().dyn_cast<Pattern *>());

lib/Sema/CSDiagnostics.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2574,13 +2574,25 @@ class NoopCheckedCast final : public CheckedCastBaseFailure {
25742574
bool diagnoseAsError() override;
25752575

25762576
private:
2577-
bool diagnoseIfExpr() const;
2577+
bool diagnoseIsExpr() const;
25782578

25792579
bool diagnoseForcedCastExpr() const;
25802580

25812581
bool diagnoseConditionalCastExpr() const;
25822582
};
25832583

2584+
/// Warn situations where the compiler can statically know a runtime
2585+
/// checked cast always succeed.
2586+
class NoopExistentialToCFTypeCheckedCast final : public CheckedCastBaseFailure {
2587+
public:
2588+
NoopExistentialToCFTypeCheckedCast(const Solution &solution, Type fromType,
2589+
Type toType, CheckedCastKind kind,
2590+
ConstraintLocator *locator)
2591+
: CheckedCastBaseFailure(solution, fromType, toType, kind, locator) {}
2592+
2593+
bool diagnoseAsError() override;
2594+
};
2595+
25842596
/// Warn situations where the compiler can statically know a runtime
25852597
/// check is not supported.
25862598
class UnsupportedRuntimeCheckedCastFailure final
@@ -2594,6 +2606,18 @@ class UnsupportedRuntimeCheckedCastFailure final
25942606
bool diagnoseAsError() override;
25952607
};
25962608

2609+
/// Emit a warning when compiler can detect that checked cast would fail at
2610+
/// runtime based on statically known types.
2611+
class CheckedCastToUnrelatedFailure final : public CheckedCastBaseFailure {
2612+
public:
2613+
CheckedCastToUnrelatedFailure(const Solution &solution, Type fromType,
2614+
Type toType, CheckedCastKind kind,
2615+
ConstraintLocator *locator)
2616+
: CheckedCastBaseFailure(solution, fromType, toType, kind, locator) {}
2617+
2618+
bool diagnoseAsError() override;
2619+
};
2620+
25972621
/// Diagnose situations when static member reference has invalid result
25982622
/// type which disqualifies it from being used on a protocol metatype base.
25992623
///

0 commit comments

Comments
 (0)