Skip to content

Commit 8ab3ce6

Browse files
authored
Merge pull request #3889 from dotty-staging/fix-#2675
Fix #2675: Properly handle undetermined type variables in unapply
2 parents 6998fd0 + 9dca57b commit 8ab3ce6

19 files changed

+226
-116
lines changed

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

Lines changed: 1 addition & 1 deletion
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/Flags.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,6 @@ object Flags {
327327
/** A method that has default params */
328328
final val DefaultParameterized = termFlag(27, "<defaultparam>")
329329

330-
/** A type that is defined by a type bind */
331-
final val BindDefinedType = typeFlag(27, "<bind-defined>")
332-
333330
/** Symbol is inlined */
334331
final val Inline = commonFlag(29, "inline")
335332

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

Lines changed: 8 additions & 0 deletions
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/TypeComparer.scala

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,23 +1093,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
10931093
val tparam = tr.symbol
10941094
typr.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.isRef(tparam)}")
10951095
if (bound.isRef(tparam)) false
1096-
else bound match {
1097-
case bound: TypeRef
1098-
if bound.symbol.is(BindDefinedType) &&
1099-
ctx.gadt.bounds.contains(bound.symbol) &&
1100-
!tr.symbol.is(BindDefinedType) =>
1101-
// Avoid having pattern-bound types in gadt bounds,
1102-
// as these might be eliminated once the pattern is typechecked.
1103-
// Pattern-bound type symbols should be narrowed first, only if that fails
1104-
// should symbols in the environment be constrained.
1105-
narrowGADTBounds(bound, tr, !isUpper)
1106-
case _ =>
1107-
val oldBounds = ctx.gadt.bounds(tparam)
1108-
val newBounds =
1109-
if (isUpper) TypeBounds(oldBounds.lo, oldBounds.hi & bound)
1110-
else TypeBounds(oldBounds.lo | bound, oldBounds.hi)
1111-
isSubType(newBounds.lo, newBounds.hi) &&
1112-
{ ctx.gadt.setBounds(tparam, newBounds); true }
1096+
else {
1097+
val oldBounds = ctx.gadt.bounds(tparam)
1098+
val newBounds =
1099+
if (isUpper) TypeBounds(oldBounds.lo, oldBounds.hi & bound)
1100+
else TypeBounds(oldBounds.lo | bound, oldBounds.hi)
1101+
isSubType(newBounds.lo, newBounds.hi) &&
1102+
{ ctx.gadt.setBounds(tparam, newBounds); true }
11131103
}
11141104
}
11151105

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

