scala3
scala3 copied to clipboard
inline match breaks simple example, documentation?
Compiler version
3.0.2
Minimized code
(transparent) inline def f(x: Int) = inline x match
case 1 => 1
case _ => 0
@main def m =
val v = 1
println(f(v))
Output
0
Expectation
Give the same output as (namely 1)
inline def f(x: Int) = x match
case 1 => 1
case _ => 0
and if not, be documented in the "Inline Matches" section of https://docs.scala-lang.org/scala3/reference/metaprogramming/inline.html.
(Note that this does work when v
has singleton type 1
)
I suspect the following issue is related to this:
trait X[T]:
val value = "X"
given X[Int]()
transparent inline def f[T](x: T) = summonFrom {
case xt: X[T] => xt.value
case _ => "None"
}
transparent inline def g[Tup <: NonEmptyTuple](t: Tup) = f(t.head)
transparent inline def h[Tup <: NonEmptyTuple](t: Tup) = t match
case (head *: _) => f(head)
@main def m =
val t = (1, "s")
println(g(t))
println(h(t))
returns different values for g
and h
.
However, an inline match doesn't work because
cannot reduce inline match with
scrutinee: t : (t : (Int, String))
patterns : case *:.unapply[Any, Tuple](head @ _, _):Any *: Tuple
transparent inline def h[Tup <: NonEmptyTuple](t: Tup) = inline t match
I found this single line in the "Semantics-Preserving Inlining for Metaprogramming" paper on the topic:
Unlike the inline if, this reduction is not necessarily equivalent to its runtime counterpart when we have more type information.
which is about match not being semantics-preserving. I guess this reduces this issue to updating the docs?
We can mention that inline match works on the static type of an expression, not its runtime type, hence matching the wildcard case when v == 1
but its type is Int
That makes it so a chain of calling -> inline match -> summonFrom
like in https://github.com/lampepfl/dotty/issues/13774#issuecomment-946696449 can never work?
The reason being that inline match works on static types, hence can be evaluated before summonFrom
, hence leaves the types not specific enough for that to work?
The documentation says If there is enough static information to unambiguously take a branch
. In this case we only know that the type of x
is Int
. This implies that it is ambiguous as if the value is 1 then either branch would work and therefore we should not be able to reduce the inline match
.
This came up last year at #10919, where @odersky said the behavior is not just intended, but "specified".
But I really don't see any wording at https://docs.scala-lang.org/scala3/reference/metaprogramming/inline.html that specifies it.
In fact, as Nicolas observes, "If there is enough static information to unambiguously take a branch" (wording from that page) can be read as requiring that compilation fail, since the cases overlap.
I agree with you @SethTisue. Assuming the doc is correct in saying:
"A match expression in the body of an inline method definition may be prefixed by the inline modifier. If there is enough static information to unambiguously take a branch, the expression is reduced to that branch and the type of the result is taken. If not, a compile-time error is raised that reports that the match cannot be reduced."
My conclusion (please challenge this) is that inline match is supposed to preserve semantics (like inline if does), but it doesn't.
Is it safe to assume we need a fix to reflect this semantic preserving behaviour?
In case this assumption is wrong, @odersky can you please clarify what you mean in very old #10919, what you say the behaviour is not just intended, but specified?
Speaking as a non-expert, which makes me an audience for the simple example, I think the doc is quite clear.
However, I would remove the weaselly adverb, "unambiguously". I would also prefer "select" or "pick" to "take". I'd also prefer not to split an infinitive. (I also double-checked, the character's name is Ron Weasley.)
If there is enough static information to select a branch, the expression is reduced...
It already says "static type":
The example below ... picks a case based on its static type
But I sympathize with the OP, where there was not enough inlining to have the salutary effect. 🖖
Maybe a lint could warn about non-unambiguously specified inline matches. In the example, if a branch matches on a narrow type (1
) but the (inlined) scrutinee is a wider type (Int
), then annoy me by pointing that out. Maybe the lint is only triggered by singleton types (which might suggest "I was expecting runtime-like semantics").
Also, on the semantics of "semantics", to speak of "preserving" them, one must distinguish "compiletime" and "runtime", a difference encapsulated by the word "static". Unfortunately, that word is overloaded: connotations accrue to it as by "static cling".
If there is enough type information at compile time to select a branch, the expression is reduced...
That may be usefully redundant.
Surprisingly warning-free
transparent inline def f(x: Any): Any =
inline x match
case x: Any => (x, x)
case x: Any => x
I "committed" my formulation in the proposed PR, but I'm open to suggestions and amendments or emendations that are not mendacious.