Skip to content

Unnamed parameters after named ones #18122

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

Closed
prolativ opened this issue Jul 3, 2023 · 3 comments · Fixed by #18363
Closed

Unnamed parameters after named ones #18122

prolativ opened this issue Jul 3, 2023 · 3 comments · Fixed by #18363

Comments

@prolativ
Copy link
Contributor

prolativ commented Jul 3, 2023

Compiler version

3.3.2-RC1-bin-20230630-c629090-NIGHTLY and before

Minimized code

The following snippet contains multiple cases obtained by uncommenting one of the commented lines at a time.

object Test {
  def foo1(x: Int, y: Int, z: Int) = println((x, y, z))
  def foo2(x: Int = 0, y: Int, z: Int) = println((x, y, z))
  def bar1(x: Int, ys: Int*) = println((x, ys))
  def bar2(x: Int = 0, ys: Int*) = println((x, ys))

  def main(args: Array[String]) = {
    // foo1(1, y = 2, 3)
    // foo2(1, y = 2, 3)
    // foo1(y = 1, 2, x = 3)
    // foo2(y = 1, 2, x = 3)
    // foo1(y = 1, 2, z = 3)
    // foo2(y = 1, 2, z = 3)
    // foo1(y = 1, 2)
    // foo2(y = 1, 2)

    // bar1()
    // bar2()
    // bar1(1)
    // bar2(1)
    // bar1(x = 1)
    // bar2(x = 1)
    // bar1(ys = 1)
    // bar2(ys = 1)
    // bar1(1, 2)
    // bar2(1, 2)
    // bar1(1, ys = 2)
    // bar2(1, ys = 2)
    // bar1(x = 1, 2)
    // bar2(x = 1, 2)
    // bar1(x = 1, ys = 2)
    // bar2(x = 1, ys = 2)
    // bar1(ys = 1, x = 2)
    // bar2(ys = 1, x = 2)
    // bar1(1, 2, 3)
    // bar2(1, 2, 3)
    // bar1(1, ys = 2, 3)
    // bar2(1, ys = 2, 3)
    // bar1(x = 1, 2, 3)
    // bar2(x = 1, 2, 3)
    // bar1(x = 1, ys = 2, 3)
    // bar2(x = 1, ys = 2, 3)
    // bar1(x = 1, 2, ys = 3)
    // bar2(x = 1, 2, ys = 3)
    // bar1(ys = 1, 2, x = 3)
    // bar2(ys = 1, 2, x = 3)
  }
}

Output

code scala 3.nightly scala 2.13.11
foo1(1, y = 2, 3) (1,2,3) (1,2,3)
foo2(1, y = 2, 3) (1,2,3) (1,2,3)
foo1(y = 1, 2, x = 3) (3,1,2) positional after named
foo2(y = 1, 2, x = 3) (3,1,2) positional after named
foo1(y = 1, 2, z = 3) missing argument x positional after named
foo2(y = 1, 2, z = 3) too many arguments positional after named
foo1(y = 1, 2) missing argument x positional after named, missing arguments x, z
foo2(y = 1, 2) (0,1,2) positional after named, missing argument z
code scala 3.nightly scala 2.13.11
bar1() missing argument x missing arguments x, ys
bar2() (0,ArraySeq()) def bar2 not compiling; missing argument ys
bar1(1) (1,ArraySeq()) (1,List())
bar2(1) (1,ArraySeq()) def bar2 not compiling
bar1(x = 1) (1,ArraySeq()) (1,List())
bar2(x = 1) (1,ArraySeq()) def bar2 not compiling
bar1(ys = 1) missing argument x vararg required exactly once
bar2(ys = 1) (0,ArraySeq(1)) def bar2 not compiling; vararg required exactly once
bar1(1, 2) (1,ArraySeq(2)) (1,ArraySeq(2))
bar2(1, 2) (1,ArraySeq(2)) def bar2 not compiling
bar1(1, ys = 2) (1,ArraySeq(2)) (1,ArraySeq(2))
bar2(1, ys = 2) (1,ArraySeq(2)) def bar2 not compiling
bar1(x = 1, 2) (1,ArraySeq(2)) (1,ArraySeq(2))
bar2(x = 1, 2) (1,ArraySeq(2)) def bar2 not compiling
bar1(x = 1, ys = 2) (1,ArraySeq(2)) (1,ArraySeq(2))
bar2(x = 1, ys = 2) (1,ArraySeq(2)) def bar2 not compiling
bar1(ys = 1, x = 2) (2,ArraySeq(1)) (2,List(1))
bar2(ys = 1, x = 2) (2,ArraySeq(1)) def bar2 not compiling
bar1(1, 2, 3) (1,ArraySeq(2, 3)) (1,ArraySeq(2, 3))
bar2(1, 2, 3) (1,ArraySeq(2, 3)) def bar2 not compiling
bar1(1, ys = 2, 3) (1,ArraySeq(2, 3)) (1,ArraySeq(2, 3))
bar2(1, ys = 2, 3) (1,ArraySeq(2, 3)) def bar2 not compiling
bar1(x = 1, 2, 3) (1,ArraySeq(2, 3)) (1,ArraySeq(2, 3))
bar2(x = 1, 2, 3) (1,ArraySeq(2, 3)) def bar2 not compiling
bar1(x = 1, ys = 2, 3) (1,ArraySeq(2, 3)) (1,ArraySeq(2, 3))
bar2(x = 1, ys = 2, 3) (1,ArraySeq(2, 3)) def bar2 not compiling
bar1(x = 1, 2, ys = 3) (1,ArraySeq(3, 2)) ys already specified
bar2(x = 1, 2, ys = 3) (1,ArraySeq(3, 2)) def bar2 not compiling; ys already specified
bar1(ys = 2, 3) missing argument x positional after named
bar2(ys = 2, 3) (0,ArraySeq(2, 3)) def bar2 not compiling; positional after named
bar1(ys = 1, 2, x = 3) (3,ArraySeq(1, 2)) positional after named
bar2(ys = 1, 2, x = 3) (3,ArraySeq(1, 2)) def bar2 not compiling; positional after named

