Skip to content

3.0 noescape by default #3948

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2310,6 +2310,15 @@ ERROR(closure_noescape_use,none,
ERROR(decl_closure_noescape_use,none,
"declaration closing over non-escaping parameter %0 may allow it to escape",
(Identifier))
ERROR(passing_noescape_to_escaping,none,
"passing non-escaping parameter %0 to function expecting an @escaping closure",
(Identifier))
ERROR(assigning_noescape_to_escaping,none,
"assigning non-escaping parameter %0 to an @escaping closure",
(Identifier))
ERROR(general_noescape_to_escaping,none,
"using non-escaping parameter %0 in a context expecting an @escaping closure",
(Identifier))

ERROR(capture_across_type_decl,none,
"%0 declaration cannot close over value %1 defined in outer scope",
Expand Down
61 changes: 61 additions & 0 deletions lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3593,6 +3593,62 @@ addTypeCoerceFixit(InFlightDiagnostic &diag, ConstraintSystem *CS,
return false;
}

/// Try to diagnose common errors involving implicitly non-escaping parameters
/// of function type, giving more specific and simpler diagnostics, attaching
/// notes on the parameter, and offering fixits to insert @escaping. Returns
/// true if it detects and issues an error, false if it does nothing.
static bool tryDiagnoseNonEscapingParameterToEscaping(Expr *expr, Type srcType,
Type dstType,
ConstraintSystem *CS) {
assert(expr && CS);
// Need to be referencing a parameter of function type
auto declRef = dyn_cast<DeclRefExpr>(expr);
if (!declRef || !isa<ParamDecl>(declRef->getDecl()) ||
!declRef->getType()->is<FunctionType>())
return false;

// Must be from non-escaping function to escaping function
auto srcFT = srcType->getAs<FunctionType>();
auto destFT = dstType->getAs<FunctionType>();
if (!srcFT || !destFT || !srcFT->isNoEscape() || destFT->isNoEscape())
return false;

// Function types must be equivalent modulo @escaping, @convention, etc.
if (!destFT->isEqual(srcFT->withExtInfo(destFT->getExtInfo())))
return false;

// Pick a specific diagnostic for the specific use
auto paramDecl = cast<ParamDecl>(declRef->getDecl());
switch (CS->getContextualTypePurpose()) {
case CTP_CallArgument:
CS->TC.diagnose(declRef->getLoc(), diag::passing_noescape_to_escaping,
paramDecl->getName());
break;
case CTP_AssignSource:
CS->TC.diagnose(declRef->getLoc(), diag::assigning_noescape_to_escaping,
paramDecl->getName());
break;

default:
CS->TC.diagnose(declRef->getLoc(), diag::general_noescape_to_escaping,
paramDecl->getName());
break;
}

// Give a note and fixit
InFlightDiagnostic note = CS->TC.diagnose(
paramDecl->getLoc(), srcFT->isAutoClosure() ? diag::noescape_autoclosure
: diag::noescape_parameter,
paramDecl->getName());

if (!srcFT->isAutoClosure()) {
note.fixItInsert(paramDecl->getTypeLoc().getSourceRange().Start,
"@escaping ");
} // TODO: add in a fixit for autoclosure

return true;
}

bool FailureDiagnosis::diagnoseContextualConversionError() {
// If the constraint system has a contextual type, then we can test to see if
// this is the problem that prevents us from solving the system.
Expand Down Expand Up @@ -3855,6 +3911,11 @@ bool FailureDiagnosis::diagnoseContextualConversionError() {
}
}

// Try for better/more specific diagnostics for non-escaping to @escaping
if (tryDiagnoseNonEscapingParameterToEscaping(expr, exprType, contextualType,
CS))
return true;

// When complaining about conversion to a protocol type, complain about
// conformance instead of "conversion".
if (contextualType->is<ProtocolType>() ||
Expand Down
8 changes: 6 additions & 2 deletions test/attr/attr_autoclosure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ func overloadedEach<P: P2>(_ source: P, _ closure: @escaping () -> ()) {
struct S : P2 {
typealias Element = Int
func each(_ closure: @autoclosure () -> ()) {
overloadedEach(self, closure) // expected-error {{invalid conversion from non-escaping function of type '@autoclosure () -> ()' to potentially escaping function type '() -> ()'}}
// expected-note@-1{{parameter 'closure' is implicitly non-escaping because it was declared @autoclosure}}

overloadedEach(self, closure) // expected-error {{passing non-escaping parameter 'closure' to function expecting an @escaping closure}}
}
}

Expand Down Expand Up @@ -87,7 +89,9 @@ class Sub : Super {
func func12_sink(_ x: @escaping () -> Int) { }

func func12a(_ x: @autoclosure () -> Int) {
func12_sink(x) // expected-error{{invalid conversion from non-escaping function of type '@autoclosure () -> Int' to potentially escaping function type '() -> Int'}}
// expected-note@-1{{parameter 'x' is implicitly non-escaping because it was declared @autoclosure}}

func12_sink(x) // expected-error {{passing non-escaping parameter 'x' to function expecting an @escaping closure}}
}
func func12b(_ x: @autoclosure(escaping) () -> Int) {
func12_sink(x)
Expand Down
45 changes: 45 additions & 0 deletions test/attr/attr_escaping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,48 @@ func wrongParamType(a: @escaping Int) {} // expected-error {{@escaping attribute
func conflictingAttrs(_ fn: @noescape @escaping () -> Int) {} // expected-error {{@escaping conflicts with @noescape}}

func takesEscaping(_ fn: @escaping () -> Int) {} // ok

func callEscapingWithNoEscape(_ fn: () -> Int) {
// expected-note@-1{{parameter 'fn' is implicitly non-escaping}} {{37-37=@escaping }}
// expected-note@-2{{parameter 'fn' is implicitly non-escaping}} {{37-37=@escaping }}

takesEscaping(fn) // expected-error{{passing non-escaping parameter 'fn' to function expecting an @escaping closure}}
let _ = fn // expected-error{{non-escaping parameter 'fn' may only be called}}
}

typealias IntSugar = Int
func callSugared(_ fn: () -> IntSugar) {
// expected-note@-1{{parameter 'fn' is implicitly non-escaping}} {{24-24=@escaping }}

takesEscaping(fn) // expected-error{{passing non-escaping parameter 'fn' to function expecting an @escaping closure}}
}

struct StoresClosure {
var closure : () -> Int
init(_ fn: () -> Int) {
// expected-note@-1{{parameter 'fn' is implicitly non-escaping}} {{14-14=@escaping }}

closure = fn // expected-error{{assigning non-escaping parameter 'fn' to an @escaping closure}}
}

func arrayPack(_ fn: () -> Int) -> [()->Int] {
// expected-note@-1{{parameter 'fn' is implicitly non-escaping}} {{24-24=@escaping }}

return [fn] // expected-error{{using non-escaping parameter 'fn' in a context expecting an @escaping closure}}
}

func arrayPack(_ fn: @escaping () -> Int, _ fn2 : () -> Int) -> [()->Int] {
// expected-note@-1{{parameter 'fn2' is implicitly non-escaping}} {{53-53=@escaping }}

return [fn, fn2] // expected-error{{using non-escaping parameter 'fn2' in a context expecting an @escaping closure}}
}
}

func takesEscapingBlock(_ fn: @escaping @convention(block) () -> Void) {
fn()
}
func callEscapingWithNoEscapeBlock(_ fn: () -> Void) {
// expected-note@-1{{parameter 'fn' is implicitly non-escaping}} {{42-42=@escaping }}

takesEscapingBlock(fn) // expected-error{{passing non-escaping parameter 'fn' to function expecting an @escaping closure}}
}
7 changes: 4 additions & 3 deletions test/attr/attr_noescape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func takesNoEscapeClosure(_ fn : () -> Int) {
// expected-note@-1{{parameter 'fn' is implicitly non-escaping}} {{34-34=@escaping }}
// expected-note@-2{{parameter 'fn' is implicitly non-escaping}} {{34-34=@escaping }}
// expected-note@-3{{parameter 'fn' is implicitly non-escaping}} {{34-34=@escaping }}
// expected-note@-4{{parameter 'fn' is implicitly non-escaping}} {{34-34=@escaping }}
takesNoEscapeClosure { 4 } // ok

_ = fn() // ok
Expand All @@ -32,7 +33,7 @@ func takesNoEscapeClosure(_ fn : () -> Int) {

takesNoEscapeClosure(fn) // ok

doesEscape(fn) // expected-error {{invalid conversion from non-escaping function of type '() -> Int' to potentially escaping function type '() -> Int'}}
doesEscape(fn) // expected-error {{passing non-escaping parameter 'fn' to function expecting an @escaping closure}}
takesGenericClosure(4, fn) // ok
takesGenericClosure(4) { fn() } // ok.
}
Expand Down Expand Up @@ -254,8 +255,8 @@ func curriedFlatMap2<A, B>(_ x: [A]) -> (@noescape (A) -> [B]) -> [B] {

func bad(_ a : @escaping (Int)-> Int) -> Int { return 42 }
func escapeNoEscapeResult(_ x: [Int]) -> (@noescape (Int) -> Int) -> Int {
return { f in
bad(f) // expected-error {{invalid conversion from non-escaping function of type '(Int) -> Int' to potentially escaping function type '(Int) -> Int'}}
return { f in // expected-note{{parameter 'f' is implicitly non-escaping}}
bad(f) // expected-error {{passing non-escaping parameter 'f' to function expecting an @escaping closure}}
}
}

Expand Down