Skip to content

Commit 07ced9d

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 08cef8c commit 07ced9d

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
@@ -154,6 +154,59 @@ object Inferencing {
154154
tree
155155
}
156156

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

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)