scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.82k stars 1.05k forks source link

Bad type inference leading to crash at runtime #17074

Open OlegYch opened 1 year ago

OlegYch commented 1 year ago

Using slick 3.5.0-M2 (built for scala2.13) https://scastie.scala-lang.org/rQ5sOqVyTXmn7BuqMt3hkw

Compiler version

3.0.0 - 3.3.0-RC3

Minimized code

import slick.memory.MemoryProfile.api._
@main def main = 
    val xx = Rep.None[Int].getOrElse(0)

Output

crashes at runtime with

java.lang.ClassCastException: class slick.lifted.Rep$$anon$1 cannot be cast to class slick.lifted.ConstColumn 

Expectation

xx should be inferred as Rep[Int] instead of ConstColumn[Int]

OlegYch commented 1 year ago

otoh this might be a slick-induced bug https://github.com/slick/slick/issues/2674

nafg commented 1 year ago

Why do you think it's a type inference issue?

What is slick.lifted.Rep$$anon$1?

OlegYch commented 1 year ago

@nafg because if i type it explicitly as Rep[Int] then it runs fine https://scastie.scala-lang.org/OlegYch/SqtUB2ThRSOCHIofOFigQA

nafg commented 1 year ago

Yeah it is a type inference issue. Not sure where it's getting ConstColumn from.

Try on the nafg/dottyquery branch compiled for scala 3.

nafg commented 1 year ago

If it works then it's either a scala 2.13 TASTy issue or something changed on that branch, I guess.

OlegYch commented 1 year ago

@nafg my guess is it is related to slick.lifted.Shape machinery, there are some suspicious asInstanceOfs there

OlegYch commented 1 year ago

after some liberal sprinkling of ??? i can confirm that val xx = Rep.None[Int].getOrElse(0) runs on nafg/dottyquery and is typed as slick.lifted.Rep[Int]

prolativ commented 1 year ago

This is how val xx (without an explicit type ascription) is seen after the typer compilation phase: a) in scala 2.13.10

val xx: slick.lifted.Rep[Int]           = slick.memory.MemoryProfile.api.anyOptionExtensionMethods[Int, slick.lifted.Rep[Int] ]        (none)(lifted.this.OptionLift.repOptionLift[slick.lifted.Rep          [Int], Int](lifted.this.Shape.repColumnShape[Int, Nothing                      ] (slick.memory.MemoryProfile.api.intColumnType) ) ).getOrElse[Int, slick.lifted.ConstColumn[Int]](0)(lifted.this.Shape.primitiveShape [Int, slick.lifted.FlatShapeLevel](slick.memory.MemoryProfile.api.intColumnType), lifted.this.OptionLift.repOptionLift[slick.lifted.ConstColumn[Int],   Int](lifted.this.Shape.repColumnShape[Int,                      Nothing] (slick.memory.MemoryProfile.api.intColumnType) ) );

b) in scala 3.2.2

val xx: slick.lifted.ConstColumn[Int]   = slick.memory.MemoryProfile.api.anyOptionExtensionMethods[Int, slick.lifted.ConstColumn[Int] ](none)(slick.lifted.OptionLift.repOptionLift[slick.lifted.ConstColumn[Int] , Int](slick.lifted.Shape.repColumnShape[Int, slick.lifted.FlatShapeLevel ] (slick.memory.MemoryProfile.api.intColumnType) ) ).getOrElse[Int, slick.lifted.ConstColumn[Int]](0)(slick.lifted.Shape.primitiveShape[Int, slick.lifted.FlatShapeLevel](slick.memory.MemoryProfile.api.intColumnType), slick.lifted.OptionLift.repOptionLift[slick.lifted.ConstColumn[Int] , Int](slick.lifted.Shape.repColumnShape[Int, slick.lifted.FlatShapeLevel] (slick.memory.MemoryProfile.api.intColumnType) ) ) ()

(I formatted the outputs so that it's good to compare them in a text editor when one is put directly below the other)

It looks like the main difference is that in scala 2 the type parameters for Shape.repColumnShape are inferred as [Int, Nothing] but they're [Int, slick.lifted.FlatShapeLevel] in scala 3

prolativ commented 1 year ago

Ok, now I think that what actually matters here is that:

anyOptionExtensionMethods(Rep.None[Int])

as a stand-alone expression is typed as

anyOptionExtensionMethods[Int, Rep[Int]](Rep.None[Int])

in both scala 2 and 3. When

.getOrElse(0)

is added then the typing in scala 2 doesn't change, however scala 3 tries to infer a more specific type and the typing changes to

anyOptionExtensionMethods[Int, ConstColumn[Int]](Rep.None[Int])

The implementation of getOrElse has some assumptions about how types are inferred but this doesn't seem to work like this in scala 3