Skip to content

Commit d69a42d

Browse files
committed
[CS] Preserve compatibility for collection coercions
Previously we could allow some invalid coercions to sneak past Sema. In most cases these would either cause crashes later down the pipeline or miscompiles. However, for coercions between collections, we emitted somewhat reasonable code that performed a force cast. This commit aims to preserve compatibility with those collection coercions that previously compiled, and emits a warning telling the user to use either 'as?' or 'as!' instead.
1 parent 6c7d695 commit d69a42d

File tree

10 files changed

+291
-42
lines changed

10 files changed

+291
-42
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,10 @@ ERROR(missing_unwrap_optional_try,none,
10681068
ERROR(missing_forced_downcast,none,
10691069
"%0 is not convertible to %1; "
10701070
"did you mean to use 'as!' to force downcast?", (Type, Type))
1071+
WARNING(coercion_may_fail_warning,none,
1072+
"coercion from %0 to %1 may fail; use 'as?' or 'as!' instead",
1073+
(Type, Type))
1074+
10711075
ERROR(missing_explicit_conversion,none,
10721076
"%0 is not implicitly convertible to %1; "
10731077
"did you mean to use 'as' to explicitly convert?", (Type, Type))

lib/Sema/CSDiagnostics.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6199,3 +6199,11 @@ bool MissingQuialifierInMemberRefFailure::diagnoseAsError() {
61996199
emitDiagnostic(choice, diag::decl_declared_here, choice->getFullName());
62006200
return true;
62016201
}
6202+
6203+
bool CoercionAsForceCastFailure::diagnoseAsError() {
6204+
auto *coercion = cast<CoerceExpr>(getRawAnchor());
6205+
emitDiagnostic(coercion->getLoc(), diag::coercion_may_fail_warning,
6206+
getFromType(), getToType())
6207+
.highlight(coercion->getSourceRange());
6208+
return true;
6209+
}

lib/Sema/CSDiagnostics.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,6 +1977,16 @@ class MissingQuialifierInMemberRefFailure final : public FailureDiagnostic {
19771977
bool diagnoseAsError();
19781978
};
19791979

1980+
/// Emits a warning about an attempt to use the 'as' operator as the 'as!'
1981+
/// operator.
1982+
class CoercionAsForceCastFailure final : public ContextualFailure {
1983+
public:
1984+
CoercionAsForceCastFailure(const Solution &solution, Type fromType,
1985+
Type toType, ConstraintLocator *locator)
1986+
: ContextualFailure(solution, fromType, toType, locator) {}
1987+
1988+
bool diagnoseAsError() override;
1989+
};
19801990
} // end namespace constraints
19811991
} // end namespace swift
19821992

lib/Sema/CSFix.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,3 +1279,17 @@ AddQualifierToAccessTopLevelName::create(ConstraintSystem &cs,
12791279
ConstraintLocator *locator) {
12801280
return new (cs.getAllocator()) AddQualifierToAccessTopLevelName(cs, locator);
12811281
}
1282+
1283+
bool AllowCoercionToForceCast::diagnose(const Solution &solution,
1284+
bool asNote) const {
1285+
CoercionAsForceCastFailure failure(solution, getFromType(), getToType(),
1286+
getLocator());
1287+
return failure.diagnose(asNote);
1288+
}
1289+
1290+
AllowCoercionToForceCast *
1291+
AllowCoercionToForceCast::create(ConstraintSystem &cs, Type fromType,
1292+
Type toType, ConstraintLocator *locator) {
1293+
return new (cs.getAllocator())
1294+
AllowCoercionToForceCast(cs, fromType, toType, locator);
1295+
}

lib/Sema/CSFix.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ enum class FixKind : uint8_t {
248248
/// Member shadows a top-level name, such a name could only be accessed by
249249
/// prefixing it with a module name.
250250
AddQualifierToAccessTopLevelName,
251+
252+
/// A warning fix that allows a coercion to perform a force-cast.
253+
AllowCoercionToForceCast,
251254
};
252255

