Typing of eta-expansion of method having specific type signature

I ran into a problem concerning how scala types eta-expansions of methods. Here is a minimal example:

class MethodHolder:
  def method(x: Int): 4 = 4

val a = new MethodHolder
val b = a.method

Scala types b by default as val b: Int => Int, whilst manually giving the more specific type val b: Int => 4 type checks just fine. Is this by design? I’m doing some transparent inline things where it would be nice if scala gave the more specifc type.

The compile tends to widen things. Did you try final val?

Thank you for the reply! Unfortunately final does not seem to work. Not for the example or for my actual usecase.

Yes, I see even this fails:

def method(x: Int): 4 = 4
final val b = method
val c: Int => 4 = b //error

I would say this is a bug in the compiler.

There is a ticket about why

scala> def m(i: Int): 42 = 42
[[syntax trees at end of                     typer]] // rs$line$2
package <empty> {
  final lazy module val rs$line$2: rs$line$2 = new rs$line$2()
  final module class rs$line$2() extends Object() { this: rs$line$2.type =>
    def m(i: Int): 42 = 42
  }
}

def m(i: Int): 42

scala> val f: Int => Unit = m
[[syntax trees at end of                     typer]] // rs$line$3
package <empty> {
  final lazy module val rs$line$3: rs$line$3 = new rs$line$3()
  final module class rs$line$3() extends Object() { this: rs$line$3.type =>
    val f: Int => Unit = (i: Int) =>
      {
        m(i)
        ()
      }
  }
}

val f: Int => Unit = Lambda/0x0000000066498000@4b1c6936

which is because eta-expansion gives you the function and its body or result has the expected type Unit, so the result is discarded. Similarly for Int => 42. The expected type matters, but maybe there is a SIP – I didn’t realize the precise type effort had stalled, but now I’m curious to read the history. That was SIP-48, it was even a pretty good SIP number and was also pull request number 48.

The complaint on the other ticket was about this diagnostic:

scala> val f: Int => 27 = m
-- [E007] Type Mismatch Error: -------------------------------------------------
1 |val f: Int => 27 = m
  |                   ^
  |                   Found:    (42 : Int)
  |                   Required: (27 : Int)

Separately, I just had a PR that rewrites final val to inline def, more or less, because Java enums are not constant, so concurrent.duration.DAYS can’t be used in annotations. The spec is that final val of a constant means inline the RHS, and that works only for constants via folding and not inlining. (A def is not constant, even if its type is a literal type.)

For your use case, even with everything inline transparent, the inferred result of the function is widened (in the absence of an expected type), as you said.

scala> inline transparent def m(inline i: Int): 42 = 42
def m(i: Int): 42

scala> inline transparent def f = m
def f: Int => Int

scala> val x: 42 = f(27)
-- [E007] Type Mismatch Error: -------------------------------------------------
1 |val x: 42 = f(27)
  |            ^^^^^
  |            Found:    Int
  |            Required: (42 : Int)
  |
  | longer explanation available when compiling with `-explain`
1 error found

scala> def f: Int => 42 = m
def f: Int => 42

scala> val x: 42 = f(27)
val x: 42 = 42