Lines changed: 1 addition & 1 deletion
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/TreePickler.scala

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,6 @@ class TreePickler(pickler: TastyPickler) {
161161
pickleConstant(value)
162162
case tpe: NamedType =>
163163
val sym = tpe.symbol
164-
def pickleDirectRef() = {
165-
writeByte(if (tpe.isType) TYPEREFdirect else TERMREFdirect)
166-
pickleSymRef(sym)
167-
}
168164
def pickleExternalRef(sym: Symbol) = {
169165
def pickleCore() = {
170166
pickleNameAndSig(sym.name, tpe.signature)
@@ -188,17 +184,10 @@ class TreePickler(pickler: TastyPickler) {
188184
writeByte(if (tpe.isType) TYPEREFpkg else TERMREFpkg)
189185
pickleName(sym.fullName)
190186
}
191-
else if (tpe.prefix == NoPrefix)
192-
if (sym is Flags.BindDefinedType) {
193-
registerDef(sym)
194-
writeByte(BIND)
195-
withLength {
196-
pickleName(sym.name)
197-
pickleType(sym.info)
198-
pickleDirectRef()
199-
}
200-
}
201-
else pickleDirectRef()
187+
else if (tpe.prefix == NoPrefix) {
188+
writeByte(if (tpe.isType) TYPEREFdirect else TERMREFdirect)
189+
pickleSymRef(sym)
190+
}
202191
else if (isLocallyDefined(sym)) {
203192
writeByte(if (tpe.isType) TYPEREFsymbol else TERMREFsymbol)
204193
pickleSymRef(sym); pickleType(tpe.prefix)
@@ -445,7 +434,9 @@ class TreePickler(pickler: TastyPickler) {
445434
case Bind(name, body) =>
446435
registerDef(tree.symbol)
447436
writeByte(BIND)
448-
withLength { pickleName(name); pickleType(tree.symbol.info); pickleTree(body) }
437+
withLength {
438+
pickleName(name); pickleType(tree.symbol.info); pickleTree(body)
439+
}
449440
case Alternative(alts) =>
450441
writeByte(ALTERNATIVE)
451442
withLength { alts.foreach(pickleTree) }

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

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,10 @@ class TreeUnpickler(reader: TastyReader,
109109
while (nextByte == PARAMS || nextByte == TYPEPARAM) skipTree()
110110

111111
/** Record all directly nested definitions and templates in current tree
112-
* as `OwnerTree`s in `buf`
112+
* as `OwnerTree`s in `buf`.
113+
* A complication concerns member definitions. These are lexically nested in a
114+
* Template node, but need to be listed separately in the OwnerTree of the enclosing class
115+
* in order not to confuse owner chains.
113116
*/
114117
def scanTree(buf: ListBuffer[OwnerTree], mode: MemberDefMode = AllDefs): Unit = {
115118
val start = currentAddr
@@ -118,8 +121,15 @@ class TreeUnpickler(reader: TastyReader,
118121
case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | TEMPLATE =>
119122
val end = readEnd()
120123
for (i <- 0 until numRefs(tag)) readNat()
121-
if (tag == TEMPLATE) scanTrees(buf, end, MemberDefsOnly)
122-
if (mode != NoMemberDefs) buf += new OwnerTree(start, tag, fork, end)
124+
if (tag == TEMPLATE) {
125+
// Read all member definitions now, whereas non-members are children of
126+
// template's owner tree.
127+
val nonMemberReader = fork
128+
scanTrees(buf, end, MemberDefsOnly)
129+
buf += new OwnerTree(start, tag, nonMemberReader, end)
130+
}
131+
else if (mode != NoMemberDefs)
132+
buf += new OwnerTree(start, tag, fork, end)
123133
goto(end)
124134
case tag =>
125135
if (mode == MemberDefsOnly) skipTree(tag)
@@ -132,7 +142,11 @@ class TreeUnpickler(reader: TastyReader,
132142
}
133143
else {
134144
for (i <- 0 until nrefs) readNat()
135-
scanTrees(buf, end)
145+
if (tag == BIND) {
146+
buf += new OwnerTree(start, tag, fork, end)
147+
goto(end)
148+
}
149+
else scanTrees(buf, end)
136150
}
137151
}
138152
else if (tag >= firstNatASTTreeTag) { readNat(); scanTree(buf) }
@@ -267,12 +281,6 @@ class TreeUnpickler(reader: TastyReader,
267281
OrType(readType(), readType())
268282
case SUPERtype =>
269283
SuperType(readType(), readType())
270-
case BIND =>
271-
val sym = ctx.newSymbol(ctx.owner, readName().toTypeName, BindDefinedType, readType(),
272-
coord = coordAt(start))
273-
registerSym(start, sym)
274-
if (currentAddr != end) readType()
275-
TypeRef(NoPrefix, sym)
276284
case POLYtype =>
277285
readMethodic(PolyType, _.toTypeName)
278286
case METHODtype =>
@@ -431,6 +439,8 @@ class TreeUnpickler(reader: TastyReader,
431439
def createSymbol()(implicit ctx: Context): Symbol = nextByte match {
432440
case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM =>
433441
createMemberSymbol()
442+
case BIND =>
443+
createBindSymbol()
434444
case TEMPLATE =>
435445
val localDummy = ctx.newLocalDummy(ctx.owner)
436446
registerSym(currentAddr, localDummy)
@@ -439,6 +449,25 @@ class TreeUnpickler(reader: TastyReader,
439449
throw new Error(s"illegal createSymbol at $currentAddr, tag = $tag")
440450
}
441451

452+
private def createBindSymbol()(implicit ctx: Context): Symbol = {
453+
val start = currentAddr
454+
val tag = readByte()
455+
val end = readEnd()
456+
var name: Name = readName()
457+
nextUnsharedTag match {
458+
case TYPEBOUNDS | TYPEALIAS => name = name.toTypeName
459+
case _ =>
460+
}
461+
val typeReader = fork
462+
val completer = new LazyType {
463+
def complete(denot: SymDenotation)(implicit ctx: Context) =
464+
denot.info = typeReader.readType()
465+
}
466+
val sym = ctx.newSymbol(ctx.owner, name, EmptyFlags, completer, coord = coordAt(start))
467+
registerSym(start, sym)
468+
sym
469+
}
470+
442471
/** Create symbol of member definition or parameter node and enter in symAtAddr map
443472
* @return the created symbol
444473
*/
@@ -994,10 +1023,9 @@ class TreeUnpickler(reader: TastyReader,
9941023
val elemtpt = readTpt()
9951024
SeqLiteral(until(end)(readTerm()), elemtpt)
9961025
case BIND =>
997-
val name = readName()
998-
val info = readType()
999-
val sym = ctx.newSymbol(ctx.owner, name, EmptyFlags, info, coord = coordAt(start))
1000-
registerSym(start, sym)
1026+
val sym = symAtAddr.getOrElse(start, forkAt(start).createSymbol())
1027+
readName()
1028+
readType()
10011029
Bind(sym, readTerm())
10021030
case ALTERNATIVE =>
10031031
Alternative(until(end)(readTerm()))
@@ -1154,11 +1182,16 @@ class TreeUnpickler(reader: TastyReader,
11541182
*/
11551183
class OwnerTree(val addr: Addr, tag: Int, reader: TreeReader, val end: Addr) {
11561184

1185+
private var myChildren: List[OwnerTree] = null
1186+
11571187
/** All definitions that have the definition at `addr` as closest enclosing definition */
1158-
lazy val children: List[OwnerTree] = {
1159-
val buf = new ListBuffer[OwnerTree]
1160-
reader.scanTrees(buf, end, if (tag == TEMPLATE) NoMemberDefs else AllDefs)
1161-
buf.toList
1188+
def children: List[OwnerTree] = {
1189+
if (myChildren == null) myChildren = {
1190+
val buf = new ListBuffer[OwnerTree]
1191+
reader.scanTrees(buf, end, if (tag == TEMPLATE) NoMemberDefs else AllDefs)
1192+
buf.toList
1193+
}
1194+
myChildren
11621195
}
11631196

11641197
/** Find the owner of definition at `addr` */
@@ -1177,13 +1210,19 @@ class TreeUnpickler(reader: TastyReader,
11771210
}
11781211
catch {
11791212
case ex: TreeWithoutOwner =>
1180-
println(i"no owner for $addr among $cs") // DEBUG
1213+
pickling.println(i"no owner for $addr among $cs%, %")
1214+
throw ex
1215+
}
1216+
try search(children, NoSymbol)
1217+
catch {
1218+
case ex: TreeWithoutOwner =>
1219+
pickling.println(s"ownerTree = $ownerTree")
11811220
throw ex
11821221
}
1183-
search(children, NoSymbol)
11841222
}
11851223

1186-
override def toString = s"OwnerTree(${addr.index}, ${end.index}"
1224+
override def toString =
1225+
s"OwnerTree(${addr.index}, ${end.index}, ${if (myChildren == null) "?" else myChildren.mkString(" ")})"
11871226
}
11881227
}
11891228

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

Lines changed: 3 additions & 2 deletions
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

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

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

Lines changed: 14 additions & 8 deletions
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/TypeAssigner.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ trait TypeAssigner {
362362
def assignType(tree: untpd.Apply, fn: Tree, args: List[Tree])(implicit ctx: Context) = {
363363
val ownType = fn.tpe.widen match {
364364
case fntpe: MethodType =>
365-
if (sameLength(fntpe.paramInfos, args) || ctx.phase.prev.relaxedTyping || true)
365+
if (sameLength(fntpe.paramInfos, args) || ctx.phase.prev.relaxedTyping)
366366
if (fntpe.isDependent) safeSubstParams(fntpe.resultType, fntpe.paramRefs, args.tpes)
367367
else fntpe.resultType
368368
else

0 commit comments

Comments
 (0)