bug
bug copied to clipboard
Missing type parameter inference for stable identifier patterns.
This is both a report of a defect in the specification and a minor enhancement request for the implementation.
GADT-style dependent case analysis works for objects:
scala> trait T[X]
defined trait T
scala> object O extends T[int]
defined module O
scala> def f[X] : T[X] => X = { case O => 42 }
f: [X](T[X]) => X
O is syntactically a stable identifier pattern (8.1.4), but dependent case analysis isn't defined for them in section 8.3 of the specification. Their treatment seems straightforwardly analogous to the treatment of constructor patterns, though: { case O => 42 } just gets translated to { case _ : O.type => 42 }. This description is currently missing from the specification.
Moreover, it seems that with the current implementation, the above rule only works for objects, not for other stable identifiers.
scala> val V = new T[int] {}
V: java.lang.Object with T[int] = $$anon$$1@23194cf5
scala> def g[X] : T[X] => X = { case V => 42 }
<console>:6: error: type mismatch;
found : Int(42)
required: X
def g[X] : T[X] => X = { case V => 42 }
Still, manual translation to a type pattern works all right:
scala> def g[X] : T[X] => X = { case _ : V.type => 42 }
g: [X](T[X]) => X
scala> g(V)
res1: int = 42
This seems inconsistent. I very much think all stable identifiers should be treated uniformly. My immediate motivation for this is that it makes writing a typecase somewhat neater. Given:
val IntC = classOf[Int]
val StringC = classOf[String]
Currently one has to write:
def g[X] : Class[X] => X = {
case _ : IntC.type => 42
case _ : StringC.type => "forty-two"
}
Instead of the prettier:
def g[X] : Class[X] => X = {
case IntC => 42
case StringC => "forty-two"
}
Imported From: https://issues.scala-lang.org/browse/SI-785?orig=1 Reporter: Lauri Alanko (lealanko) See #7456
Lauri Alanko (lealanko) said: All right, this is a bigger mess than I first thought. A number of issues arise.
The specification states (8.1.4) that v matches a stable identifier pattern r if r == v. In actuality, the test is v == r.
class C(name : String) {
override def equals(o : Any) = {
Console.println(name + " == ...")
super.equals(o)
}
}
object O extends C("O")
val V = new C("V")
scala> val ov = O match { case V => true; case _ => false }
O == ...
ov: Boolean = false
Either the specification or implementation is in error.
When matching against an object, even though it is a stable identifier pattern, no == -based comparison is made.
scala> val ov = V match { case O => true; case _ => false }
ov: Boolean = false
Presumably direct reference equality is used, and this is indeed necessary for an object pattern's type refinement properties to be safe.
Thus, an object pattern as currently implemented is not a stable identifier pattern, despite the spec.
An object pattern provides stronger type refinement than a singleton type pattern.
object O
scala> def f[X] : X => X = { case O => O }
f: [X](X) => X
scala> def g[X] : X => X = { case _:O.type => O }
<console>:5: error: type mismatch;
found : O.type (with underlying type object O)
required: X
def g[X] : X => X = { case _:O.type => O }
Presumably matching to O gives us constraints X <: O.type <: X on the right-hand side of the case clause, since the only possible subtypes of a singleton type are Null and Nothing, and from the match we know that X must be inhabited by a non-null value.
However, for an explicit type pattern, we only get the constraint X <: O.type, since we're using the generic rule for types which cannot assume that there couldn't be strict non-null subtypes.
Singleton type patterns should be given special treatment to make them equivalent with the object patterns, and/or there should be a way to match non-object stable identifier patterns with as strong type refinements as object patterns get.
Summary:
Object patterns currently behave differently both from other stable identifier patterns and from singleton type patterns. It is impossible to get similar matching for other stable names. The specification is greatly out of sync with the implementation.
Here's a sketch of the situation and my view of how it should be:
pattern matched with type refinement
spec impl ideal spec impl ideal
object == eq eq none? strong strong
other stable id == ==(rev) eq none? none strong
singleton type eq eq eq weak weak weak or strong
The point of stable identifiers is that they have these wonderful singleton types. It seems silly to have a pattern that is constrained to stable identifiers, yet compares them with a user-definable equals() and provides no type refinements. An equals()-tested value could just as well be provided by an arbitrary expression, so you could just as well add a pattern { expr } which would be evaluated during the match, and the result compared with == against the matched value.
Yet, on the other hand, it would also be at times useful to have
def f[X] : Class[X] => X = { case { classOf[int] } => 42 }
provide the proper type refinements, which of course requires that the match is done with eq. It's silly that currently one has to write:
def f[X] : Class[X] => X = {
val IC = classOf[int]
{ case _ : IC.type => 42 }
}
As a general rule in programming language design, I think that if a variable is used only once, it should be inlinable away. Currently this is not possible in Scala.
So, both eq-compared type-refinable matches and ==-compared non-refinable matches have their place, and I think both need better support from the language than they currently have.
Lauri Alanko (lealanko) said: One more quirk that I discovered: a type refinement in a pattern match clause doesn't seem to work properly when the refined type is used in a, uh, "type refinement" in the different sense of explicitly providing type constraints. With 2.7.1.r15804-b20080816212059, the following fails:
object T3 {
class A { type T }
case class B[X]
object C extends B[Int]
def f[X](b : B[X]) : A { type T = X } = b match {
case C => new A { type T = Int }
}
}
T3.scala:6: error: type mismatch;
found : T3.A{ ... }
required: T3.A{type T = X}
case C => new A { type T = Int }
However, this can be worked around by using a simple wrapper class around the refined type. With the following change it works:
def f[X](b : B[X]) : A { type T = X } = {
case class W[Y](a : A { type T = Y })
val w : W[X] = b match {
case C => W[Int](new A { type T = Int })
}
w.a
}
@harrah said: The patch I added for #3887 causes 1) and 2) in the first comment to behave as specified ( == called on the pattern). The dependent case analysis presented in this bug appears to no longer work. That is, the first example gives the same compile error as the second.
@paulp said: Replying to [comment:8 harrah]:
The patch I added for #3887 causes 1) and 2) in the first comment to behave as specified ( == called on the pattern). The dependent case analysis presented in this bug appears to no longer work. That is, the first example gives the same compile error as the second.
Can you clarify what is no longer working? I can't tell what you are referring to. I find about the same behavior for everything I tried.
scala> trait T[X]
defined trait T
scala> object O extends T[Int]
defined module O
scala> def f[X] : T[X] => X = { case O => 42 }
f: [X](T[X]) => X
scala> val V = new T[Int] { }
V: java.lang.Object with T[Int] = $$anon$$1@50ec2522
scala> def g[X] : T[X] => X = { case V => 42 }
<console>:7: error: type mismatch;
found : Int(42)
required: X
def g[X] : T[X] => X = { case V => 42 }
^
scala> def g[X] : T[X] => X = { case _ : V.type => 42 }
g: [X](T[X]) => X
scala> g(V)
res0: Int = 42
@harrah said: Yeah, you're right, sorry. I don't know if I was using a bad build or failed to notice that the error was about 'int'.
@paulp said: For the record, I implemented much of this ticket a while ago and it is available here: https://github.com/paulp/scala/tree/issue/785
@retronym said: See also #7456