Skip to content

Commit 9ea9ef4

Browse files
committed
Fix #2675: Properly handle undetermined type variables in unapply
In non-variant contexts, we used to always maximize them, but this is unsound. See neg/patternUnsoundness.scala. We now create fresh abstract types in these situations.
1 parent c2e5011 commit 9ea9ef4

File tree

11 files changed

+66
-50
lines changed

11 files changed

+66
-50
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
158158
def TypeBoundsTree(lo: Tree, hi: Tree)(implicit ctx: Context): TypeBoundsTree =
159159
ta.assignType(untpd.TypeBoundsTree(lo, hi), lo, hi)
160160

161-
def Bind(sym: TermSymbol, body: Tree)(implicit ctx: Context): Bind =
161+
def Bind(sym: Symbol, body: Tree)(implicit ctx: Context): Bind =
162162
ta.assignType(untpd.Bind(sym.name, body), sym)
163163

164164
/** A pattern corresponding to `sym: tpe` */

compiler/src/dotty/tools/dotc/core/Symbols.scala

+8
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,14 @@ trait Symbols { this: Context =>
219219
modFlags | PackageCreationFlags, clsFlags | PackageCreationFlags,
220220
Nil, decls)
221221

222+
/** Define a new symbol associated with a Bind or pattern wildcard and
223+
* make it gadt narrowable.
224+
*/
225+
def newPatternBoundSymbol(name: Name, info: Type, pos: Position) = {
226+
val sym = newSymbol(owner, name, Case, info, coord = pos)
227+
if (name.isTypeName) gadt.setBounds(sym, info.bounds)
228+
sym
229+
}
222230

