scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
230 stars 21 forks source link

Type inference involving `getClass` on private trait doesn't work #12974

Open palanga opened 2 months ago

palanga commented 2 months ago

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.

palanga commented 2 months ago

I would like to take a look at this.

sjrd commented 2 months ago

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 +.

palanga commented 2 months ago

@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

SethTisue commented 2 months ago

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.

palanga commented 2 months ago

Yes please — let's make sure we're all on the same page on the exact code required.

Done!

SethTisue commented 2 months ago

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?

lrytz commented 2 months ago

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.

FTR, [this is Java's invention](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#getClass())

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

som-snytt commented 2 months ago

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.