Skip to content

[Diagnostics][Qol] SR-11295 Emit diagnostics for same type coercion. #26710

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
20 changes: 12 additions & 8 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -949,18 +949,22 @@ ERROR(super_initializer_not_in_initializer,none,
WARNING(isa_is_always_true,none, "'%0' test is always true",
(StringRef))
WARNING(isa_is_foreign_check,none,
"'is' test is always true because %0 is a Core Foundation type",
(Type))
"'is' test is always true because %0 is a Core Foundation type",
(Type))
WARNING(conditional_downcast_coercion,none,
"conditional cast from %0 to %1 always succeeds",
(Type, Type))

"conditional cast from %0 to %1 always succeeds",
(Type, Type))
WARNING(unnecessary_same_type_coercion,none,
"redundant cast to %0 has no effect",
(Type))
WARNING(unnecessary_same_typealias_type_coercion,none,
"redundant cast from %0 to %1 has no effect",
(Type, Type))
WARNING(forced_downcast_noop,none,
"forced cast of %0 to same type has no effect", (Type))

WARNING(forced_downcast_coercion,none,
"forced cast from %0 to %1 always succeeds; did you mean to use 'as'?",
(Type, Type))
"forced cast from %0 to %1 always succeeds; did you mean to use 'as'?",
(Type, Type))

// Note: the Boolean at the end indicates whether bridging is required after
// the cast.
Expand Down
34 changes: 31 additions & 3 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4427,9 +4427,7 @@ bool InaccessibleMemberFailure::diagnoseAsError() {
auto &cs = getConstraintSystem();
auto *locator =
cs.getConstraintLocator(baseExpr, ConstraintLocator::Member);
if (llvm::any_of(cs.getFixes(), [&](const ConstraintFix *fix) {
return fix->getLocator() == locator;
}))
if (cs.hasFixFor(locator))
return false;
}

Expand Down Expand Up @@ -5058,6 +5056,36 @@ bool ThrowingFunctionConversionFailure::diagnoseAsError() {
return true;
}

bool UnnecessaryCoercionFailure::diagnoseAsError() {
auto expr = cast<CoerceExpr>(getAnchor());
auto sourceRange =
SourceRange(expr->getLoc(), expr->getCastTypeLoc().getSourceRange().End);

if (isa<TypeAliasType>(getFromType().getPointer()) &&
isa<TypeAliasType>(getToType().getPointer())) {
auto fromTypeAlias = cast<TypeAliasType>(getFromType().getPointer());
auto toTypeAlias = cast<TypeAliasType>(getToType().getPointer());
// If the typealias are different, we need a warning
// mentioning both types.
if (fromTypeAlias->getDecl() != toTypeAlias->getDecl()) {
emitDiagnostic(expr->getLoc(),
diag::unnecessary_same_typealias_type_coercion,
getFromType(), getToType())

.fixItRemove(sourceRange);
} else {
emitDiagnostic(expr->getLoc(), diag::unnecessary_same_type_coercion,
getToType())
.fixItRemove(sourceRange);
}
} else {
emitDiagnostic(expr->getLoc(), diag::unnecessary_same_type_coercion,
getToType())
.fixItRemove(sourceRange);
}
return true;
}

