Skip to content

[SR-13899] [Sema] Adjustments on coerce to checked cast diagnostics #34883

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
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
10 changes: 7 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1108,9 +1108,13 @@ ERROR(missing_unwrap_optional_try,none,
"value of optional type %0 not unwrapped; did you mean to use 'try!' "
"or chain with '?'?",
(Type))
ERROR(missing_forced_downcast,none,
"%0 is not convertible to %1; "
"did you mean to use 'as!' to force downcast?", (Type, Type))
ERROR(cannot_coerce_to_type, none,
"%0 is not convertible to %1", (Type, Type))
NOTE(missing_forced_downcast, none,
"did you mean to use 'as!' to force downcast?", ())
NOTE(missing_optional_downcast, none,
"did you mean to use 'as?' to conditionally downcast?", ())

WARNING(coercion_may_fail_warning,none,
"coercion from %0 to %1 may fail; use 'as?' or 'as!' instead",
(Type, Type))
Expand Down
15 changes: 10 additions & 5 deletions include/swift/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -1728,20 +1728,25 @@ class UseRawValue final : public ConstraintFix {
Type expectedType, ConstraintLocator *locator);
};

/// Replace a coercion ('as') with a forced checked cast ('as!').
/// Replace a coercion ('as') with runtime checked cast ('as!' or 'as?').
class CoerceToCheckedCast final : public ContextualMismatch {
CoerceToCheckedCast(ConstraintSystem &cs, Type fromType, Type toType,
ConstraintLocator *locator)
bool useConditionalCast, ConstraintLocator *locator)
: ContextualMismatch(cs, FixKind::CoerceToCheckedCast, fromType, toType,
locator) {}
locator),
UseConditionalCast(useConditionalCast) {}
bool UseConditionalCast = false;

public:
std::string getName() const override { return "as to as!"; }
std::string getName() const override {
return UseConditionalCast ? "as to as?" : "as to as!";
}

bool diagnose(const Solution &solution, bool asNote = false) const override;

static CoerceToCheckedCast *attempt(ConstraintSystem &cs, Type fromType,
Type toType, ConstraintLocator *locator);
Type toType, bool useConditionalCast,
ConstraintLocator *locator);
};

