Skip to content

Sema: NSValue-to-value-type casts are failable and should be checked. #6774

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
23 changes: 16 additions & 7 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -75,18 +77,25 @@ 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,
// A downcast from a dictionary type to another dictionary type.
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 {
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownFoundationEntities.def
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 4 additions & 2 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
2 changes: 1 addition & 1 deletion lib/AST/Pattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<IsPattern>(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
Expand Down
45 changes: 40 additions & 5 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -2988,6 +2988,7 @@ namespace {
}
expr->setCastKind(castKind);
break;
case CheckedCastKind::Swift3BridgingDowncast:
case CheckedCastKind::ArrayDowncast:
case CheckedCastKind::DictionaryDowncast:
case CheckedCastKind::SetDowncast:
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -3329,7 +3344,7 @@ namespace {
case CheckedCastKind::Unresolved:
return nullptr;
case CheckedCastKind::Coercion:
case CheckedCastKind::BridgingCast: {
case CheckedCastKind::BridgingCoercion: {
if (SuppressDiagnostics)
return nullptr;

Expand All @@ -3355,6 +3370,7 @@ namespace {
}

// Valid casts.
case CheckedCastKind::Swift3BridgingDowncast:
case CheckedCastKind::ArrayDowncast:
case CheckedCastKind::DictionaryDowncast:
case CheckedCastKind::SetDowncast:
Expand Down Expand Up @@ -3399,7 +3415,7 @@ namespace {
break;

case CheckedCastKind::Coercion:
case CheckedCastKind::BridgingCast: {
case CheckedCastKind::BridgingCoercion: {
if (SuppressDiagnostics)
return nullptr;

Expand Down Expand Up @@ -3427,6 +3443,7 @@ namespace {
}

// Valid casts.
case CheckedCastKind::Swift3BridgingDowncast:
case CheckedCastKind::ArrayDowncast:
case CheckedCastKind::DictionaryDowncast:
case CheckedCastKind::SetDowncast:
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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! ") +
Expand Down
16 changes: 13 additions & 3 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2414,15 +2414,15 @@ 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
// solve this yet.
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
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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.
Expand Down
Loading