Skip to content

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

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

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
@@ -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
11 changes: 9 additions & 2 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
@@ -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)
|| {
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
@@ -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 _ =>
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala
Original file line number Diff line number Diff line change
@@ -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,
15 changes: 15 additions & 0 deletions tests/neg-custom-args/fatal-warnings/i15662.scala
Original file line number Diff line number Diff line change
@@ -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"))