Help needed: SE-0035 design detail

Hi all,

I'm in the process of implementing SE-0035, which limits capturing inout
parameter to @noescape contexts. The proposal is clear on capture behavior for
the following:

1. closure literals
2. nested function passed as arguments.

But I'm not sure what to do with this case, in which 'x' escapes.

func captureAndEscape(inout x: Int) -> () -> Void { func foo()
{ _ = x } return foo }

The most obvious answer is it should be considered with the same rule as
a closure literal, but a nested function can not have @noescape in its type
(for now anyways).

So, should this be legal, then? If not, where/how should the error be?

Ideally IMO, we would consider a reference to a local function to be `@noescape` or not based on how the reference is used, rather than the type of the function declaration itself:

func escapes(_: () -> ())
func noescapes(_: @noescape () -> ())

func foo(inout x: Int) -> () -> () {
  func local() { _ = x }

  local() // full application doesn't form a closure, ref is @noescape here

  noescapes(local) // parameter is noescape, so ref is noescape

  escapes(local) // parameter is escapable, so ref is escapable

  var x = local // assigning to var forms a closure, so ref is escapabale

  return local // returning forms a closure, so ref is escapable
}

-Joe

···

On Apr 10, 2016, at 12:46 PM, Daniel Duan via swift-dev <swift-dev@swift.org> wrote:

Hi all,

I'm in the process of implementing SE-0035, which limits capturing inout
parameter to @noescape contexts. The proposal is clear on capture behavior for
the following:

1. closure literals
2. nested function passed as arguments.

But I'm not sure what to do with this case, in which 'x' escapes.

func captureAndEscape(inout x: Int) -> () -> Void { func foo()
{ _ = x } return foo }

The most obvious answer is it should be considered with the same rule as
a closure literal, but a nested function can not have @noescape in its type
(for now anyways).

So, should this be legal, then? If not, where/how should the error be?

Great! I'll check return statements in addition to ApplyExpr arguments.

- Daniel Duan

Hi all,

I'm in the process of implementing SE-0035, which limits capturing inout
parameter to @noescape contexts. The proposal is clear on capture behavior for
the following:

1. closure literals
2. nested function passed as arguments.

But I'm not sure what to do with this case, in which 'x' escapes.

func captureAndEscape(inout x: Int) -> () -> Void { func foo()
{ _ = x } return foo }

The most obvious answer is it should be considered with the same rule as
a closure literal, but a nested function can not have @noescape in its type
(for now anyways).

So, should this be legal, then? If not, where/how should the error be?

Ideally IMO, we would consider a reference to a local function to be `@noescape` or not based on how the reference is used, rather than the type of the function declaration itself:

func escapes(_: () -> ())
func noescapes(_: @noescape () -> ())

func foo(inout x: Int) -> () -> () {
  func local() { _ = x }

  local() // full application doesn't form a closure, ref is @noescape here

  noescapes(local) // parameter is noescape, so ref is noescape

  escapes(local) // parameter is escapable, so ref is escapable

  var x = local // assigning to var forms a closure, so ref is escapabale

  return local // returning forms a closure, so ref is escapable
}

-Joe

···

On Mon, Apr 11, 2016 at 8:44 AM -0700, "Joe Groff" <jgroff@apple.com> wrote:

On Apr 10, 2016, at 12:46 PM, Daniel Duan via swift-dev wrote:

Joe Groff via swift-dev <swift-dev <at> swift.org> writes:

  return local // returning forms a closure, so ref is escapable

My plan was to check all return statements with FuncDecl as results, if
any of them has inout captures, complain.

But this diagnosis is too coarse. A function can capture from any level of
outer scope, so sometimes it's safe to let a inout escape, as long as the
reference is still inside the scope it's captured from. Example:

func a(inout x: Int) -> () -> Void {
    func b() -> () -> Void {
        func c() {
            _ = x
        }
        return c // is this safe? We'll see…
    }

    let f = b() // 'x' captured by f hasn't *really* escaped.

    return f // now we have a problem
}

It's unclear whether the statement 'return c' is problematic.

So there are two paths:

1. make *any* escaping inout capture an error
2. track down original scope of each inout capture, compare it with the return
   statement.

At the moment I haven't looked into how feasible 2 is.

What's your opinion?

A local analysis is fine, and more predictable anyways. We already impose these constraints on @noescape parameters. If the local function reference appears as part of a function application or as a noescape argument, then treat the reference as noescape, otherwise consider it to escape.

-Joe

···

On Apr 11, 2016, at 1:28 PM, Daniel Duan via swift-dev <swift-dev@swift.org> wrote:

Joe Groff via swift-dev <swift-dev <at> swift.org> writes:

return local // returning forms a closure, so ref is escapable

My plan was to check all return statements with FuncDecl as results, if
any of them has inout captures, complain.

But this diagnosis is too coarse. A function can capture from any level of
outer scope, so sometimes it's safe to let a inout escape, as long as the
reference is still inside the scope it's captured from. Example:

func a(inout x: Int) -> () -> Void {
   func b() -> () -> Void {
       func c() {
           _ = x
       }
       return c // is this safe? We'll see…
   }

   let f = b() // 'x' captured by f hasn't *really* escaped.

   return f // now we have a problem
}

It's unclear whether the statement 'return c' is problematic.

So there are two paths:

1. make *any* escaping inout capture an error
2. track down original scope of each inout capture, compare it with the return
  statement.

At the moment I haven't looked into how feasible 2 is.

Ah, closures do get the same treatment here. Good point.

- Daniel Duan

···

On Mon, Apr 11, 2016 at 1:39 PM -0700, "Joe Groff" <jgroff@apple.com> wrote:

On Apr 11, 2016, at 1:28 PM, Daniel Duan via swift-dev wrote:

Joe Groff via swift-dev swift.org> writes:

return local // returning forms a closure, so ref is escapable

My plan was to check all return statements with FuncDecl as results, if
any of them has inout captures, complain.

But this diagnosis is too coarse. A function can capture from any level of
outer scope, so sometimes it's safe to let a inout escape, as long as the
reference is still inside the scope it's captured from. Example:

func a(inout x: Int) -> () -> Void {
   func b() -> () -> Void {
       func c() {
           _ = x
       }
       return c // is this safe? We'll see…
   }

   let f = b() // 'x' captured by f hasn't *really* escaped.

   return f // now we have a problem
}

It's unclear whether the statement 'return c' is problematic.

So there are two paths:

1. make *any* escaping inout capture an error
2. track down original scope of each inout capture, compare it with the return
  statement.

At the moment I haven't looked into how feasible 2 is.

A local analysis is fine, and more predictable anyways. We already impose these constraints on @noescape parameters. If the local function reference appears as part of a function application or as a noescape argument, then treat the reference as noescape, otherwise consider it to escape.

-Joe