Skip to content

Commit 49f9dc6

Browse files
committed
Be more careful when constraining parameter types wrt scrutinees
i3989a.scala gives an example which is rejected under the new scheme. This would pass and fail at runtime under the previous scheme.
1 parent 7ae7560 commit 49f9dc6

File tree

5 files changed

+71
-4
lines changed

5 files changed

+71
-4
lines changed

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,59 @@ object Inferencing {
153153
tree
154154
}
155155

156+
/** Derive information about a pattern type by comparing it with some variant of the
157+
* static scrutinee type. We have the following situation in case of a (dynamic) pattern match:
158+
*
159+
* StaticScrutineeType PatternType
160+
* \ /
161+
* DynamicScrutineeType
162+
*
163+
* If `PatternType` is not a subtype of `StaticScrutineeType, there's no information to be gained.
164+
* Now let's say we can prove that `PatternType <: StaticScrutineeType`.
165+
*
166+
* StaticScrutineeType
167+
* | \
168+
* | \
169+
* | \
170+
* | PatternType
171+
* | /
172+
* DynamicScrutineeType
173+
*
174+
* What can we say about the relationship of parameter types between `PatternType` and
175+
* `DynamicScrutineeType`?
176+
*
177+
* - If `DynamicScrutineeType` refines the type parameters of `StaticScrutineeType`
178+
* in the same way as `PatternType`, the subtype test `PatternType <:< StaticScrutineeType`
179+
* tells us all we need to know.
180+
* - Otherwise, if variant refinement is a possibility we can only make predictions
181+
* about invariant parameters of `StaticScrutineeType`. Hence we do a subtype test
182+
* where `PatternType <: widenVariantParams(StaticScrutineeType)`, where `widenVariantParams`
183+
* replaces all type argument of variant parameters with empty bounds.
184+
*/
185+
def constrainPatternType(tp: Type, pt: Type)(implicit ctx: Context) = {
186+
def refinementIsInvariant(tp: Type): Boolean = tp match {
187+
case tp: ClassInfo => tp.cls.is(Final) || tp.cls.is(Case)
188+
case tp: TypeProxy => refinementIsInvariant(tp.underlying)
189+
case tp: AndOrType => refinementIsInvariant(tp.tp1) && refinementIsInvariant(tp.tp2)
190+
case _ => false
191+
}
192+
193+
def widenVariantParams = new TypeMap {
194+
def apply(tp: Type) = mapOver(tp) match {
195+
case tp @ AppliedType(tycon, args) =>
196+
val args1 = args.zipWithConserve(tycon.typeParams)((arg, tparam) =>
197+
if (tparam.paramVariance != 0) TypeBounds.empty else arg
198+
)
199+
tp.derivedAppliedType(tycon, args1)
200+
case tp =>
201+
tp
202+
}
203+
}
204+
205+
val widePt = if (ctx.scala2Mode || refinementIsInvariant(tp)) pt else widenVariantParams(pt)
206+
(tp <:< widePt)(ctx.addMode(Mode.GADTflexible))
207+
}
208+
156209
/** The list of uninstantiated type variables bound by some prefix of type `T` which
157210
* occur in at least one formal parameter type of a prefix application.
158211
* Considered prefixes are:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ class Typer extends Namer
568568
def typedTpt = checkSimpleKinded(typedType(tree.tpt))
569569
def handlePattern: Tree = {
570570
val tpt1 = typedTpt
571-
if (!ctx.isAfterTyper) tpt1.tpe.<:<(pt)(ctx.addMode(Mode.GADTflexible))
571+
if (!ctx.isAfterTyper) constrainPatternType(tpt1.tpe, pt)
572572
// special case for an abstract type that comes with a class tag
573573
tryWithClassTag(ascription(tpt1, isWildcard = true), pt)
574574
}

tests/neg/i3989a.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
object Test extends App {
2+
trait A[+X]
3+
class B[+X](val x: X) extends A[X]
4+
class C[+X](x: Any) extends B[Any](x) with A[X]
5+
def f(a: A[Int]): Int = a match {
6+
case a: B[_] => a.x // error
7+
case _ => 0
8+
}
9+
f(new C[Int]("foo"))
10+
}

tests/pos/t3880.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
abstract class Bar[+B] {
22
}
3-
abstract class C1[+B] extends Bar[B] {
3+
final class C1[+B] extends Bar[B] {
44
private[this] def g(x: C1[B]): Unit = ()
55

66
// this method is fine: notice that it allows the call to g,
77
// which requires C1[B], even though we matched on C1[_].
8-
// (That is good news.)
8+
// (That is good news, but is sound only because C1 is final; see #3989
9+
// and compare with i3989a.scala.
910
private[this] def f1(x: Bar[B]): Unit = x match {
1011
case x: C1[_] => g(x)
1112
}

tests/pos/t6084.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package object foo { type X[T, U] = (T => U) }
22

33
package foo {
4-
abstract class Foo[T, U](val d: T => U) extends (T => U) {
4+
// Note that Foo must be final because of #3989.
5+
final class Foo[T, U](val d: T => U) extends (T => U) {
56
def f1(r: X[T, U]) = r match { case x: Foo[_,_] => x.d } // inferred ok
67
def f2(r: X[T, U]): (T => U) = r match { case x: Foo[_,_] => x.d } // dealiased ok
78
def f3(r: X[T, U]): X[T, U] = r match { case x: Foo[_,_] => x.d } // alias not ok
89

10+
def apply(x: T): U = d(x)
11+
912
// x.d : foo.this.package.type.X[?scala.reflect.internal.Types$NoPrefix$?.T, ?scala.reflect.internal.Types$NoPrefix$?.U] ~>scala.this.Function1[?scala.reflect.internal.Types$NoPrefix$?.T, ?scala.reflect.internal.Types$NoPrefix$?.U]
1013
// at scala.Predef$.assert(Predef.scala:170)
1114
// at scala.tools.nsc.Global.assert(Global.scala:235)

0 commit comments

Comments
 (0)