bug
bug copied to clipboard
Type inference involving `getClass` on private trait doesn't work
Reproduction steps
On scala 2.13.13 (scala 3 infers correctly)
package style
object bug {
private[style] trait Style
private[style] class StyleSheet private (private val stylesByType: Map[Class[? <: Style], Style]) {
/**
* Add a new Style to this StyleSheet. If this StyleSheet already contains a
* Style definition of the same type, the new value overrides the old one.
*/
private def +(style: Style): StyleSheet = new StyleSheet(this.stylesByType + (style.getClass -> style))
}
}
gives the following error:
[error] /bug/src/main/scala/style/bug.scala:12:98: type mismatch;
[error] found : (Class[?0], style.bug.Style) where type ?0
[error] required: (Class[_ <: style.bug.Style], style.bug.Style)
[error] private def +(style: Style): StyleSheet = new StyleSheet(this.stylesByType + (style.getClass -> style))
[error] ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
but if I remove the private[style]
annotation on Style
, or if I cast style.getClass
as Class[Style]
, it compiles and works correctly.
Problem
I expect the compiler to infer the type correctly whether it is a private or non private trait. In scala 3, the compiler infers it correctly.
I would like to take a look at this.
That shouldn't be allowed to compile for a more important reason: the private type Style
appears in the public API of Stylesheet
, both as constructor parameter and as parameter or +
.
@sjrd I tried to simplify it as much as I can but in my real scenario, Style
was just package private
as well as StyleSheet
and +
was a private def
. I can edit the code snippet if you want
I can edit the code snippet if you want
Yes please — let's make sure we're all on the same page on the exact code required.
Yes please — let's make sure we're all on the same page on the exact code required.
Done!
minimized even further, it's:
trait Bug {
private trait Style
private def foo(style: Style): Set[Class[? <: Style]] =
Set(style.getClass)
}
@lrytz It seems puzzling to me that private-ness would even affect typing here. Curious if you have (or anyone has) a hunch what mechanism might be involved here?
There's always more magic to discover.
scala> :power
scala> class C
scala> typeOf[C].member(TermName("getClass")).tpe
val res0: $r.intp.global.Type = (): Class[_]
scala> (new C).getClass
val res1: Class[_ <: C] = class C
The bound is made up here: https://github.com/scala/scala/blob/v2.13.13/src/compiler/scala/tools/nsc/typechecker/Typers.scala#L645-L646. I guess that can be fixed for private classes.
The actual result type is Class<? extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called
When Seth quotes that line of doc on the ticket, he says, "In Java 6...". It's interesting to see what tools our grandparents used to solve problems.
The special handling was introduced for 2.10.