in lib/Sema/TypeCheckConstraints.cpp [1287:1871]
CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType,
Type toType,
CheckedCastContextKind contextKind,
DeclContext *dc,
SourceLoc diagLoc,
Expr *fromExpr,
SourceRange diagToRange) {
// Determine whether we should suppress diagnostics.
const bool suppressDiagnostics =
contextKind == CheckedCastContextKind::None ||
contextKind == CheckedCastContextKind::Coercion;
assert((suppressDiagnostics || diagLoc.isValid()) &&
"diagnostics require a valid source location");
SourceRange diagFromRange;
if (fromExpr)
diagFromRange = fromExpr->getSourceRange();
// If the from/to types are equivalent or convertible, this is a coercion.
bool unwrappedIUO = false;
if (fromType->isEqual(toType) ||
(isConvertibleTo(fromType, toType, dc, &unwrappedIUO) &&
!unwrappedIUO)) {
return CheckedCastKind::Coercion;
}
// Check for a bridging conversion.
// Anything bridges to AnyObject.
if (toType->isAnyObject())
return CheckedCastKind::BridgingCoercion;
if (isObjCBridgedTo(fromType, toType, dc, &unwrappedIUO) && !unwrappedIUO){
return CheckedCastKind::BridgingCoercion;
}
Type origFromType = fromType;
Type origToType = toType;
auto *module = dc->getParentModule();
auto &diags = dc->getASTContext().Diags;
bool optionalToOptionalCast = false;
// Local function to indicate failure.
auto failed = [&] {
if (suppressDiagnostics) {
return CheckedCastKind::Unresolved;
}
// Explicit optional-to-optional casts always succeed because a nil
// value of any optional type can be cast to any other optional type.
if (optionalToOptionalCast)
return CheckedCastKind::ValueCast;
diags.diagnose(diagLoc, diag::downcast_to_unrelated, origFromType,
origToType)
.highlight(diagFromRange)
.highlight(diagToRange);
return CheckedCastKind::ValueCast;
};
// TODO: Explore optionals using the same strategy used by the
// runtime.
// For now, if the target is more optional than the source,
// just defer it out for the runtime to handle.
while (auto toValueType = toType->getOptionalObjectType()) {
auto fromValueType = fromType->getOptionalObjectType();
if (!fromValueType) {
return CheckedCastKind::ValueCast;
}
toType = toValueType;
fromType = fromValueType;
optionalToOptionalCast = true;
}
// On the other hand, casts can decrease optionality monadically.
unsigned extraFromOptionals = 0;
while (auto fromValueType = fromType->getOptionalObjectType()) {
fromType = fromValueType;
++extraFromOptionals;
}
// If the unwrapped from/to types are equivalent or bridged, this isn't a real
// downcast. Complain.
auto &Context = dc->getASTContext();
if (extraFromOptionals > 0) {
switch (typeCheckCheckedCast(fromType, toType,
CheckedCastContextKind::None, dc,
SourceLoc(), nullptr, SourceRange())) {
case CheckedCastKind::Coercion:
case CheckedCastKind::BridgingCoercion: {
// Treat this as a value cast so we preserve the semantics.
return CheckedCastKind::ValueCast;
}
case CheckedCastKind::ArrayDowncast:
case CheckedCastKind::DictionaryDowncast:
case CheckedCastKind::SetDowncast:
case CheckedCastKind::ValueCast:
break;
case CheckedCastKind::Unresolved:
return failed();
}
}
auto checkElementCast = [&](Type fromElt, Type toElt,
CheckedCastKind castKind) -> CheckedCastKind {
switch (typeCheckCheckedCast(fromElt, toElt, CheckedCastContextKind::None,
dc, SourceLoc(), nullptr, SourceRange())) {
case CheckedCastKind::Coercion:
return CheckedCastKind::Coercion;
case CheckedCastKind::BridgingCoercion:
return CheckedCastKind::BridgingCoercion;
case CheckedCastKind::ArrayDowncast:
case CheckedCastKind::DictionaryDowncast:
case CheckedCastKind::SetDowncast:
case CheckedCastKind::ValueCast:
return castKind;
case CheckedCastKind::Unresolved:
// Even though we know the elements cannot be downcast, we cannot return
// failed() here as it's possible for an empty Array, Set or Dictionary to
// be cast to any element type at runtime (SR-6192). The one exception to
// this is when we're checking whether we can treat a coercion as a checked
// cast because we don't want to tell the user to use as!, as it's probably
// the wrong suggestion.
if (contextKind == CheckedCastContextKind::Coercion)
return failed();
return castKind;
}
llvm_unreachable("invalid cast type");
};
// Check for casts between specific concrete types that cannot succeed.
if (auto toElementType = ConstraintSystem::isArrayType(toType)) {
if (auto fromElementType = ConstraintSystem::isArrayType(fromType)) {
return checkElementCast(*fromElementType, *toElementType,
CheckedCastKind::ArrayDowncast);
}
}
if (auto toKeyValue = ConstraintSystem::isDictionaryType(toType)) {
if (auto fromKeyValue = ConstraintSystem::isDictionaryType(fromType)) {
bool hasCoercion = false;
enum { NoBridging, BridgingCoercion }
hasBridgingConversion = NoBridging;
bool hasCast = false;
switch (typeCheckCheckedCast(fromKeyValue->first, toKeyValue->first,
CheckedCastContextKind::None, dc,
SourceLoc(), nullptr, SourceRange())) {
case CheckedCastKind::Coercion:
hasCoercion = true;
break;
case CheckedCastKind::BridgingCoercion:
hasBridgingConversion = std::max(hasBridgingConversion,
BridgingCoercion);
break;
case CheckedCastKind::Unresolved:
// Handled the same as in checkElementCast; see comment there for
// rationale.
if (contextKind == CheckedCastContextKind::Coercion)
return failed();
LLVM_FALLTHROUGH;
case CheckedCastKind::ArrayDowncast:
case CheckedCastKind::DictionaryDowncast:
case CheckedCastKind::SetDowncast:
case CheckedCastKind::ValueCast:
hasCast = true;
break;
}
switch (typeCheckCheckedCast(fromKeyValue->second, toKeyValue->second,
CheckedCastContextKind::None, dc,
SourceLoc(), nullptr, SourceRange())) {
case CheckedCastKind::Coercion:
hasCoercion = true;
break;
case CheckedCastKind::BridgingCoercion:
hasBridgingConversion = std::max(hasBridgingConversion,
BridgingCoercion);
break;
case CheckedCastKind::Unresolved:
// Handled the same as in checkElementCast; see comment there for
// rationale.
if (contextKind == CheckedCastContextKind::Coercion)
return failed();
LLVM_FALLTHROUGH;
case CheckedCastKind::ArrayDowncast:
case CheckedCastKind::DictionaryDowncast:
case CheckedCastKind::SetDowncast:
case CheckedCastKind::ValueCast:
hasCast = true;
break;
}
if (hasCast) return CheckedCastKind::DictionaryDowncast;
switch (hasBridgingConversion) {
case NoBridging:
break;
case BridgingCoercion:
return CheckedCastKind::BridgingCoercion;
}
assert(hasCoercion && "Not a coercion?");
(void)hasCoercion;
return CheckedCastKind::Coercion;
}
}
if (auto toElementType = ConstraintSystem::isSetType(toType)) {
if (auto fromElementType = ConstraintSystem::isSetType(fromType)) {
return checkElementCast(*fromElementType, *toElementType,
CheckedCastKind::SetDowncast);
}
}
if (auto toTuple = toType->getAs<TupleType>()) {
if (auto fromTuple = fromType->getAs<TupleType>()) {
if (fromTuple->getNumElements() != toTuple->getNumElements())
return failed();
for (unsigned i = 0, n = toTuple->getNumElements(); i != n; ++i) {
const auto &fromElt = fromTuple->getElement(i);
const auto &toElt = toTuple->getElement(i);
// We should only perform name validation if both elements have a label,
// because unlabeled tuple elements can be converted to labeled ones
// e.g.
//
// let tup: (Any, Any) = (1, 1)
// _ = tup as! (a: Int, Int)
if ((!fromElt.getName().empty() && !toElt.getName().empty()) &&
fromElt.getName() != toElt.getName())
return failed();
auto result = checkElementCast(fromElt.getType(), toElt.getType(),
CheckedCastKind::ValueCast);
if (result == CheckedCastKind::Unresolved)
return result;
}
return CheckedCastKind::ValueCast;
}
}
assert(!toType->isAny() && "casts to 'Any' should've been handled above");
assert(!toType->isAnyObject() &&
"casts to 'AnyObject' should've been handled above");
// A cast from a function type to an existential type (except `Any`)
// or an archetype type (with constraints) cannot succeed
auto toArchetypeType = toType->is<ArchetypeType>();
auto fromFunctionType = fromType->is<FunctionType>();
auto toExistentialType = toType->isAnyExistentialType();
auto toConstrainedArchetype = false;
if (toArchetypeType) {
auto archetype = toType->castTo<ArchetypeType>();
toConstrainedArchetype = !archetype->getConformsTo().empty();
}
if (fromFunctionType &&
(toExistentialType || (toArchetypeType && toConstrainedArchetype))) {
switch (contextKind) {
case CheckedCastContextKind::ConditionalCast:
case CheckedCastContextKind::ForcedCast:
diags.diagnose(diagLoc, diag::downcast_to_unrelated, origFromType,
origToType)
.highlight(diagFromRange)
.highlight(diagToRange);
// If we're referring to a function with a return value (not Void) then
// emit a fix-it suggesting to add `()` to call the function
if (auto DRE = dyn_cast<DeclRefExpr>(fromExpr)) {
if (auto FD = dyn_cast<FuncDecl>(DRE->getDecl())) {
if (!FD->getResultInterfaceType()->isVoid()) {
diags.diagnose(diagLoc, diag::downcast_to_unrelated_fixit,
FD->getBaseIdentifier())
.fixItInsertAfter(fromExpr->getEndLoc(), "()");
}
}
}
return CheckedCastKind::ValueCast;
case CheckedCastContextKind::IsPattern:
case CheckedCastContextKind::EnumElementPattern:
case CheckedCastContextKind::IsExpr:
case CheckedCastContextKind::None:
case CheckedCastContextKind::Coercion:
break;
}
}
// If we can bridge through an Objective-C class, do so.
if (Type bridgedToClass = getDynamicBridgedThroughObjCClass(dc, fromType,
toType)) {
switch (typeCheckCheckedCast(bridgedToClass, fromType,
CheckedCastContextKind::None, dc, SourceLoc(),
nullptr, SourceRange())) {
case CheckedCastKind::ArrayDowncast:
case CheckedCastKind::BridgingCoercion:
case CheckedCastKind::Coercion:
case CheckedCastKind::DictionaryDowncast:
case CheckedCastKind::SetDowncast:
case CheckedCastKind::ValueCast:
return CheckedCastKind::ValueCast;
case CheckedCastKind::Unresolved:
break;
}
}
// If we can bridge through an Objective-C class, do so.
if (Type bridgedFromClass = getDynamicBridgedThroughObjCClass(dc, toType,
fromType)) {
switch (typeCheckCheckedCast(toType, bridgedFromClass,
CheckedCastContextKind::None, dc, SourceLoc(),
nullptr, SourceRange())) {
case CheckedCastKind::ArrayDowncast:
case CheckedCastKind::BridgingCoercion:
case CheckedCastKind::Coercion:
case CheckedCastKind::DictionaryDowncast:
case CheckedCastKind::SetDowncast:
case CheckedCastKind::ValueCast:
return CheckedCastKind::ValueCast;
case CheckedCastKind::Unresolved:
break;
}
}
// Strip metatypes. If we can cast two types, we can cast their metatypes.
bool metatypeCast = false;
while (auto toMetatype = toType->getAs<MetatypeType>()) {
auto fromMetatype = fromType->getAs<MetatypeType>();
if (!fromMetatype)
break;
metatypeCast = true;
toType = toMetatype->getInstanceType();
fromType = fromMetatype->getInstanceType();
}
// Strip an inner layer of potentially existential metatype.
bool toExistentialMetatype = false;
bool fromExistentialMetatype = false;
if (auto toMetatype = toType->getAs<AnyMetatypeType>()) {
if (auto fromMetatype = fromType->getAs<AnyMetatypeType>()) {
toExistentialMetatype = toType->is<ExistentialMetatypeType>();
fromExistentialMetatype = fromType->is<ExistentialMetatypeType>();
toType = toMetatype->getInstanceType();
fromType = fromMetatype->getInstanceType();
}
}
bool toArchetype = toType->is<ArchetypeType>();
bool fromArchetype = fromType->is<ArchetypeType>();
bool toExistential = toType->isExistentialType();
bool fromExistential = fromType->isExistentialType();
bool toRequiresClass;
if (toType->isExistentialType())
toRequiresClass = toType->getExistentialLayout().requiresClass();
else
toRequiresClass = toType->mayHaveSuperclass();
bool fromRequiresClass;
if (fromType->isExistentialType())
fromRequiresClass = fromType->getExistentialLayout().requiresClass();
else
fromRequiresClass = fromType->mayHaveSuperclass();
// Casts between metatypes only succeed if none of the types are existentials
// or if one is an existential and the other is a generic type because there
// may be protocol conformances unknown at compile time.
if (metatypeCast) {
if ((toExistential || fromExistential) && !(fromArchetype || toArchetype))
return failed();
}
// Casts from an existential metatype to a protocol metatype always fail,
// except when the existential type is 'Any'.
if (fromExistentialMetatype &&
!fromType->isAny() &&
!toExistentialMetatype &&
toExistential)
return failed();
// Casts to or from generic types can't be statically constrained in most
// cases, because there may be protocol conformances we don't statically
// know about.
if (toExistential || fromExistential || fromArchetype || toArchetype ||
toRequiresClass || fromRequiresClass) {
// Cast to and from AnyObject always succeed.
if (!metatypeCast &&
!fromExistentialMetatype &&
!toExistentialMetatype &&
(toType->isAnyObject() || fromType->isAnyObject()))
return CheckedCastKind::ValueCast;
// If we have a cast from an existential type to a concrete type that we
// statically know doesn't conform to the protocol, mark the cast as always
// failing. For example:
//
// struct S {}
// enum FooError: Error { case bar }
//
// func foo() {
// do {
// throw FooError.bar
// } catch is X { /* Will always fail */
// print("Caught bar error")
// }
// }
//
auto constraint = fromType;
if (auto existential = constraint->getAs<ExistentialType>())
constraint = existential->getConstraintType();
if (auto *protocolDecl =
dyn_cast_or_null<ProtocolDecl>(constraint->getAnyNominal())) {
if (!couldDynamicallyConformToProtocol(toType, protocolDecl, module)) {
return failed();
}
} else if (auto protocolComposition =
constraint->getAs<ProtocolCompositionType>()) {
if (llvm::any_of(protocolComposition->getMembers(),
[&](Type protocolType) {
if (auto protocolDecl = dyn_cast_or_null<ProtocolDecl>(
protocolType->getAnyNominal())) {
return !couldDynamicallyConformToProtocol(
toType, protocolDecl, module);
}
return false;
})) {
return failed();
}
}
// If neither type is class-constrained, anything goes.
if (!fromRequiresClass && !toRequiresClass)
return CheckedCastKind::ValueCast;
if (!fromRequiresClass && toRequiresClass) {
// If source type is abstract, anything goes.
if (fromExistential || fromArchetype)
return CheckedCastKind::ValueCast;
// Otherwise, we're casting a concrete non-class type to a
// class-constrained archetype or existential, which will
// probably fail, but we'll try more casts below.
}
if (fromRequiresClass && !toRequiresClass) {
// If destination type is abstract, anything goes.
if (toExistential || toArchetype)
return CheckedCastKind::ValueCast;
// Otherwise, we're casting a class-constrained archetype
// or existential to a non-class concrete type, which
// will probably fail, but we'll try more casts below.
}
if (fromRequiresClass && toRequiresClass) {
// Ok, we are casting between class-like things. Let's see if we have
// explicit superclass bounds.
Type toSuperclass;
if (toType->getClassOrBoundGenericClass())
toSuperclass = toType;
else
toSuperclass = toType->getSuperclass();
Type fromSuperclass;
if (fromType->getClassOrBoundGenericClass())
fromSuperclass = fromType;
else
fromSuperclass = fromType->getSuperclass();
// Unless both types have a superclass bound, we have no further
// information.
if (!toSuperclass || !fromSuperclass)
return CheckedCastKind::ValueCast;
// Compare superclass bounds.
if (fromSuperclass->isBindableToSuperclassOf(toSuperclass))
return CheckedCastKind::ValueCast;
// An upcast is also OK.
if (toSuperclass->isBindableToSuperclassOf(fromSuperclass))
return CheckedCastKind::ValueCast;
}
}
if (toType->isAnyHashable() || fromType->isAnyHashable()) {
return CheckedCastKind::ValueCast;
}
// We perform an upcast while rebinding generic parameters if it's possible
// to substitute the generic arguments of the source type with the generic
// archetypes of the destination type. Or, if it's possible to substitute
// the generic arguments of the destination type with the generic archetypes
// of the source type, we perform a downcast instead.
if (toType->isBindableTo(fromType) || fromType->isBindableTo(toType))
return CheckedCastKind::ValueCast;
// Objective-C metaclasses are subclasses of NSObject in the ObjC runtime,
// so casts from NSObject to potentially-class metatypes may succeed.
if (auto nsObject = Context.getNSObjectType()) {
if (fromType->isEqual(nsObject)) {
if (auto toMeta = toType->getAs<MetatypeType>()) {
if (toMeta->getInstanceType()->mayHaveSuperclass()
|| toMeta->getInstanceType()->is<ArchetypeType>())
return CheckedCastKind::ValueCast;
}
if (toType->is<ExistentialMetatypeType>())
return CheckedCastKind::ValueCast;
}
}
// We can conditionally cast from NSError to an Error-conforming type.
// This is handled in the runtime, so it doesn't need a special cast
// kind.
if (Context.LangOpts.EnableObjCInterop) {
auto nsObject = Context.getNSObjectType();
auto nsErrorTy = Context.getNSErrorType();
if (auto errorTypeProto = Context.getProtocol(KnownProtocolKind::Error)) {
if (conformsToProtocol(toType, errorTypeProto, module)) {
if (nsErrorTy) {
if (isSubtypeOf(fromType, nsErrorTy, dc)
// Don't mask "always true" warnings if NSError is cast to
// Error itself.
&& !isSubtypeOf(fromType, toType, dc))
return CheckedCastKind::ValueCast;
}
}
if (conformsToProtocol(fromType, errorTypeProto, module)) {
// Cast of an error-conforming type to NSError or NSObject.
if ((nsObject && toType->isEqual(nsObject)) ||
(nsErrorTy && toType->isEqual(nsErrorTy)))
return CheckedCastKind::BridgingCoercion;
}
}
// Any class-like type could be dynamically cast to NSObject or NSError
// via an Error conformance.
if (fromType->mayHaveSuperclass() &&
((nsObject && toType->isEqual(nsObject)) ||
(nsErrorTy && toType->isEqual(nsErrorTy)))) {
return CheckedCastKind::ValueCast;
}
}
// The runtime doesn't support casts to CF types and always lets them succeed.
// This "always fails" diagnosis makes no sense when paired with the CF
// one.
auto clas = toType->getClassOrBoundGenericClass();
if (clas && clas->getForeignClassKind() == ClassDecl::ForeignKind::CFType)
return CheckedCastKind::ValueCast;
// Don't warn on casts that change the generic parameters of ObjC generic
// classes. This may be necessary to force-fit ObjC APIs that depend on
// covariance, or for APIs where the generic parameter annotations in the
// ObjC headers are inaccurate.
if (clas && clas->isTypeErasedGenericClass()) {
if (fromType->getClassOrBoundGenericClass() == clas)
return CheckedCastKind::ValueCast;
}
return failed();
}