Skip to content

Undeterministic eta expansion for a method with default arguments #19266

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

Open
matwojcik opened this issue Dec 14, 2023 · 4 comments · May be fixed by #19540
Open

Undeterministic eta expansion for a method with default arguments #19266

matwojcik opened this issue Dec 14, 2023 · 4 comments · May be fixed by #19540
Labels

Comments

@matwojcik
Copy link

matwojcik commented Dec 14, 2023

Compiler version

3.3.1

Minimized code

Code from the gist:

  def fn1(x: Int, y: Int = 5)(z: Int) = Some(x+y+z)
  def fn2(x: Int)(y: Int) = Some(x+y)

  def program = 
    fn1(4) // why this compiles
    fn2(2) // when this does not
    5
scala-cli compile Unused.scala -Wnonunit-statement

or

scala-cli compile https://gist.github.com/matwojcik/85c0527502871b0a672441728601afc9 -Wnonunit-statement

Output

[error] missing argument list for value of type Int => Some[Int]
[error]     fn2(2) // when this does not

Expectation

I would expect that either:

  • both fn1 and fn2 calls fail with missing argument
  • both fn1 and fn2 calls warn with unused value of type Int => Some[Int] (add : Unit to discard silently)

If you adapt this code to scala 2.13 (add object - see https://gist.github.com/matwojcik/55e9e27c9581f136dcd3266d1b4a48ab) and run

scala-cli compile --scala 2.13 -Xsource:3 [Unused.scala](https://gist.github.com/matwojcik/55e9e27c9581f136dcd3266d1b4a48ab) -Wnonunit-statement

then the result is:

[warn] ./Unused213.scala:8:5
[warn] unused value of type Int => Some[Int] (add `: Unit` to discard silently)
[warn]     fn2(2) // when this does not
[warn]     ^^^^^^
[warn] ./Unused213.scala:7:5
[warn] unused value of type Int => Some[Int] (add `: Unit` to discard silently)
[warn]     fn1(4) // why this compiles
[warn]     ^^^^^^

So something that I expected in the first place - warning about non used value. Scala3 though is apparently doing eta expansion not in the deterministic way here - when the function is having a default parameters.

@matwojcik matwojcik added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Dec 14, 2023
@som-snytt
Copy link
Contributor

The expansion looks normal -Vprint, so maybe it's a spurious error during type check? that is corrected by eta expansion.

The other note is if you comment out the erroneous line, -Wnonunit-statement does not notice the unused closure in Scala 3. I don't know if there is a ticket for that, but it sounds familiar.

@dwijnand dwijnand added area:typer stat:needs triage Every issue needs to have an "area" and "itype" label area:default-parameters area:linting Linting warnings enabled with -W or -Xlint and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Dec 18, 2023
i10416 added a commit to i10416/dotty that referenced this issue Jan 26, 2024
@i10416
Copy link
Contributor

i10416 commented Jan 26, 2024

I think the difference in your example is intentional.

  def fn1(x: Int, y: Int = 5)(z: Int) = Some(x+y+z)
  def fn2(x: Int)(y: Int) = Some(x+y)

  def program = 
    fn1(4) // why this compiles
    fn2(2) // when this does not
    5

Default value for by-value parameter may have side-effect on eta-expansion, so it is reasonable not to warn " A pure expression does nothing in statement position" and not to raise error. Perhaps, we should warn NonUnitUnusedValue.

On the other hand, method without default argument and method with default arguments for by-name parameters are pure. It does not perform side-effect on eta-expansion. Pure synthetic lambda in statement position is prohibited according to #11769. 1

It seems expected behavior to raise error rather than warning.

The difference is, for example, in the following code block, fn1(1) mutates arr, but f2(2) won't.

import scala.collection.mutable.ArrayBuffer
import scala.util.Random

val arr = ArrayBuffer[Int]()

def fn0(x: Int)(y: String) = Some(s"$x,$y")
def fn1(x: Int, y: Unit = arr.addOne(Random.nextInt()))(z:Double) = Some(s"$x,$y,$z")
def fn2(x: Int, y: => Unit = arr.addOne(Random.nextInt()))(z:Double) = Some(s"$x,$y,$z")

// This is pure and falls into the missing argument arm
// here https://github.com/lampepfl/dotty/blob/1716bcd9dbefbef88def848c09768a698b6b9ed9/tests/pos-with-compiler-cc/dotc/typer/Typer.scala#L4203
// Thus, it shouldn't compile.
fn0(0)
// This is impure(perform side-effect to mutate `arr`). Therefore this passes through `checkStatementPurity`.
fn1(1)
// This is pure and falls into the missing argument arm 
// https://github.com/lampepfl/dotty/blob/1716bcd9dbefbef88def848c09768a698b6b9ed9/tests/pos-with-compiler-cc/dotc/typer/Typer.scala#L4203
// Thus, it shouldn't compile.
fn2(2)

Footnotes

  1. It says "Disallow lambdas in statement position", but it prohibits only pure lambdas in statement position.

@odersky
Copy link
Contributor

odersky commented Feb 23, 2024

@i10416 Excellent analysis!

@som-snytt
Copy link
Contributor

som-snytt commented Feb 23, 2024

Per my previous comment, I would expect -Wnonunit-statement to warn about the nowarn cases. Oh I guess that's what the PR intends to remedy.

class C {
  def i = "42".toInt
  def f(x: Int, y: Int = 42)(z: Int) = x+y+z
  def test: Int = {
    f(27)       // nowarn bc side-effect; but also no nonunit in statement pos
    f(27, i)    // nowarn bc side-effect; but also no nonunit in statement pos
    f(27, 42)   // missing args bc synthetic
    f(27)(_)    // warn pure in statement pos
    f(27, i)(_) // warn pure in statement pos
    5
  }
}

The experience on the previous "disallow synthetic lambdas" PR, that test frameworks like weird code, happened again for Wnonunit-statement where ScalaTest assert breaks the check in user code.

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

Successfully merging a pull request may close this issue.

5 participants