Skip to content

Commit 16641da

Browse files
authored
Merge pull request #24525 from xedin/diag-noescape-param-assingment-5.1
[5.1][ConstraintSystem] Improve @escaping parameter diagnostics
2 parents da5fa52 + ee71e92 commit 16641da

12 files changed

+227
-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, Type))
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: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ void constraints::simplifyLocator(Expr *&anchor,
142142
continue;
143143
}
144144

145+
if (auto subscriptExpr = dyn_cast<SubscriptExpr>(anchor)) {
146+
anchor = subscriptExpr->getIndex();
147+
path = path.slice(1);
148+
continue;
149+
}
150+
145151
if (auto objectLiteralExpr = dyn_cast<ObjectLiteralExpr>(anchor)) {
146152
targetAnchor = nullptr;
147153
targetPath.clear();
@@ -203,10 +209,10 @@ void constraints::simplifyLocator(Expr *&anchor,
203209
continue;
204210

205211
case ConstraintLocator::NamedTupleElement:
206-
case ConstraintLocator::TupleElement:
212+
case ConstraintLocator::TupleElement: {
207213
// Extract tuple element.
214+
unsigned index = path[0].getValue();
208215
if (auto tupleExpr = dyn_cast<TupleExpr>(anchor)) {
209-
unsigned index = path[0].getValue();
210216
if (index < tupleExpr->getNumElements()) {
211217
// Append this extraction to the target locator path.
212218
if (targetAnchor) {
@@ -218,7 +224,16 @@ void constraints::simplifyLocator(Expr *&anchor,
218224
continue;
219225
}
220226
}
227+
228+
if (auto *CE = dyn_cast<CollectionExpr>(anchor)) {
229+
if (index < CE->getNumElements()) {
230+
anchor = CE->getElement(index);
231+
path = path.slice(1);
232+
continue;
233+
}
234+
}
221235
break;
236+
}
222237

223238
case ConstraintLocator::ApplyArgToParam:
224239
// Extract tuple element.
@@ -2049,34 +2064,9 @@ bool FailureDiagnosis::diagnoseNonEscapingParameterToEscaping(
20492064
if (!srcFT || !dstFT || !srcFT->isNoEscape() || dstFT->isNoEscape())
20502065
return false;
20512066

2052-
// Pick a specific diagnostic for the specific use
2053-
auto paramDecl = cast<ParamDecl>(declRef->getDecl());
2054-
switch (dstPurpose) {
2055-
case CTP_CallArgument:
2056-
CS.TC.diagnose(declRef->getLoc(), diag::passing_noescape_to_escaping,
2057-
paramDecl->getName());
2058-
break;
2059-
case CTP_AssignSource:
2060-
CS.TC.diagnose(declRef->getLoc(), diag::assigning_noescape_to_escaping,
2061-
paramDecl->getName());
2062-
break;
2063-
2064-
default:
2065-
CS.TC.diagnose(declRef->getLoc(), diag::general_noescape_to_escaping,
2066-
paramDecl->getName());
2067-
break;
2068-
}
2069-
2070-
// Give a note and fixit
2071-
InFlightDiagnostic note = CS.TC.diagnose(
2072-
paramDecl->getLoc(), diag::noescape_parameter, paramDecl->getName());
2073-
2074-
if (!paramDecl->isAutoClosure()) {
2075-
note.fixItInsert(paramDecl->getTypeLoc().getSourceRange().Start,
2076-
"@escaping ");
2077-
} // TODO: add in a fixit for autoclosure
2078-
2079-
return true;
2067+
NoEscapeFuncToTypeConversionFailure failure(
2068+
expr, CS, CS.getConstraintLocator(expr), dstType);
2069+
return failure.diagnoseAsError();
20802070
}
20812071

20822072
bool FailureDiagnosis::diagnoseContextualConversionError(

lib/Sema/CSDiagnostics.cpp

Lines changed: 135 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);
@@ -446,6 +449,138 @@ bool NoEscapeFuncToTypeConversionFailure::diagnoseAsError() {
446449
return true;
447450
}
448451

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

12911299
if (matchFunctionRepresentations(func1->getExtInfo().getRepresentation(),
12921300
func2->getExtInfo().getRepresentation(),

lib/Sema/ConstraintLocator.cpp

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

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

lib/Sema/ConstraintLocator.h

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

567+
/// Determine whether this locator points to the generic parameter.
568+
bool isForGenericParameter() const;
569+
567570
/// Produce a profile of this locator, for use in a folding set.
568571
static void Profile(llvm::FoldingSetNodeID &id, Expr *anchor,
569572
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)