253256
class ConstraintFix {
@@ -1737,6 +1740,34 @@ class AllowNonClassTypeToConvertToAnyObject final : public ContextualMismatch {
17371740
create(ConstraintSystem &cs, Type type, ConstraintLocator *locator);
17381741
};
17391742

1743+
/// A warning fix to maintain compatibility with the following:
1744+
///
1745+
/// \code
1746+
/// func foo(_ arr: [Any]?) {
1747+
/// _ = (arr ?? []) as [NSObject]
1748+
/// }
1749+
/// \endcode
1750+
///
1751+
/// which performs a force-cast of the array's elements, despite being spelled
1752+
/// as a coercion.
1753+
class AllowCoercionToForceCast final : public ContextualMismatch {
1754+
AllowCoercionToForceCast(ConstraintSystem &cs, Type fromType, Type toType,
1755+
ConstraintLocator *locator)
1756+
: ContextualMismatch(cs, FixKind::AllowCoercionToForceCast, fromType,
1757+
toType, locator, /*warning*/ true) {}
1758+
1759+
public:
1760+
std::string getName() const {
1761+
return "allow coercion to be treated as a force-cast";
1762+
}
1763+
1764+
bool diagnose(const Solution &solution, bool asNote = false) const;
1765+
1766+
static AllowCoercionToForceCast *create(ConstraintSystem &cs, Type fromType,
1767+
Type toType,
1768+
ConstraintLocator *locator);
1769+
};
1770+
17401771
} // end namespace constraints
17411772
} // end namespace swift
17421773

lib/Sema/CSSimplify.cpp

Lines changed: 100 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7218,6 +7218,7 @@ ConstraintSystem::simplifyBridgingConstraint(Type type1,
72187218
return { type, count };
72197219
};
72207220

7221+
const auto rawType1 = type1;
72217222
type1 = getFixedTypeRecursive(type1, flags, /*wantRValue=*/true);
72227223
type2 = getFixedTypeRecursive(type2, flags, /*wantRValue=*/true);
72237224

@@ -7258,6 +7259,85 @@ ConstraintSystem::simplifyBridgingConstraint(Type type1,
72587259
return SolutionKind::Solved;
72597260
}
72607261

7262+
// In a previous version of Swift, we could accidently drop the coercion
7263+
// constraint in certain cases. In most cases this led to either miscompiles
7264+
// or crashes later down the pipeline, but for coercions between collections
7265+
// we generated somewhat reasonable code that performed a force cast. To
7266+
// maintain compatibility with that behavior, allow the coercion between
7267+
// two collections, but add a warning fix telling the user to use as! or as?
7268+
// instead.
7269+
//
7270+
// We only need to perform this compatibility logic if the LHS type is a
7271+
// (potentially optional) type variable, as only such a constraint could have
7272+
// been previously been left unsolved.
7273+
//
7274+
// FIXME: Once we get a new language version, change this condition to only
7275+
// preserve compatibility for Swift 5.x mode.
7276+
auto canUseCompatFix =
7277+
rawType1->lookThroughAllOptionalTypes()->isTypeVariableOrMember();
7278+
7279+
// Unless we're allowing the collection compatibility fix, the source cannot
7280+
// be more optional than the destination.
7281+
if (!canUseCompatFix && numFromOptionals > numToOptionals)
7282+
return SolutionKind::Error;
7283+
7284+
auto makeCollectionResult = [&](SolutionKind result) -> SolutionKind {
7285+
// If we encountered an error and can use the compatibility fix, do so.
7286+
if (canUseCompatFix) {
7287+
if (numFromOptionals > numToOptionals || result == SolutionKind::Error) {
7288+
auto *loc = getConstraintLocator(locator);
7289+
auto *fix = AllowCoercionToForceCast::create(*this, type1, type2, loc);
7290+
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
7291+
}
7292+
}
7293+
return result;
7294+
};
7295+
7296+
// Bridging the elements of an array.
7297+
if (auto fromElement = isArrayType(unwrappedFromType)) {
7298+
if (auto toElement = isArrayType(unwrappedToType)) {
7299+
countOptionalInjections();
7300+
auto result = simplifyBridgingConstraint(
7301+
*fromElement, *toElement, subflags,
7302+
locator.withPathElement(LocatorPathElt::GenericArgument(0)));
7303+
return makeCollectionResult(result);
7304+
}
7305+
}
7306+
7307+
// Bridging the keys/values of a dictionary.
7308+
if (auto fromKeyValue = isDictionaryType(unwrappedFromType)) {
7309+
if (auto toKeyValue = isDictionaryType(unwrappedToType)) {
7310+
ConstraintFix *compatFix = nullptr;
7311+
if (canUseCompatFix) {
7312+
compatFix = AllowCoercionToForceCast::create(
7313+
*this, type1, type2, getConstraintLocator(locator));
7314+
}
7315+
addExplicitConversionConstraint(fromKeyValue->first, toKeyValue->first,
7316+
ForgetChoice,
7317+
locator.withPathElement(
7318+
LocatorPathElt::GenericArgument(0)),
7319+
compatFix);
7320+
addExplicitConversionConstraint(fromKeyValue->second, toKeyValue->second,
7321+
ForgetChoice,
7322+
locator.withPathElement(
7323+
LocatorPathElt::GenericArgument(1)),
7324+
compatFix);
7325+
countOptionalInjections();
7326+
return makeCollectionResult(SolutionKind::Solved);
7327+
}
7328+
}
7329+
7330+
// Bridging the elements of a set.
7331+
if (auto fromElement = isSetType(unwrappedFromType)) {
7332+
if (auto toElement = isSetType(unwrappedToType)) {
7333+
countOptionalInjections();
7334+
auto result = simplifyBridgingConstraint(
7335+
*fromElement, *toElement, subflags,
7336+
locator.withPathElement(LocatorPathElt::GenericArgument(0)));
7337+
return makeCollectionResult(result);
7338+
}
7339+
}
7340+
72617341
// The source cannot be more optional than the destination, because bridging
72627342
// conversions don't allow us to implicitly check for a value in the optional.
72637343
if (numFromOptionals > numToOptionals) {
@@ -7348,42 +7428,6 @@ ConstraintSystem::simplifyBridgingConstraint(Type type1,
73487428
}
73497429
}
73507430

