Skip to content

Commit 62b2da8

Browse files
committed
[ConstraintSystem] Improve @escaping parameter diagnostics
Detect difference in escapiness while matching function types in the solver and record a fix that suggests to add @escaping attribute where appropriate. Also emit a tailored diagnostic when non-escaping parameter type is used as a type of a generic parameter.
1 parent 943d891 commit 62b2da8

File tree

10 files changed

+178
-44
lines changed

10 files changed

+178
-44
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3073,10 +3073,15 @@ NOTE(silence_debug_description_in_interpolation_segment_call,none,
30733073
NOTE(noescape_parameter,none,
30743074
"parameter %0 is implicitly non-escaping",
30753075
(Identifier))
3076+
NOTE(generic_parameters_always_escaping,none,
3077+
"generic parameters are always considered '@escaping'", ())
30763078

30773079
ERROR(passing_noescape_to_escaping,none,
30783080
"passing non-escaping parameter %0 to function expecting an @escaping closure",
30793081
(Identifier))
3082+
ERROR(converting_noespace_param_to_generic_type,none,
3083+
"converting non-escaping parameter %0 to generic parameter %1 may allow it to escape",
3084+
(Identifier, Identifier))
30803085
ERROR(assigning_noescape_to_escaping,none,
30813086
"assigning non-escaping parameter %0 to an @escaping closure",
30823087
(Identifier))

lib/Sema/CSDiag.cpp

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,26 @@ void constraints::simplifyLocator(Expr *&anchor,
173173
continue;
174174

175175
case ConstraintLocator::NamedTupleElement:
176-
case ConstraintLocator::TupleElement:
176+
case ConstraintLocator::TupleElement: {
177177
// Extract tuple element.
178+
unsigned index = path[0].getValue();
178179
if (auto tupleExpr = dyn_cast<TupleExpr>(anchor)) {
179-
unsigned index = path[0].getValue();
180180
if (index < tupleExpr->getNumElements()) {
181181
anchor = tupleExpr->getElement(index);
182182
path = path.slice(1);
183183
continue;
184184
}
185185
}
186+
187+
if (auto *CE = dyn_cast<CollectionExpr>(anchor)) {
188+
if (index < CE->getNumElements()) {
189+
anchor = CE->getElement(index);
190+
path = path.slice(1);
191+
continue;
192+
}
193+
}
186194
break;
195+
}
187196

188197
case ConstraintLocator::ApplyArgToParam:
189198
// Extract tuple element.
@@ -1994,34 +2003,9 @@ bool FailureDiagnosis::diagnoseNonEscapingParameterToEscaping(
19942003
if (!srcFT || !dstFT || !srcFT->isNoEscape() || dstFT->isNoEscape())
19952004
return false;
19962005

1997-
// Pick a specific diagnostic for the specific use
1998-
auto paramDecl = cast<ParamDecl>(declRef->getDecl());
1999-
switch (dstPurpose) {
2000-
case CTP_CallArgument:
2001-
CS.TC.diagnose(declRef->getLoc(), diag::passing_noescape_to_escaping,
2002-
paramDecl->getName());
2003-
break;
2004-
case CTP_AssignSource:
2005-
CS.TC.diagnose(declRef->getLoc(), diag::assigning_noescape_to_escaping,
2006-
paramDecl->getName());
2007-
break;
2008-
2009-
default:
2010-
CS.TC.diagnose(declRef->getLoc(), diag::general_noescape_to_escaping,
2011-
paramDecl->getName());
2012-
break;
2013-
}
2014-
2015-
// Give a note and fixit
2016-
InFlightDiagnostic note = CS.TC.diagnose(
2017-
paramDecl->getLoc(), diag::noescape_parameter, paramDecl->getName());
2018-
2019-
if (!paramDecl->isAutoClosure()) {
2020-
note.fixItInsert(paramDecl->getTypeLoc().getSourceRange().Start,
2021-
"@escaping ");
2022-
} // TODO: add in a fixit for autoclosure
2023-
2024-
return true;
2006+
NoEscapeFuncToTypeConversionFailure failure(
2007+
expr, CS, CS.getConstraintLocator(expr), dstType);
2008+
return failure.diagnoseAsError();
20252009
}
20262010

20272011
bool FailureDiagnosis::diagnoseContextualConversionError(

lib/Sema/CSDiagnostics.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ bool LabelingFailure::diagnoseAsError() {
426426
bool NoEscapeFuncToTypeConversionFailure::diagnoseAsError() {
427427
auto *anchor = getAnchor();
428428

429+
if (diagnoseParameterUse())
430+
return true;
431+
429432
if (ConvertTo) {
430433
emitDiagnostic(anchor->getLoc(), diag::converting_noescape_to_type,
431434
ConvertTo);
@@ -450,6 +453,123 @@ bool NoEscapeFuncToTypeConversionFailure::diagnoseAsError() {
450453
return true;
451454
}
452455

456+
bool NoEscapeFuncToTypeConversionFailure::diagnoseParameterUse() const {
457+
// If the other side is not a function, we have common case diagnostics
458+
// which handle function-to-type conversion diagnostics.
459+
if (!ConvertTo || !ConvertTo->is<FunctionType>())
460+
return false;
461+
462+
auto *anchor = getAnchor();
463+
auto *locator = getLocator();
464+
auto diagnostic = diag::general_noescape_to_escaping;
465+
466+
ParamDecl *PD = nullptr;
467+
if (auto *DRE = dyn_cast<DeclRefExpr>(anchor)) {
468+
PD = dyn_cast<ParamDecl>(DRE->getDecl());
469+
470+
// If anchor is not a parameter declaration there
471+
// is no need to dig up more information.
472+
if (!PD)
473+
return false;
474+
475+
// Let's check whether this is a function parameter passed
476+
// as an argument to another function which accepts @escaping
477+
// function at that position.
478+
auto path = locator->getPath();
479+
if (!path.empty() &&
480+
(path.back().getKind() == ConstraintLocator::ApplyArgToParam)) {
481+
if (auto paramType =
482+
getParameterTypeFor(getRawAnchor(), path.back().getValue2())) {
483+
// If this is a situation when non-escaping parameter is passed
484+
// to the argument which represents generic parameter, there is
485+
// a tailored diagnostic for that.
486+
if (auto *GP = paramType->getAs<GenericTypeParamType>()) {
487+
emitDiagnostic(anchor->getLoc(),
488+
diag::converting_noespace_param_to_generic_type,
489+
PD->getName(), GP->getName());
490+
491+
emitDiagnostic(GP->getDecl(),
492+
diag::generic_parameters_always_escaping);
493+
return true;
494+
}
495+
}
496+
497+
// If there are no generic parameters involved, this could
498+
// only mean that parameter is expecting @escaping function type.
499+
diagnostic = diag::passing_noescape_to_escaping;
500+
}
501+
} else if (auto *AE = dyn_cast<AssignExpr>(getRawAnchor())) {
502+
if (auto *DRE = dyn_cast<DeclRefExpr>(AE->getSrc())) {
503+
PD = dyn_cast<ParamDecl>(DRE->getDecl());
504+
diagnostic = diag::assigning_noescape_to_escaping;
505+
}
506+
}
507+
508+
if (!PD)
509+
return false;
510+
511+
emitDiagnostic(anchor->getLoc(), diagnostic, PD->getName());
512+
513+
// Give a note and fix-it
514+
auto note =
515+
emitDiagnostic(PD->getLoc(), diag::noescape_parameter, PD->getName());
516+
517+
if (!PD->isAutoClosure()) {
518+
note.fixItInsert(PD->getTypeLoc().getSourceRange().Start, "@escaping ");
519+
} // TODO: add in a fixit for autoclosure
520+
521+
return true;
522+
}
523+
524+
Type NoEscapeFuncToTypeConversionFailure::getParameterTypeFor(
525+
Expr *expr, unsigned paramIdx) const {
526+
auto &cs = getConstraintSystem();
527+
ConstraintLocator *locator = nullptr;
528+
529+
if (auto *call = dyn_cast<CallExpr>(expr)) {
530+
auto *fnExpr = call->getFn();
531+
if (isa<UnresolvedDotExpr>(fnExpr)) {
532+
locator = cs.getConstraintLocator(fnExpr, ConstraintLocator::Member);
533+
} else if (isa<UnresolvedMemberExpr>(fnExpr)) {
534+
locator =
535+
cs.getConstraintLocator(fnExpr, ConstraintLocator::UnresolvedMember);
536+
} else if (auto *TE = dyn_cast<TypeExpr>(fnExpr)) {
537+
locator = cs.getConstraintLocator(call,
538+
{ConstraintLocator::ApplyFunction,
539+
ConstraintLocator::ConstructorMember},
540+
/*summaryFlags=*/0);
541+
} else {
542+
locator = cs.getConstraintLocator(fnExpr);
543+
}
544+
} else if (auto *SE = dyn_cast<SubscriptExpr>(expr)) {
545+
locator = cs.getConstraintLocator(SE, ConstraintLocator::SubscriptMember);
546+
}
547+
548+
if (!locator)
549+
return Type();
550+
551+
auto choice = getOverloadChoiceIfAvailable(locator);
552+
if (!choice)
553+
return Type();
554+
555+
if (auto *fnType = choice->openedType->getAs<FunctionType>()) {
556+
const auto &param = fnType->getParams()[paramIdx];
557+
558+
auto paramType = param.getPlainType();
559+
if (auto *typeVar = paramType->getAs<TypeVariableType>()) {
560+
auto *locator = typeVar->getImpl().getLocator();
561+
if (locator->isForGenericParameter()) {
562+
const auto &GP = locator->getPath().back();
563+
return GP.getGenericParameter();
564+
}
565+
}
566+
567+
return paramType;
568+
}
569+
570+
return Type();
571+
}
572+
453573
bool MissingForcedDowncastFailure::diagnoseAsError() {
454574
if (hasComplexLocator())
455575
return false;

lib/Sema/CSDiagnostics.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,16 @@ class NoEscapeFuncToTypeConversionFailure final : public FailureDiagnostic {
466466
: FailureDiagnostic(expr, cs, locator), ConvertTo(toType) {}
467467

468468
bool diagnoseAsError() override;
469+
470+
private:
471+
/// Emit tailored diagnostics for no-escape parameter conversions e.g.
472+
/// passing such parameter as an @escaping argument, or trying to
473+
/// assign it to a variable which expects @escaping function.
474+
bool diagnoseParameterUse() const;
475+
476+
/// Retrieve a type of the parameter at give index for call or
477+
/// subscript invocation represented by given expression node.
478+
Type getParameterTypeFor(Expr *expr, unsigned paramIdx) const;
469479
};
470480

471481
class MissingForcedDowncastFailure final : public FailureDiagnostic {

lib/Sema/CSSimplify.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,8 +1320,16 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
13201320
// A non-@noescape function type can be a subtype of a @noescape function
13211321
// type.
13221322
if (func1->isNoEscape() != func2->isNoEscape() &&
1323-
(func1->isNoEscape() || kind < ConstraintKind::Subtype))
1324-
return getTypeMatchFailure(locator);
1323+
(func1->isNoEscape() || kind < ConstraintKind::Subtype)) {
1324+
if (!shouldAttemptFixes())
1325+
return getTypeMatchFailure(locator);
1326+
1327+
auto *fix = MarkExplicitlyEscaping::create(
1328+
*this, getConstraintLocator(locator), func2);
1329+
1330+
if (recordFix(fix))
1331+
return getTypeMatchFailure(locator);
1332+
}
13251333

13261334
if (matchFunctionRepresentations(func1->getExtInfo().getRepresentation(),
13271335
func2->getExtInfo().getRepresentation(),

test/Constraints/function.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,22 +85,22 @@ sr590(())
8585
sr590((1, 2))
8686

8787
// SR-2657: Poor diagnostics when function arguments should be '@escaping'.
88-
private class SR2657BlockClass<T> {
88+
private class SR2657BlockClass<T> { // expected-note 3 {{generic parameters are always considered '@escaping'}}
8989
let f: T
9090
init(f: T) { self.f = f }
9191
}
9292

9393
func takesAny(_: Any) {}
9494

95-
func foo(block: () -> (), other: () -> Int) { // expected-note 2 {{parameter 'block' is implicitly non-escaping}}
95+
func foo(block: () -> (), other: () -> Int) {
9696
let _ = SR2657BlockClass(f: block)
9797
// expected-error@-1 {{converting non-escaping value to 'T' may allow it to escape}}
9898
let _ = SR2657BlockClass<()->()>(f: block)
99-
// expected-error@-1 {{passing non-escaping parameter 'block' to function expecting an @escaping closure}}
99+
// expected-error@-1 {{converting non-escaping parameter 'block' to generic parameter 'T' may allow it to escape}}
100100
let _: SR2657BlockClass<()->()> = SR2657BlockClass(f: block)
101-
// expected-error@-1 {{converting non-escaping value to 'T' may allow it to escape}}
101+
// expected-error@-1 {{converting non-escaping parameter 'block' to generic parameter 'T' may allow it to escape}}
102102
let _: SR2657BlockClass<()->()> = SR2657BlockClass<()->()>(f: block)
103-
// expected-error@-1 {{passing non-escaping parameter 'block' to function expecting an @escaping closure}}
103+
// expected-error@-1 {{converting non-escaping parameter 'block' to generic parameter 'T' may allow it to escape}}
104104
_ = SR2657BlockClass<Any>(f: block) // expected-error {{converting non-escaping value to 'Any' may allow it to escape}}
105105
_ = SR2657BlockClass<Any>(f: other) // expected-error {{converting non-escaping value to 'Any' may allow it to escape}}
106106
takesAny(block) // expected-error {{converting non-escaping value to 'Any' may allow it to escape}}

test/Constraints/function_conversion.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ takesAny(consumeEscaping)
5959

6060
var noEscapeParam: ((Int) -> Int) -> () = consumeNoEscape
6161
var escapingParam: (@escaping (Int) -> Int) -> () = consumeEscaping
62-
noEscapeParam = escapingParam // expected-error {{cannot assign value of type '(@escaping (Int) -> Int) -> ()' to type '((Int) -> Int) -> ()}}
62+
noEscapeParam = escapingParam // expected-error {{converting non-escaping value to '(Int) -> Int' may allow it to escape}}
6363

6464
escapingParam = takesAny
6565
noEscapeParam = takesAny // expected-error {{converting non-escaping value to 'Any' may allow it to escape}}

test/attr/attr_autoclosure.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ func rdar_20591571() {
188188

189189
func id<T>(_: T) -> T {}
190190
func same<T>(_: T, _: T) {}
191+
// expected-note@-1 2 {{generic parameters are always considered '@escaping'}}
191192

192193
func takesAnAutoclosure(_ fn: @autoclosure () -> Int, _ efn: @escaping @autoclosure () -> Int) {
193194
// These are OK -- they count as non-escaping uses
@@ -198,8 +199,8 @@ func rdar_20591571() {
198199
let _ = efn
199200

200201
_ = id(fn) // expected-error {{converting non-escaping value to 'T' may allow it to escape}}
201-
_ = same(fn, { 3 }) // expected-error {{converting non-escaping value to 'T' may allow it to escape}}
202-
_ = same({ 3 }, fn) // expected-error {{converting non-escaping value to 'T' may allow it to escape}}
202+
_ = same(fn, { 3 }) // expected-error {{converting non-escaping parameter 'fn' to generic parameter 'T' may allow it to escape}}
203+
_ = same({ 3 }, fn) // expected-error {{converting non-escaping parameter 'fn' to generic parameter 'T' may allow it to escape}}
203204

204205
withoutActuallyEscaping(fn) { _ in } // Ok
205206
withoutActuallyEscaping(fn) { (_: () -> Int) in } // Ok

test/attr/attr_escaping.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ struct StoresClosure {
3939
return [fn] // expected-error{{using non-escaping parameter 'fn' in a context expecting an @escaping closure}}
4040
}
4141

42+
func dictPack(_ fn: () -> Int) -> [String: () -> Int] {
43+
// expected-note@-1{{parameter 'fn' is implicitly non-escaping}} {{23-23=@escaping }}
44+
return ["ultimate answer": fn] // expected-error{{using non-escaping parameter 'fn' in a context expecting an @escaping closure}}
45+
}
46+
4247
func arrayPack(_ fn: @escaping () -> Int, _ fn2 : () -> Int) -> [() -> Int] {
4348
// expected-note@-1{{parameter 'fn2' is implicitly non-escaping}} {{53-53=@escaping }}
4449

@@ -147,7 +152,8 @@ class FooClass {
147152
}
148153
var computedEscaping : (@escaping ()->Int)->Void {
149154
get { return stored! }
150-
set(newValue) { stored = newValue } // expected-error{{cannot assign value of type '(@escaping () -> Int) -> Void' to type 'Optional<(() -> Int) -> Void>'}}
155+
set(newValue) { stored = newValue } // expected-error{{assigning non-escaping parameter 'newValue' to an @escaping closure}}
156+
// expected-note@-1 {{parameter 'newValue' is implicitly non-escaping}}
151157
}
152158
}
153159

test/attr/attr_noescape.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,9 @@ func overloadedEach<P: P2, T>(_ source: P, _ transform: @escaping (P.Element) ->
203203
struct S : P2 {
204204
typealias Element = Int
205205
func each(_ transform: @noescape (Int) -> ()) { // expected-error{{unknown attribute 'noescape'}}
206-
overloadedEach(self, // expected-error {{cannot invoke 'overloadedEach' with an argument list of type '(S, (Int) -> (), Int)'}}
207-
// expected-note @-1 {{overloads for 'overloadedEach' exist with these partially matching parameter lists: (O, @escaping (O.Element) -> (), T), (P, @escaping (P.Element) -> (), T)}}
208-
transform, 1)
206+
// expected-note@-1 {{parameter 'transform' is implicitly non-escaping}}
207+
overloadedEach(self, transform, 1)
208+
// expected-error@-1 {{passing non-escaping parameter 'transform' to function expecting an @escaping closure}}
209209
}
210210
}
211211

0 commit comments

Comments
 (0)