class RemoveInvalidCall final : public ConstraintFix {
Expand Down
34 changes: 26 additions & 8 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -964,20 +964,29 @@ bool NoEscapeFuncToTypeConversionFailure::diagnoseParameterUse() const {
return true;
}

ASTNode MissingForcedDowncastFailure::getAnchor() const {
ASTNode InvalidCoercionFailure::getAnchor() const {
auto anchor = FailureDiagnostic::getAnchor();
if (auto *assignExpr = getAsExpr<AssignExpr>(anchor))
return assignExpr->getSrc();
return anchor;
}

bool MissingForcedDowncastFailure::diagnoseAsError() {
bool InvalidCoercionFailure::diagnoseAsError() {
auto fromType = getFromType();
auto toType = getToType();

emitDiagnostic(diag::missing_forced_downcast, fromType, toType)
.highlight(getSourceRange())
.fixItReplace(getLoc(), "as!");
emitDiagnostic(diag::cannot_coerce_to_type, fromType, toType);

if (UseConditionalCast) {
emitDiagnostic(diag::missing_optional_downcast)
.highlight(getSourceRange())
.fixItReplace(getLoc(), "as?");
} else {
emitDiagnostic(diag::missing_forced_downcast)
.highlight(getSourceRange())
.fixItReplace(getLoc(), "as!");
}

return true;
}

Expand Down Expand Up @@ -1051,10 +1060,19 @@ bool MissingExplicitConversionFailure::diagnoseAsError() {
if (needsParensOutside)
insertAfter += ")";

auto diagID =
useAs ? diag::missing_explicit_conversion : diag::missing_forced_downcast;
auto diag = emitDiagnostic(diagID, fromType, toType);
auto diagnose = [&]() {
if (useAs) {
return emitDiagnostic(diag::missing_explicit_conversion, fromType,
toType);
} else {
// Emit error diagnostic.
emitDiagnostic(diag::cannot_coerce_to_type, fromType, toType);
// Emit and return note suggesting as! where the fix-it will be placed.
return emitDiagnostic(diag::missing_forced_downcast);
}
};

auto diag = diagnose();
if (!insertBefore.empty()) {
diag.fixItInsert(getSourceRange().Start, insertBefore);
}
Expand Down
13 changes: 8 additions & 5 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -1939,12 +1939,15 @@ class ArgumentMismatchFailure : public ContextualFailure {
bool diagnoseMisplacedMissingArgument() const;
};

/// Replace a coercion ('as') with a forced checked cast ('as!').
class MissingForcedDowncastFailure final : public ContextualFailure {
/// Replace a coercion ('as') with a runtime checked cast ('as!' or 'as?').
class InvalidCoercionFailure final : public ContextualFailure {
bool UseConditionalCast;

public:
MissingForcedDowncastFailure(const Solution &solution, Type fromType,
Type toType, ConstraintLocator *locator)
: ContextualFailure(solution, fromType, toType, locator) {}
InvalidCoercionFailure(const Solution &solution, Type fromType, Type toType,
bool useConditionalCast, ConstraintLocator *locator)
: ContextualFailure(solution, fromType, toType, locator),
UseConditionalCast(useConditionalCast) {}

ASTNode getAnchor() const override;

Expand Down
7 changes: 4 additions & 3 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,14 @@ TreatRValueAsLValue *TreatRValueAsLValue::create(ConstraintSystem &cs,

bool CoerceToCheckedCast::diagnose(const Solution &solution,
bool asNote) const {
MissingForcedDowncastFailure failure(solution, getFromType(), getToType(),
getLocator());
InvalidCoercionFailure failure(solution, getFromType(), getToType(),
UseConditionalCast, getLocator());
return failure.diagnose(asNote);
}

CoerceToCheckedCast *CoerceToCheckedCast::attempt(ConstraintSystem &cs,
Type fromType, Type toType,
bool useConditionalCast,
ConstraintLocator *locator) {
// If any of the types has a type variable, don't add the fix.
if (fromType->hasTypeVariable() || toType->hasTypeVariable())
Expand All @@ -159,7 +160,7 @@ CoerceToCheckedCast *CoerceToCheckedCast::attempt(ConstraintSystem &cs,
return nullptr;

return new (cs.getAllocator())
CoerceToCheckedCast(cs, fromType, toType, locator);
CoerceToCheckedCast(cs, fromType, toType, useConditionalCast, locator);
}

bool TreatArrayLiteralAsDictionary::diagnose(const Solution &solution,
Expand Down
27 changes: 22 additions & 5 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3551,9 +3551,26 @@ bool ConstraintSystem::repairFailures(
getConstraintLocator(coercion->getSubExpr())))
return true;

// Repair a coercion ('as') with a forced checked cast ('as!').
if (auto *coerceToCheckCastFix = CoerceToCheckedCast::attempt(
*this, lhs, rhs, getConstraintLocator(locator))) {
// If the result type of the coercion has an value to optional conversion
// we can instead suggest the conditional downcast as it is safer in
// situations like conditional binding.
auto useConditionalCast = llvm::any_of(
ConstraintRestrictions,
[&](std::tuple<Type, Type, ConversionRestrictionKind> restriction) {
ConversionRestrictionKind restrictionKind;
Type type1, type2;
std::tie(type1, type2, restrictionKind) = restriction;

if (restrictionKind != ConversionRestrictionKind::ValueToOptional)
return false;

return rhs->isEqual(type1);
});

// Repair a coercion ('as') with a runtime checked cast ('as!' or 'as?').
if (auto *coerceToCheckCastFix =
CoerceToCheckedCast::attempt(*this, lhs, rhs, useConditionalCast,
getConstraintLocator(locator))) {
conversionsOrFixes.push_back(coerceToCheckCastFix);
return true;
}
Expand Down Expand Up @@ -9560,8 +9577,8 @@ ConstraintSystem::simplifyRestrictedConstraintImpl(
loc->isLastElement<LocatorPathElt::ApplyArgToParam>() ||
loc->isForOptionalTry()) {
if (restriction == ConversionRestrictionKind::Superclass) {
if (auto *fix =
CoerceToCheckedCast::attempt(*this, fromType, toType, loc))
if (auto *fix = CoerceToCheckedCast::attempt(
*this, fromType, toType, /*useConditionalCast*/ false, loc))
return !recordFix(fix, impact);
}

Expand Down
3 changes: 2 additions & 1 deletion test/ClangImporter/Dispatch_test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ func test2(_ queue: DispatchQueue) {

// Make sure the dispatch types are actually distinct types!
let _ = queue as DispatchSource // expected-error {{cannot convert value of type 'DispatchQueue' to type 'DispatchSource' in coercion}}
let _ = base as DispatchSource // expected-error {{'NSObjectProtocol' is not convertible to 'DispatchSource'; did you mean to use 'as!' to force downcast?}}
let _ = base as DispatchSource // expected-error {{'NSObjectProtocol' is not convertible to 'DispatchSource'}}
// expected-note@-1 {{did you mean to use 'as!' to force downcast?}} {{16-18=as!}}
}

extension dispatch_queue_t {} // expected-error {{'dispatch_queue_t' is unavailable}}
Expand Down
3 changes: 2 additions & 1 deletion test/ClangImporter/Security_test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import Security

_ = kSecClass as CFString
_ = kSecClassGenericPassword as CFString
_ = kSecClassGenericPassword as CFDictionary // expected-error {{'CFString?' is not convertible to 'CFDictionary'}} {{30-32=as!}}
_ = kSecClassGenericPassword as CFDictionary // expected-error {{'CFString?' is not convertible to 'CFDictionary'}}
// expected-note@-1 {{did you mean to use 'as!' to force downcast?}} {{30-32=as!}}

func testIntegration() {
// Based on code in <rdar://problem/17162475>.
Expand Down
13 changes: 9 additions & 4 deletions test/ClangImporter/objc_bridging_custom.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,16 @@ func testExplicitConversion(objc: APPManufacturerInfo<NSString>,
swift: ManufacturerInfo<NSString>) {
// Bridging to Swift
let _ = objc as ManufacturerInfo<NSString>
let _ = objc as ManufacturerInfo<NSNumber> // expected-error{{'APPManufacturerInfo<NSString>' is not convertible to 'ManufacturerInfo<NSNumber>'; did you mean to use 'as!' to force downcast?}}
let _ = objc as ManufacturerInfo<NSObject> // expected-error{{'APPManufacturerInfo<NSString>' is not convertible to 'ManufacturerInfo<NSObject>'; did you mean to use 'as!' to force downcast?}}
let _ = objc as ManufacturerInfo<NSNumber> // expected-error{{'APPManufacturerInfo<NSString>' is not convertible to 'ManufacturerInfo<NSNumber>'}}
// expected-note@-1 {{did you mean to use 'as!' to force downcast?}} {{16-18=as!}}
let _ = objc as ManufacturerInfo<NSObject> // expected-error{{'APPManufacturerInfo<NSString>' is not convertible to 'ManufacturerInfo<NSObject>'}}
// expected-note@-1 {{did you mean to use 'as!' to force downcast?}} {{16-18=as!}}

// Bridging to Objective-C
let _ = swift as APPManufacturerInfo<NSString>
let _ = swift as APPManufacturerInfo<NSNumber> // expected-error{{'ManufacturerInfo<NSString>' is not convertible to 'APPManufacturerInfo<NSNumber>'; did you mean to use 'as!' to force downcast?}}
let _ = swift as APPManufacturerInfo<NSObject> // expected-error{{'ManufacturerInfo<NSString>' is not convertible to 'APPManufacturerInfo<NSObject>'; did you mean to use 'as!' to force downcast?}}
let _ = swift as APPManufacturerInfo<NSNumber> // expected-error{{'ManufacturerInfo<NSString>' is not convertible to 'APPManufacturerInfo<NSNumber>'}}
// expected-note@-1 {{did you mean to use 'as!' to force downcast?}} {{17-19=as!}}
let _ = swift as APPManufacturerInfo<NSObject> // expected-error{{'ManufacturerInfo<NSString>' is not convertible to 'APPManufacturerInfo<NSObject>'}}
// expected-note@-1 {{did you mean to use 'as!' to force downcast?}} {{17-19=as!}}

}
6 changes: 4 additions & 2 deletions test/ClangImporter/objc_parse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -540,10 +540,12 @@ func testStrangeSelectors(obj: StrangeSelectors) {

func testProtocolQualified(_ obj: CopyableNSObject, cell: CopyableSomeCell,
plainObj: NSObject, plainCell: SomeCell) {
_ = obj as NSObject // expected-error {{'CopyableNSObject' (aka 'NSCopying & NSObjectProtocol') is not convertible to 'NSObject'; did you mean to use 'as!' to force downcast?}} {{11-13=as!}}
_ = obj as NSObject // expected-error {{'CopyableNSObject' (aka 'NSCopying & NSObjectProtocol') is not convertible to 'NSObject'}}
// expected-note@-1 {{did you mean to use 'as!' to force downcast?}} {{11-13=as!}}
_ = obj as NSObjectProtocol
_ = obj as NSCopying
_ = obj as SomeCell // expected-error {{'CopyableNSObject' (aka 'NSCopying & NSObjectProtocol') is not convertible to 'SomeCell'; did you mean to use 'as!' to force downcast?}} {{11-13=as!}}
_ = obj as SomeCell // expected-error {{'CopyableNSObject' (aka 'NSCopying & NSObjectProtocol') is not convertible to 'SomeCell'}}
// expected-note@-1 {{did you mean to use 'as!' to force downcast?}} {{11-13=as!}}

_ = cell as NSObject
_ = cell as NSObjectProtocol
Expand Down
3 changes: 2 additions & 1 deletion test/Constraints/ErrorBridging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ var ns4 = compo as NSError
ns4 = compo // expected-error{{cannot assign value of type 'HairyError & Runcible' to type 'NSError'}}

let e1 = ns1 as? FooError
let e1fix = ns1 as FooError // expected-error{{did you mean to use 'as!'}} {{17-19=as!}}
let e1fix = ns1 as FooError // expected-error {{'NSError' is not convertible to 'FooError'}}
// expected-note@-1{{did you mean to use 'as!' to force downcast?}} {{17-19=as!}}

let esub = ns1 as Error
let esub2 = ns1 as? Error // expected-warning{{conditional cast from 'NSError' to 'Error' always succeeds}}
Expand Down
12 changes: 6 additions & 6 deletions test/Constraints/bridging-nsnumber-and-nsvalue.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@ func bridgeNSNumberBackToSpecificType(object: ${ObjectType},
dictBoth: [${ObjectType}: ${ObjectType}],
set: Set<${ObjectType}>) {
% for Type in ValueTypes:
_ = object as ${Type} // expected-error{{use 'as!'}}
_ = object as ${Type} // expected-error{{is not convertible to}} expected-note {{use 'as!'}}
_ = object is ${Type}
_ = object as? ${Type}
_ = object as! ${Type}

_ = optional as ${Type}? // expected-error{{use 'as!'}}
_ = optional as ${Type}? // expected-error{{is not convertible to}} expected-note {{use 'as!'}}
_ = optional is ${Type}?
_ = optional as? ${Type}?
_ = optional as! ${Type}?

_ = optional as ${Type} // expected-error{{use 'as!'}}
_ = optional as ${Type} // expected-error{{is not convertible to}} expected-note {{use 'as!'}}
_ = optional is ${Type}
_ = optional as? ${Type}
_ = optional as! ${Type}
Expand All @@ -82,7 +82,7 @@ func bridgeNSNumberBackToSpecificType(object: ${ObjectType},
_ = dictKeys as? [${Type}: Any]
_ = dictKeys as! [${Type}: Any]

_ = dictKeys as [${Type}: AnyObject] // expected-error{{use 'as!'}}
_ = dictKeys as [${Type}: AnyObject] // expected-error{{is not convertible to}} expected-note {{use 'as!'}}
_ = dictKeys is [${Type}: AnyObject]
_ = dictKeys as? [${Type}: AnyObject]
_ = dictKeys as! [${Type}: AnyObject]
Expand All @@ -92,7 +92,7 @@ func bridgeNSNumberBackToSpecificType(object: ${ObjectType},
_ = dictValues as? [AnyHashable: ${Type}]
_ = dictValues as! [AnyHashable: ${Type}]

_ = dictValues as [NSObject: ${Type}] // expected-error{{use 'as!'}}
_ = dictValues as [NSObject: ${Type}] // expected-error{{is not convertible to}} expected-note {{use 'as!'}}
_ = dictValues is [NSObject: ${Type}]
_ = dictValues as? [NSObject: ${Type}]
_ = dictValues as! [NSObject: ${Type}]
Expand All @@ -107,7 +107,7 @@ func bridgeNSNumberBackToSpecificType(object: ${ObjectType},
_ = dictBoth as? [${Type}: ${ObjectType}]
_ = dictBoth as! [${Type}: ${ObjectType}]

_ = dictBoth as [${Type}: ${Type}] // expected-error{{use 'as!'}}
_ = dictBoth as [${Type}: ${Type}] // expected-error{{is not convertible to}} expected-note {{use 'as!'}}
_ = dictBoth is [${Type}: ${Type}]
_ = dictBoth as? [${Type}: ${Type}]
_ = dictBoth as! [${Type}: ${Type}]
Expand Down
27 changes: 18 additions & 9 deletions test/Constraints/bridging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,24 @@ func arrayToNSArray() {
// NSArray -> Array
func nsArrayToArray(_ nsa: NSArray) {
var arr1: [AnyObject] = nsa // expected-error{{'NSArray' is not implicitly convertible to '[AnyObject]'; did you mean to use 'as' to explicitly convert?}} {{30-30= as [AnyObject]}}
var _: [BridgedClass] = nsa // expected-error{{'NSArray' is not convertible to '[BridgedClass]'}} {{30-30= as! [BridgedClass]}}
var _: [OtherClass] = nsa // expected-error{{'NSArray' is not convertible to '[OtherClass]'}} {{28-28= as! [OtherClass]}}
var _: [BridgedStruct] = nsa // expected-error{{'NSArray' is not convertible to '[BridgedStruct]'}} {{31-31= as! [BridgedStruct]}}
var _: [NotBridgedStruct] = nsa // expected-error{{use 'as!' to force downcast}}
var _: [BridgedClass] = nsa // expected-error{{'NSArray' is not convertible to '[BridgedClass]'}}
// expected-note@-1{{did you mean to use 'as!' to force downcast?}} {{30-30= as! [BridgedClass]}}
var _: [OtherClass] = nsa // expected-error{{'NSArray' is not convertible to '[OtherClass]'}}
// expected-note@-1{{did you mean to use 'as!' to force downcast?}} {{28-28= as! [OtherClass]}}
var _: [BridgedStruct] = nsa // expected-error{{'NSArray' is not convertible to '[BridgedStruct]'}}
// expected-note@-1{{did you mean to use 'as!' to force downcast?}} {{31-31= as! [BridgedStruct]}}
var _: [NotBridgedStruct] = nsa // expected-error{{'NSArray' is not convertible to '[NotBridgedStruct]'}}
// expected-note@-1{{did you mean to use 'as!' to force downcast?}} {{34-34= as! [NotBridgedStruct]}}

var _: [AnyObject] = nsa as [AnyObject]
var _: [BridgedClass] = nsa as [BridgedClass] // expected-error{{'NSArray' is not convertible to '[BridgedClass]'; did you mean to use 'as!' to force downcast?}} {{31-33=as!}}
var _: [OtherClass] = nsa as [OtherClass] // expected-error{{'NSArray' is not convertible to '[OtherClass]'; did you mean to use 'as!' to force downcast?}} {{29-31=as!}}
var _: [BridgedStruct] = nsa as [BridgedStruct] // expected-error{{'NSArray' is not convertible to '[BridgedStruct]'; did you mean to use 'as!' to force downcast?}} {{32-34=as!}}
var _: [NotBridgedStruct] = nsa as [NotBridgedStruct] // expected-error{{use 'as!' to force downcast}}
var _: [BridgedClass] = nsa as [BridgedClass] // expected-error{{'NSArray' is not convertible to '[BridgedClass]'}}
// expected-note@-1{{did you mean to use 'as!' to force downcast?}} {{31-33=as!}}
var _: [OtherClass] = nsa as [OtherClass] // expected-error{{'NSArray' is not convertible to '[OtherClass]'}}
// expected-note@-1{{did you mean to use 'as!' to force downcast?}} {{29-31=as!}}
var _: [BridgedStruct] = nsa as [BridgedStruct] // expected-error{{'NSArray' is not convertible to '[BridgedStruct]'}}
// expected-note@-1{{did you mean to use 'as!' to force downcast?}} {{32-34=as!}}
var _: [NotBridgedStruct] = nsa as [NotBridgedStruct] // expected-error{{'NSArray' is not convertible to '[NotBridgedStruct]'}}
// expected-note@-1{{did you mean to use 'as!' to force downcast?}} {{35-37=as!}}

var arr6: Array = nsa as Array
arr6 = arr1
Expand Down Expand Up @@ -211,7 +219,8 @@ func rdar18330319(_ s: String, d: [String : AnyObject]) {
func rdar19551164a(_ s: String, _ a: [String]) {}
func rdar19551164b(_ s: NSString, _ a: NSArray) {
rdar19551164a(s, a) // expected-error{{'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?}}{{18-18= as String}}
// expected-error@-1{{'NSArray' is not convertible to '[String]'; did you mean to use 'as!' to force downcast?}}{{21-21= as! [String]}}
// expected-error@-1{{'NSArray' is not convertible to '[String]'}}
// expected-note@-2 {{did you mean to use 'as!' to force downcast?}}{{21-21= as! [String]}}
}

// rdar://problem/19695671
Expand Down
Loading