7351-
// Bridging the elements of an array.
7352-
if (auto fromElement = isArrayType(unwrappedFromType)) {
7353-
if (auto toElement = isArrayType(unwrappedToType)) {
7354-
countOptionalInjections();
7355-
return simplifyBridgingConstraint(
7356-
*fromElement, *toElement, subflags,
7357-
locator.withPathElement(LocatorPathElt::GenericArgument(0)));
7358-
}
7359-
}
7360-
7361-
// Bridging the keys/values of a dictionary.
7362-
if (auto fromKeyValue = isDictionaryType(unwrappedFromType)) {
7363-
if (auto toKeyValue = isDictionaryType(unwrappedToType)) {
7364-
addExplicitConversionConstraint(fromKeyValue->first, toKeyValue->first,
7365-
ForgetChoice,
7366-
locator.withPathElement(
7367-
LocatorPathElt::GenericArgument(0)));
7368-
addExplicitConversionConstraint(fromKeyValue->second, toKeyValue->second,
7369-
ForgetChoice,
7370-
locator.withPathElement(
7371-
LocatorPathElt::GenericArgument(1)));
7372-
countOptionalInjections();
7373-
return SolutionKind::Solved;
7374-
}
7375-
}
7376-
7377-
// Bridging the elements of a set.
7378-
if (auto fromElement = isSetType(unwrappedFromType)) {
7379-
if (auto toElement = isSetType(unwrappedToType)) {
7380-
countOptionalInjections();
7381-
return simplifyBridgingConstraint(
7382-
*fromElement, *toElement, subflags,
7383-
locator.withPathElement(LocatorPathElt::GenericArgument(0)));
7384-
}
7385-
}
7386-
73877431
return SolutionKind::Error;
73887432
}
73897433

