bug icon indicating copy to clipboard operation
bug copied to clipboard

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

Open palanga opened this issue 11 months ago • 8 comments

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 avatar Mar 25 '24 13:03 palanga

I would like to take a look at this.

palanga avatar Mar 25 '24 13:03 palanga

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 avatar Mar 25 '24 14:03 sjrd

@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

palanga avatar Mar 25 '24 14:03 palanga

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.

SethTisue avatar Mar 25 '24 14:03 SethTisue

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

Done!

palanga avatar Mar 25 '24 14:03 palanga

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?

SethTisue avatar Mar 25 '24 15:03 SethTisue

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

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

lrytz avatar Mar 26 '24 08:03 lrytz

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.

som-snytt avatar Mar 26 '24 14:03 som-snytt