Skip to content

Commit f88c955

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 534e52c commit f88c955

File tree

10 files changed

+162
-10
lines changed

10 files changed

+162
-10
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,10 @@ ERROR(missing_unwrap_optional_try,none,
10571057
ERROR(missing_forced_downcast,none,
10581058
"%0 is not convertible to %1; "
10591059
"did you mean to use 'as!' to force downcast?", (Type, Type))
1060+
WARNING(coercion_may_fail_warning,none,
1061+
"coercion from %0 to %1 may fail; use 'as?' or 'as!' instead",
1062+
(Type, Type))
1063+
10601064
ERROR(missing_explicit_conversion,none,
10611065
"%0 is not implicitly convertible to %1; "
10621066
"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
@@ -6184,3 +6184,11 @@ bool MissingQuialifierInMemberRefFailure::diagnoseAsError() {
61846184
emitDiagnostic(choice, diag::decl_declared_here, choice->getFullName());
61856185
return true;
61866186
}
6187+
6188+
bool CoercionAsForceCastFailure::diagnoseAsError() {
6189+
auto *coercion = cast<CoerceExpr>(getRawAnchor());
6190+
emitDiagnostic(coercion->getLoc(), diag::coercion_may_fail_warning,
6191+
getFromType(), getToType())
6192+
.highlight(coercion->getSourceRange());
6193+
return true;
6194+
}

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
@@ -1276,3 +1276,17 @@ AddQualifierToAccessTopLevelName::create(ConstraintSystem &cs,
12761276
ConstraintLocator *locator) {
12771277
return new (cs.getAllocator()) AddQualifierToAccessTopLevelName(cs, locator);
12781278
}
1279+
1280+
bool AllowCoercionToForceCast::diagnose(const Solution &solution,
1281+
bool asNote) const {
1282+
CoercionAsForceCastFailure failure(solution, getFromType(), getToType(),
1283+
getLocator());
1284+
return failure.diagnose(asNote);
1285+
}
1286+
1287+
AllowCoercionToForceCast *
1288+
AllowCoercionToForceCast::create(ConstraintSystem &cs, Type fromType,
1289+
Type toType, ConstraintLocator *locator) {
1290+
return new (cs.getAllocator())
1291+
AllowCoercionToForceCast(cs, fromType, toType, locator);
1292+
}

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 {
@@ -1728,6 +1731,34 @@ class AllowNonClassTypeToConvertToAnyObject final : public ContextualMismatch {
17281731
create(ConstraintSystem &cs, Type type, ConstraintLocator *locator);
17291732
};
17301733

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

lib/Sema/CSSimplify.cpp

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7212,6 +7212,7 @@ ConstraintSystem::simplifyBridgingConstraint(Type type1,
72127212
return { type, count };
72137213
};
72147214

7215+
const auto rawType1 = type1;
72157216
type1 = getFixedTypeRecursive(type1, flags, /*wantRValue=*/true);
72167217
type2 = getFixedTypeRecursive(type2, flags, /*wantRValue=*/true);
72177218

@@ -7342,13 +7343,41 @@ ConstraintSystem::simplifyBridgingConstraint(Type type1,
73427343
}
73437344
}
73447345

7346+
// In a previous version of Swift, we could accidently drop the coercion
7347+
// constraint in certain cases. In most cases this led to either miscompiles
7348+
// or crashes later down the pipeline, but for coercions between collections
7349+
// we generated somewhat reasonable code that performed a force cast. To
7350+
// maintain compatibility with that behavior, allow the coercion between
7351+
// two collections, but add a warning fix telling the user to use as! or as?
7352+
// instead.
7353+
//
7354+
// We only need to perform this compatibility logic if the LHS type is a
7355+
// (potentially optional) type variable, as only such a constraint could have
7356+
// been previously been left unsolved.
7357+
//
7358+
// FIXME: Once we get a new language version, change this condition to only
7359+
// preserve compatibility for Swift 5.x mode.
7360+
auto canUseCompatFix =
7361+
rawType1->lookThroughAllOptionalTypes()->isTypeVariableOrMember();
7362+
7363+
auto makeCollectionResult = [&](SolutionKind result) -> SolutionKind {
7364+
// If we encountered an error and can use the compatibility fix, do so.
7365+
if (canUseCompatFix && result == SolutionKind::Error) {
7366+
auto *loc = getConstraintLocator(locator);
7367+
auto *fix = AllowCoercionToForceCast::create(*this, type1, type2, loc);
7368+
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
7369+
}
7370+
return result;
7371+
};
7372+
73457373
// Bridging the elements of an array.
73467374
if (auto fromElement = isArrayType(unwrappedFromType)) {
73477375
if (auto toElement = isArrayType(unwrappedToType)) {
73487376
countOptionalInjections();
7349-
return simplifyBridgingConstraint(
7377+
auto result = simplifyBridgingConstraint(
73507378
*fromElement, *toElement, subflags,
73517379
locator.withPathElement(LocatorPathElt::GenericArgument(0)));
7380+
return makeCollectionResult(result);
73527381
}
73537382
}
73547383

@@ -7358,11 +7387,13 @@ ConstraintSystem::simplifyBridgingConstraint(Type type1,
73587387
addExplicitConversionConstraint(fromKeyValue->first, toKeyValue->first,
73597388
ForgetChoice,
73607389
locator.withPathElement(
7361-
LocatorPathElt::GenericArgument(0)));
7390+
LocatorPathElt::GenericArgument(0)),
7391+
/*warnOnFailure*/ canUseCompatFix);
73627392
addExplicitConversionConstraint(fromKeyValue->second, toKeyValue->second,
73637393
ForgetChoice,
73647394
locator.withPathElement(
7365-
LocatorPathElt::GenericArgument(1)));
7395+
LocatorPathElt::GenericArgument(1)),
7396+
/*warnOnFailure*/ canUseCompatFix);
73667397
countOptionalInjections();
73677398
return SolutionKind::Solved;
73687399
}
@@ -7372,9 +7403,10 @@ ConstraintSystem::simplifyBridgingConstraint(Type type1,
73727403
if (auto fromElement = isSetType(unwrappedFromType)) {
73737404
if (auto toElement = isSetType(unwrappedToType)) {
73747405
countOptionalInjections();
7375-
return simplifyBridgingConstraint(
7406+
auto result = simplifyBridgingConstraint(
73767407
*fromElement, *toElement, subflags,
73777408
locator.withPathElement(LocatorPathElt::GenericArgument(0)));
7409+
return makeCollectionResult(result);
73787410
}
73797411
}
73807412

@@ -9332,7 +9364,8 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
93329364
case FixKind::ExplicitlyConstructRawRepresentable:
93339365
case FixKind::SpecifyBaseTypeForContextualMember:
93349366
case FixKind::CoerceToCheckedCast:
9335-
case FixKind::SpecifyObjectLiteralTypeImport: {
9367+
case FixKind::SpecifyObjectLiteralTypeImport:
9368+
case FixKind::AllowCoercionToForceCast: {
93369369
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
93379370
}
93389371

@@ -9796,7 +9829,7 @@ void ConstraintSystem::addFixConstraint(ConstraintFix *fix, ConstraintKind kind,
97969829

97979830
void ConstraintSystem::addExplicitConversionConstraint(
97989831
Type fromType, Type toType, RememberChoice_t rememberChoice,
9799-
ConstraintLocatorBuilder locator) {
9832+
ConstraintLocatorBuilder locator, bool warnOnFailure) {
98009833
SmallVector<Constraint *, 3> constraints;
98019834

98029835
auto locatorPtr = getConstraintLocator(locator);
@@ -9814,12 +9847,23 @@ void ConstraintSystem::addExplicitConversionConstraint(
98149847
fromType, toType, locatorPtr);
98159848
constraints.push_back(bridgingConstraint);
98169849

9850+
// If we're allowed to emit a warning on failure, add an additional constraint
9851+
// to the disjunction that just records a warning fix and succeeds.
9852+
if (warnOnFailure) {
9853+
auto *fix =
9854+
AllowCoercionToForceCast::create(*this, fromType, toType, locatorPtr);
9855+
constraints.push_back(
9856+
Constraint::createFixed(*this, ConstraintKind::BridgingConversion, fix,
9857+
fromType, toType, locatorPtr));
9858+
}
9859+
98179860
addDisjunctionConstraint(constraints, locator, rememberChoice);
98189861
}
98199862

98209863
ConstraintSystem::SolutionKind
98219864
ConstraintSystem::simplifyConstraint(const Constraint &constraint) {
9822-
switch (constraint.getKind()) {
9865+
auto matchKind = constraint.getKind();
9866+
switch (matchKind) {
98239867
case ConstraintKind::Bind:
98249868
case ConstraintKind::Equal:
98259869
case ConstraintKind::BindParam:
@@ -9830,7 +9874,6 @@ ConstraintSystem::simplifyConstraint(const Constraint &constraint) {
98309874
case ConstraintKind::OperatorArgumentConversion:
98319875
case ConstraintKind::OpaqueUnderlyingType: {
98329876
// Relational constraints.
9833-
auto matchKind = constraint.getKind();
98349877

98359878
// If there is a fix associated with this constraint, apply it.
98369879
if (auto fix = constraint.getFix()) {
@@ -9854,6 +9897,13 @@ ConstraintSystem::simplifyConstraint(const Constraint &constraint) {
98549897
}
98559898

98569899
case ConstraintKind::BridgingConversion:
9900+
// If there is a fix associated with this constraint, apply it.
9901+
if (auto fix = constraint.getFix()) {
9902+
return simplifyFixConstraint(fix, constraint.getFirstType(),
9903+
constraint.getSecondType(), matchKind, None,
9904+
constraint.getLocator());
9905+
}
9906+
98579907
return simplifyBridgingConstraint(constraint.getFirstType(),
98589908
constraint.getSecondType(),
98599909
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 warnOnFailure Whether a warning fix should be used for recovery if
2865+
/// the conversion fails. This is used to preserve compatibility.
28642866
void addExplicitConversionConstraint(Type fromType, Type toType,
28652867
RememberChoice_t rememberChoice,
2866-
ConstraintLocatorBuilder locator);
2868+
ConstraintLocatorBuilder locator,
2869+
bool warnOnFailure = false);
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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,28 @@ func test_coercions_with_overloaded_operator(str: String, optStr: String?, veryO
263263
_ = (veryOptString ?? "" ?? "") as [String] // expected-error {{cannot convert value of type 'String???' to type '[String]' in coercion}}
264264
_ = (veryOptString ?? "" ?? "" ?? "" ?? "") as [String] // expected-error {{cannot convert value of type 'String???' to type '[String]' in coercion}}
265265
}
266+
267+
func id<T>(_ x: T) -> T { x }
268+
269+
func test_compatibility_coercions(_ arr: [Int], _ optArr: [Int]?) {
270+
// Don't fix the simple case where no type variable is introduced, that was
271+
// always disallowed.
272+
_ = arr as [String] // expected-error {{cannot convert value of type '[Int]' to type '[String]' in coercion}}
273+
// expected-note@-1 {{arguments to generic parameter 'Element' ('Int' and 'String') are expected to be equal}}
274+
275+
// Apply the compatibility logic when a type variable is introduced. It's
276+
// unfortunate that this means we'll temporarily accept code we didn't before,
277+
// but it at least means we shouldn't break compatibility with anything.
278+
_ = id(arr) as [String] // expected-warning {{coercion from '[Int]' to '[String]' may fail; use 'as?' or 'as!' instead}}
279+
280+
_ = (arr ?? []) as [String] // expected-warning {{coercion from '[Int]' to '[String]' may fail; use 'as?' or 'as!' instead}}
281+
// expected-warning@-1 {{left side of nil coalescing operator '??' has non-optional type '[Int]', so the right side is never used}}
282+
_ = (arr ?? [] ?? []) as [String] // expected-warning {{coercion from '[Int]' to '[String]' may fail; use 'as?' or 'as!' instead}}
283+
// expected-warning@-1 2{{left side of nil coalescing operator '??' has non-optional type '[Int]', so the right side is never used}}
284+
_ = (optArr ?? []) as [String] // expected-warning {{coercion from '[Int]' to '[String]' may fail; use 'as?' or 'as!' instead}}
285+
286+
// In this case the array literal can be inferred to be [String], so totally
287+
// valid.
288+
_ = ([] ?? []) as [String] // expected-warning {{left side of nil coalescing operator '??' has non-optional type '[String]', so the right side is never used}}
289+
_ = (([] as Optional) ?? []) as [String]
290+
}

test/SILGen/collection_downcast.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,10 @@ func testSetDowncastBridgedConditional(_ dict: Set<NSObject>)
239239
// CHECK-NOT: destroy_value [[SET]]
240240
return dict as? Set<BridgedSwift>
241241
}
242+
243+
// CHECK-LABEL: sil hidden [ossa] @$s19collection_downcast35testDeprecatedAnyToNSObjectCoercionyySayypGSgF
244+
func testDeprecatedAnyToNSObjectCoercion(_ arr: [Any]?) {
245+
// CHECK: [[CAST_FN:%.+]] = function_ref @$ss15_arrayForceCastySayq_GSayxGr0_lF : $@convention(thin) <τ_0_0, τ_0_1> (@guaranteed Array<τ_0_0>) -> @owned Array<τ_0_1>
246+
// CHECK: apply [[CAST_FN]]<Any, NSObject>({{%.+}}) : $@convention(thin) <τ_0_0, τ_0_1> (@guaranteed Array<τ_0_0>) -> @owned Array<τ_0_1> // user: %20
247+
_ = (arr ?? []) as [NSObject]
248+
}

0 commit comments

Comments
 (0)