Skip to content

Commit 31ac04f

Browse files
[Sema] Abstract checked cast fix logic to static function and minor adjustments
1 parent f05e98f commit 31ac04f

File tree

2 files changed

+137
-115
lines changed

2 files changed

+137
-115
lines changed

include/swift/Sema/CSFix.h

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2209,18 +2209,30 @@ class SpecifyBaseTypeForOptionalUnresolvedMember final : public ConstraintFix {
22092209
MemberLookupResult result, ConstraintLocator *locator);
22102210
};
22112211

2212-
class AllowCheckedCastCoercibleOptionalType final : public ContextualMismatch {
2212+
class CheckedCastContextualMismatchWarningBase : public ContextualMismatch {
2213+
protected:
2214+
CheckedCastContextualMismatchWarningBase(ConstraintSystem &cs,
2215+
FixKind fixKind, Type fromType,
2216+
Type toType, CheckedCastKind kind,
2217+
ConstraintLocator *locator)
2218+
: ContextualMismatch(cs, fixKind, fromType, toType, locator,
2219+
/*isWarning*/ true),
2220+
CastKind(kind) {}
2221+
CheckedCastKind CastKind;
2222+
};
2223+
2224+
class AllowCheckedCastCoercibleOptionalType final
2225+
: public CheckedCastContextualMismatchWarningBase {
22132226
AllowCheckedCastCoercibleOptionalType(ConstraintSystem &cs, Type fromType,
22142227
Type toType, CheckedCastKind kind,
22152228
ConstraintLocator *locator)
2216-
: ContextualMismatch(cs, FixKind::AllowCheckedCastCoercibleOptionalType,
2217-
fromType, toType, locator, /*isWarning*/ true),
2218-
CastKind(kind) {}
2219-
CheckedCastKind CastKind;
2229+
: CheckedCastContextualMismatchWarningBase(
2230+
cs, FixKind::AllowCheckedCastCoercibleOptionalType, fromType,
2231+
toType, kind, locator) {}
22202232

22212233
public:
22222234
std::string getName() const override {
2223-
return "checked cast coecible optional";
2235+
return "checked cast coercible optional";
22242236
}
22252237
bool diagnose(const Solution &solution, bool asNote = false) const override;
22262238

@@ -2229,15 +2241,14 @@ class AllowCheckedCastCoercibleOptionalType final : public ContextualMismatch {
22292241
ConstraintLocator *locator);
22302242
};
22312243

2232-
class AllowAlwaysSucceedCheckedCast final : public ContextualMismatch {
2244+
class AllowAlwaysSucceedCheckedCast final
2245+
: public CheckedCastContextualMismatchWarningBase {
22332246
AllowAlwaysSucceedCheckedCast(ConstraintSystem &cs, Type fromType,
22342247
Type toType, CheckedCastKind kind,
22352248
ConstraintLocator *locator)
2236-
: ContextualMismatch(cs, FixKind::AllowUnsupportedRuntimeCheckedCast,
2237-
fromType, toType, locator,
2238-
/*isWarning*/ true),
2239-
CastKind(kind) {}
2240-
CheckedCastKind CastKind;
2249+
: CheckedCastContextualMismatchWarningBase(
2250+
cs, FixKind::AllowUnsupportedRuntimeCheckedCast, fromType, toType,
2251+
kind, locator) {}
22412252

22422253
public:
22432254
std::string getName() const override { return "checked cast always succeed"; }
@@ -2249,15 +2260,14 @@ class AllowAlwaysSucceedCheckedCast final : public ContextualMismatch {
22492260
ConstraintLocator *locator);
22502261
};
22512262

2252-
class AllowUnsupportedRuntimeCheckedCast final : public ContextualMismatch {
2263+
class AllowUnsupportedRuntimeCheckedCast final
2264+
: public CheckedCastContextualMismatchWarningBase {
22532265
AllowUnsupportedRuntimeCheckedCast(ConstraintSystem &cs, Type fromType,
22542266
Type toType, CheckedCastKind kind,
22552267
ConstraintLocator *locator)
2256-
: ContextualMismatch(cs, FixKind::AllowUnsupportedRuntimeCheckedCast,
2257-
fromType, toType, locator,
2258-
/*isWarning*/ true),
2259-
CastKind(kind) {}
2260-
CheckedCastKind CastKind;
2268+
: CheckedCastContextualMismatchWarningBase(
2269+
cs, FixKind::AllowUnsupportedRuntimeCheckedCast, fromType, toType,
2270+
kind, locator) {}
22612271

22622272
public:
22632273
std::string getName() const override {

lib/Sema/CSSimplify.cpp

Lines changed: 109 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -5963,6 +5963,98 @@ static CheckedCastKind getCheckedCastKind(ConstraintSystem *cs,
59635963
return CheckedCastKind::ValueCast;
59645964
}
59655965

5966+
// Optional types always conform to `ExpressibleByNilLiteral`.
5967+
static bool isCastToExpressibleByNilLiteral(ConstraintSystem &cs, Type fromType,
5968+
Type toType) {
5969+
auto &ctx = cs.getASTContext();
5970+
auto *nilLiteral =
5971+
ctx.getProtocol(KnownProtocolKind::ExpressibleByNilLiteral);
5972+
return toType->isEqual(nilLiteral->getDeclaredType()) &&
5973+
fromType->getOptionalObjectType();
5974+
}
5975+
5976+
static ConstraintFix *attemptFixForCheckedConvertibleTypes(
5977+
ConstraintSystem &cs, Type origFromType, Type origToType, Type fromType,
5978+
Type toType, SmallVector<Type, 4> fromOptionals,
5979+
SmallVector<Type, 4> toOptionals, ConstraintSystem::TypeMatchOptions flags,
5980+
ConstraintLocatorBuilder locator) {
5981+
5982+
if (flags.contains(ConstraintSystem::TypeMatchFlags::TMF_ApplyingFix))
5983+
return nullptr;
5984+
5985+
// Both types have to be fixed.
5986+
if (fromType->hasTypeVariable() || toType->hasTypeVariable())
5987+
return nullptr;
5988+
5989+
SmallVector<LocatorPathElt, 4> path;
5990+
auto anchor = locator.getLocatorParts(path);
5991+
5992+
auto *castExpr = getAsExpr<ExplicitCastExpr>(anchor);
5993+
if (!castExpr)
5994+
return nullptr;
5995+
5996+
unsigned extraOptionals = fromOptionals.size() - toOptionals.size();
5997+
// Removing the optionality from to type when the force cast expr is an IUO.
5998+
const auto *const TR = castExpr->getCastTypeRepr();
5999+
if (isExpr<ForcedCheckedCastExpr>(anchor) && TR &&
6000+
TR->getKind() == TypeReprKind::ImplicitlyUnwrappedOptional) {
6001+
extraOptionals++;
6002+
}
6003+
6004+
// In cases of 'try?' where origFromType isn't optional that meaning
6005+
// sub-expression isn't optional, always adds one level of optionality
6006+
// because the result of the expression is always an optional type
6007+
// regardless of language mode.
6008+
auto *sub = castExpr->getSubExpr()->getSemanticsProvidingExpr();
6009+
if (isExpr<OptionalTryExpr>(sub) && !origFromType->getOptionalObjectType()) {
6010+
origFromType = OptionalType::get(fromType);
6011+
extraOptionals++;
6012+
}
6013+
6014+
// Except for forced cast expressions, if optionals are more than a single
6015+
// level difference, we don't need to record any fix.
6016+
if (!isExpr<ForcedCheckedCastExpr>(anchor) && extraOptionals > 1)
6017+
return nullptr;
6018+
6019+
// Always succeed
6020+
if (isCastToExpressibleByNilLiteral(cs, origFromType, toType)) {
6021+
return AllowAlwaysSucceedCheckedCast::create(
6022+
cs, fromType, toType, CheckedCastKind::Coercion,
6023+
cs.getConstraintLocator(locator));
6024+
}
6025+
6026+
// If both original are metatypes we have to use them because most of the
6027+
// logic on how correctly handle metatypes casting is on
6028+
// typeCheckCheckedCast.
6029+
if (origFromType->is<AnyMetatypeType>() &&
6030+
origToType->is<AnyMetatypeType>()) {
6031+
fromType = origFromType;
6032+
toType = origToType;
6033+
}
6034+
6035+
auto castKind = TypeChecker::typeCheckCheckedCast(
6036+
fromType, toType, CheckedCastContextKind::None, cs.DC, SourceLoc(),
6037+
nullptr, SourceRange());
6038+
if (!(castKind == CheckedCastKind::Coercion ||
6039+
castKind == CheckedCastKind::BridgingCoercion))
6040+
return nullptr;
6041+
6042+
if (auto *fix = AllowUnsupportedRuntimeCheckedCast::attempt(
6043+
cs, fromType, toType, castKind, cs.getConstraintLocator(locator))) {
6044+
return fix;
6045+
}
6046+
if (extraOptionals > 0) {
6047+
return AllowCheckedCastCoercibleOptionalType::create(
6048+
cs, origFromType, origToType, castKind,
6049+
cs.getConstraintLocator(locator));
6050+
} else {
6051+
// No optionals, just a trivial cast that always succeed.
6052+
return AllowAlwaysSucceedCheckedCast::create(
6053+
cs, origFromType, origToType, castKind,
6054+
cs.getConstraintLocator(locator));
6055+
}
6056+
}
6057+
59666058
ConstraintSystem::SolutionKind
59676059
ConstraintSystem::simplifyCheckedCastConstraint(
59686060
Type fromType, Type toType,
@@ -6041,100 +6133,14 @@ ConstraintSystem::simplifyCheckedCastConstraint(
60416133
break;
60426134
} while (true);
60436135

6044-
auto fixForCheckedConvertibleTypes = [&]() -> ConstraintFix * {
6045-
// Both types are known.
6046-
if (fromType->hasTypeVariable() || toType->hasTypeVariable())
6047-
return nullptr;
6048-
6049-
if (flags.contains(TMF_ApplyingFix))
6050-
return nullptr;
6051-
6052-
SmallVector<LocatorPathElt, 4> path;
6053-
auto anchor = locator.getLocatorParts(path);
6054-
auto *castExpr = getAsExpr<ExplicitCastExpr>(anchor);
6055-
if (!castExpr)
6056-
return nullptr;
6057-
6058-
unsigned extraOptionals = fromOptionals.size() - toOptionals.size();
6059-
// Removing the optionality from to type when the force cast expr is an IUO.
6060-
const auto *const TR = castExpr->getCastTypeRepr();
6061-
if (isExpr<ForcedCheckedCastExpr>(anchor) && TR &&
6062-
TR->getKind() == TypeReprKind::ImplicitlyUnwrappedOptional) {
6063-
extraOptionals++;
6064-
}
6065-
6066-
// In some special cases e.g.
6067-
// struct A {
6068-
// func opt() throws -> Int { return 3 }
6069-
// }
6070-
// let a: A? = A()
6071-
// let _ = (try? a?.opt()) as? Int
6072-
//
6073-
// If original from type is a non-optional and subExpr is an
6074-
// OptionalTryExpr, to make sure we handle this correctly lets add the
6075-
// optionality because the result of the expression is always an optional
6076-
// type regardless of language mode.
6077-
auto *sub = castExpr->getSubExpr()->getSemanticsProvidingExpr();
6078-
if (isExpr<OptionalTryExpr>(sub) &&
6079-
!origFromType->getOptionalObjectType()) {
6080-
origFromType = OptionalType::get(fromType);
6081-
extraOptionals++;
6082-
}
6083-
6084-
// Except for forced cast expressions, if optionals are more than a single
6085-
// level difference, we don't need to record the fix.
6086-
if (!isExpr<ForcedCheckedCastExpr>(anchor) && extraOptionals > 1)
6087-
return nullptr;
6088-
6089-
// Explicit handling cast to ExpressibleByNilLiteral because
6090-
// optional types trivially satisfy the conformance.
6091-
auto &ctx = getASTContext();
6092-
auto *nilLiteral =
6093-
ctx.getProtocol(KnownProtocolKind::ExpressibleByNilLiteral);
6094-
if (toType->isEqual(nilLiteral->getDeclaredType()) &&
6095-
origFromType->getOptionalObjectType()) {
6096-
return AllowAlwaysSucceedCheckedCast::create(
6097-
*this, fromType, toType, CheckedCastKind::Coercion,
6098-
getConstraintLocator(locator));
6099-
}
6100-
6101-
// If both original are metatypes we have to use them because most of the
6102-
// logic on how correctly handle metatypes casting is on
6103-
// typeCheckCheckedCast.
6104-
if (origFromType->is<AnyMetatypeType>() &&
6105-
origToType->is<AnyMetatypeType>()) {
6106-
fromType = origFromType;
6107-
toType = origToType;
6108-
}
6109-
6110-
auto castKind = TypeChecker::typeCheckCheckedCast(
6111-
fromType, toType, CheckedCastContextKind::None, DC, SourceLoc(),
6112-
nullptr, SourceRange());
6113-
if (!(castKind == CheckedCastKind::Coercion ||
6114-
castKind == CheckedCastKind::BridgingCoercion))
6115-
return nullptr;
6116-
6117-
if (auto *fix = AllowUnsupportedRuntimeCheckedCast::attempt(
6118-
*this, fromType, toType, castKind, getConstraintLocator(locator))) {
6119-
return fix;
6120-
}
6121-
if (extraOptionals > 0) {
6122-
return AllowCheckedCastCoercibleOptionalType::create(
6123-
*this, origFromType, origToType, castKind,
6124-
getConstraintLocator(locator));
6125-
} else {
6126-
// No optionals, just a trivial cast that always succeed.
6127-
return AllowAlwaysSucceedCheckedCast::create(
6128-
*this, origFromType, origToType, castKind,
6129-
getConstraintLocator(locator));
6130-
}
6131-
};
6136+
auto attemptRecordCastFixIfSolved = [&](SolutionKind result) {
6137+
if (result != SolutionKind::Solved)
6138+
return;
61326139

6133-
auto recordCastFixIfNeeded = [&](SolutionKind result) {
6134-
if (result == SolutionKind::Solved) {
6135-
if (auto *fix = fixForCheckedConvertibleTypes()) {
6136-
(void)recordFix(fix);
6137-
}
6140+
if (auto *fix = attemptFixForCheckedConvertibleTypes(
6141+
*this, origFromType, origToType, fromType, toType, fromOptionals,
6142+
toOptionals, flags, locator)) {
6143+
(void)recordFix(fix);
61386144
}
61396145
};
61406146

@@ -6146,9 +6152,10 @@ ConstraintSystem::simplifyCheckedCastConstraint(
61466152

61476153
auto result = simplifyCheckedCastConstraint(
61486154
fromBaseType, toBaseType, subflags | TMF_ApplyingFix, locator);
6149-
recordCastFixIfNeeded(result);
6155+
attemptRecordCastFixIfSolved(result);
61506156
return result;
61516157
}
6158+
61526159
case CheckedCastKind::DictionaryDowncast: {
61536160
Type fromKeyType, fromValueType;
61546161
std::tie(fromKeyType, fromValueType) = *isDictionaryType(fromType);
@@ -6163,7 +6170,7 @@ ConstraintSystem::simplifyCheckedCastConstraint(
61636170

61646171
auto result = simplifyCheckedCastConstraint(
61656172
fromValueType, toValueType, subflags | TMF_ApplyingFix, locator);
6166-
recordCastFixIfNeeded(result);
6173+
attemptRecordCastFixIfSolved(result);
61676174
return result;
61686175
}
61696176

@@ -6172,7 +6179,7 @@ ConstraintSystem::simplifyCheckedCastConstraint(
61726179
auto toBaseType = *isSetType(toType);
61736180
auto result = simplifyCheckedCastConstraint(
61746181
fromBaseType, toBaseType, subflags | TMF_ApplyingFix, locator);
6175-
recordCastFixIfNeeded(result);
6182+
attemptRecordCastFixIfSolved(result);
61766183
return result;
61776184
}
61786185

@@ -6187,7 +6194,12 @@ ConstraintSystem::simplifyCheckedCastConstraint(
61876194
getConstraintLocator(locator));
61886195
}
61896196

6190-
if (auto *fix = fixForCheckedConvertibleTypes()) {
6197+
// Attempts to record warning fixes when both types are known by the
6198+
// compiler and we can infer that the runtime checked cast will always
6199+
// succeed or fail.
6200+
if (auto *fix = attemptFixForCheckedConvertibleTypes(
6201+
*this, origFromType, origToType, fromType, toType, fromOptionals,
6202+
toOptionals, flags, locator)) {
61916203
(void)recordFix(fix);
61926204
}
61936205

0 commit comments

Comments
 (0)