bool InOutConversionFailure::diagnoseAsError() {
auto *anchor = getAnchor();
auto *locator = getLocator();
Expand Down
35 changes: 28 additions & 7 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -709,8 +709,8 @@ class ContextualFailure : public FailureDiagnostic {
/// If we're trying to convert something to `nil`.
bool diagnoseConversionToNil() const;

// If we're trying to convert something of type "() -> T" to T,
// then we probably meant to call the value.
/// If we're trying to convert something of type "() -> T" to T,
/// then we probably meant to call the value.
bool diagnoseMissingFunctionCall() const;

/// Produce a specialized diagnostic if this is an invalid conversion to Bool.
Expand Down Expand Up @@ -1462,11 +1462,11 @@ class MutatingMemberRefOnImmutableBase final : public FailureDiagnostic {
bool diagnoseAsError() override;
};

// Diagnose an attempt to use AnyObject as the root type of a KeyPath
//
// ```swift
// let keyPath = \AnyObject.bar
// ```
/// Diagnose an attempt to use AnyObject as the root type of a KeyPath
///
/// ```swift
/// let keyPath = \AnyObject.bar
/// ```
class AnyObjectKeyPathRootFailure final : public FailureDiagnostic {

public:
Expand Down Expand Up @@ -1776,6 +1776,27 @@ class ExpandArrayIntoVarargsFailure final : public ContextualFailure {
void tryDropArrayBracketsFixIt(Expr *anchor) const;
};

/// Diagnose a situation where there is an explicit type coercion
/// to the same type e.g.:
///
/// ```swift
/// Double(1) as Double // redundant cast to 'Double' has no effect
/// 1 as Double as Double // redundant cast to 'Double' has no effect
/// let string = "String"
/// let s = string as String // redundant cast to 'String' has no effect
/// ```
class UnnecessaryCoercionFailure final
: public ContextualFailure {

public:
UnnecessaryCoercionFailure(Expr *root, ConstraintSystem &cs,
Type fromType, Type toType,
ConstraintLocator *locator)
: ContextualFailure(root, cs, fromType, toType, locator) {}

bool diagnoseAsError() override;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More of a meta-comment - It looks like we add more and more warnings so it might make sense to do a follow-up by adding diagnoseAsWarning() method to the FailureDiagnostic interface and rename classes like this to be Warning instead of Error...

};

/// Diagnose a situation there is a mismatch between argument and parameter
/// types e.g.:
///
Expand Down
32 changes: 32 additions & 0 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,38 @@ IgnoreContextualType *IgnoreContextualType::create(ConstraintSystem &cs,
IgnoreContextualType(cs, resultTy, specifiedTy, locator);
}

bool RemoveUnnecessaryCoercion::diagnose(Expr *root, bool asNote) const {
auto &cs = getConstraintSystem();
UnnecessaryCoercionFailure failure(root, cs, getFromType(), getToType(),
getLocator());
return failure.diagnose(asNote);
}

bool RemoveUnnecessaryCoercion::attempt(ConstraintSystem &cs, Type fromType,
Type toType,
ConstraintLocatorBuilder locator) {
auto last = locator.last();
bool isExplicitCoercion =
last && last->is<LocatorPathElt::ExplicitTypeCoercion>();
if (!isExplicitCoercion)
return false;

auto expr = cast<CoerceExpr>(locator.getAnchor());
auto toTypeRepr = expr->getCastTypeLoc().getTypeRepr();

// only diagnosing for coercion where the left-side is a DeclRefExpr
// or a explicit/implicit coercion e.g. Double(1) as Double
if (!isa<ImplicitlyUnwrappedOptionalTypeRepr>(toTypeRepr) &&
(isa<DeclRefExpr>(expr->getSubExpr()) ||
isa<CoerceExpr>(expr->getSubExpr()))) {
auto *fix = new (cs.getAllocator()) RemoveUnnecessaryCoercion(
cs, fromType, toType, cs.getConstraintLocator(locator));

return cs.recordFix(fix);
}
return false;
}

bool IgnoreAssignmentDestinationType::diagnose(Expr *root, bool asNote) const {
auto &cs = getConstraintSystem();
auto *AE = cast<AssignExpr>(getAnchor());
Expand Down
22 changes: 22 additions & 0 deletions lib/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ enum class FixKind : uint8_t {
/// Allow a single tuple parameter to be matched with N arguments
/// by forming all of the given arguments into a single tuple.
AllowTupleSplatForSingleParameter,

/// Remove an unnecessary coercion ('as') if the types are already equal.
/// e.g. Double(1) as Double
RemoveUnnecessaryCoercion,

/// Allow a single argument type mismatch. This is the most generic
/// failure related to argument-to-parameter conversions.
Expand Down Expand Up @@ -1340,6 +1344,24 @@ class IgnoreContextualType : public ContextualMismatch {
ConstraintLocator *locator);
};

class RemoveUnnecessaryCoercion : public ContextualMismatch {
protected:
RemoveUnnecessaryCoercion(ConstraintSystem &cs, Type fromType, Type toType,
ConstraintLocator *locator)
: ContextualMismatch(cs, FixKind::RemoveUnnecessaryCoercion, fromType,
toType, locator, /*isWarning*/ true) {}

public:
std::string getName() const override {
return "remove unnecessary explicit type coercion";
}

bool diagnose(Expr *root, bool asNote = false) const override;

static bool attempt(ConstraintSystem &cs, Type fromType, Type toType,
ConstraintLocatorBuilder locator);
};

class IgnoreAssignmentDestinationType final : public ContextualMismatch {
IgnoreAssignmentDestinationType(ConstraintSystem &cs, Type sourceTy,
Type destTy, ConstraintLocator *locator)
Expand Down
28 changes: 23 additions & 5 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2587,7 +2587,8 @@ bool ConstraintSystem::repairFailures(
});
};

if (path.empty()) {
if (path.empty() ||
path.back().is<LocatorPathElt::ExplicitTypeCoercion>()) {
if (!anchor)
return false;

Expand Down Expand Up @@ -3218,9 +3219,16 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
// let's defer it until later proper check.
if (!(desugar1->is<DependentMemberType>() &&
desugar2->is<DependentMemberType>())) {
// If the types are obviously equivalent, we're done.
if (desugar1->isEqual(desugar2) && !isa<InOutType>(desugar2)) {
return getTypeMatchSuccess();
if (desugar1->isEqual(desugar2)) {
if (kind >= ConstraintKind::Conversion &&
!flags.contains(TMF_ApplyingFix)) {
if (RemoveUnnecessaryCoercion::attempt(*this, type1, type2,
getConstraintLocator(locator))) {
return getTypeMatchFailure(locator);
}
}
if (!isa<InOutType>(desugar2))
return getTypeMatchSuccess();
}
}

Expand Down Expand Up @@ -8029,6 +8037,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
case FixKind::GenericArgumentsMismatch:
case FixKind::AllowMutatingMemberOnRValueBase:
case FixKind::AllowTupleSplatForSingleParameter:
case FixKind::RemoveUnnecessaryCoercion:
llvm_unreachable("handled elsewhere");
}

Expand Down Expand Up @@ -8335,11 +8344,20 @@ void ConstraintSystem::addExplicitConversionConstraint(
SmallVector<Constraint *, 3> constraints;

auto locatorPtr = getConstraintLocator(locator);
ConstraintLocator *coerceLocator = locatorPtr;

if (allowFixes && shouldAttemptFixes()) {
auto *anchor = locator.getAnchor();
if (isa<CoerceExpr>(anchor) && !anchor->isImplicit()) {
coerceLocator =
getConstraintLocator(anchor, LocatorPathElt::ExplicitTypeCoercion());
}
}

// Coercion (the common case).
Constraint *coerceConstraint =
Constraint::create(*this, ConstraintKind::Conversion,
fromType, toType, locatorPtr);
fromType, toType, coerceLocator);
coerceConstraint->setFavored();
constraints.push_back(coerceConstraint);

Expand Down
8 changes: 7 additions & 1 deletion lib/Sema/ConstraintLocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ void ConstraintLocator::Profile(llvm::FoldingSetNodeID &id, Expr *anchor,
case KeyPathRoot:
case KeyPathValue:
case KeyPathComponentResult:
case ExplicitTypeCoercion:
case Condition:
auto numValues = numNumericValuesInPathElement(elt.getKind());
for (unsigned i = 0; i < numValues; ++i)
Expand Down Expand Up @@ -133,6 +134,7 @@ unsigned LocatorPathElt::getNewSummaryFlags() const {
case ConstraintLocator::KeyPathRoot:
case ConstraintLocator::KeyPathValue:
case ConstraintLocator::KeyPathComponentResult:
case ConstraintLocator::ExplicitTypeCoercion:
case ConstraintLocator::Condition:
return 0;

Expand Down Expand Up @@ -458,7 +460,11 @@ void ConstraintLocator::dump(SourceManager *sm, raw_ostream &out) {
case KeyPathComponentResult:
out << "key path component result";
break;


case ExplicitTypeCoercion:
out << "type coercion";
break;

case Condition:
out << "condition expression";
break;
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/ConstraintLocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class ConstraintLocator : public llvm::FoldingSetNode {
case KeyPathRoot:
case KeyPathValue:
case KeyPathComponentResult:
case ExplicitTypeCoercion:
case Condition:
return 0;

Expand Down
3 changes: 3 additions & 0 deletions lib/Sema/ConstraintLocatorPathElts.def
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ SIMPLE_LOCATOR_PATH_ELT(UnresolvedMember)
/// The candidate witness during protocol conformance checking.
CUSTOM_LOCATOR_PATH_ELT(Witness)

/// An explicit type coercion.
SIMPLE_LOCATOR_PATH_ELT(ExplicitTypeCoercion)

/// The condition associated with 'if' expression or ternary operator.
SIMPLE_LOCATOR_PATH_ELT(Condition)

Expand Down
5 changes: 5 additions & 0 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3000,6 +3000,11 @@ void constraints::simplifyLocator(Expr *&anchor,
path = path.slice(1);
continue;
}

case ConstraintLocator::ExplicitTypeCoercion: {
path = path.slice(1);
continue;
}

default:
// FIXME: Lots of other cases to handle.
Expand Down
3 changes: 2 additions & 1 deletion test/ClangImporter/Darwin_test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ _ = nil as Fract? // expected-error{{use of undeclared type 'Fract'}}
_ = nil as Darwin.Fract? // okay

_ = 0 as OSErr
_ = noErr as OSStatus // noErr is from the overlay
// noErr is from the overlay
_ = noErr as OSStatus // expected-warning {{redundant cast to 'OSStatus' (aka 'Int32') has no effect}} {{11-23=}}
_ = 0 as UniChar

_ = ProcessSerialNumber()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ func testSuppressed() {
#endif

func testMacro() {
_ = CONSTANT as CInt
_ = CONSTANT as CInt // expected-warning {{redundant cast to 'CInt' (aka 'Int32') has no effect}} {{16-24=}}
}

func testFoundationOverlay() {
_ = NSUTF8StringEncoding // no ambiguity
_ = NSUTF8StringEncoding as UInt // and we should get the overlay version
// and we should get the overlay version
_ = NSUTF8StringEncoding as UInt // expected-warning {{redundant cast to 'UInt' has no effect}} {{28-36=}}
}

#if !SILGEN
Expand Down
4 changes: 2 additions & 2 deletions test/ClangImporter/Security_test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import Security

_ = kSecClass as CFString
_ = kSecClassGenericPassword as CFString
_ = kSecClass as CFString // expected-warning {{redundant cast to 'CFString' has no effect}} {{15-27=}}
_ = kSecClassGenericPassword as CFString // expected-warning {{redundant cast to 'CFString' has no effect}} {{30-42=}}
_ = kSecClassGenericPassword as CFDictionary // expected-error {{'CFString?' is not convertible to 'CFDictionary'}} {{30-32=as!}}

func testIntegration() {
Expand Down
2 changes: 1 addition & 1 deletion test/ClangImporter/attr-swift_private.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public func testTopLevel() {

_ = __PrivAnonymousA
_ = __E0PrivA
_ = __PrivE1A as __PrivE1
_ = __PrivE1A as __PrivE1 // expected-warning {{redundant cast to '__PrivE1' has no effect}} {{15-27=}}
_ = NSEnum.__privA
_ = NSEnum.B
_ = NSOptions.__privA
Expand Down
4 changes: 2 additions & 2 deletions test/ClangImporter/cf.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ func testTollFree1(_ ccmduct: CCMutableDuct) {
}

func testChainedAliases(_ fridge: CCRefrigerator) {
_ = fridge as CCRefrigerator
_ = fridge as CCRefrigerator // expected-warning {{redundant cast to 'CCRefrigerator' has no effect}} {{14-32=}}

_ = fridge as CCFridge
_ = fridge as CCFridge // expected-warning {{redundant cast to 'CCFridge' (aka 'CCRefrigerator') has no effect}} {{14-26=}}
_ = fridge as CCFridgeRef // expected-error{{'CCFridgeRef' has been renamed to 'CCFridge'}} {{17-28=CCFridge}}
}

Expand Down
2 changes: 1 addition & 1 deletion test/ClangImporter/cfuncs_parse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func test_cfunc2(_ i: Int) {
#else
let f = cfunc2(i, 17)
#endif
_ = f as Float
_ = f as Float // expected-warning {{redundant cast to 'Float' has no effect}} {{9-18=}}
cfunc2(b:17, a:i) // expected-error{{extraneous argument labels 'b:a:' in call}}
// expected-error@-1 {{cannot convert value of type 'Int' to expected argument type 'Int32'}}
cfunc2(17, i) // expected-error{{cannot convert value of type 'Int' to expected argument type 'Int32'}}
Expand Down
Loading