mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Merge pull request #26577 from xedin/improve-force-downcast-diags
[Diagnostics] Make force downcast fix contextual and move it to `repa…
This commit is contained in:
@@ -921,8 +921,9 @@ bool MissingExplicitConversionFailure::diagnoseAsError() {
|
|||||||
if (auto *paren = dyn_cast<ParenExpr>(anchor))
|
if (auto *paren = dyn_cast<ParenExpr>(anchor))
|
||||||
anchor = paren->getSubExpr();
|
anchor = paren->getSubExpr();
|
||||||
|
|
||||||
auto fromType = getType(anchor)->getRValueType();
|
auto fromType = getFromType();
|
||||||
Type toType = resolveType(ConvertingTo);
|
Type toType = getToType();
|
||||||
|
|
||||||
if (!toType->hasTypeRepr())
|
if (!toType->hasTypeRepr())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -544,45 +544,6 @@ public:
|
|||||||
bool diagnoseAsError() override;
|
bool diagnoseAsError() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Diagnose failures related attempt to implicitly convert types which
|
|
||||||
/// do not support such implicit converstion.
|
|
||||||
/// "as" or "as!" has to be specified explicitly in cases like that.
|
|
||||||
class MissingExplicitConversionFailure final : public FailureDiagnostic {
|
|
||||||
Type ConvertingTo;
|
|
||||||
|
|
||||||
public:
|
|
||||||
MissingExplicitConversionFailure(Expr *expr, ConstraintSystem &cs,
|
|
||||||
ConstraintLocator *locator, Type toType)
|
|
||||||
: FailureDiagnostic(expr, cs, locator), ConvertingTo(toType) {}
|
|
||||||
|
|
||||||
bool diagnoseAsError() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool exprNeedsParensBeforeAddingAs(Expr *expr) {
|
|
||||||
auto *DC = getDC();
|
|
||||||
auto &TC = getTypeChecker();
|
|
||||||
|
|
||||||
auto asPG = TC.lookupPrecedenceGroup(
|
|
||||||
DC, DC->getASTContext().Id_CastingPrecedence, SourceLoc());
|
|
||||||
if (!asPG)
|
|
||||||
return true;
|
|
||||||
return exprNeedsParensInsideFollowingOperator(TC, DC, expr, asPG);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool exprNeedsParensAfterAddingAs(Expr *expr, Expr *rootExpr) {
|
|
||||||
auto *DC = getDC();
|
|
||||||
auto &TC = getTypeChecker();
|
|
||||||
|
|
||||||
auto asPG = TC.lookupPrecedenceGroup(
|
|
||||||
DC, DC->getASTContext().Id_CastingPrecedence, SourceLoc());
|
|
||||||
if (!asPG)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return exprNeedsParensOutsideFollowingOperator(TC, DC, expr, rootExpr,
|
|
||||||
asPG);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Diagnose failures related to attempting member access on optional base
|
/// Diagnose failures related to attempting member access on optional base
|
||||||
/// type without optional chaining or force-unwrapping it first.
|
/// type without optional chaining or force-unwrapping it first.
|
||||||
class MemberAccessOnOptionalBaseFailure final : public FailureDiagnostic {
|
class MemberAccessOnOptionalBaseFailure final : public FailureDiagnostic {
|
||||||
@@ -739,6 +700,44 @@ private:
|
|||||||
void tryComputedPropertyFixIts(Expr *expr) const;
|
void tryComputedPropertyFixIts(Expr *expr) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Diagnose failures related attempt to implicitly convert types which
|
||||||
|
/// do not support such implicit converstion.
|
||||||
|
/// "as" or "as!" has to be specified explicitly in cases like that.
|
||||||
|
class MissingExplicitConversionFailure final : public ContextualFailure {
|
||||||
|
public:
|
||||||
|
MissingExplicitConversionFailure(Expr *expr, ConstraintSystem &cs,
|
||||||
|
Type fromType, Type toType,
|
||||||
|
ConstraintLocator *locator)
|
||||||
|
: ContextualFailure(expr, cs, fromType, toType, locator) {}
|
||||||
|
|
||||||
|
bool diagnoseAsError() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool exprNeedsParensBeforeAddingAs(Expr *expr) {
|
||||||
|
auto *DC = getDC();
|
||||||
|
auto &TC = getTypeChecker();
|
||||||
|
|
||||||
|
auto asPG = TC.lookupPrecedenceGroup(
|
||||||
|
DC, DC->getASTContext().Id_CastingPrecedence, SourceLoc());
|
||||||
|
if (!asPG)
|
||||||
|
return true;
|
||||||
|
return exprNeedsParensInsideFollowingOperator(TC, DC, expr, asPG);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool exprNeedsParensAfterAddingAs(Expr *expr, Expr *rootExpr) {
|
||||||
|
auto *DC = getDC();
|
||||||
|
auto &TC = getTypeChecker();
|
||||||
|
|
||||||
|
auto asPG = TC.lookupPrecedenceGroup(
|
||||||
|
DC, DC->getASTContext().Id_CastingPrecedence, SourceLoc());
|
||||||
|
if (!asPG)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return exprNeedsParensOutsideFollowingOperator(TC, DC, expr, rootExpr,
|
||||||
|
asPG);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// Diagnose failures related to passing value of some type
|
/// Diagnose failures related to passing value of some type
|
||||||
/// to `inout` or pointer parameter, without explicitly specifying `&`.
|
/// to `inout` or pointer parameter, without explicitly specifying `&`.
|
||||||
class MissingAddressOfFailure final : public ContextualFailure {
|
class MissingAddressOfFailure final : public ContextualFailure {
|
||||||
|
|||||||
@@ -48,21 +48,24 @@ void ConstraintFix::dump() const {print(llvm::errs()); }
|
|||||||
|
|
||||||
std::string ForceDowncast::getName() const {
|
std::string ForceDowncast::getName() const {
|
||||||
llvm::SmallString<16> name;
|
llvm::SmallString<16> name;
|
||||||
name += "force downcast (as! ";
|
name += "force downcast (";
|
||||||
name += DowncastTo->getString();
|
name += getFromType()->getString();
|
||||||
|
name += " as! ";
|
||||||
|
name += getToType()->getString();
|
||||||
name += ")";
|
name += ")";
|
||||||
return name.c_str();
|
return name.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ForceDowncast::diagnose(Expr *expr, bool asNote) const {
|
bool ForceDowncast::diagnose(Expr *expr, bool asNote) const {
|
||||||
MissingExplicitConversionFailure failure(expr, getConstraintSystem(),
|
auto &cs = getConstraintSystem();
|
||||||
getLocator(), DowncastTo);
|
MissingExplicitConversionFailure failure(expr, cs, getFromType(), getToType(),
|
||||||
|
getLocator());
|
||||||
return failure.diagnose(asNote);
|
return failure.diagnose(asNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
ForceDowncast *ForceDowncast::create(ConstraintSystem &cs, Type toType,
|
ForceDowncast *ForceDowncast::create(ConstraintSystem &cs, Type fromType,
|
||||||
ConstraintLocator *locator) {
|
Type toType, ConstraintLocator *locator) {
|
||||||
return new (cs.getAllocator()) ForceDowncast(cs, toType, locator);
|
return new (cs.getAllocator()) ForceDowncast(cs, fromType, toType, locator);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ForceOptional::diagnose(Expr *root, bool asNote) const {
|
bool ForceOptional::diagnose(Expr *root, bool asNote) const {
|
||||||
|
|||||||
@@ -243,22 +243,6 @@ protected:
|
|||||||
ConstraintSystem &getConstraintSystem() const { return CS; }
|
ConstraintSystem &getConstraintSystem() const { return CS; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Append 'as! T' to force a downcast to the specified type.
|
|
||||||
class ForceDowncast final : public ConstraintFix {
|
|
||||||
Type DowncastTo;
|
|
||||||
|
|
||||||
ForceDowncast(ConstraintSystem &cs, Type toType, ConstraintLocator *locator)
|
|
||||||
: ConstraintFix(cs, FixKind::ForceDowncast, locator), DowncastTo(toType) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::string getName() const override;
|
|
||||||
bool diagnose(Expr *root, bool asNote = false) const override;
|
|
||||||
|
|
||||||
static ForceDowncast *create(ConstraintSystem &cs, Type toType,
|
|
||||||
ConstraintLocator *locator);
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Introduce a '!' to force an optional unwrap.
|
/// Introduce a '!' to force an optional unwrap.
|
||||||
class ForceOptional final : public ConstraintFix {
|
class ForceOptional final : public ConstraintFix {
|
||||||
Type BaseType;
|
Type BaseType;
|
||||||
@@ -510,6 +494,22 @@ public:
|
|||||||
ConstraintLocator *locator);
|
ConstraintLocator *locator);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Append 'as! T' to force a downcast to the specified type.
|
||||||
|
class ForceDowncast final : public ContextualMismatch {
|
||||||
|
ForceDowncast(ConstraintSystem &cs, Type fromType, Type toType,
|
||||||
|
ConstraintLocator *locator)
|
||||||
|
: ContextualMismatch(cs, FixKind::ForceDowncast, fromType, toType,
|
||||||
|
locator) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::string getName() const override;
|
||||||
|
|
||||||
|
bool diagnose(Expr *root, bool asNote = false) const override;
|
||||||
|
|
||||||
|
static ForceDowncast *create(ConstraintSystem &cs, Type fromType, Type toType,
|
||||||
|
ConstraintLocator *locator);
|
||||||
|
};
|
||||||
|
|
||||||
/// Introduce a '&' to take the address of an lvalue.
|
/// Introduce a '&' to take the address of an lvalue.
|
||||||
class AddAddressOf final : public ContextualMismatch {
|
class AddAddressOf final : public ContextualMismatch {
|
||||||
AddAddressOf(ConstraintSystem &cs, Type argTy, Type paramTy,
|
AddAddressOf(ConstraintSystem &cs, Type argTy, Type paramTy,
|
||||||
|
|||||||
@@ -2186,6 +2186,58 @@ static ConstraintFix *fixPropertyWrapperFailure(
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool canBridgeThroughCast(ConstraintSystem &cs, Type fromType,
|
||||||
|
Type toType) {
|
||||||
|
// If we have a value of type AnyObject that we're trying to convert to
|
||||||
|
// a class, force a downcast.
|
||||||
|
// FIXME: Also allow types bridged through Objective-C classes.
|
||||||
|
if (fromType->isAnyObject() && toType->getClassOrBoundGenericClass())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto &TC = cs.getTypeChecker();
|
||||||
|
auto bridged = TC.getDynamicBridgedThroughObjCClass(cs.DC, fromType, toType);
|
||||||
|
if (!bridged)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Note: don't perform this recovery for NSNumber;
|
||||||
|
if (auto classType = bridged->getAs<ClassType>()) {
|
||||||
|
SmallString<16> scratch;
|
||||||
|
if (classType->getDecl()->isObjC() &&
|
||||||
|
classType->getDecl()->getObjCRuntimeName(scratch) == "NSNumber")
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
repairViaBridgingCast(ConstraintSystem &cs, Type fromType, Type toType,
|
||||||
|
SmallVectorImpl<RestrictionOrFix> &conversionsOrFixes,
|
||||||
|
ConstraintLocatorBuilder locator) {
|
||||||
|
auto objectType1 = fromType->getOptionalObjectType();
|
||||||
|
auto objectType2 = toType->getOptionalObjectType();
|
||||||
|
|
||||||
|
if (objectType1 && !objectType2) {
|
||||||
|
auto *anchor = locator.trySimplifyToExpr();
|
||||||
|
if (!anchor)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (auto *overload = cs.findSelectedOverloadFor(anchor)) {
|
||||||
|
auto *decl = overload->Choice.getDeclOrNull();
|
||||||
|
if (decl &&
|
||||||
|
decl->getAttrs().hasAttribute<ImplicitlyUnwrappedOptionalAttr>())
|
||||||
|
fromType = objectType1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canBridgeThroughCast(cs, fromType, toType))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
conversionsOrFixes.push_back(ForceDowncast::create(
|
||||||
|
cs, fromType, toType, cs.getConstraintLocator(locator)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempt to repair typing failures and record fixes if needed.
|
/// Attempt to repair typing failures and record fixes if needed.
|
||||||
/// \return true if at least some of the failures has been repaired
|
/// \return true if at least some of the failures has been repaired
|
||||||
/// successfully, which allows type matcher to continue.
|
/// successfully, which allows type matcher to continue.
|
||||||
@@ -2301,6 +2353,9 @@ bool ConstraintSystem::repairFailures(
|
|||||||
|
|
||||||
if (repairByAnyToAnyObjectCast(lhs, rhs))
|
if (repairByAnyToAnyObjectCast(lhs, rhs))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
if (repairViaBridgingCast(*this, lhs, rhs, conversionsOrFixes, locator))
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -2321,7 +2376,10 @@ bool ConstraintSystem::repairFailures(
|
|||||||
case ConstraintLocator::ApplyArgToParam: {
|
case ConstraintLocator::ApplyArgToParam: {
|
||||||
auto loc = getConstraintLocator(locator);
|
auto loc = getConstraintLocator(locator);
|
||||||
if (repairByInsertingExplicitCall(lhs, rhs))
|
if (repairByInsertingExplicitCall(lhs, rhs))
|
||||||
return true;
|
break;
|
||||||
|
|
||||||
|
if (repairViaBridgingCast(*this, lhs, rhs, conversionsOrFixes, locator))
|
||||||
|
break;
|
||||||
|
|
||||||
if (lhs->getOptionalObjectType() && !rhs->getOptionalObjectType()) {
|
if (lhs->getOptionalObjectType() && !rhs->getOptionalObjectType()) {
|
||||||
conversionsOrFixes.push_back(
|
conversionsOrFixes.push_back(
|
||||||
@@ -2444,10 +2502,13 @@ bool ConstraintSystem::repairFailures(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (repairByInsertingExplicitCall(lhs, rhs))
|
if (repairByInsertingExplicitCall(lhs, rhs))
|
||||||
return true;
|
break;
|
||||||
|
|
||||||
if (repairByAnyToAnyObjectCast(lhs, rhs))
|
if (repairByAnyToAnyObjectCast(lhs, rhs))
|
||||||
return true;
|
break;
|
||||||
|
|
||||||
|
if (repairViaBridgingCast(*this, lhs, rhs, conversionsOrFixes, locator))
|
||||||
|
break;
|
||||||
|
|
||||||
// If both types are key path, the only differences
|
// If both types are key path, the only differences
|
||||||
// between them are mutability and/or root, value type mismatch.
|
// between them are mutability and/or root, value type mismatch.
|
||||||
@@ -3317,32 +3378,6 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a value of type AnyObject that we're trying to convert to
|
|
||||||
// a class, force a downcast.
|
|
||||||
// FIXME: Also allow types bridged through Objective-C classes.
|
|
||||||
if (objectType1->isAnyObject() &&
|
|
||||||
type2->getClassOrBoundGenericClass()) {
|
|
||||||
conversionsOrFixes.push_back(
|
|
||||||
ForceDowncast::create(*this, type2, getConstraintLocator(locator)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we could perform a bridging cast, try it.
|
|
||||||
if (auto bridged =
|
|
||||||
TC.getDynamicBridgedThroughObjCClass(DC, objectType1, type2)) {
|
|
||||||
// Note: don't perform this recovery for NSNumber;
|
|
||||||
bool useFix = true;
|
|
||||||
if (auto classType = bridged->getAs<ClassType>()) {
|
|
||||||
SmallString<16> scratch;
|
|
||||||
if (classType->getDecl()->isObjC() &&
|
|
||||||
classType->getDecl()->getObjCRuntimeName(scratch) == "NSNumber")
|
|
||||||
useFix = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useFix)
|
|
||||||
conversionsOrFixes.push_back(
|
|
||||||
ForceDowncast::create(*this, type2, getConstraintLocator(locator)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!type1->is<LValueType>() && type2->is<InOutType>()) {
|
if (!type1->is<LValueType>() && type2->is<InOutType>()) {
|
||||||
// If we have a concrete type that's an rvalue, "fix" it.
|
// If we have a concrete type that's an rvalue, "fix" it.
|
||||||
conversionsOrFixes.push_back(
|
conversionsOrFixes.push_back(
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ func rdar20029786(_ ns: NSString?) {
|
|||||||
|
|
||||||
let s3: NSString? = "str" as String? // expected-error {{cannot convert value of type 'String?' to specified type 'NSString?'}}{{39-39= as NSString?}}
|
let s3: NSString? = "str" as String? // expected-error {{cannot convert value of type 'String?' to specified type 'NSString?'}}{{39-39= as NSString?}}
|
||||||
|
|
||||||
var s4: String = ns ?? "str" // expected-error{{cannot convert value of type 'NSString' to specified type 'String'}}{{31-31= as String}}
|
var s4: String = ns ?? "str" // expected-error{{'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?}} {{20-20=(}} {{31-31=) as String}}
|
||||||
var s5: String = (ns ?? "str") as String // fixed version
|
var s5: String = (ns ?? "str") as String // fixed version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ for view in rootView.subviews as! [View] { // expected-warning{{immutable value
|
|||||||
doFoo()
|
doFoo()
|
||||||
}
|
}
|
||||||
|
|
||||||
for view:View in rootView.subviews { // expected-error{{'AnyObject' is not convertible to 'View'}}
|
for view:View in rootView.subviews { // expected-error{{cannot convert sequence element type 'AnyObject' to expected type 'View'}}
|
||||||
doFoo()
|
doFoo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ func testUpcastBridge() {
|
|||||||
dictOB = dictBB as [ObjC: BridgedToObjC]
|
dictOB = dictBB as [ObjC: BridgedToObjC]
|
||||||
|
|
||||||
dictBB = dictBO // expected-error{{cannot assign value of type '[BridgedToObjC : ObjC]' to type '[BridgedToObjC : BridgedToObjC]'}}
|
dictBB = dictBO // expected-error{{cannot assign value of type '[BridgedToObjC : ObjC]' to type '[BridgedToObjC : BridgedToObjC]'}}
|
||||||
|
// expected-note@-1 {{arguments to generic parameter 'Value' ('ObjC' and 'BridgedToObjC') are expected to be equal}}
|
||||||
dictBB = dictOB // expected-error{{cannot assign value of type '[ObjC : BridgedToObjC]' to type '[BridgedToObjC : BridgedToObjC]'}}
|
dictBB = dictOB // expected-error{{cannot assign value of type '[ObjC : BridgedToObjC]' to type '[BridgedToObjC : BridgedToObjC]'}}
|
||||||
|
// expected-note@-1 {{arguments to generic parameter 'Key' ('ObjC' and 'BridgedToObjC') are expected to be equal}}
|
||||||
|
|
||||||
dictDO = dictBB // expected-error{{cannot assign value of type '[BridgedToObjC : BridgedToObjC]' to type '[DerivesObjC : ObjC]'}}
|
dictDO = dictBB // expected-error{{cannot assign value of type '[BridgedToObjC : BridgedToObjC]' to type '[DerivesObjC : ObjC]'}}
|
||||||
//expected-note@-1 {{arguments to generic parameter 'Key' ('BridgedToObjC' and 'DerivesObjC') are expected to be equal}}
|
//expected-note@-1 {{arguments to generic parameter 'Key' ('BridgedToObjC' and 'DerivesObjC') are expected to be equal}}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ func testUpcastBridge() {
|
|||||||
|
|
||||||
// Upcast object to bridged type
|
// Upcast object to bridged type
|
||||||
setB = setO // expected-error{{cannot assign value of type 'Set<ObjC>' to type 'Set<BridgedToObjC>'}}
|
setB = setO // expected-error{{cannot assign value of type 'Set<ObjC>' to type 'Set<BridgedToObjC>'}}
|
||||||
|
// expected-note@-1 {{arguments to generic parameter 'Element' ('ObjC' and 'BridgedToObjC') are expected to be equal}}
|
||||||
|
|
||||||
// Failed upcast
|
// Failed upcast
|
||||||
setD = setB // expected-error{{cannot assign value of type 'Set<BridgedToObjC>' to type 'Set<DerivesObjC>'}}
|
setD = setB // expected-error{{cannot assign value of type 'Set<BridgedToObjC>' to type 'Set<DerivesObjC>'}}
|
||||||
|
|||||||
Reference in New Issue
Block a user