diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index b33ae8d55d7..c3cf7f6fb9b 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -812,6 +812,10 @@ ERROR(missing_unwrap_optional_try,none, ERROR(missing_forced_downcast,none, "%0 is not convertible to %1; " "did you mean to use 'as!' to force downcast?", (Type, Type)) +WARNING(missing_forced_downcast_swift3_compat_warning,none, + "bridging %0 to %1 may fail at runtime and will become a checked " + "cast in Swift 4; did you mean to use 'as!' to force downcast?", + (Type, Type)) ERROR(missing_explicit_conversion,none, "%0 is not implicitly convertible to %1; " "did you mean to use 'as' to explicitly convert?", (Type, Type)) diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index 112e9246e1c..7785788bfc3 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -62,10 +62,12 @@ enum class ExprKind : uint8_t { #include "swift/AST/ExprNodes.def" }; -/// Discriminates the different kinds of checked cast supported. +/// Discriminates certain kinds of checked cast that have specialized diagnostic +/// and/or code generation peephole behavior. /// -/// This enumeration should not exist. Only the collection downcast kinds are -/// currently significant. Please don't add new kinds. +/// This enumeration should not have any semantic effect on the behavior of a +/// well-typed program, since the runtime can perform all casts that are +/// statically accepted. enum class CheckedCastKind : unsigned { /// The kind has not been determined yet. Unresolved, @@ -75,7 +77,7 @@ enum class CheckedCastKind : unsigned { /// The requested cast is an implicit conversion, so this is a coercion. Coercion = First_Resolved, - /// A non-value-changing checked cast. + /// A checked cast with no known specific behavior. ValueCast, // A downcast from an array type to another array type. ArrayDowncast, @@ -83,10 +85,17 @@ enum class CheckedCastKind : unsigned { DictionaryDowncast, // A downcast from a set type to another set type. SetDowncast, - /// A bridging cast. - BridgingCast, + /// A bridging conversion that always succeeds. + BridgingCoercion, + /// A bridging conversion that may fail, because there are multiple Swift + /// value types that bridge to the same Cocoa object type. + /// + /// This kind is only used for Swift 3 compatibility diagnostics and is + /// treated the same as 'BridgingCoercion' otherwise. In Swift 4 or later, + /// any conversions with this kind show up as ValueCasts. + Swift3BridgingDowncast, - Last_CheckedCastKind = BridgingCast, + Last_CheckedCastKind = Swift3BridgingDowncast, }; enum class AccessSemantics : unsigned char { diff --git a/include/swift/AST/KnownFoundationEntities.def b/include/swift/AST/KnownFoundationEntities.def index 812cd3e711a..70303855bda 100644 --- a/include/swift/AST/KnownFoundationEntities.def +++ b/include/swift/AST/KnownFoundationEntities.def @@ -32,6 +32,7 @@ FOUNDATION_ENTITY(NSSet) FOUNDATION_ENTITY(NSString) FOUNDATION_ENTITY(NSUInteger) FOUNDATION_ENTITY(NSURL) +FOUNDATION_ENTITY(NSValue) FOUNDATION_ENTITY(NSZone) #undef FOUNDATION_ENTITY diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index df2e4d0d7d7..7ce30380425 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -4114,8 +4114,10 @@ StringRef swift::getCheckedCastKindName(CheckedCastKind kind) { return "dictionary_downcast"; case CheckedCastKind::SetDowncast: return "set_downcast"; - case CheckedCastKind::BridgingCast: - return "bridging_cast"; + case CheckedCastKind::BridgingCoercion: + return "bridging_coercion"; + case CheckedCastKind::Swift3BridgingDowncast: + return "bridging_downcast"; } llvm_unreachable("bad checked cast name"); } diff --git a/lib/AST/Pattern.cpp b/lib/AST/Pattern.cpp index 1475d27c099..443cfc3d285 100644 --- a/lib/AST/Pattern.cpp +++ b/lib/AST/Pattern.cpp @@ -298,7 +298,7 @@ bool Pattern::isRefutablePattern() const { // If this is an always matching 'is' pattern, then it isn't refutable. if (auto *is = dyn_cast(Node)) if (is->getCastKind() == CheckedCastKind::Coercion || - is->getCastKind() == CheckedCastKind::BridgingCast) + is->getCastKind() == CheckedCastKind::BridgingCoercion) return; // If this is an ExprPattern that isn't resolved yet, do some simple diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 16e715dc2aa..63987759d10 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -2973,7 +2973,7 @@ namespace { break; case CheckedCastKind::Coercion: - case CheckedCastKind::BridgingCast: + case CheckedCastKind::BridgingCoercion: // Check is trivially true. tc.diagnose(expr->getLoc(), diag::isa_is_always_true, "is"); expr->setCastKind(castKind); @@ -2988,6 +2988,7 @@ namespace { } expr->setCastKind(castKind); break; + case CheckedCastKind::Swift3BridgingDowncast: case CheckedCastKind::ArrayDowncast: case CheckedCastKind::DictionaryDowncast: case CheckedCastKind::SetDowncast: @@ -3296,6 +3297,20 @@ namespace { Expr *sub = expr->getSubExpr(); Type toInstanceType = toType->lookThroughAllAnyOptionalTypes(); + + // Warn about NSNumber and NSValue bridging coercions we accepted in + // Swift 3 but which can fail at runtime. + if (tc.Context.LangOpts.isSwiftVersion3() + && tc.typeCheckCheckedCast(sub->getType(), toInstanceType, + CheckedCastContextKind::None, + dc, SourceLoc(), sub, SourceRange()) + == CheckedCastKind::Swift3BridgingDowncast) { + tc.diagnose(expr->getLoc(), + diag::missing_forced_downcast_swift3_compat_warning, + sub->getType(), toInstanceType) + .fixItReplace(expr->getAsLoc(), "as!"); + } + sub = buildObjCBridgeExpr(sub, toInstanceType, locator); if (!sub) return nullptr; expr->setSubExpr(sub); @@ -3329,7 +3344,7 @@ namespace { case CheckedCastKind::Unresolved: return nullptr; case CheckedCastKind::Coercion: - case CheckedCastKind::BridgingCast: { + case CheckedCastKind::BridgingCoercion: { if (SuppressDiagnostics) return nullptr; @@ -3355,6 +3370,7 @@ namespace { } // Valid casts. + case CheckedCastKind::Swift3BridgingDowncast: case CheckedCastKind::ArrayDowncast: case CheckedCastKind::DictionaryDowncast: case CheckedCastKind::SetDowncast: @@ -3399,7 +3415,7 @@ namespace { break; case CheckedCastKind::Coercion: - case CheckedCastKind::BridgingCast: { + case CheckedCastKind::BridgingCoercion: { if (SuppressDiagnostics) return nullptr; @@ -3427,6 +3443,7 @@ namespace { } // Valid casts. + case CheckedCastKind::Swift3BridgingDowncast: case CheckedCastKind::ArrayDowncast: case CheckedCastKind::DictionaryDowncast: case CheckedCastKind::SetDowncast: @@ -7015,17 +7032,35 @@ bool ConstraintSystem::applySolutionFix(Expr *expr, fromType, toType, CheckedCastContextKind::None, DC, coerceExpr->getLoc(), subExpr, coerceExpr->getCastTypeLoc().getSourceRange()); - + switch (castKind) { // Invalid cast. case CheckedCastKind::Unresolved: // Fix didn't work, let diagnoseFailureForExpr handle this. return false; case CheckedCastKind::Coercion: - case CheckedCastKind::BridgingCast: + case CheckedCastKind::BridgingCoercion: llvm_unreachable("Coercions handled in other disjunction branch"); // Valid casts. + case CheckedCastKind::Swift3BridgingDowncast: { + // Swift 3 accepted coercions from NSNumber and NSValue to Swift + // value types, even though there are multiple Swift types that + // bridge to those classes, and the bridging operation back into Swift + // is type-checked. For compatibility, downgrade to a warning. + assert(TC.Context.LangOpts.isSwiftVersion3() + && "should only appear in Swift 3 compat mode"); + + TC.diagnose(coerceExpr->getLoc(), + diag::missing_forced_downcast_swift3_compat_warning, + fromType, toType) + .highlight(coerceExpr->getSourceRange()) + .fixItReplace(coerceExpr->getLoc(), "as!"); + + // This is just a warning, so allow the expression to type-check. + return false; + } + case CheckedCastKind::ArrayDowncast: case CheckedCastKind::DictionaryDowncast: case CheckedCastKind::SetDowncast: diff --git a/lib/Sema/CSDiag.cpp b/lib/Sema/CSDiag.cpp index 55bd0df2e0c..514af84da3c 100644 --- a/lib/Sema/CSDiag.cpp +++ b/lib/Sema/CSDiag.cpp @@ -3890,7 +3890,7 @@ addTypeCoerceFixit(InFlightDiagnostic &diag, ConstraintSystem *CS, llvm::raw_svector_ostream OS(buffer); toType->print(OS); bool canUseAs = Kind == CheckedCastKind::Coercion || - Kind == CheckedCastKind::BridgingCast; + Kind == CheckedCastKind::BridgingCoercion; diag.fixItInsert(Lexer::getLocForEndOfToken(CS->DC->getASTContext().SourceMgr, expr->getEndLoc()), (llvm::Twine(canUseAs ? " as " : " as! ") + diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 0cfb63db7ec..d528f82e377 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -2414,7 +2414,7 @@ ConstraintSystem::simplifyCheckedCastConstraint( }; do { - // Dig out the fixed type to which this type refers. + // Dig out the fixed type this type refers to. fromType = getFixedTypeRecursive(fromType, flags, /*wantRValue=*/true); // If we hit a type variable without a fixed type, we can't @@ -2422,7 +2422,7 @@ ConstraintSystem::simplifyCheckedCastConstraint( if (fromType->isTypeVariableOrMember()) return formUnsolved(); - // Dig out the fixed type to which this type refers. + // Dig out the fixed type this type refers to. toType = getFixedTypeRecursive(toType, flags, /*wantRValue=*/true); // If we hit a type variable without a fixed type, we can't @@ -2512,7 +2512,8 @@ ConstraintSystem::simplifyCheckedCastConstraint( } case CheckedCastKind::Coercion: - case CheckedCastKind::BridgingCast: + case CheckedCastKind::BridgingCoercion: + case CheckedCastKind::Swift3BridgingDowncast: case CheckedCastKind::Unresolved: llvm_unreachable("Not a valid result"); } @@ -3397,6 +3398,15 @@ ConstraintSystem::simplifyBridgingConstraint(Type type1, Type bridgedValueType; if (auto objcClass = TC.Context.getBridgedToObjC(DC, unwrappedToType, &bridgedValueType)) { + // Bridging NSNumber to NSValue is one-way, since there are multiple Swift + // value types that bridge to those object types. It requires a checked + // cast to get back. + // We accepted these coercions in Swift 3 mode, so we have to live with + // them (but give a warning) in that language mode. + if (!TC.Context.LangOpts.isSwiftVersion3() + && TC.isObjCClassWithMultipleSwiftBridgedTypes(objcClass, DC)) + return SolutionKind::Error; + // If the bridged value type is generic, the generic arguments // must either match or be bridged. // FIXME: This should be an associated type of the protocol. diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index d9e98ff87c2..c578fba8e7d 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -2901,10 +2901,19 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType, !unwrappedIUO)) { return CheckedCastKind::Coercion; } - + // Check for a bridging conversion. - if (isObjCBridgedTo(fromType, toType, dc, &unwrappedIUO) && !unwrappedIUO) - return CheckedCastKind::BridgingCast; + // Anything bridges to AnyObject in ObjC interop mode. + if (Context.LangOpts.EnableObjCInterop + && toType->isAnyObject()) + return CheckedCastKind::BridgingCoercion; + + // Do this check later in Swift 3 mode so that we check for NSNumber and + // NSValue casts (and container casts thereof) first. + if (!Context.LangOpts.isSwiftVersion3() + && isObjCBridgedTo(fromType, toType, dc, &unwrappedIUO) && !unwrappedIUO){ + return CheckedCastKind::BridgingCoercion; + } Type origFromType = fromType; Type origToType = toType; @@ -2955,117 +2964,132 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType, // If the unwrapped from/to types are equivalent or bridged, this isn't a real // downcast. Complain. - if (fromType->isEqual(toType) || - isExplicitlyConvertibleTo(fromType, toType, dc)) { - assert(extraFromOptionals > 0 && "No extra 'from' optionals?"); - - // FIXME: Add a Fix-It, when the caller provides us with enough information. - if (!suppressDiagnostics) { - bool isBridged = - !fromType->isEqual(toType) && !isConvertibleTo(fromType, toType, dc); + if (extraFromOptionals > 0) { + switch (typeCheckCheckedCast(fromType, toType, + CheckedCastContextKind::None, dc, + SourceLoc(), nullptr, SourceRange())) { + case CheckedCastKind::Coercion: + case CheckedCastKind::BridgingCoercion: { + // FIXME: Add a Fix-It, when the caller provides us with enough + // information. + if (!suppressDiagnostics) { + bool isBridged = + !fromType->isEqual(toType) && !isConvertibleTo(fromType, toType, dc); - switch (contextKind) { - case CheckedCastContextKind::None: - llvm_unreachable("suppressing diagnostics"); + switch (contextKind) { + case CheckedCastContextKind::None: + llvm_unreachable("suppressing diagnostics"); - case CheckedCastContextKind::ForcedCast: { - std::string extraFromOptionalsStr(extraFromOptionals, '!'); - auto diag = diagnose(diagLoc, diag::downcast_same_type, - origFromType, origToType, - extraFromOptionalsStr, - isBridged); - diag.highlight(diagFromRange); - diag.highlight(diagToRange); - - /// Add the '!''s needed to adjust the type. - diag.fixItInsertAfter(diagFromRange.End, - std::string(extraFromOptionals, '!')); - if (isBridged) { - // If it's bridged, we still need the 'as' to perform the bridging. - diag.fixItReplaceChars(diagLoc, diagLoc.getAdvancedLocOrInvalid(3), - "as"); - } else { - // Otherwise, implicit conversions will handle it in most cases. - SourceLoc afterExprLoc = Lexer::getLocForEndOfToken(Context.SourceMgr, - diagFromRange.End); - - diag.fixItRemove(SourceRange(afterExprLoc, diagToRange.End)); - } - break; - } - - case CheckedCastContextKind::ConditionalCast: - // If we're only unwrapping a single optional, that optional value is - // effectively carried through to the underlying conversion, making this - // the moral equivalent of a map. Complain that one can do this with - // 'as' more effectively. - if (extraFromOptionals == 1) { - // A single optional is carried through. It's better to use 'as' to - // the appropriate optional type. - auto diag = diagnose(diagLoc, diag::conditional_downcast_same_type, + case CheckedCastContextKind::ForcedCast: { + std::string extraFromOptionalsStr(extraFromOptionals, '!'); + auto diag = diagnose(diagLoc, diag::downcast_same_type, origFromType, origToType, - fromType->isEqual(toType) ? 0 - : isBridged ? 2 - : 1); + extraFromOptionalsStr, + isBridged); diag.highlight(diagFromRange); diag.highlight(diagToRange); + /// Add the '!''s needed to adjust the type. + diag.fixItInsertAfter(diagFromRange.End, + std::string(extraFromOptionals, '!')); if (isBridged) { - // For a bridged cast, replace the 'as?' with 'as'. + // If it's bridged, we still need the 'as' to perform the bridging. diag.fixItReplaceChars(diagLoc, diagLoc.getAdvancedLocOrInvalid(3), "as"); - - // Make sure we'll cast to the appropriately-optional type by adding - // the '?'. - // FIXME: Parenthesize! - diag.fixItInsertAfter(diagToRange.End, "?"); } else { - // Just remove the cast; implicit conversions will handle it. - SourceLoc afterExprLoc = - Lexer::getLocForEndOfToken(Context.SourceMgr, diagFromRange.End); + // Otherwise, implicit conversions will handle it in most cases. + SourceLoc afterExprLoc = Lexer::getLocForEndOfToken(Context.SourceMgr, + diagFromRange.End); - if (afterExprLoc.isValid() && diagToRange.isValid()) - diag.fixItRemove(SourceRange(afterExprLoc, diagToRange.End)); + diag.fixItRemove(SourceRange(afterExprLoc, diagToRange.End)); } + break; } - // If there is more than one extra optional, don't do anything: this - // conditional cast is trying to unwrap some levels of optional; - // let the runtime handle it. - break; + case CheckedCastContextKind::ConditionalCast: + // If we're only unwrapping a single optional, that optional value is + // effectively carried through to the underlying conversion, making this + // the moral equivalent of a map. Complain that one can do this with + // 'as' more effectively. + if (extraFromOptionals == 1) { + // A single optional is carried through. It's better to use 'as' to + // the appropriate optional type. + auto diag = diagnose(diagLoc, diag::conditional_downcast_same_type, + origFromType, origToType, + fromType->isEqual(toType) ? 0 + : isBridged ? 2 + : 1); + diag.highlight(diagFromRange); + diag.highlight(diagToRange); - case CheckedCastContextKind::IsExpr: - // If we're only unwrapping a single optional, we could have just - // checked for 'nil'. - if (extraFromOptionals == 1) { - auto diag = diagnose(diagLoc, diag::is_expr_same_type, - origFromType, origToType); - diag.highlight(diagFromRange); - diag.highlight(diagToRange); + if (isBridged) { + // For a bridged cast, replace the 'as?' with 'as'. + diag.fixItReplaceChars(diagLoc, diagLoc.getAdvancedLocOrInvalid(3), + "as"); - diag.fixItReplace(SourceRange(diagLoc, diagToRange.End), "!= nil"); + // Make sure we'll cast to the appropriately-optional type by adding + // the '?'. + // FIXME: Parenthesize! + diag.fixItInsertAfter(diagToRange.End, "?"); + } else { + // Just remove the cast; implicit conversions will handle it. + SourceLoc afterExprLoc = + Lexer::getLocForEndOfToken(Context.SourceMgr, diagFromRange.End); - // Add parentheses if needed. - if (!fromExpr->canAppendCallParentheses()) { - diag.fixItInsert(fromExpr->getStartLoc(), "("); - diag.fixItInsertAfter(fromExpr->getEndLoc(), ")"); + if (afterExprLoc.isValid() && diagToRange.isValid()) + diag.fixItRemove(SourceRange(afterExprLoc, diagToRange.End)); + } } + + // If there is more than one extra optional, don't do anything: this + // conditional cast is trying to unwrap some levels of optional; + // let the runtime handle it. + break; + + case CheckedCastContextKind::IsExpr: + // If we're only unwrapping a single optional, we could have just + // checked for 'nil'. + if (extraFromOptionals == 1) { + auto diag = diagnose(diagLoc, diag::is_expr_same_type, + origFromType, origToType); + diag.highlight(diagFromRange); + diag.highlight(diagToRange); + + diag.fixItReplace(SourceRange(diagLoc, diagToRange.End), "!= nil"); + + // Add parentheses if needed. + if (!fromExpr->canAppendCallParentheses()) { + diag.fixItInsert(fromExpr->getStartLoc(), "("); + diag.fixItInsertAfter(fromExpr->getEndLoc(), ")"); + } + } + + // If there is more than one extra optional, don't do anything: this + // is performing a deeper check that the runtime will handle. + break; + + case CheckedCastContextKind::IsPattern: + case CheckedCastContextKind::EnumElementPattern: + // Note: Don't diagnose these, because the code is testing whether + // the optionals can be unwrapped. + break; } - - // If there is more than one extra optional, don't do anything: this - // is performing a deeper check that the runtime will handle. - break; - - case CheckedCastContextKind::IsPattern: - case CheckedCastContextKind::EnumElementPattern: - // Note: Don't diagnose these, because the code is testing whether - // the optionals can be unwrapped. - break; } + + // Treat this as a value cast so we preserve the semantics. + return CheckedCastKind::ValueCast; } - // Treat this as a value cast so we preserve the semantics. - return CheckedCastKind::ValueCast; + case CheckedCastKind::Swift3BridgingDowncast: + case CheckedCastKind::ArrayDowncast: + case CheckedCastKind::DictionaryDowncast: + case CheckedCastKind::SetDowncast: + case CheckedCastKind::ValueCast: + break; + + case CheckedCastKind::Unresolved: + return failed(); + } } // Check for casts between specific concrete types that cannot succeed. @@ -3079,8 +3103,11 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType, case CheckedCastKind::Coercion: return CheckedCastKind::Coercion; - case CheckedCastKind::BridgingCast: - return CheckedCastKind::BridgingCast; + case CheckedCastKind::BridgingCoercion: + return CheckedCastKind::BridgingCoercion; + + case CheckedCastKind::Swift3BridgingDowncast: + return CheckedCastKind::Swift3BridgingDowncast; case CheckedCastKind::ArrayDowncast: case CheckedCastKind::DictionaryDowncast: @@ -3097,7 +3124,8 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType, if (auto toKeyValue = cs.isDictionaryType(toType)) { if (auto fromKeyValue = cs.isDictionaryType(fromType)) { bool hasCoercion = false; - bool hasBridgingConversion = false; + enum { NoBridging, BridgingCoercion, Swift3BridgingDowncast } + hasBridgingConversion = NoBridging; bool hasCast = false; switch (typeCheckCheckedCast(fromKeyValue->first, toKeyValue->first, CheckedCastContextKind::None, dc, @@ -3106,8 +3134,13 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType, hasCoercion = true; break; - case CheckedCastKind::BridgingCast: - hasBridgingConversion = true; + case CheckedCastKind::BridgingCoercion: + hasBridgingConversion = std::max(hasBridgingConversion, + BridgingCoercion); + break; + case CheckedCastKind::Swift3BridgingDowncast: + hasBridgingConversion = std::max(hasBridgingConversion, + Swift3BridgingDowncast); break; case CheckedCastKind::ArrayDowncast: @@ -3128,8 +3161,13 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType, hasCoercion = true; break; - case CheckedCastKind::BridgingCast: - hasBridgingConversion = true; + case CheckedCastKind::BridgingCoercion: + hasBridgingConversion = std::max(hasBridgingConversion, + BridgingCoercion); + break; + case CheckedCastKind::Swift3BridgingDowncast: + hasBridgingConversion = std::max(hasBridgingConversion, + Swift3BridgingDowncast); break; case CheckedCastKind::ArrayDowncast: @@ -3144,7 +3182,14 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType, } if (hasCast) return CheckedCastKind::DictionaryDowncast; - if (hasBridgingConversion) return CheckedCastKind::BridgingCast; + switch (hasBridgingConversion) { + case NoBridging: + break; + case BridgingCoercion: + return CheckedCastKind::BridgingCoercion; + case Swift3BridgingDowncast: + return CheckedCastKind::Swift3BridgingDowncast; + } assert(hasCoercion && "Not a coercion?"); return CheckedCastKind::Coercion; } @@ -3158,8 +3203,11 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType, case CheckedCastKind::Coercion: return CheckedCastKind::Coercion; - case CheckedCastKind::BridgingCast: - return CheckedCastKind::BridgingCast; + case CheckedCastKind::BridgingCoercion: + return CheckedCastKind::BridgingCoercion; + + case CheckedCastKind::Swift3BridgingDowncast: + return CheckedCastKind::Swift3BridgingDowncast; case CheckedCastKind::ArrayDowncast: case CheckedCastKind::DictionaryDowncast: @@ -3173,6 +3221,19 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType, } } + // We accepted `NSNumber`-to-`*Int*` and `NSValue`-to-struct as bridging + // conversions in Swift 3. For compatibility, we need to distinguish these + // cases so we can accept them as coercions (with a warning). + if (Context.LangOpts.isSwiftVersion3() && extraFromOptionals == 0) { + // Do the check for a bridging conversion now that we deferred above. + if (isObjCBridgedTo(fromType, toType, dc, &unwrappedIUO) && !unwrappedIUO) { + if (isObjCClassWithMultipleSwiftBridgedTypes(fromType, dc)) { + return CheckedCastKind::Swift3BridgingDowncast; + } + return CheckedCastKind::BridgingCoercion; + } + } + // If we can bridge through an Objective-C class, do so. if (Type bridgedToClass = getDynamicBridgedThroughObjCClass(dc, fromType, toType)) { @@ -3180,7 +3241,8 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType, CheckedCastContextKind::None, dc, SourceLoc(), nullptr, SourceRange())) { case CheckedCastKind::ArrayDowncast: - case CheckedCastKind::BridgingCast: + case CheckedCastKind::BridgingCoercion: + case CheckedCastKind::Swift3BridgingDowncast: case CheckedCastKind::Coercion: case CheckedCastKind::DictionaryDowncast: case CheckedCastKind::SetDowncast: @@ -3199,7 +3261,8 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType, CheckedCastContextKind::None, dc, SourceLoc(), nullptr, SourceRange())) { case CheckedCastKind::ArrayDowncast: - case CheckedCastKind::BridgingCast: + case CheckedCastKind::BridgingCoercion: + case CheckedCastKind::Swift3BridgingDowncast: case CheckedCastKind::Coercion: case CheckedCastKind::DictionaryDowncast: case CheckedCastKind::SetDowncast: diff --git a/lib/Sema/TypeCheckPattern.cpp b/lib/Sema/TypeCheckPattern.cpp index b3e77300e54..129461a4f12 100644 --- a/lib/Sema/TypeCheckPattern.cpp +++ b/lib/Sema/TypeCheckPattern.cpp @@ -1304,7 +1304,7 @@ bool TypeChecker::coercePatternToType(Pattern *&P, DeclContext *dc, Type type, case CheckedCastKind::Unresolved: return true; case CheckedCastKind::Coercion: - case CheckedCastKind::BridgingCast: + case CheckedCastKind::BridgingCoercion: // If this is an 'as' pattern coercing between two different types, then // it is "useful" because it is providing a different type to the // sub-pattern. If this is an 'is' pattern or an 'as' pattern where the @@ -1326,6 +1326,7 @@ bool TypeChecker::coercePatternToType(Pattern *&P, DeclContext *dc, Type type, subOptions|TR_FromNonInferredPattern); case CheckedCastKind::ValueCast: + case CheckedCastKind::Swift3BridgingDowncast: IP->setCastKind(castKind); break; } diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 5a573bc53bd..61d744af8c1 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -156,12 +156,39 @@ Type TypeChecker::getNSObjectType(DeclContext *dc) { } Type TypeChecker::getNSErrorType(DeclContext *dc) { - return getObjectiveCNominalType(*this, NSObjectType, Context.Id_Foundation, + return getObjectiveCNominalType(*this, NSErrorType, Context.Id_Foundation, Context.getSwiftId( KnownFoundationEntity::NSError), dc); } +Type TypeChecker::getNSNumberType(DeclContext *dc) { + return getObjectiveCNominalType(*this, NSNumberType, Context.Id_Foundation, + Context.getSwiftId( + KnownFoundationEntity::NSNumber), + dc); +} + +Type TypeChecker::getNSValueType(DeclContext *dc) { + return getObjectiveCNominalType(*this, NSValueType, Context.Id_Foundation, + Context.getSwiftId( + KnownFoundationEntity::NSValue), + dc); +} + +bool TypeChecker::isObjCClassWithMultipleSwiftBridgedTypes(Type t, + DeclContext *dc) { + if (auto nsNumber = getNSNumberType(dc)) { + if (t->isEqual(nsNumber)) + return true; + } + if (auto nsValue = getNSValueType(dc)) { + if (t->isEqual(nsValue)) + return true; + } + return false; +} + Type TypeChecker::getObjCSelectorType(DeclContext *dc) { return getObjectiveCNominalType(*this, ObjCSelectorType, Context.Id_ObjectiveC, diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index faae9b738c2..ef8ecb61dcf 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -645,6 +645,8 @@ private: Type UInt8Type; Type NSObjectType; Type NSErrorType; + Type NSNumberType; + Type NSValueType; Type ObjCSelectorType; Type ExceptionType; @@ -733,8 +735,13 @@ public: Type getUInt8Type(DeclContext *dc); Type getNSObjectType(DeclContext *dc); Type getNSErrorType(DeclContext *dc); + Type getNSNumberType(DeclContext *dc); + Type getNSValueType(DeclContext *dc); Type getObjCSelectorType(DeclContext *dc); Type getExceptionType(DeclContext *dc, SourceLoc loc); + + /// True if `t` is an ObjC class that multiple Swift value types bridge into. + bool isObjCClassWithMultipleSwiftBridgedTypes(Type t, DeclContext *dc); /// \brief Try to resolve an IdentTypeRepr, returning either the referenced /// Type or an ErrorType in case of error. diff --git a/test/Compatibility/bridging-nsnumber-and-nsvalue.swift.gyb b/test/Compatibility/bridging-nsnumber-and-nsvalue.swift.gyb new file mode 100644 index 00000000000..23781f7c961 --- /dev/null +++ b/test/Compatibility/bridging-nsnumber-and-nsvalue.swift.gyb @@ -0,0 +1,130 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: %gyb %s -o %t/bridging-nsnumber-and-nsvalue.swift +// RUN: %target-swift-frontend -typecheck -verify %t/bridging-nsnumber-and-nsvalue.swift -swift-version 3 + +// REQUIRES: objc_interop + +// Swift 3.0 accepted "nsNumber as Int" or "nsValue as NSRect" as if they were +// safe coercions, when in fact they may fail if the object doesn't represent +// the right Swift type. + +import Foundation +import CoreGraphics + +%{ +coercionTypes = { + 'NSNumber': [ + 'Int', + 'UInt', + 'Int64', + 'UInt64', + 'Int32', + 'UInt32', + 'Int16', + 'UInt16', + 'Int8', + 'UInt8', + 'Float', + 'Double', + 'CGFloat', + ], + 'NSValue': [ + 'CGRect', + 'CGPoint', + 'CGSize', + 'NSRange', + ], +} +}% + +// For testing purposes, make everything Hashable. Don't do this at home +extension Equatable { + public static func ==(x: Self, y: Self) -> Bool { + fatalError("hella cray") + } +} +extension Hashable { public var hashValue: Int { fatalError("trill hiphy") } } +extension NSRange: Hashable {} +extension CGSize: Hashable {} +extension CGPoint: Hashable {} +extension CGRect: Hashable {} + +% for ObjectType, ValueTypes in coercionTypes.items(): +func bridgeNSNumberBackToSpecificType(object: ${ObjectType}, + optional: ${ObjectType}?, + array: [${ObjectType}], + dictKeys: [${ObjectType}: Any], + dictValues: [AnyHashable: ${ObjectType}], + dictBoth: [${ObjectType}: ${ObjectType}], + set: Set<${ObjectType}>) { +% for Type in ValueTypes: + _ = object as ${Type} // expected-warning{{use 'as!'}} + _ = object is ${Type} + _ = object as? ${Type} + _ = object as! ${Type} + + _ = optional as ${Type}? // expected-warning{{use 'as!'}} + _ = optional is ${Type}? + _ = optional as? ${Type}? + _ = optional as! ${Type}? + + // NB: This remains an error, since optional-to-nonoptional requires a + // nil check + _ = optional as ${Type} // expected-error{{use 'as!'}} + _ = optional is ${Type} + _ = optional as? ${Type} + _ = optional as! ${Type} + + _ = array as [${Type}] // expected-warning{{use 'as!'}} + _ = array is [${Type}] + _ = array as? [${Type}] + _ = array as! [${Type}] + + _ = dictKeys as [${Type}: Any] // expected-warning{{use 'as!'}} + _ = dictKeys is [${Type}: Any] + _ = dictKeys as? [${Type}: Any] + _ = dictKeys as! [${Type}: Any] + + _ = dictKeys as [${Type}: AnyObject] // expected-warning{{use 'as!'}} + _ = dictKeys is [${Type}: AnyObject] + _ = dictKeys as? [${Type}: AnyObject] + _ = dictKeys as! [${Type}: AnyObject] + + _ = dictValues as [AnyHashable: ${Type}] // expected-warning{{use 'as!'}} + _ = dictValues is [AnyHashable: ${Type}] + _ = dictValues as? [AnyHashable: ${Type}] + _ = dictValues as! [AnyHashable: ${Type}] + + _ = dictValues as [NSObject: ${Type}] // expected-warning{{use 'as!'}} + _ = dictValues is [NSObject: ${Type}] + _ = dictValues as? [NSObject: ${Type}] + _ = dictValues as! [NSObject: ${Type}] + + _ = dictBoth as [${ObjectType}: ${Type}] // expected-warning{{use 'as!'}} + _ = dictBoth is [${ObjectType}: ${Type}] + _ = dictBoth as? [${ObjectType}: ${Type}] + _ = dictBoth as! [${ObjectType}: ${Type}] + + _ = dictBoth as [${Type}: ${ObjectType}] // expected-warning{{use 'as!'}} + _ = dictBoth is [${Type}: ${ObjectType}] + _ = dictBoth as? [${Type}: ${ObjectType}] + _ = dictBoth as! [${Type}: ${ObjectType}] + + _ = dictBoth as [${Type}: ${Type}] // expected-warning{{use 'as!'}} + _ = dictBoth is [${Type}: ${Type}] + _ = dictBoth as? [${Type}: ${Type}] + _ = dictBoth as! [${Type}: ${Type}] + + _ = set as Set<${Type}> // expected-warning{{use 'as!'}} + _ = set is Set<${Type}> + _ = set as? Set<${Type}> + _ = set as! Set<${Type}> +% end + + _ = object is String // expected-warning{{always fails}} + _ = [object] is String // expected-warning{{always fails}} + _ = object as? String // expected-warning{{always fails}} + _ = object as! String // expected-warning{{always fails}} +} +% end diff --git a/test/Constraints/bridging-nsnumber-and-nsvalue.swift.gyb b/test/Constraints/bridging-nsnumber-and-nsvalue.swift.gyb new file mode 100644 index 00000000000..561aa857ec6 --- /dev/null +++ b/test/Constraints/bridging-nsnumber-and-nsvalue.swift.gyb @@ -0,0 +1,123 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: %gyb %s -o %t/bridging-nsnumber-and-nsvalue.swift +// RUN: %target-swift-frontend -typecheck -verify %t/bridging-nsnumber-and-nsvalue.swift -swift-version 4 + +// REQUIRES: objc_interop + +import Foundation +import CoreGraphics + +%{ +coercionTypes = { + 'NSNumber': [ + 'Int', + 'UInt', + 'Int64', + 'UInt64', + 'Int32', + 'UInt32', + 'Int16', + 'UInt16', + 'Int8', + 'UInt8', + 'Float', + 'Double', + 'CGFloat', + ], + 'NSValue': [ + 'CGRect', + 'CGPoint', + 'CGSize', + 'NSRange', + ], +} +}% + +// For testing purposes, make everything Hashable. Don't do this at home +extension Equatable { + public static func ==(x: Self, y: Self) -> Bool { + fatalError("hella cray") + } +} +extension Hashable { public var hashValue: Int { fatalError("trill hiphy") } } +extension NSRange: Hashable {} +extension CGSize: Hashable {} +extension CGPoint: Hashable {} +extension CGRect: Hashable {} + +% for ObjectType, ValueTypes in coercionTypes.items(): +func bridgeNSNumberBackToSpecificType(object: ${ObjectType}, + optional: ${ObjectType}?, + array: [${ObjectType}], + dictKeys: [${ObjectType}: Any], + dictValues: [AnyHashable: ${ObjectType}], + dictBoth: [${ObjectType}: ${ObjectType}], + set: Set<${ObjectType}>) { +% for Type in ValueTypes: + _ = object as ${Type} // expected-error{{use 'as!'}} + _ = object is ${Type} + _ = object as? ${Type} + _ = object as! ${Type} + + _ = optional as ${Type}? // expected-error{{use 'as!'}} + _ = optional is ${Type}? + _ = optional as? ${Type}? + _ = optional as! ${Type}? + + _ = optional as ${Type} // expected-error{{use 'as!'}} + _ = optional is ${Type} + _ = optional as? ${Type} + _ = optional as! ${Type} + + _ = array as [${Type}] // expected-error{{use 'as!'}} + _ = array is [${Type}] + _ = array as? [${Type}] + _ = array as! [${Type}] + + _ = dictKeys as [${Type}: Any] // expected-error{{use 'as!'}} + _ = dictKeys is [${Type}: Any] + _ = dictKeys as? [${Type}: Any] + _ = dictKeys as! [${Type}: Any] + + _ = dictKeys as [${Type}: AnyObject] // expected-error{{use 'as!'}} + _ = dictKeys is [${Type}: AnyObject] + _ = dictKeys as? [${Type}: AnyObject] + _ = dictKeys as! [${Type}: AnyObject] + + _ = dictValues as [AnyHashable: ${Type}] // expected-error{{use 'as!'}} + _ = dictValues is [AnyHashable: ${Type}] + _ = dictValues as? [AnyHashable: ${Type}] + _ = dictValues as! [AnyHashable: ${Type}] + + _ = dictValues as [NSObject: ${Type}] // expected-error{{use 'as!'}} + _ = dictValues is [NSObject: ${Type}] + _ = dictValues as? [NSObject: ${Type}] + _ = dictValues as! [NSObject: ${Type}] + + _ = dictBoth as [${ObjectType}: ${Type}] // expected-error{{use 'as!'}} + _ = dictBoth is [${ObjectType}: ${Type}] + _ = dictBoth as? [${ObjectType}: ${Type}] + _ = dictBoth as! [${ObjectType}: ${Type}] + + _ = dictBoth as [${Type}: ${ObjectType}] // expected-error{{use 'as!'}} + _ = dictBoth is [${Type}: ${ObjectType}] + _ = dictBoth as? [${Type}: ${ObjectType}] + _ = dictBoth as! [${Type}: ${ObjectType}] + + _ = dictBoth as [${Type}: ${Type}] // expected-error{{use 'as!'}} + _ = dictBoth is [${Type}: ${Type}] + _ = dictBoth as? [${Type}: ${Type}] + _ = dictBoth as! [${Type}: ${Type}] + + _ = set as Set<${Type}> // expected-error{{use 'as!'}} + _ = set is Set<${Type}> + _ = set as? Set<${Type}> + _ = set as! Set<${Type}> +% end + + _ = object is String // expected-warning{{always fails}} + _ = object as? String // expected-warning{{always fails}} + _ = object as! String // expected-warning{{always fails}} +} +% end diff --git a/test/Constraints/casts_objc.swift b/test/Constraints/casts_objc.swift index 2414641e364..e790be363e9 100644 --- a/test/Constraints/casts_objc.swift +++ b/test/Constraints/casts_objc.swift @@ -48,49 +48,43 @@ let _: NSString! = unsafeDowncast(r22507759) // expected-error {{generic parame // rdar://problem/29496775 / SR-3319 func sr3319(f: CGFloat, n: NSNumber) { let _ = [f].map { $0 as NSNumber } - let _ = [n].map { $0 as CGFloat } + let _ = [n].map { $0 as! CGFloat } } func alwaysSucceedingConditionalCasts(f: CGFloat, n: NSNumber) { let _ = f as? NSNumber // expected-warning{{conditional cast from 'CGFloat' to 'NSNumber' always succeeds}} - let _ = n as? CGFloat // expected-warning{{conditional cast from 'NSNumber' to 'CGFloat' always succeeds}} + let _ = n as? CGFloat } func optionalityReducingCasts(f: CGFloat?, n: NSNumber?) { let _ = f as? NSNumber // expected-warning{{conditional downcast from 'CGFloat?' to 'NSNumber' is a bridging conversion; did you mean to use 'as'?}} let _ = f as! NSNumber // expected-warning{{forced cast from 'CGFloat?' to 'NSNumber' only unwraps and bridges; did you mean to use '!' with 'as'?}} - let _ = n as? CGFloat // expected-warning{{conditional downcast from 'NSNumber?' to 'CGFloat' is a bridging conversion; did you mean to use 'as'?}} - let _ = n as! CGFloat // expected-warning{{forced cast from 'NSNumber?' to 'CGFloat' only unwraps and bridges; did you mean to use '!' with 'as'?}} + let _ = n as? CGFloat + let _ = n as! CGFloat } func optionalityMatchingCasts(f: CGFloat?, n: NSNumber?) { let _ = f as NSNumber? let _ = f as? NSNumber? // expected-warning{{conditional cast from 'CGFloat?' to 'NSNumber?' always succeeds}} let _ = f as! NSNumber? // expected-warning{{forced cast from 'CGFloat?' to 'NSNumber?' always succeeds; did you mean to use 'as'?}}{{13-16=as}} - let _ = n as CGFloat? - let _ = n as? CGFloat? // expected-warning{{conditional cast from 'NSNumber?' to 'CGFloat?' always succeeds}} - let _ = n as! CGFloat? // expected-warning{{forced cast from 'NSNumber?' to 'CGFloat?' always succeeds; did you mean to use 'as'?}}{{13-16=as}} + let _ = n as? CGFloat? + let _ = n as! CGFloat? } func optionalityMatchingCastsIUO(f: CGFloat?!, n: NSNumber?!) { let _ = f as NSNumber? let _ = f as? NSNumber? // expected-warning{{conditional downcast from 'CGFloat?!' to 'NSNumber?' is a bridging conversion; did you mean to use 'as'?}} let _ = f as! NSNumber? // expected-warning{{forced cast from 'CGFloat?!' to 'NSNumber?' only unwraps and bridges; did you mean to use '!' with 'as'?}} - let _ = n as CGFloat? - let _ = n as? CGFloat? // expected-warning{{conditional downcast from 'NSNumber?!' to 'CGFloat?' is a bridging conversion; did you mean to use 'as'?}} - let _ = n as! CGFloat? // expected-warning{{forced cast from 'NSNumber?!' to 'CGFloat?' only unwraps and bridges; did you mean to use '!' with 'as'?}} + let _ = n as? CGFloat? + let _ = n as! CGFloat? } func optionalityMismatchingCasts(f: CGFloat, n: NSNumber, fooo: CGFloat???, nooo: NSNumber???) { _ = f as NSNumber? _ = f as NSNumber?? - _ = n as CGFloat? - _ = n as CGFloat?? let _ = fooo as NSNumber?? // expected-error{{'CGFloat???' is not convertible to 'NSNumber??'; did you mean to use 'as!' to force downcast?}} let _ = fooo as NSNumber???? // okay: injects extra optionals - let _ = nooo as CGFloat?? // expected-error{{'NSNumber???' is not convertible to 'CGFloat??'; did you mean to use 'as!' to force downcast?}} - let _ = nooo as CGFloat???? // okay: injects extra optionals } func anyObjectCasts(xo: [Int]?, xooo: [Int]???, x: [Int]) {