@@ -9331,7 +9375,8 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
93319375
case FixKind::ExplicitlyConstructRawRepresentable:
93329376
case FixKind::SpecifyBaseTypeForContextualMember:
93339377
case FixKind::CoerceToCheckedCast:
9334-
case FixKind::SpecifyObjectLiteralTypeImport: {
9378+
case FixKind::SpecifyObjectLiteralTypeImport:
9379+
case FixKind::AllowCoercionToForceCast: {
93359380
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
93369381
}
93379382

@@ -9795,7 +9840,7 @@ void ConstraintSystem::addFixConstraint(ConstraintFix *fix, ConstraintKind kind,
97959840

97969841
void ConstraintSystem::addExplicitConversionConstraint(
97979842
Type fromType, Type toType, RememberChoice_t rememberChoice,
9798-
ConstraintLocatorBuilder locator) {
9843+
ConstraintLocatorBuilder locator, ConstraintFix *compatFix) {
97999844
SmallVector<Constraint *, 3> constraints;
98009845

98019846
auto locatorPtr = getConstraintLocator(locator);
@@ -9813,12 +9858,21 @@ void ConstraintSystem::addExplicitConversionConstraint(
98139858
fromType, toType, locatorPtr);
98149859
constraints.push_back(bridgingConstraint);
98159860

9861+
// If we're allowed to use a compatibility fix that emits a warning on
9862+
// failure, add it to the disjunction so that it's recorded on failure.
9863+
if (compatFix) {
9864+
constraints.push_back(
9865+
Constraint::createFixed(*this, ConstraintKind::BridgingConversion,
9866+
compatFix, fromType, toType, locatorPtr));
9867+
}
9868+
98169869
addDisjunctionConstraint(constraints, locator, rememberChoice);
98179870
}
98189871

98199872
ConstraintSystem::SolutionKind
98209873
ConstraintSystem::simplifyConstraint(const Constraint &constraint) {
9821-
switch (constraint.getKind()) {
9874+
auto matchKind = constraint.getKind();
9875+
switch (matchKind) {
98229876
case ConstraintKind::Bind:
98239877
case ConstraintKind::Equal:
98249878
case ConstraintKind::BindParam:
@@ -9829,7 +9883,6 @@ ConstraintSystem::simplifyConstraint(const Constraint &constraint) {
98299883
case ConstraintKind::OperatorArgumentConversion:
98309884
case ConstraintKind::OpaqueUnderlyingType: {
98319885
// Relational constraints.
9832-
auto matchKind = constraint.getKind();
98339886

98349887
// If there is a fix associated with this constraint, apply it.
98359888
if (auto fix = constraint.getFix()) {
@@ -9853,6 +9906,13 @@ ConstraintSystem::simplifyConstraint(const Constraint &constraint) {
98539906
}
98549907

98559908
case ConstraintKind::BridgingConversion:
9909+
// If there is a fix associated with this constraint, apply it.
9910+
if (auto fix = constraint.getFix()) {
9911+
return simplifyFixConstraint(fix, constraint.getFirstType(),
9912+
constraint.getSecondType(), matchKind, None,
9913+
constraint.getLocator());
9914+
}
9915+
98569916
return simplifyBridgingConstraint(constraint.getFirstType(),
98579917
constraint.getSecondType(),
98589918
None, constraint.getLocator());

lib/Sema/ConstraintSystem.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2861,9 +2861,12 @@ class ConstraintSystem {
28612861
/// \param rememberChoice Whether the conversion disjunction should record its
28622862
/// choice.
28632863
/// \param locator The locator.
2864+
/// \param compatFix A compatibility fix that can be applied if the conversion
2865+
/// fails.
28642866
void addExplicitConversionConstraint(Type fromType, Type toType,
28652867
RememberChoice_t rememberChoice,
2866-
ConstraintLocatorBuilder locator);
2868+
ConstraintLocatorBuilder locator,
2869+
ConstraintFix *compatFix = nullptr);
28672870

28682871
/// Add a disjunction constraint.
28692872
void

test/Constraints/bridging.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ func bridgeTupleToAnyObject() {
373373

374374
// Array defaulting and bridging type checking error per rdar://problem/54274245
375375
func rdar54274245(_ arr: [Any]?) {
376-
_ = (arr ?? []) as [NSObject] // expected-error {{type of expression is ambiguous without more context}}
376+
_ = (arr ?? []) as [NSObject] // expected-warning {{coercion from '[Any]' to '[NSObject]' may fail; use 'as?' or 'as!' instead}}
377377
}
378378

379379
// rdar://problem/60501780 - failed to infer NSString as a value type of a dictionary

test/Constraints/casts.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,63 @@ func test_coercions_with_overloaded_operator(str: String, optStr: String?, veryO
275275
_ = ohno(ohno(ohno(str))) as String???
276276
_ = ohno(ohno(ohno(str))) as Int // expected-error {{cannot convert value of type 'String???' to type 'Int' in coercion}}
277277
}
278+
279+
func id<T>(_ x: T) -> T { x }
280+
281+
func test_compatibility_coercions(_ arr: [Int], _ optArr: [Int]?, _ dict: [String: Int], _ set: Set<Int>) {
282+
// Successful coercions don't raise a warning.
283+
_ = arr as [Any]?
284+
_ = dict as [String: Int]?
285+
_ = set as Set<Int>
286+
287+
// Don't fix the simple case where no type variable is introduced, that was
288+
// always disallowed.
289+
_ = arr as [String] // expected-error {{cannot convert value of type '[Int]' to type '[String]' in coercion}}
290+
// expected-note@-1 {{arguments to generic parameter 'Element' ('Int' and 'String') are expected to be equal}}
291+
_ = dict as [String: String] // expected-error {{cannot convert value of type '[String : Int]' to type '[String : String]' in coercion}}
292+
// expected-note@-1 {{arguments to generic parameter 'Value' ('Int' and 'String') are expected to be equal}}
293+
_ = dict as [String: String]? // expected-error {{cannot convert value of type '[String : Int]' to type '[String : String]?' in coercion}}
294+
_ = (dict as [String: Int]?) as [String: Int] // expected-error {{value of optional type '[String : Int]?' must be unwrapped to a value of type '[String : Int]'}}
295+
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}}
296+
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}
297+
_ = set as Set<String> // expected-error {{cannot convert value of type 'Set<Int>' to type 'Set<String>' in coercion}}
298+
// expected-note@-1 {{arguments to generic parameter 'Element' ('Int' and 'String') are expected to be equal}}
299+
300+
// Apply the compatibility logic when a type variable is introduced. It's
301+
// unfortunate that this means we'll temporarily accept code we didn't before,
302+
// but it at least means we shouldn't break compatibility with anything.
303+
_ = id(arr) as [String] // expected-warning {{coercion from '[Int]' to '[String]' may fail; use 'as?' or 'as!' instead}}
304+
305+
_ = (arr ?? []) as [String] // expected-warning {{coercion from '[Int]' to '[String]' may fail; use 'as?' or 'as!' instead}}
306+
// expected-warning@-1 {{left side of nil coalescing operator '??' has non-optional type '[Int]', so the right side is never used}}
307+
_ = (arr ?? [] ?? []) as [String] // expected-warning {{coercion from '[Int]' to '[String]' may fail; use 'as?' or 'as!' instead}}
308+
// expected-warning@-1 2{{left side of nil coalescing operator '??' has non-optional type '[Int]', so the right side is never used}}
309+
_ = (optArr ?? []) as [String] // expected-warning {{coercion from '[Int]' to '[String]' may fail; use 'as?' or 'as!' instead}}
310+
311+
// Allow the coercion to increase optionality.
312+
_ = (arr ?? []) as [String]? // expected-warning {{coercion from '[Int]' to '[String]?' may fail; use 'as?' or 'as!' instead}}
313+
// expected-warning@-1 {{left side of nil coalescing operator '??' has non-optional type '[Int]', so the right side is never used}}
314+
_ = (arr ?? []) as [String?]? // expected-warning {{coercion from '[Int]' to '[String?]?' may fail; use 'as?' or 'as!' instead}}
315+
// expected-warning@-1 {{left side of nil coalescing operator '??' has non-optional type '[Int]', so the right side is never used}}
316+
_ = (arr ?? []) as [String??]?? // expected-warning {{coercion from '[Int]' to '[String??]??' may fail; use 'as?' or 'as!' instead}}
317+
// expected-warning@-1 {{left side of nil coalescing operator '??' has non-optional type '[Int]', so the right side is never used}}
318+
_ = (dict ?? [:]) as [String: String?]? // expected-warning {{coercion from '[String : Int]' to '[String : String?]?' may fail; use 'as?' or 'as!' instead}}
319+
// expected-warning@-1 {{left side of nil coalescing operator '??' has non-optional type '[String : Int]', so the right side is never used}}
320+
_ = (set ?? []) as Set<String>?? // expected-warning {{coercion from 'Set<Int>' to 'Set<String>??' may fail; use 'as?' or 'as!' instead}}
321+
// expected-warning@-1 {{left side of nil coalescing operator '??' has non-optional type 'Set<Int>', so the right side is never used}}
322+
323+
// Allow the coercion to decrease optionality.
324+
_ = ohno(ohno(ohno(arr))) as [String] // expected-warning {{coercion from '[Int]???' to '[String]' may fail; use 'as?' or 'as!' instead}}
325+
_ = ohno(ohno(ohno(arr))) as [Int] // expected-warning {{coercion from '[Int]???' to '[Int]' may fail; use 'as?' or 'as!' instead}}
326+
_ = ohno(ohno(ohno(Set<Int>()))) as Set<String> // expected-warning {{coercion from 'Set<Int>???' to 'Set<String>' may fail; use 'as?' or 'as!' instead}}
327+
_ = ohno(ohno(ohno(["": ""]))) as [Int: String] // expected-warning {{coercion from '[String : String]???' to '[Int : String]' may fail; use 'as?' or 'as!' instead}}
328+
_ = ohno(ohno(ohno(dict))) as [String: Int] // expected-warning {{coercion from '[String : Int]???' to '[String : Int]' may fail; use 'as?' or 'as!' instead}}
329+
330+
// In this case the array literal can be inferred to be [String], so totally
331+
// valid.
332+
_ = ([] ?? []) as [String] // expected-warning {{left side of nil coalescing operator '??' has non-optional type '[String]', so the right side is never used}}
333+
_ = (([] as Optional) ?? []) as [String]
334+
335+
// The array can also be inferred to be [Any].
336+
_ = ([] ?? []) as Array // expected-warning {{left side of nil coalescing operator '??' has non-optional type '[Any]', so the right side is never used}}
337+
}

0 commit comments

Comments
 (0)