Skip to content

Commit c4c4401

Browse files
committed
Don't trust case class extractors with explicit type arguments
#6551 introduced an exception where type tests of parameterized case classes were not flagged as "type test cannot be checked at runtime". The idea was that the parameters would be inferred by GADT reasoning and would therefore be correct. But with #15356 we now also allow explicit type arguments in extractors. If these are given we cannot guarantee that a type test for a case class in an unapply will always succeed. So we need an unchecked warning.
1 parent 79d9a6f commit c4c4401

File tree

5 files changed

+42
-7
lines changed

5 files changed

+42
-7
lines changed

Diff for: compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

+6
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
121121
case _ => Nil
122122
}
123123

124+
/** Is tree explicitly parameterized with type arguments? */
125+
def hasExplicitTypeArgs(tree: Tree): Boolean = tree match
126+
case TypeApply(tycon, args) =>
127+
args.exists(arg => !arg.span.isZeroExtent && !tycon.span.contains(arg.span))
128+
case _ => false
129+
124130
/** Is tree a path? */
125131
def isPath(tree: Tree): Boolean = unsplice(tree) match {
126132
case Ident(_) | This(_) | Super(_, _) => true

Diff for: compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala

+9-2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ trait ConstraintHandling {
7474
protected def necessaryConstraintsOnly(using Context): Boolean =
7575
ctx.mode.is(Mode.GadtConstraintInference) || myNecessaryConstraintsOnly
7676

77+
protected var trustBounds = true
78+
7779
def checkReset() =
7880
assert(addConstraintInvocations == 0)
7981
assert(frozenConstraint == false)
@@ -260,12 +262,17 @@ trait ConstraintHandling {
260262
// If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure
261263
// that `param >: bound`.
262264
val narrowedBounds =
263-
val saved = homogenizeArgs
265+
val savedHomogenizeArgs = homogenizeArgs
266+
val savedTrustBounds = trustBounds
264267
homogenizeArgs = Config.alignArgsInAnd
265268
try
269+
trustBounds = false
266270
if isUpper then oldBounds.derivedTypeBounds(lo, hi & bound)
267271
else oldBounds.derivedTypeBounds(lo | bound, hi)
268-
finally homogenizeArgs = saved
272+
finally
273+
homogenizeArgs = savedHomogenizeArgs
274+
trustBounds = savedTrustBounds
275+
//println(i"narrow bounds for $param from $oldBounds to $narrowedBounds")
269276
val c1 = constraint.updateEntry(param, narrowedBounds)
270277
(c1 eq constraint)
271278
|| {

Diff for: compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+7-3
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
536536
|| narrowGADTBounds(tp2, tp1, approx, isUpper = false))
537537
&& (isBottom(tp1) || GADTusage(tp2.symbol))
538538

539-
isSubApproxHi(tp1, info2.lo) || compareGADT || tryLiftedToThis2 || fourthTry
539+
isSubApproxHi(tp1, info2.lo) && (trustBounds || isSubApproxHi(tp1, info2.hi))
540+
|| compareGADT
541+
|| tryLiftedToThis2
542+
|| fourthTry
540543

541544
case _ =>
542545
val cls2 = tp2.symbol
@@ -786,14 +789,15 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
786789
def fourthTry: Boolean = tp1 match {
787790
case tp1: TypeRef =>
788791
tp1.info match {
789-
case TypeBounds(_, hi1) =>
792+
case info1 @ TypeBounds(lo1, hi1) =>
790793
def compareGADT =
791794
tp1.symbol.onGadtBounds(gbounds1 =>
792795
isSubTypeWhenFrozen(gbounds1.hi, tp2)
793796
|| narrowGADTBounds(tp1, tp2, approx, isUpper = true))
794797
&& (tp2.isAny || GADTusage(tp1.symbol))
795798

796-
(!caseLambda.exists || canWidenAbstract) && isSubType(hi1, tp2, approx.addLow)
799+
(!caseLambda.exists || canWidenAbstract)
800+
&& isSubType(hi1, tp2, approx.addLow) && (trustBounds || isSubType(lo1, tp2, approx.addLow))
797801
|| compareGADT
798802
|| tryLiftedToThis1
799803
case _ =>

Diff for: compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package dotty.tools.dotc
1+
package dotty.tools
2+
package dotc
23
package transform
34

45
import scala.annotation.tailrec
@@ -388,7 +389,9 @@ object PatternMatcher {
388389
case Typed(pat, tpt) =>
389390
val isTrusted = pat match {
390391
case UnApply(extractor, _, _) =>
391-
extractor.symbol.is(Synthetic) && extractor.symbol.owner.linkedClass.is(Case)
392+
extractor.symbol.is(Synthetic)
393+
&& extractor.symbol.owner.linkedClass.is(Case)
394+
&& !hasExplicitTypeArgs(extractor)
392395
case _ => false
393396
}
394397
TestPlan(TypeTest(tpt, isTrusted), scrutinee, tree.span,

Diff for: tests/neg-custom-args/fatal-warnings/i15662.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
case class Composite[T](v: T)
2+
3+
def m(composite: Composite[_]): Unit =
4+
composite match {
5+
case Composite[Int](v) => println(v) // error: cannot be checked at runtime
6+
case _ => println("OTHER")
7+
}
8+
9+
def m2(composite: Composite[_]): Unit =
10+
composite match {
11+
case Composite(v) => println(v) // ok
12+
}
13+
14+
@main def Test =
15+
m(Composite("This is String"))

0 commit comments

Comments
 (0)