Skip to content

Commit 360ce72

Browse files
Merge pull request #38117 from LucianoPAlmeida/SR14720-escaping
[SR-14720][Sema] Improve assign escapeness mismatch diagnostic for function type parameters
2 parents 80b6fbd + 0519dd7 commit 360ce72

File tree

5 files changed

+147
-5
lines changed

5 files changed

+147
-5
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3890,6 +3890,12 @@ ERROR(converting_noattrfunc_to_type,none,
38903890
"converting %select{non-escaping|non-concurrent function}0 value to %1 "
38913891
"may %select{allow it to escape|introduce data races}0",
38923892
(unsigned, Type))
3893+
NOTE(escape_expected_at_parameter_position,none,
3894+
"parameter #%0 expects escaping value of type %1",
3895+
(unsigned, Type))
3896+
NOTE(add_explicit_escaping,none,
3897+
"add explicit @escaping to function parameter #%0",
3898+
(unsigned))
38933899

38943900
ERROR(converting_func_loses_global_actor,none,
38953901
"converting function value of type %0 to %1 loses global actor %2",

lib/Sema/CSDiagnostics.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,94 @@ bool AttributedFuncToTypeConversionFailure::diagnoseAsError() {
905905
return true;
906906
}
907907

908+
static VarDecl *getDestinationVarDecl(AssignExpr *AE,
909+
const Solution &solution) {
910+
ConstraintLocator *locator = nullptr;
911+
if (auto *URDE = dyn_cast<UnresolvedDotExpr>(AE->getDest())) {
912+
locator = solution.getConstraintLocator(URDE, {ConstraintLocator::Member});
913+
} else if (auto *declRef = dyn_cast<DeclRefExpr>(AE->getDest())) {
914+
locator = solution.getConstraintLocator(declRef);
915+
}
916+
if (!locator)
917+
return nullptr;
918+
919+
auto overload = solution.getOverloadChoiceIfAvailable(locator);
920+
if (!overload)
921+
return nullptr;
922+
923+
return dyn_cast_or_null<VarDecl>(overload->choice.getDecl());
924+
}
925+
926+
bool AttributedFuncToTypeConversionFailure::
927+
diagnoseFunctionParameterEscapenessMismatch(AssignExpr *AE) const {
928+
auto loc = getLocator();
929+
if (attributeKind != Escaping)
930+
return false;
931+
932+
if (!loc->findLast<LocatorPathElt::FunctionArgument>())
933+
return false;
934+
935+
auto destType = getType(AE->getDest())->lookThroughAllOptionalTypes();
936+
auto destFnType = destType->castTo<FunctionType>();
937+
auto sourceType = getType(AE->getSrc())->lookThroughAllOptionalTypes();
938+
939+
// The tuple locator element will give us the exact parameter mismatch
940+
// position.
941+
auto tupleElt = loc->getLastElementAs<LocatorPathElt::TupleElement>();
942+
auto mismatchPosition = tupleElt ? tupleElt->getIndex() : 0;
943+
auto param = destFnType->getParams()[mismatchPosition];
944+
945+
emitDiagnostic(diag::cannot_convert_assign, sourceType, destType);
946+
emitDiagnosticAt(AE->getDest()->getLoc(),
947+
diag::escape_expected_at_parameter_position,
948+
mismatchPosition, param.getParameterType());
949+
950+
auto &solution = getSolution();
951+
auto decl = getDestinationVarDecl(AE, solution);
952+
// We couldn't find a declaration to add an extra note with a fix-it but
953+
// the main diagnostic was already covered.
954+
if (!decl)
955+
return true;
956+
957+
auto declRepr = decl->getTypeReprOrParentPatternTypeRepr();
958+
class TopLevelFuncReprFinder : public ASTWalker {
959+
bool walkToTypeReprPre(TypeRepr *TR) override {
960+
FnRepr = dyn_cast<FunctionTypeRepr>(TR);
961+
return FnRepr == nullptr;
962+
}
963+
964+
public:
965+
FunctionTypeRepr *FnRepr;
966+
TopLevelFuncReprFinder() : FnRepr(nullptr) {}
967+
};
968+
969+
// Look to find top-level function repr that maybe inside optional
970+
// representations.
971+
TopLevelFuncReprFinder fnFinder;
972+
declRepr->walk(fnFinder);
973+
974+
auto declFnRepr = fnFinder.FnRepr;
975+
if (!declFnRepr)
976+
return true;
977+
978+
auto note = emitDiagnosticAt(decl->getLoc(), diag::add_explicit_escaping,
979+
mismatchPosition);
980+
auto argsRepr = declFnRepr->getArgsTypeRepr();
981+
auto argRepr = argsRepr->getElement(mismatchPosition).Type;
982+
if (!param.isAutoClosure()) {
983+
note.fixItInsert(argRepr->getStartLoc(), "@escaping ");
984+
} else {
985+
auto attrRepr = dyn_cast<AttributedTypeRepr>(argRepr);
986+
if (attrRepr) {
987+
auto autoclosureEndLoc = Lexer::getLocForEndOfToken(
988+
getASTContext().SourceMgr,
989+
attrRepr->getAttrs().getLoc(TAK_autoclosure));
990+
note.fixItInsertAfter(autoclosureEndLoc, " @escaping");
991+
}
992+
}
993+
return true;
994+
}
995+
908996
bool AttributedFuncToTypeConversionFailure::diagnoseParameterUse() const {
909997
auto convertTo = getToType();
910998
// If the other side is not a function, we have common case diagnostics
@@ -961,6 +1049,11 @@ bool AttributedFuncToTypeConversionFailure::diagnoseParameterUse() const {
9611049
diagnostic = diag::passing_noattrfunc_to_attrfunc;
9621050
}
9631051
} else if (auto *AE = getAsExpr<AssignExpr>(getRawAnchor())) {
1052+
// Attempt to diagnose escape/non-escape mismatch in function
1053+
// parameter position.
1054+
if (diagnoseFunctionParameterEscapenessMismatch(AE))
1055+
return true;
1056+
9641057
if (auto *DRE = dyn_cast<DeclRefExpr>(AE->getSrc())) {
9651058
PD = dyn_cast<ParamDecl>(DRE->getDecl());
9661059
diagnostic = diag::assigning_noattrfunc_to_attrfunc;

lib/Sema/CSDiagnostics.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,11 @@ class AttributedFuncToTypeConversionFailure final : public ContextualFailure {
740740
/// argument, or trying to assign it to a variable which expects @escaping
741741
/// or @Sendable function.
742742
bool diagnoseParameterUse() const;
743+
744+
/// Emit a tailored diagnostic for a no-escape/espace mismatch for function
745+
/// arguments where the mismatch has to take into account that a
746+
/// function type subtype relation in the parameter position is contravariant.
747+
bool diagnoseFunctionParameterEscapenessMismatch(AssignExpr *) const;
743748
};
744749

745750
/// Diagnose failure where a global actor attribute is dropped when

test/Constraints/function_conversion.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ func twoFns(_ f: (Int) -> Int, _ g: @escaping (Int) -> Int) {
5757
takesAny(consumeNoEscape)
5858
takesAny(consumeEscaping)
5959

60-
var noEscapeParam: ((Int) -> Int) -> () = consumeNoEscape
60+
var noEscapeParam: ((Int) -> Int) -> () = consumeNoEscape // expected-note {{add explicit @escaping to function parameter}}{{21-21=@escaping }}
6161
var escapingParam: (@escaping (Int) -> Int) -> () = consumeEscaping
62-
noEscapeParam = escapingParam // expected-error {{converting non-escaping value to '(Int) -> Int' may allow it to escape}}
62+
noEscapeParam = escapingParam // expected-error {{cannot assign value of type '(@escaping (Int) -> Int) -> ()' to type '((Int) -> Int) -> ()'}}
63+
// expected-note@-1{{parameter #0 expects escaping value of type '(Int) -> Int'}}
6364

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

test/attr/attr_escaping.swift

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,15 @@ func takesNoEscapeFunction(fn: () -> ()) { // expected-note {{parameter 'fn' is
146146

147147

148148
class FooClass {
149-
var stored : Optional<(()->Int)->Void> = nil
149+
var stored : Optional<(()->Int)->Void> = nil // expected-note {{add explicit @escaping to function parameter #0}} {{26-26=@escaping }}
150150
var computed : (()->Int)->Void {
151151
get { return stored! }
152152
set(newValue) { stored = newValue } // ok
153153
}
154154
var computedEscaping : (@escaping ()->Int)->Void {
155155
get { return stored! }
156-
set(newValue) { stored = newValue } // expected-error{{assigning non-escaping parameter 'newValue' to an @escaping closure}}
157-
// expected-note@-1 {{parameter 'newValue' is implicitly non-escaping}}
156+
set(newValue) { stored = newValue } // expected-error{{cannot assign value of type '(@escaping () -> Int) -> Void' to type '(() -> Int) -> Void'}}
157+
// expected-note@-1{{parameter #0 expects escaping value of type '() -> Int'}}
158158
}
159159
}
160160

@@ -230,3 +230,40 @@ extension SR_9760 {
230230

231231
// SR-9178
232232
func foo<T>(_ x: @escaping T) {} // expected-error 1{{@escaping attribute only applies to function types}}
233+
234+
// SR-14720
235+
var global: ((() -> Void) -> Void)? = nil // expected-note {{add explicit @escaping to function parameter #0}} {{15-15=@escaping }}
236+
237+
class SR14720 {
238+
let ok: (@escaping () -> Void) -> Void // OK
239+
let callback: (() -> Void) -> Void // expected-note {{add explicit @escaping to function parameter #0}} {{18-18=@escaping }}
240+
let callback1: (() -> Void, () -> Void) -> Void // expected-note {{add explicit @escaping to function parameter #1}} {{31-31=@escaping }}
241+
let callbackAuto: (@autoclosure () -> Void) -> Void // expected-note {{add explicit @escaping to function parameter #0}} {{34-34= @escaping}}
242+
let callbackOpt: ((() -> Void) -> Void)? // expected-note{{add explicit @escaping to function parameter #0}} {{22-22=@escaping }}
243+
244+
init(f: @escaping (@escaping() -> Void) -> Void) {
245+
self.callback = f // expected-error{{cannot assign value of type '(@escaping () -> Void) -> Void' to type '(() -> Void) -> Void'}}
246+
// expected-note@-1{{parameter #0 expects escaping value of type '() -> Void'}}
247+
self.ok = f // Ok
248+
}
249+
250+
init(af: @escaping (@escaping() -> Void) -> Void) {
251+
self.callbackOpt = af // expected-error{{cannot assign value of type '(@escaping () -> Void) -> Void' to type '(() -> Void) -> Void'}}
252+
// expected-note@-1{{parameter #0 expects escaping value of type '() -> Void'}}
253+
}
254+
255+
init(ag: @escaping (@escaping() -> Void) -> Void) {
256+
global = ag // expected-error{{cannot assign value of type '(@escaping () -> Void) -> Void' to type '(() -> Void) -> Void'}}
257+
// expected-note@-1{{parameter #0 expects escaping value of type '() -> Void'}}
258+
}
259+
260+
init(a: @escaping (@escaping () -> Void) -> Void) {
261+
self.callbackAuto = a // expected-error{{cannot assign value of type '(@escaping () -> Void) -> Void' to type '(@autoclosure () -> Void) -> Void'}}
262+
// expected-note@-1{{parameter #0 expects escaping value of type '() -> Void'}}
263+
}
264+
265+
init(f: @escaping (() -> Void, @escaping() -> Void) -> Void) {
266+
self.callback1 = f // expected-error{{cannot assign value of type '(() -> Void, @escaping () -> Void) -> Void' to type '(() -> Void, () -> Void) -> Void'}}
267+
// expected-note@-1{{parameter #1 expects escaping value of type '() -> Void'}}
268+
}
269+
}

0 commit comments

Comments
 (0)