Compiler can't infer map transfer closure return type?


(Neil Faiman) #1

Using the version of Swift in Xcode 7.2.1, I compile the following program:

    let x1 = [1,2].map { _ in
        return "abc"
    }

    let x2 = [1,2].map { _ in
        let x = "abc"
        return x
    }

The assignment to x1 compiles successfully, but the assignment to x2 produces this error:

    Cannot invoke 'map' with an argument list of type '(@noescape (Int) throws -> _)'
    Expected an argument list of type '(@noescape (Self.Generator.Element) throws -> T)'

It seems that in both calls, the transform closure is returning a String, so I don’t see why the compiler can infer the return type in one case, but not in the other. Am I missing something obvious?

Thanks,

  Neil Faiman


(Dennis Weissmann) #2

Hi Neil,

indeed the compiler should be able to infer the type here.

The error message in the latest development snapshot (swift-DEVELOPMENT-SNAPSHOT-2016-03-01-a) is a little better:

Expression type '[_]' is ambiguous without more context

You can work around like this:

let x2 = [1,2].map { _ -> String in
  let x = "abc"
  return x
}

or

let x2: [String] = [1,2].map { _ in
  let x = "abc"
  return x
}

- Dennis

···

On Mar 7, 2016, at 12:55 AM, Neil Faiman via swift-users <swift-users@swift.org> wrote:

Using the version of Swift in Xcode 7.2.1, I compile the following program:

   let x1 = [1,2].map { _ in
       return "abc"
   }

   let x2 = [1,2].map { _ in
       let x = "abc"
       return x
   }

The assignment to x1 compiles successfully, but the assignment to x2 produces this error:

   Cannot invoke 'map' with an argument list of type '(@noescape (Int) throws -> _)'
   Expected an argument list of type '(@noescape (Self.Generator.Element) throws -> T)'

It seems that in both calls, the transform closure is returning a String, so I don’t see why the compiler can infer the return type in one case, but not in the other. Am I missing something obvious?

Thanks,

  Neil Faiman
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Chris Lattner) #3

Hi Neil,

indeed the compiler should be able to infer the type here.

The error message in the latest development snapshot (swift-DEVELOPMENT-SNAPSHOT-2016-03-01-a) is a little better:

Yep, further improvements are tracked by:
<rdar://problem/24209966> QoI: When a non-single-expression closure type can't be inferred, say that we didn't look at the body
<rdar://problem/22123191> QoI: multi-line closure with failure to infer result type should add a fixit

-Chris

···

On Mar 7, 2016, at 1:44 AM, Dennis Weissmann via swift-users <swift-users@swift.org> wrote:

Expression type '[_]' is ambiguous without more context

You can work around like this:

let x2 = [1,2].map { _ -> String in
  let x = "abc"
  return x
}

or

let x2: [String] = [1,2].map { _ in
  let x = "abc"
  return x
}

- Dennis

On Mar 7, 2016, at 12:55 AM, Neil Faiman via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Using the version of Swift in Xcode 7.2.1, I compile the following program:

   let x1 = [1,2].map { _ in
       return "abc"
   }

   let x2 = [1,2].map { _ in
       let x = "abc"
       return x
   }

The assignment to x1 compiles successfully, but the assignment to x2 produces this error:

   Cannot invoke 'map' with an argument list of type '(@noescape (Int) throws -> _)'
   Expected an argument list of type '(@noescape (Self.Generator.Element) throws -> T)'

It seems that in both calls, the transform closure is returning a String, so I don’t see why the compiler can infer the return type in one case, but not in the other. Am I missing something obvious?

Thanks,

  Neil Faiman
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Chris Lattner) #4

Oops, sorry, those are internal bugs, not external ones. In any case, the intent is clear: we should say why we failed to infer the type, and do further analysis in the error case to produce a fixit when we can tell what was “meant”.

-Chris

···

On Mar 7, 2016, at 10:06 AM, Chris Lattner <clattner@apple.com> wrote:

