Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't trust case class extractors with explicit type arguments #15669

Merged
merged 1 commit into from Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Expand Up @@ -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
Expand Down
11 changes: 9 additions & 2 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
|| {
Expand Down
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Expand Up @@ -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
Expand Down Expand Up @@ -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 _ =>
Expand Down
7 changes: 5 additions & 2 deletions 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
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions 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"))