-
Notifications
You must be signed in to change notification settings - Fork 10.4k
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-1528] Swift compiler requires closures without parameter lists to reference all arguments #44137
Comments
I'm not convinced this is an "obvious bug". We deliberately put in checking for this even whene there is plenty of context because we didn't want people to accidentally ignore parameters. Maybe the compromise is to remove that check for single-expression closures? |
Comment by erica sadun (JIRA) Does stuff still work after removing that check? If so, that works for me. However, I'd clear the policy with coreswift team. I just happen to have a proposal if you want to submit it to them. |
I don't actually know if it works. I was (mildly) objecting to removing it for all closures without going through evolution. |
This was discussed on swift-evolution, and Chris declared it an "obvious bug" there. |
Comment by erica sadun (JIRA) https://gist.github.com/erica/3731e24fc252c8e66850e0e02f491281 |
This is a very annoying bug. I don't see why we need to protect people from "accidentally" ignoring parameters. In regular functions it's possible to ignore params without error (not that it makes sense). John McCall also considers this a bug, via: See also duplicate SR-6612 for other code examples and inexplicable error messages. |
An example: one may have a closure which is a factory of sorts. The closure may provide parameters from which to construct a Something. Alternatively, one may also pull that information from surrounding lexical scope to create a Something OR maybe just return a default Something.
In any case, using the input data should optional. Sometimes you use it, sometimes you don't. Pulling another example from the dupe ^^, an iterator that provides an index. Sometimes you reference the index, sometimes you don't:
|
Copied from another bug marked as a dup, concise test cases which may be helpful: func foo(_ closure:(Int,Int,Int)->Void) {
closure(1,2,3)
}
// All of these should work: (last one is debatable; others are not IMO)
foo { print($0) } // ERROR: "expects 3 arguments, but 1 was used"
foo { print($1) } // ERROR: "expects 3 arguments, but 2 were used" Huh? There was only 1 used.
foo { print($0,$1) } // ERROR: "expects 3 arguments, but 2 were used"
foo { print($0,$1,$2) } // OK
foo { print($2) } // OK
foo { } // ERROR: "expects 3 arguments, which cannot be implicitly ignored" |
I would enjoy seeing this change implemented. I've wanted this in the past when I wanted to create an array of reference type instances in one line. The example code I wished would work is this: let numberOfViews = 20
let views = (0 ..< numberOfViews).map { UIView() } I forget now what my solution was, but I think I ended up finding a different one-liner for this purpose. But it definitely wasn't: let views = Array(repeating: UIView(), count: numberOfViews) which of course yields an array with repeated references to the same UIView instance. |
Oh, right, the fix is just to explicitly ignore the input: let views = (0 ..< numberOfViews).map { _ in UIView() } Regardless, would be nice to get rid of that extra little bit of boilerplate. |
Comment by Michaell Long (JIRA) Just noting that this issue appears all of the time when using RxSwift. let saved = saving
.filter { $0.contact != nil }
.map { _ in "Contact information saved." }
let processing = Observable
.from([validating.map { _ in true }, saved.map { _ in false }])
.merge() In both of the above cases I don't care about the incoming value and I want to replace the value in the stream with a new value. In other words, the code should look like: let saved = saving
.filter { $0.contact != nil }
.map { "Contact information saved." }
let processing = Observable
.from([validating.map { true }, saved.map { false }])
.merge() But which you can't do, because Swift requires the boilerplate "_ in" when the value is ignored. |
Comment by Steven Van Impe (JIRA) The Swift Programming Language states that: “If you use these shorthand argument names within your closure expression, you can omit the closure’s argument list from its definition, and the number and type of the shorthand argument names will be inferred from the expected function type.” If this is indeed a bug, can you bump its priority a bit? My students run into this issue so often that I actually have to include it in my course, and try to sell it as a safety feature. |
This is still a major hole in closure usage. Given multiple arguments using shorthand argument names, the compiler requires the last argument to be declared.
This also kills code completion until you type the last argument. Sometimes you have to type the last argument on it's own line just so you can get code completion to work temporarily. So there are two usability problems, the general limitation and code completion. Since the limitation seems artificial (noted above as an "obvious bug") I suggest we resolve it now. NOTE: fixing this would be non-breaking. Existing code would just work and new code could take advantage of the improvement. Can we do it now? |
Just found an even sillier part of this. If you write: You've gotta use both params to use that closure But if you write it as: You now have a tuple (note the extra pair of parenthesis). Code that uses both arguments will keep working as swift will transparently turn that tuple into the arguments of the closure, no problem. If you then want to use only the first argument, you can refer to it as $0.0, treating the tuple as a tuple, and it'll work too, and won't complain. Quite a silly workaround. Can we fix this please? |
Environment
Swift 2.2
Additional Detail from JIRA
md5: 2ed8141c3cd057578dc20e6b58317765
is duplicated by:
relates to:
Issue Description:
Swift closures that do not explicitly declare an internal parameter list must reference all arguments using implicit $n shorthand names. If they do not, Swift complains that the contextual type for the closure argument "expects n arguments, which cannot be implicitly ignored." This requirement diminishes the efficacy of Swift's $n syntactic sugar. Eliminating the requirement means:
{} becomes a valid 'noop' closure in any context requiring a Void-returning closure.
Implementations can discard unnecessary code cruft and streamline their minimum implementation from { _(, _)* in } to {}.
{ expression } becomes a valid closure in any context requiring a return value. The expression can offer a simple expression or literal, such as { 42 }.
The closure can mention some of its parameters without having to mention all of its parameters.
All the following closures should be valid and compile without error or warning:
{{let _: () -> Void = {}
let _: (Int) -> Void = {}
let _: (Int, Int) -> Int = { 5 }
let _: (Int, Int) -> Int = { $0 }
let _: (Int, Int) -> Int = { $1 }
doThing(withCompletion: {})
let x: (T) -> Void = {}}}
Chris L writes: "I consider this to be an obvious bug in the compiler. I don’t think it requires a proposal. Unfortunately it is non-trivial to fix…"
The text was updated successfully, but these errors were encountered: