Skip to content

Commit 9f275ca

Browse files
authored
Merge pull request #15383 from xedin/rdar-38648760
[Sema] Improve diagnostics for non-escaping function types
2 parents dbfee4d + 79b14c8 commit 9f275ca

File tree

10 files changed

+106
-31
lines changed

10 files changed

+106
-31
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2805,6 +2805,9 @@ ERROR(assigning_noescape_to_escaping,none,
28052805
ERROR(general_noescape_to_escaping,none,
28062806
"using non-escaping parameter %0 in a context expecting an @escaping closure",
28072807
(Identifier))
2808+
ERROR(converting_noescape_to_type,none,
2809+
"converting non-escaping value to %0 may allow it to escape",
2810+
(Type))
28082811

28092812
ERROR(capture_across_type_decl,none,
28102813
"%0 declaration cannot close over value %1 defined in outer scope",

lib/Sema/CSApply.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7817,7 +7817,9 @@ bool ConstraintSystem::applySolutionFix(Expr *expr,
78177817
// If we didn't manage to resolve directly to an expression, we don't
78187818
// have a great diagnostic to give, so bail.
78197819
if (!resolved || !resolved->getAnchor() ||
7820-
!resolved->getPath().empty())
7820+
(!resolved->getPath().empty() &&
7821+
fix.first.getKind() != FixKind::ExplicitlyEscaping &&
7822+
fix.first.getKind() != FixKind::ExplicitlyEscapingToAny))
78217823
return false;
78227824

78237825
Expr *affected = resolved->getAnchor();
@@ -7974,6 +7976,30 @@ bool ConstraintSystem::applySolutionFix(Expr *expr,
79747976
}
79757977
return false;
79767978
}
7979+
7980+
case FixKind::ExplicitlyEscaping: {
7981+
auto path = locator->getPath();
7982+
auto *anchor = locator->getAnchor();
7983+
7984+
if (path.empty())
7985+
return false;
7986+
7987+
auto &last = path.back();
7988+
if (last.getKind() == ConstraintLocator::Archetype) {
7989+
auto *archetype = last.getArchetype();
7990+
TC.diagnose(anchor->getLoc(), diag::converting_noescape_to_type,
7991+
archetype);
7992+
return true;
7993+
}
7994+
break;
7995+
}
7996+
7997+
case FixKind::ExplicitlyEscapingToAny: {
7998+
auto *anchor = locator->getAnchor();
7999+
TC.diagnose(anchor->getLoc(), diag::converting_noescape_to_type,
8000+
getASTContext().TheAnyType);
8001+
return true;
8002+
}
79778003
}
79788004

79798005
// FIXME: It would be really nice to emit a follow-up note showing where