Abbreviated error messages explained:

  • positional after named - positional after named argument (scala 2)
  • too many arguments - too many arguments for method ...
  • missing argument x - missing argument for parameter x of method ... (scala 3), not enough arguments for method ... Unspecified value parameter x (scala 2)
  • def bar2 not compiling - a parameter section with a `*`-parameter is not allowed to have default arguments (scala 2)
  • vararg required exactly once - when using named arguments, the vararg parameter has to be specified exactly once

Expectation

  • IMO scala 2 is right to disallow using unnamed arguments after named ones - otherwise it's quite unclear whether such an argument fills the first missing parameter slot (which I would personally assume, given that such usage is not disallowed completely) or the slot following the previous named parameter (which seems to be the case in scala 3, taking e.g. the behaviour of foo1(y = 1, 2, x = 3) into account)
  • Given that a user knows the current behaviour of the compiler for separate named and unnamed arguments in scala 3, the behaviour for varargs might seem consistent in some way, but otherwise it's also rather surprising. My personal first guess about named vararg argument was that I should pass a sequence like ys = Seq(2, 3) instead of ys = 2, 3, but this doesn't work. Should any of these syntaxes be legal at all? Especially the results for bar2(ys = 2, 3) and bar2(x = 1, 2, ys = 3) seem very confusing to me
  • The error messages for foo1(y = 1, 2, z = 3), foo2(y = 1, 2, z = 3), foo1(y = 1, 2) and bar1(ys = 2, 3) should be improved - currently it's not clear if the fix would be to add a name to an unnamed argument or to add another (possibly unnamed) argument
@prolativ
Copy link
Contributor Author

prolativ commented Jul 3, 2023

@odersky what behaviour would you expect here?

@odersky
Copy link
Contributor

odersky commented Jul 14, 2023

I think Scala 2's behavior is reasonable here.

Except I think it's OK to accept bar2. Not sure why Scala 2 disallows it.

@som-snytt
Copy link
Contributor

som-snytt commented Jul 14, 2023

The Urtext comment on Scala 2:

          /* Treats an application which uses named or default arguments.
           * Also works if names + a vararg used: when names are used, the vararg
           * parameter has to be specified exactly once. Note that combining varargs
           * and defaults is ruled out by typedDefDef.
           */

Recalling that named args can be merely documentary: f(x = 1, y = 2, z = 3) is f(1, 2, 3) without transforming.

Then perhaps f(y = 2, 3) where y is repeating always means f(y = 2, y = 3) where the y accumulates all unnamed args.

Then f(y = 2, 3, x = 1) makes sense. (Same as f(y = List(2, 3): _*, x = 1).)

In particular, bar2(x = 1, 2, ys = 3) switching the order of y args doesn't make sense by the previous rule that adding a name at a position where that name is already understood should not change the application.

I was considering tweaking applications for Scala 2, so if I do that and any changes are made here, I will try to align with them.

prolativ added a commit that referenced this issue Aug 21, 2023
Issue "positional after named argument" errors if a positional argument
follows a named argument and one of the following is true:

- There is a formal argument before the argument position that has not
yet been instantiated with a previous actual argument, (either named or
positional), or
- The formal parameter at the argument position is also mentioned in a
subsequent named parameter.

This brings the behavior largely in line with Scala 2.

Fixes #18122
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants