From c4c44017e351d706156442e291c033b6bf6dedd5 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 13 Jul 2022 18:09:25 +0200 Subject: [PATCH] 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. --- compiler/src/dotty/tools/dotc/ast/TreeInfo.scala | 6 ++++++ .../tools/dotc/core/ConstraintHandling.scala | 11 +++++++++-- .../src/dotty/tools/dotc/core/TypeComparer.scala | 10 +++++++--- .../tools/dotc/transform/PatternMatcher.scala | 7 +++++-- tests/neg-custom-args/fatal-warnings/i15662.scala | 15 +++++++++++++++ 5 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 tests/neg-custom-args/fatal-warnings/i15662.scala diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 42d24daf3255..553a73a6f4c6 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -121,6 +121,12 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case _ => Nil } + /** Is tree explicitly parameterized with type arguments? */ + def hasExplicitTypeArgs(tree: Tree): Boolean = tree match + case TypeApply(tycon, args) => + args.exists(arg => !arg.span.isZeroExtent && !tycon.span.contains(arg.span)) + case _ => false + /** Is tree a path? */ def isPath(tree: Tree): Boolean = unsplice(tree) match { case Ident(_) | This(_) | Super(_, _) => true diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 6c9bd1bb6577..7b96062dda95 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -74,6 +74,8 @@ trait ConstraintHandling { protected def necessaryConstraintsOnly(using Context): Boolean = ctx.mode.is(Mode.GadtConstraintInference) || myNecessaryConstraintsOnly + protected var trustBounds = true + def checkReset() = assert(addConstraintInvocations == 0) assert(frozenConstraint == false) @@ -260,12 +262,17 @@ trait ConstraintHandling { // If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure // that `param >: bound`. val narrowedBounds = - val saved = homogenizeArgs + val savedHomogenizeArgs = homogenizeArgs + val savedTrustBounds = trustBounds homogenizeArgs = Config.alignArgsInAnd try + trustBounds = false if isUpper then oldBounds.derivedTypeBounds(lo, hi & bound) else oldBounds.derivedTypeBounds(lo | bound, hi) - finally homogenizeArgs = saved + finally + homogenizeArgs = savedHomogenizeArgs + trustBounds = savedTrustBounds + //println(i"narrow bounds for $param from $oldBounds to $narrowedBounds") val c1 = constraint.updateEntry(param, narrowedBounds) (c1 eq constraint) || { diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 6d79f377c84e..e9e7f1b3e52d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -536,7 +536,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling || narrowGADTBounds(tp2, tp1, approx, isUpper = false)) && (isBottom(tp1) || GADTusage(tp2.symbol)) - isSubApproxHi(tp1, info2.lo) || compareGADT || tryLiftedToThis2 || fourthTry + isSubApproxHi(tp1, info2.lo) && (trustBounds || isSubApproxHi(tp1, info2.hi)) + || compareGADT + || tryLiftedToThis2 + || fourthTry case _ => val cls2 = tp2.symbol @@ -786,14 +789,15 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def fourthTry: Boolean = tp1 match { case tp1: TypeRef => tp1.info match { - case TypeBounds(_, hi1) => + case info1 @ TypeBounds(lo1, hi1) => def compareGADT = tp1.symbol.onGadtBounds(gbounds1 => isSubTypeWhenFrozen(gbounds1.hi, tp2) || narrowGADTBounds(tp1, tp2, approx, isUpper = true)) && (tp2.isAny || GADTusage(tp1.symbol)) - (!caseLambda.exists || canWidenAbstract) && isSubType(hi1, tp2, approx.addLow) + (!caseLambda.exists || canWidenAbstract) + && isSubType(hi1, tp2, approx.addLow) && (trustBounds || isSubType(lo1, tp2, approx.addLow)) || compareGADT || tryLiftedToThis1 case _ => diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 201e8a0db31f..0e4e32be52dd 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package transform import scala.annotation.tailrec @@ -388,7 +389,9 @@ object PatternMatcher { case Typed(pat, tpt) => val isTrusted = pat match { case UnApply(extractor, _, _) => - extractor.symbol.is(Synthetic) && extractor.symbol.owner.linkedClass.is(Case) + extractor.symbol.is(Synthetic) + && extractor.symbol.owner.linkedClass.is(Case) + && !hasExplicitTypeArgs(extractor) case _ => false } TestPlan(TypeTest(tpt, isTrusted), scrutinee, tree.span, diff --git a/tests/neg-custom-args/fatal-warnings/i15662.scala b/tests/neg-custom-args/fatal-warnings/i15662.scala new file mode 100644 index 000000000000..1d5ff21eb3ba --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15662.scala @@ -0,0 +1,15 @@ +case class Composite[T](v: T) + +def m(composite: Composite[_]): Unit = + composite match { + case Composite[Int](v) => println(v) // error: cannot be checked at runtime + case _ => println("OTHER") + } + +def m2(composite: Composite[_]): Unit = + composite match { + case Composite(v) => println(v) // ok + } + +@main def Test = + m(Composite("This is String"))