Skip to content
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

[SR-586] $0 captures all closure argument as single tuple when no larger-numbed $N used #43203

Closed
pcantrell opened this issue Jan 20, 2016 · 11 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@pcantrell
Copy link

Previous ID SR-586
Radar None
Original Reporter @pcantrell
Type Bug
Status Resolved
Resolution Done
Environment

Swift 2.1

Additional Detail from JIRA
Votes 2
Component/s Compiler
Labels Bug
Assignee None
Priority Medium

md5: f978a8209f2dc3cd4b9e748eb484c07d

relates to:

  • SR-6612 Contextual closure type expects N arguments, but <N was used in closure body.

Issue Description:

The following code does not compile, but should:

func foo(val: Int) { }

func bar(closure: (Int,Int) -> Void) {
    closure(0, 1)
}

bar { foo($0) }       // compiler error
bar { foo($1) }       // just dandy
bar { foo($0 + $1) }  // also works

In the absence of $1, the implicit $0 variable captures all the arguments as a tuple. The compiler error is:

Cannot convert value of type (Int, Int) to expected argument type Int

John McCall indicated on swift-evolution that this is a bug, not an intentional feature:

https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160118/007095.html

@swift-ci
Copy link
Collaborator

swift-ci commented Feb 2, 2016

Comment by Robert (JIRA)

+1

This doesn't look like a bug, it actually seems like this was intentional (although I agree it is a terrible design choice). In a closure accepting N+1 arguments, you must explicitly use the largest anonymous variable, $N, in order to use any smaller arguments, unless you only use $0 in which case $0 will capture all arguments. For instance,

// error: cannot convert value of type '(Int, Int, Int, Int)' to closure result type 'Int'
let f0: (Int,Int,Int,Int) -> Int = { $0 }

// error: contextual type for closure argument list expects 4 arguments, but 2 were specified
let f1: (Int,Int,Int,Int) -> Int = { $1 }

// error: contextual type for closure argument list expects 4 arguments, but 3 were specified
let f2: (Int,Int,Int,Int) -> Int = { $2 }

// OK, returns last argument
let f3: (Int,Int,Int,Int) -> Int = { $3 }

@swift-ci
Copy link
Collaborator

swift-ci commented Feb 2, 2016

Comment by Robert (JIRA)

After a little more thought, there actually seems to be a consistent explanation for all of this behavior: Swift believes that a closure takes as many arguments as the largest $n used within the closure. The oddity of $0 capturing all arguments is really just the closure taking in the "arguments" as a tuple and treating the tuple as a single argument. In the cases of f1 and f2, Swift believes the closure takes 2 or 3 arguments, and the type of f1 and f2 is inconsistent with this. The final case works because it happens that a 4-argument closure is exactly what was needed.

@pcantrell
Copy link
Author

Robert — Yes, you’ve correctly understood the current behavior's (il)logic. The question is whether it's a bug. There’s additional detail from the core team in this swift-evolution discussion thread:

http://thread.gmane.org/gmane.comp.lang.swift.evolution/3915/

There's disagreement among members of the core team about whether the current behavior is a bug, a feature, or some of both. On the one hand, it's confusing, and it contradicts the documentation. On the other hand, people may be using the $0-is-a-tuple behavior in existing code.

I think we need a call from the team to determine whether this is something to fix in 2.x, a language change for Swift 3+, or something else.

@dduan
Copy link
Collaborator

dduan commented Jul 1, 2016

@slavapestov
Copy link
Member

SE-0110 has been implemented so this code is no longer accepted in Swift 4 mode.

@pcantrell
Copy link
Author

@slavapestov: I’m not sure this should be closed. I still think the code in the bug ought to compile in principle.

The underlying problem here is that when a closure with implicit args has highest-numbered arg $n, the compiler assumes the closure has exactly n args. It should assume that the closure has at least n args.

This snippet is perhaps a better illustration than the one in the original bug:

func foo(_ val: Int) { }

func bar(closure: (Int,Int,Int,Int,Int) -> Void) {
    closure(0, 1, 2, 3, 4)
}

bar { foo($0) } // compiler error!
bar { foo($1) } // nope!
bar { foo($2) } // sadness!
bar { foo($3) } // ennui!
bar { foo($4) } // just hunky dory

It’s a bit absurd that the compiler will infer lower-numbered unused implicit args, but not higher-numbered ones.

SE-110 doesn’t address this directly. It does shift the single tuple interpretation mentioned in the original bug statement, but nothing in SE-110 says the example code shouldn’t compile.

@pcantrell
Copy link
Author

Confirmed: this is not fixed in Swift 4. This bug should not be marked resolved. cc @slavapestov

@davidbjames
Copy link

This behavior really surprises me, and is against much of what makes Swift intuitive and logical.

Given a method like:

func each(_ visitor: (T, Int) -> Void)

I should be able to call it using the first parameter only without issue:

foo.each { $0.doSomething }

Unfortunately, this is not possible. Error

Contextual closure type '(_, Int) -> Void' expects 2 arguments, but 1 was used in closure body

Tested in Swift 4 Beta 1 (and per @pcantrell it appears to still not be addressed despite any fixes related to SE-110).

cc @slavapestov

@davidbjames
Copy link

Via: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160118/007095.html

The type-checker assumes that the closure has a tuple of arguments ($0, $1, …, $N), where $N is the largest N seen in the closure. Thus, a two-argument closure falls down if you ignore the second argument. It’s dumb, and we’ve known about it for a long time; and yet it’s been remarkably annoying to fix, and so we haven’t yet. Anyway, it’s a bug and doesn’t need to go through evolution. John.

@rjmccall do you still agree that this is a bug that needs to be fixed?

@rjmccall
Copy link
Member

If it still reproduces, I think it's still a bug? I don't know why it wouldn't be.

@davidbjames
Copy link

Yes, it still reproduces.

Created new issue SR-6612 which links to this one.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

6 participants