Skip to content

Commit 281fd99

Browse files
authored
Improve ClassTag handling to avoid invalid ClassTag generation and inference failure (#16492)
The first commit generalizes the fix for #1730, the second commit improves inference in a common situation to generate a valid ClassTag rather than emitting an error, fixing the example from #16328 (comment).
2 parents f18d774 + f216ca2 commit 281fd99

File tree

3 files changed

+41
-3
lines changed

3 files changed

+41
-3
lines changed

compiler/src/dotty/tools/dotc/typer/Synthesizer.scala

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,25 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
2828
private type SpecialHandlers = List[(ClassSymbol, SpecialHandler)]
2929

3030
val synthesizedClassTag: SpecialHandler = (formal, span) =>
31+
def instArg(tp: Type): Type = tp.stripTypeVar match
32+
// Special case to avoid instantiating `Int & S` to `Int & Nothing` in
33+
// i16328.scala. The intersection comes from an earlier instantiation
34+
// to an upper bound.
35+
// The dual situation with unions is harder to trigger because lower
36+
// bounds are usually widened during instantiation.
37+
case tp: AndOrType if tp.tp1 =:= tp.tp2 =>
38+
instArg(tp.tp1)
39+
case _ =>
40+
if isFullyDefined(tp, ForceDegree.all) then tp
41+
else NoType // this happens in tests/neg/i15372.scala
42+
3143
val tag = formal.argInfos match
32-
case arg :: Nil if isFullyDefined(arg, ForceDegree.all) =>
33-
arg match
44+
case arg :: Nil =>
45+
instArg(arg) match
3446
case defn.ArrayOf(elemTp) =>
3547
val etag = typer.inferImplicitArg(defn.ClassTagClass.typeRef.appliedTo(elemTp), span)
3648
if etag.tpe.isError then EmptyTree else etag.select(nme.wrap)
37-
case tp if hasStableErasure(tp) && !defn.isBottomClassAfterErasure(tp.typeSymbol) =>
49+
case tp if hasStableErasure(tp) && !tp.isBottomTypeAfterErasure =>
3850
val sym = tp.typeSymbol
3951
val classTagModul = ref(defn.ClassTagModule)
4052
if defn.SpecialClassTagClasses.contains(sym) then

tests/neg/i1730.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.reflect.ClassTag
2+
3+
@main def Test =
4+
val x: Array[? <: String] = Array[Int & Nothing]() // error: No ClassTag available for Int & Nothing
5+
// (was: ClassCastException: [I cannot be cast to [Ljava.lang.String)
6+
val y: Array[? <: Int] = Array[String & Nothing]() // error: No ClassTag available for String & Nothing
7+
// (was: ClassCastException: [Lscala.runtime.Nothing$; cannot be cast to [I)

tests/pos/i16328.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import scala.reflect.ClassTag
2+
3+
object Test {
4+
def getParamType[T: ClassTag](x: T => Int): T = ???
5+
6+
def id[S](x: S): S = x
7+
8+
def main(args: Array[String]) = {
9+
// worked before
10+
val a1 = getParamType((x: Int) => x)
11+
val a2: Int = a1 // ensure that we actually got a ClassTag for the right type
12+
13+
// broken before
14+
val b1 = id(getParamType((x: Int) => x)) // was error
15+
val b2: Int = b1
16+
val c1 = id(id(getParamType((x: Int) => x))) // was error
17+
val c2: Int = c1
18+
}
19+
}

0 commit comments

Comments
 (0)