Skip to content

Commit 43526d0

Browse files
authored
Merge pull request swiftlang#24431 from xedin/diag-noescape-param-assignment
[ConstraintSystem] Improve @escaping parameter diagnostics
2 parents 5043ed4 + d275c8a commit 43526d0

12 files changed

+221
-44
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3075,10 +3075,15 @@ NOTE(silence_debug_description_in_interpolation_segment_call,none,
30753075
NOTE(noescape_parameter,none,
30763076
"parameter %0 is implicitly non-escaping",
30773077
(Identifier))
3078+
NOTE(generic_parameters_always_escaping,none,
3079+
"generic parameters are always considered '@escaping'", ())
30783080

30793081
ERROR(passing_noescape_to_escaping,none,
30803082
"passing non-escaping parameter %0 to function expecting an @escaping closure",
30813083
(Identifier))
3084+
ERROR(converting_noespace_param_to_generic_type,none,
3085+
"converting non-escaping parameter %0 to generic parameter %1 may allow it to escape",
3086+
(Identifier, Type))
30823087
ERROR(assigning_noescape_to_escaping,none,
30833088
"assigning non-escaping parameter %0 to an @escaping closure",
30843089
(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: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,9 @@ bool LabelingFailure::diagnoseAsError() {
417417
bool NoEscapeFuncToTypeConversionFailure::diagnoseAsError() {
418418
auto *anchor = getAnchor();
419419

420+
if (diagnoseParameterUse())
421+
return true;
422+
420423
if (ConvertTo) {
421424
emitDiagnostic(anchor->getLoc(), diag::converting_noescape_to_type,
422425
ConvertTo);
@@ -441,6 +444,138 @@ bool NoEscapeFuncToTypeConversionFailure::diagnoseAsError() {
441444
return true;
442445
}
443446

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

lib/Sema/ConstraintLocator.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ bool ConstraintLocator::isForKeyPathComponent() const {
170170
});
171171
}
172172

173+
bool ConstraintLocator::isForGenericParameter() const {
174+
auto path = getPath();
175+
return !path.empty() &&
176+
path.back().getKind() == ConstraintLocator::GenericParameter;
177+
}
178+
173179
void ConstraintLocator::dump(SourceManager *sm) {
174180
dump(sm, llvm::errs());
175181
llvm::errs() << "\n";

lib/Sema/ConstraintLocator.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,9 @@ class ConstraintLocator : public llvm::FoldingSetNode {
568568
/// components.
569569
bool isForKeyPathComponent() const;
570570

571+
/// Determine whether this locator points to the generic parameter.
572+
bool isForGenericParameter() const;
573+
571574
/// Produce a profile of this locator, for use in a folding set.
572575
static void Profile(llvm::FoldingSetNodeID &id, Expr *anchor,
573576
ArrayRef<PathElement> path);

test/Constraints/function.swift

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,24 +85,43 @@ 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}}
107107
takesAny(other) // expected-error {{converting non-escaping value to 'Any' may allow it to escape}}
108108
}
109+
110+
protocol P {
111+
associatedtype U
112+
}
113+
114+
func test_passing_noescape_function_to_dependent_member() {
115+
struct S<T : P> { // expected-note {{generic parameters are always considered '@escaping'}}
116+
func foo(_: T.U) {}
117+
}
118+
119+
struct Q : P {
120+
typealias U = () -> Int
121+
}
122+
123+
func test(_ s: S<Q>, fn: () -> Int) {
124+
s.foo(fn)
125+
// expected-error@-1 {{converting non-escaping parameter 'fn' to generic parameter 'Q.U' (aka '() -> Int') may allow it to escape}}
126+
}
127+
}

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

0 commit comments

Comments
 (0)