On Mar 7, 2016, at 1:44 AM, Dennis Weissmann via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi Neil,

indeed the compiler should be able to infer the type here.

The error message in the latest development snapshot (swift-DEVELOPMENT-SNAPSHOT-2016-03-01-a) is a little better:

Yep, further improvements are tracked by:
<rdar://problem/24209966 <rdar://problem/24209966>> QoI: When a non-single-expression closure type can't be inferred, say that we didn't look at the body
<rdar://problem/22123191 <rdar://problem/22123191>> QoI: multi-line closure with failure to infer result type should add a fixit


(Jordan Rose) #5

Swift very deliberately does not infer types across multiple statements in closure bodies. This is mostly an implementation restriction (our type-checking system can't handle it very well), but it's also a simple rule that explains the implementation restriction, rather than having type inference in closure bodies working some of the time but not all.

As Chris pointed out, the compiler could definitely do a better job communicating the problem, but actually changing the behavior here would require significant implementation work. So this is not considered a "bug", just an implementation-driven design choice.

Jordan

···

On Mar 7, 2016, at 1:44, Dennis Weissmann via swift-users <swift-users@swift.org> wrote:

indeed the compiler should be able to infer the type here.


(Neil Faiman) #6

OK. I understand the reasoning, and agree that it makes perfect sense. Thanks for the explanation.

That said … I’ll note that Swift already must have logic to infer a single result type from multiple expressions, to wit, the ?: handling logic. In principle, one could collect all the return expressions in a closure and apply the same logic to them. This would have the happy consequence that common trivial cases like “only one return statement” would work as expected.

But I understand that this is an enhancement request rather than a bug to be fixed, and won’t hold my breath. :slight_smile:

Regards,

  Neil Faiman

···

On Mar 7, 2016, at 4:33 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Swift very deliberately does not infer types across multiple statements in closure bodies. This is mostly an implementation restriction (our type-checking system can't handle it very well), but it's also a simple rule that explains the implementation restriction, rather than having type inference in closure bodies working some of the time but not all.


(Jordan Rose) #7

Multiple expressions and multiple statements aren't the same thing. You'd really want something like this to be inferrable too:

let names = enumValues.map {
  switch $0 {
  case .Foo:
    return ".Foo"
  case .Bar:
    return ".Bar"
  case .Baz
    return .Baz
  }
}

…but note that I messed up in that last return statement, and now it's not clear what the return type is supposed to be. (Maybe the first two are the mistakes.)

(Okay, so can we collect all the expressions? No, their types may depend on previous lines, which in turn may depend on the types of the arguments, which may also not be inferrable, although in this case they are.)

There are certainly ways to handle more cases than what we handle today, but then we start getting into a weird territory where it's not clear what's handled and what isn't. (Arguably we're already there given how often people ask the question.) Today there's at least a simple rule: if the closure consists of a single expression (or a single return statement), the compiler will look at the body; otherwise it won't.

Best,
Jordan

···

On Mar 7, 2016, at 15:08, Neil Faiman <neil.swift@faiman.org> wrote:

On Mar 7, 2016, at 4:33 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

Swift very deliberately does not infer types across multiple statements in closure bodies. This is mostly an implementation restriction (our type-checking system can't handle it very well), but it's also a simple rule that explains the implementation restriction, rather than having type inference in closure bodies working some of the time but not all.

OK. I understand the reasoning, and agree that it makes perfect sense. Thanks for the explanation.

That said … I’ll note that Swift already must have logic to infer a single result type from multiple expressions, to wit, the ?: handling logic. In principle, one could collect all the return expressions in a closure and apply the same logic to them. This would have the happy consequence that common trivial cases like “only one return statement” would work as expected.


(Erica Sadun) #8

Would it be so hard to have a rule that if there's an unambiguous expression as the last line of a closure that
it automagically acts as a default warn-on-unused-result return? So many of my two-line closures would
benefit from this.

-- E

···

On Mar 7, 2016, at 6:15 PM, Jordan Rose via swift-users <swift-users@swift.org> wrote:
Today there's at least a simple rule: if the closure consists of a single expression (or a single return statement), the compiler will look at the body; otherwise it won't.


(Jordan Rose) #9

What does that mean? You can't see how the closure is being called, so how do you know if the result is used or unused?

Jordan

···

On Mar 7, 2016, at 17:21, Erica Sadun <erica@ericasadun.com> wrote:

On Mar 7, 2016, at 6:15 PM, Jordan Rose via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
Today there's at least a simple rule: if the closure consists of a single expression (or a single return statement), the compiler will look at the body; otherwise it won't.

Would it be so hard to have a rule that if there's an unambiguous expression as the last line of a closure that
it automagically acts as a default warn-on-unused-result return? So many of my two-line closures would
benefit from this.


(Erica Sadun) #10

Okay, fine.

Would it be so hard to have a rule when there's an unambiguous expression as the last line of a closure that
it automagically acts as a default return? So many of my two-line closures would benefit from this.

-- E, clarifying

···

On Mar 7, 2016, at 6:24 PM, Jordan Rose <jordan_rose@apple.com> wrote:

On Mar 7, 2016, at 17:21, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

On Mar 7, 2016, at 6:15 PM, Jordan Rose via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
Today there's at least a simple rule: if the closure consists of a single expression (or a single return statement), the compiler will look at the body; otherwise it won't.

Would it be so hard to have a rule that if there's an unambiguous expression as the last line of a closure that
it automagically acts as a default warn-on-unused-result return? So many of my two-line closures would
benefit from this.

What does that mean? You can't see how the closure is being called, so how do you know if the result is used or unused?

Jordan


(Jordan Rose) #11

Ah, I see what you mean now. I still think it's a bad idea, though—once you're in "statement mode", flow control ought to be explicit. Other languages have definitely had problems with fall-off-the-end returns <http://programmaticallyspeaking.com/why-i-hate-implicit-return-in-coffeescript.html> when it's not something people use or expect all the time.

Per my (old) discussion on the thread Javier just brought back, @warn_unused_result ought to be catching these for you, and if not then (IMHO) that's the place to be fixing things.

Jordan

···

On Mar 7, 2016, at 17:37, Erica Sadun <erica@ericasadun.com> wrote:

On Mar 7, 2016, at 6:24 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Mar 7, 2016, at 17:21, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

On Mar 7, 2016, at 6:15 PM, Jordan Rose via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
Today there's at least a simple rule: if the closure consists of a single expression (or a single return statement), the compiler will look at the body; otherwise it won't.

Would it be so hard to have a rule that if there's an unambiguous expression as the last line of a closure that
it automagically acts as a default warn-on-unused-result return? So many of my two-line closures would
benefit from this.

What does that mean? You can't see how the closure is being called, so how do you know if the result is used or unused?

Jordan

Okay, fine.

Would it be so hard to have a rule when there's an unambiguous expression as the last line of a closure that
it automagically acts as a default return? So many of my two-line closures would benefit from this.


(Erica Sadun) #12

Honestly, I can probably type the entire 8 extra characters.

-- E, who would rather pick better battles
p.s. Pull Request: https://github.com/apple/swift-evolution/pull/191

···

On Mar 8, 2016, at 6:24 PM, Jordan Rose <jordan_rose@apple.com> wrote:
Ah, I see what you mean now. I still think it's a bad idea, though—once you're in "statement mode", flow control ought to be explicit. Other languages have definitely had problems with fall-off-the-end returns <http://programmaticallyspeaking.com/why-i-hate-implicit-return-in-coffeescript.html> when it's not something people use or expect all the time.

Per my (old) discussion on the thread Javier just brought back, @warn_unused_result ought to be catching these for you, and if not then (IMHO) that's the place to be fixing things.

Jordan