223231
/** Create a stub symbol that will issue a missing reference error
224232
* when attempted to be completed.

compiler/src/dotty/tools/dotc/core/Types.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -3308,7 +3308,7 @@ object Types {
33083308
def isInstantiated(implicit ctx: Context) = instanceOpt.exists
33093309

33103310
/** Instantiate variable with given type */
3311-
private def instantiateWith(tp: Type)(implicit ctx: Context): Type = {
3311+
def instantiateWith(tp: Type)(implicit ctx: Context): Type = {
33123312
assert(tp ne this, s"self instantiation of ${tp.show}, constraint = ${ctx.typerState.constraint.show}")
33133313
typr.println(s"instantiating ${this.show} with ${tp.show}")
33143314
if ((ctx.typerState eq owningState.get) && !ctx.typeComparer.subtypeCheckInProgress)

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -994,8 +994,9 @@ class TreeUnpickler(reader: TastyReader,
994994
val elemtpt = readTpt()
995995
SeqLiteral(until(end)(readTerm()), elemtpt)
996996
case BIND =>
997-
val name = readName()
997+
var name: Name = readName()
998998
val info = readType()
999+
if (info.isInstanceOf[TypeType]) name = name.toTypeName
9991000
val sym = ctx.newSymbol(ctx.owner, name, EmptyFlags, info, coord = coordAt(start))
10001001
registerSym(start, sym)
10011002
Bind(sym, readTerm())

compiler/src/dotty/tools/dotc/transform/TailRec.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,6 @@ class TailRec extends MiniPhase with FullParameterization {
219219
val isRecursiveCall = (method eq sym)
220220
val recvWiden = prefix.tpe.widenDealias
221221

222-
223222
def continue = {
224223
val method = noTailTransform(call)
225224
val methodWithTargs = if (targs.nonEmpty) TypeApply(method, targs) else method
@@ -237,7 +236,9 @@ class TailRec extends MiniPhase with FullParameterization {
237236

238237
if (isRecursiveCall) {
239238
if (ctx.tailPos) {
240-
val receiverIsSame = enclosingClass.appliedRef.widenDealias =:= recvWiden
239+
val receiverIsSame =
240+
recvWiden <:< enclosingClass.appliedRef &&
241+
(sym.isEffectivelyFinal || enclosingClass.appliedRef <:< recvWiden)
241242
val receiverIsThis = prefix.tpe =:= thisType || prefix.tpe.widen =:= thisType
242243

243244
def rewriteTailCall(recv: Tree): Tree = {

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

+17-22
Original file line numberDiff line numberDiff line change
@@ -890,12 +890,26 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
890890
/** Produce a typed qual.unapply or qual.unapplySeq tree, or
891891
* else if this fails follow a type alias and try again.
892892
*/
893-
val unapplyFn = trySelectUnapply(qual) { sel =>
893+
var unapplyFn = trySelectUnapply(qual) { sel =>
894894
val qual1 = followTypeAlias(qual)
895895
if (qual1.isEmpty) notAnExtractor(sel)
896896
else trySelectUnapply(qual1)(_ => notAnExtractor(sel))
897897
}
898898

899+
/** Add a `Bind` node for each `bound` symbol in a type application `unapp` */
900+
def addBinders(unapp: Tree, bound: List[Symbol]) = unapp match {
901+
case TypeApply(fn, args) =>
902+
def addBinder(arg: Tree) = arg.tpe.stripTypeVar match {
903+
case ref: TypeRef if bound.contains(ref.symbol) =>
904+
tpd.Bind(ref.symbol, Ident(ref))
905+
case _ =>
906+
arg
907+
}
908+
tpd.cpy.TypeApply(unapp)(fn, args.mapConserve(addBinder))
909+
case _ =>
910+
unapp
911+
}
912+
899913
def fromScala2x = unapplyFn.symbol.exists && (unapplyFn.symbol.owner is Scala2x)
900914

901915
/** Is `subtp` a subtype of `tp` or of some generalization of `tp`?
@@ -922,27 +936,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
922936
unapp.println(i"case 1 $unapplyArgType ${ctx.typerState.constraint}")
923937
selType
924938
} else if (isSubTypeOfParent(unapplyArgType, selType)(ctx.addMode(Mode.GADTflexible))) {
925-
maximizeType(unapplyArgType) match {
926-
case Some(tvar) =>
927-
def msg =
928-
ex"""There is no best instantiation of pattern type $unapplyArgType
929-
|that makes it a subtype of selector type $selType.
930-
|Non-variant type variable ${tvar.origin} cannot be uniquely instantiated."""
931-
if (fromScala2x) {
932-
// We can't issue an error here, because in Scala 2, ::[B] is invariant
933-
// whereas List[+T] is covariant. According to the strict rule, a pattern
934-
// match of a List[C] against a case x :: xs is illegal, because
935-
// B cannot be uniquely instantiated. Of course :: should have been
936-
// covariant in the first place, but in the Scala libraries it isn't.
937-
// So for now we allow these kinds of patterns, even though they
938-
// can open unsoundness holes. See SI-7952 for an example of the hole this opens.
939-
if (ctx.settings.verbose.value) ctx.warning(msg, tree.pos)
940-
} else {
941-
unapp.println(s" ${unapplyFn.symbol.owner} ${unapplyFn.symbol.owner is Scala2x}")
942-
ctx.strictWarning(msg, tree.pos)
943-
}
944-
case _ =>
945-
}
939+
val patternBound = maximizeType(unapplyArgType, tree.pos, fromScala2x)
940+
if (patternBound.nonEmpty) unapplyFn = addBinders(unapplyFn, patternBound)
946941
unapp.println(i"case 2 $unapplyArgType ${ctx.typerState.constraint}")
947942
unapplyArgType
948943
} else {

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

+14-8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Trees._
99
import Constants._
1010
import Scopes._
1111
import ProtoTypes._
12+
import NameKinds.WildcardParamName
1213
import annotation.unchecked
1314
import util.Positions._
1415
import util.{Stats, SimpleIdentityMap}
@@ -222,23 +223,28 @@ object Inferencing {
222223
case _ => NoType
223224
}
224225

225-
/** Instantiate undetermined type variables to that type `tp` is
226-
* maximized and return None. If this is not possible, because a non-variant
227-
* typevar is not uniquely determined, return that typevar in a Some.
226+
/** Instantiate undetermined type variables so that type `tp` is maximized.
227+
* @return The list of type symbols that were created
228+
* to instantiate undetermined type variables that occur non-variantly
228229
*/
229-
def maximizeType(tp: Type)(implicit ctx: Context): Option[TypeVar] = Stats.track("maximizeType") {
230+
def maximizeType(tp: Type, pos: Position, fromScala2x: Boolean)(implicit ctx: Context): List[Symbol] = Stats.track("maximizeType") {
230231
val vs = variances(tp, alwaysTrue)
231-
var result: Option[TypeVar] = None
232+
val patternBound = new mutable.ListBuffer[Symbol]
232233
vs foreachBinding { (tvar, v) =>
233234
if (v == 1) tvar.instantiate(fromBelow = false)
234235
else if (v == -1) tvar.instantiate(fromBelow = true)
235236
else {
236237
val bounds = ctx.typerState.constraint.fullBounds(tvar.origin)
237-
if (!(bounds.hi <:< bounds.lo)) result = Some(tvar)
238-
tvar.instantiate(fromBelow = false)
238+
if (bounds.hi <:< bounds.lo || bounds.hi.classSymbol.is(Final) || fromScala2x)
239+
tvar.instantiate(fromBelow = false)
240+
else {
241+
val wildCard = ctx.newPatternBoundSymbol(WildcardParamName.fresh().toTypeName, bounds, pos)
242+
tvar.instantiateWith(wildCard.typeRef)
243+
patternBound += wildCard
244+
}
239245
}
240246
}
241-
result
247+
patternBound.toList
242248
}
243249

244250
type VarianceMap = SimpleIdentityMap[TypeVar, Integer]

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

+4-12
Original file line numberDiff line numberDiff line change
@@ -1252,16 +1252,6 @@ class Typer extends Namer
12521252
assignType(cpy.ByNameTypeTree(tree)(result1), result1)
12531253
}
12541254

1255-
/** Define a new symbol associated with a Bind or pattern wildcard and
1256-
* make it gadt narrowable.
1257-
*/
1258-
private def newPatternBoundSym(name: Name, info: Type, pos: Position)(implicit ctx: Context) = {
1259-
val flags = if (name.isTypeName) BindDefinedType else EmptyFlags
1260-
val sym = ctx.newSymbol(ctx.owner, name, flags | Case, info, coord = pos)
1261-
if (name.isTypeName) ctx.gadt.setBounds(sym, info.bounds)
1262-
sym
1263-
}
1264-
12651255
def typedTypeBoundsTree(tree: untpd.TypeBoundsTree)(implicit ctx: Context): TypeBoundsTree = track("typedTypeBoundsTree") {
12661256
val TypeBoundsTree(lo, hi) = tree
12671257
val lo1 = typed(lo)
@@ -1276,7 +1266,8 @@ class Typer extends Namer
12761266
// The bounds of the type symbol can be constrained when comparing a pattern type
12771267
// with an expected type in typedTyped. The type symbol is eliminated once
12781268
// the enclosing pattern has been typechecked; see `indexPattern` in `typedCase`.
1279-
val wildcardSym = newPatternBoundSym(tpnme.WILDCARD, tree1.tpe, tree.pos)
1269+
val wildcardSym = ctx.newPatternBoundSymbol(tpnme.WILDCARD, tree1.tpe, tree.pos)
1270+
wildcardSym.setFlag(BindDefinedType) // can we get rid of this?
12801271
tree1.withType(wildcardSym.typeRef)
12811272
}
12821273
else tree1
@@ -1296,7 +1287,8 @@ class Typer extends Namer
12961287
case _ =>
12971288
if (tree.name == nme.WILDCARD) body1
12981289
else {
1299-
val sym = newPatternBoundSym(tree.name, body1.tpe.underlyingIfRepeated(isJava = false), tree.pos)
1290+
val sym = ctx.newPatternBoundSymbol(tree.name, body1.tpe.underlyingIfRepeated(isJava = false), tree.pos)
1291+
if (sym.isType) sym.setFlag(BindDefinedType) // can we get rid of this?
13001292
if (ctx.mode.is(Mode.InPatternAlternative))
13011293
ctx.error(i"Illegal variable ${sym.name} in pattern alternative", tree.pos)
13021294
assignType(cpy.Bind(tree)(tree.name, body1), sym)

tests/neg-tailcall/t1672b.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ object Test1772B {
4646
else 1 + (try {
4747
throw new RuntimeException
4848
} catch {
49-
case _: Throwable => bar(i - 1) // old error
49+
case _: Throwable => bar(i - 1) // error: cannot rewrite recursive call
5050
})
5151
}
5252
}

tests/pos/patternUnsoundness.scala renamed to tests/neg/patternUnsoundness.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ object patternUnsoundness extends App {
1010
val y: C[Object] = x
1111

1212
y match {
13-
case d @ D(x) => d.s = new Integer(1)
13+
case d @ D(x) => d.s = new Integer(1) // error
1414
}
1515

16-
val z: String = x.s // ClassCast exception
16+
val z: String = x.s // used to throw ClassCast exception
1717
}

tests/pos-special/i2675.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class Ref[-T]
2+
3+
sealed trait Op
4+
case class Send[T](ref: Ref[T], msg: T) extends Op
5+
6+
object Main {
7+
val ref: Ref[String] = ???
8+
val op: Op = Send(ref, "hello")
9+
op match {
10+
case Send(ref, msg) =>
11+
}
12+
}
13+

0 commit comments

Comments
 (0)