lib/Sema/CSBindings.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ ConstraintSystem::getPotentialBindingForRelationalConstraint(
277277
// should be allowed to escape. As a result we allow anything
278278
// passed in to escape.
279279
if (auto *fnTy = type->getAs<AnyFunctionType>())
280-
if (typeVar->getImpl().getArchetype())
280+
if (typeVar->getImpl().getArchetype() && !shouldAttemptFixes())
281281
type = fnTy->withExtInfo(fnTy->getExtInfo().withNoEscape(false));
282282

283283
// Check whether we can perform this binding.

lib/Sema/CSSimplify.cpp

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -672,9 +672,16 @@ matchCallArguments(ConstraintSystem &cs, ConstraintKind kind,
672672

673673
// Disallow assignment of noescape function to parameter of type
674674
// Any. Allowing this would allow these functions to escape.
675-
if (auto *fnTy = argType->getAs<AnyFunctionType>())
676-
if (fnTy->isNoEscape())
675+
if (auto *fnTy = argType->getAs<AnyFunctionType>()) {
676+
if (fnTy->isNoEscape()) {
677+
// Allow assigned of 'no-escape' function with recorded fix.
678+
if (cs.shouldAttemptFixes() &&
679+
!cs.recordFix(FixKind::ExplicitlyEscaping, locator))
680+
return cs.getTypeMatchSuccess();
681+
677682
return cs.getTypeMatchFailure(locator);
683+
}
684+
}
678685

679686
return cs.getTypeMatchSuccess();
680687
}
@@ -1317,6 +1324,10 @@ ConstraintSystem::matchExistentialTypes(Type type1, Type type2,
13171324
if (!fnTy || !fnTy->isNoEscape())
13181325
return getTypeMatchSuccess();
13191326

1327+
if (shouldAttemptFixes() &&
1328+
!recordFix(FixKind::ExplicitlyEscapingToAny, locator))
1329+
return getTypeMatchSuccess();
1330+
13201331
return getTypeMatchFailure(locator);
13211332
}
13221333

@@ -1490,10 +1501,18 @@ ConstraintSystem::matchTypesBindTypeVar(
14901501
// Disallow bindings of noescape functions to type variables that
14911502
// represent an opened archetype. If we allowed this it would allow
14921503
// the noescape function to potentially escape.
1493-
if (auto *fnTy = type->getAs<AnyFunctionType>())
1494-
if (fnTy->isNoEscape())
1495-
if (typeVar->getImpl().getArchetype())
1504+
if (auto *fnTy = type->getAs<AnyFunctionType>()) {
1505+
if (fnTy->isNoEscape() && typeVar->getImpl().getArchetype()) {
1506+
if (shouldAttemptFixes()) {
1507+
if (recordFix(FixKind::ExplicitlyEscaping, locator))
1508+
return getTypeMatchFailure(locator);
1509+
1510+
// Allow no-escape function to be bound with recorded fix.
1511+
} else {
14961512
return getTypeMatchFailure(locator);
1513+
}
1514+
}
1515+
}
14971516

14981517
// Check whether the type variable must be bound to a materializable
14991518
// type.
@@ -2011,9 +2030,20 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
20112030
// Penalize conversions to Any, and disallow conversions of
20122031
// noescape functions to Any.
20132032
if (kind >= ConstraintKind::Conversion && type2->isAny()) {
2014-
if (auto *fnTy = type1->getAs<AnyFunctionType>())
2015-
if (fnTy->isNoEscape())
2016-
return getTypeMatchFailure(locator);
2033+
if (auto *fnTy = type1->getAs<AnyFunctionType>()) {
2034+
if (fnTy->isNoEscape()) {
2035+
if (shouldAttemptFixes()) {
2036+
if (recordFix(FixKind::ExplicitlyEscapingToAny, locator))
2037+
return getTypeMatchFailure(locator);
2038+
2039+
// Allow 'no-escape' functions to be converted to 'Any'
2040+
// with a recorded fix that helps us to properly diagnose
2041+
// such situations.
2042+
} else {
2043+
return getTypeMatchFailure(locator);
2044+
}
2045+
}
2046+
}
20172047

20182048
increaseScore(ScoreKind::SK_EmptyExistentialConversion);
20192049
}
@@ -4757,7 +4787,17 @@ bool ConstraintSystem::recordFix(Fix fix, ConstraintLocatorBuilder locator) {
47574787
if (worseThanBestSolution())
47584788
return true;
47594789

4760-
Fixes.push_back({fix, getConstraintLocator(locator)});
4790+
auto *loc = getConstraintLocator(locator);
4791+
auto existingFix =
4792+
llvm::find_if(Fixes, [&](std::pair<Fix, ConstraintLocator *> &e) {
4793+
// If we already have a fix like this recorded, let's not do it again,
4794+
// this situation might happen when the same fix kind is applicable to
4795+
// different overload choices.
4796+
return e.first == fix && e.second == loc;
4797+
});
4798+
4799+
if (existingFix == Fixes.end())
4800+
Fixes.push_back({fix, loc});
47614801

47624802
return false;
47634803
}
@@ -4802,6 +4842,8 @@ ConstraintSystem::simplifyFixConstraint(Fix fix, Type type1, Type type2,
48024842
return result;
48034843
}
48044844

4845+
case FixKind::ExplicitlyEscaping:
4846+
case FixKind::ExplicitlyEscapingToAny:
48054847
case FixKind::CoerceToCheckedCast:
48064848
llvm_unreachable("handled elsewhere");
48074849
}

lib/Sema/Constraint.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,9 @@ StringRef Fix::getName(FixKind kind) {
504504
return "fix: add address-of";
505505
case FixKind::CoerceToCheckedCast:
506506
return "fix: as to as!";
507+
case FixKind::ExplicitlyEscaping:
508+
case FixKind::ExplicitlyEscapingToAny:
509+
return "fix: add @escaping";
507510
}
508511

509512
llvm_unreachable("Unhandled FixKind in switch.");

lib/Sema/Constraint.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ enum RememberChoice_t : bool {
232232
enum class FixKind : uint8_t {
233233
/// Introduce a '!' to force an optional unwrap.
234234
ForceOptional,
235-
235+
236236
/// Introduce a '?.' to begin optional chaining.
237237
OptionalChaining,
238238

@@ -241,9 +241,14 @@ enum class FixKind : uint8_t {
241241

242242
/// Introduce a '&' to take the address of an lvalue.
243243
AddressOf,
244-
244+
245245
/// Replace a coercion ('as') with a forced checked cast ('as!').
246246
CoerceToCheckedCast,
247+
248+
/// Mark function type as explicitly '@escaping'.
249+
ExplicitlyEscaping,
250+
/// Mark function type as explicitly '@escaping' to be convertable to 'Any'.
251+
ExplicitlyEscapingToAny,
247252
};
248253

249254
/// Describes a fix that can be applied to a constraint before visiting it.
@@ -279,6 +284,8 @@ class Fix {
279284
LLVM_ATTRIBUTE_DEPRECATED(void dump(ConstraintSystem *cs) const
280285
LLVM_ATTRIBUTE_USED,
281286
"only for use within the debugger");
287+
288+
bool operator==(Fix const &b) { return Kind == b.Kind && Data == b.Data; }
282289
};
283290

284291

test/Constraints/function.swift

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ sr590((1, 2))
8686

8787
// SR-2657: Poor diagnostics when function arguments should be '@escaping'.
8888
private class SR2657BlockClass<T> {
89-
// expected-note@-1 {{'T' declared as parameter to type 'SR2657BlockClass'}}
90-
// expected-note@-2 {{'T' declared as parameter to type 'SR2657BlockClass'}}
9189
let f: T
9290
init(f: T) { self.f = f }
9391
}
@@ -96,17 +94,15 @@ func takesAny(_: Any) {}
9694

9795
func foo(block: () -> (), other: () -> Int) { // expected-note 2 {{parameter 'block' is implicitly non-escaping}}
9896
let _ = SR2657BlockClass(f: block)
99-
// expected-error@-1 {{generic parameter 'T' could not be inferred}}
100-
// expected-note@-2 {{explicitly specify the generic arguments to fix this issue}}
97+
// expected-error@-1 {{converting non-escaping value to 'T' may allow it to escape}}
10198
let _ = SR2657BlockClass<()->()>(f: block)
10299
// expected-error@-1 {{passing non-escaping parameter 'block' to function expecting an @escaping closure}}
103100
let _: SR2657BlockClass<()->()> = SR2657BlockClass(f: block)
104-
// expected-error@-1 {{generic parameter 'T' could not be inferred}}
105-
// expected-note@-2 {{explicitly specify the generic arguments to fix this issue}}
101+
// expected-error@-1 {{converting non-escaping value to 'T' may allow it to escape}}
106102
let _: SR2657BlockClass<()->()> = SR2657BlockClass<()->()>(f: block)
107103
// expected-error@-1 {{passing non-escaping parameter 'block' to function expecting an @escaping closure}}
108-
_ = SR2657BlockClass<Any>(f: block) // expected-error{{function produces expected type '()'; did you mean to call it with '()'?}}
109-
_ = SR2657BlockClass<Any>(f: other) // expected-error{{function produces expected type 'Int'; did you mean to call it with '()'?}}
110-
takesAny(block) // expected-error{{function produces expected type '()'; did you mean to call it with '()'?}}
111-
takesAny(other) // expected-error{{function produces expected type 'Int'; did you mean to call it with '()'?}}
104+
_ = SR2657BlockClass<Any>(f: block) // expected-error {{converting non-escaping value to 'Any' may allow it to escape}}
105+
_ = SR2657BlockClass<Any>(f: other) // expected-error {{converting non-escaping value to 'Any' may allow it to escape}}
106+
takesAny(block) // expected-error {{converting non-escaping value to 'Any' may allow it to escape}}
107+
takesAny(other) // expected-error {{converting non-escaping value to 'Any' may allow it to escape}}
112108
}

test/Constraints/function_conversion.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func takesAny(_ f: Any) {}
4949

5050
func twoFns(_ f: (Int) -> Int, _ g: @escaping (Int) -> Int) {
5151
// expected-note@-1 {{parameter 'f' is implicitly non-escaping}}
52-
takesAny(f) // expected-error {{argument type '(Int) -> Int' does not conform to expected type 'Any'}}
52+
takesAny(f) // expected-error {{converting non-escaping value to 'Any' may allow it to escape}}
5353
takesAny(g)
5454
var h = g
5555
h = f // expected-error {{assigning non-escaping parameter 'f' to an @escaping closure}}
@@ -63,4 +63,4 @@ var escapingParam: (@escaping (Int) -> Int) -> () = consumeEscaping
6363
noEscapeParam = escapingParam // expected-error {{cannot assign value of type '(@escaping (Int) -> Int) -> ()' to type '((Int) -> Int) -> ()}}
6464

6565
escapingParam = takesAny
66-
noEscapeParam = takesAny // expected-error {{cannot assign value of type '(Any) -> ()' to type '((Int) -> Int) -> ()'}}
66+
noEscapeParam = takesAny // expected-error {{converting non-escaping value to 'Any' may allow it to escape}}

test/Constraints/suspicious_bit_casts.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
func escapeByBitCast(f: () -> ()) -> () -> () {
44
return unsafeBitCast(f, to: (() -> ()).self)
5-
// expected-error@-1 {{generic parameter 'T' could not be inferred}}
5+
// expected-error@-1 {{converting non-escaping value to 'T' may allow it to escape}}
66
}
77

88
func changeFnRep(f: @escaping () -> ()) -> @convention(block) () -> () {

test/attr/attr_noescape.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ func takesGenericClosure<T>(_ a : Int, _ fn : @noescape () -> T) {} // expected-
1313
var globalAny: Any = 0
1414

1515
func assignToGlobal<T>(_ t: T) {
16-
// expected-note@-1 {{in call to function 'assignToGlobal'}}
1716
globalAny = t
1817
}
1918

@@ -56,15 +55,14 @@ func takesNoEscapeClosure(_ fn : () -> Int) {
5655
takesGenericClosure(4, fn) // ok
5756
takesGenericClosure(4) { fn() } // ok.
5857

59-
_ = [fn] // expected-error {{type of expression is ambiguous without more context}}
58+
_ = [fn] // expected-error {{converting non-escaping value to 'Element' may allow it to escape}}
6059
_ = [doesEscape(fn)] // expected-error {{'(() -> Int) -> ()' is not convertible to '(@escaping () -> Int) -> ()'}}
61-
_ = [1 : fn] // expected-error {{type of expression is ambiguous without more context}}
60+
_ = [1 : fn] // expected-error {{converting non-escaping value to 'Value' may allow it to escape}}
6261
_ = [1 : doesEscape(fn)] // expected-error {{passing non-escaping parameter 'fn' to function expecting an @escaping closure}}
6362
_ = "\(doesEscape(fn))" // expected-error {{passing non-escaping parameter 'fn' to function expecting an @escaping closure}}
6463
_ = "\(takesArray([fn]))" // expected-error {{using non-escaping parameter 'fn' in a context expecting an @escaping closure}}
6564

66-
// FIXME: Generate a more specific error about the non-escaping parameter 'fn'
67-
assignToGlobal(fn) // expected-error {{generic parameter 'T' could not be inferred}}
65+
assignToGlobal(fn) // expected-error {{converting non-escaping value to 'T' may allow it to escape}}
6866
}
6967

7068
class SomeClass {

0 commit comments

Comments
 (0)