Skip to content

Commit 3c30c22

Browse files
authored
Refinements to skolemizaton (#23513)
1. Deskolemize after fully defining type The order was the opposite before. That led to skolem types escaping in the constraint and then being installed in the inferred type of a val or def. See #23489 for a test case where this shows up. 2. Re-use skolems generated for arguments. Fixes #23489
2 parents 03a54a7 + bfa02e2 commit 3c30c22

File tree

6 files changed

+84
-31
lines changed

6 files changed

+84
-31
lines changed

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,13 @@ class PlainPrinter(_ctx: Context) extends Printer {
459459
if (idx >= 0) selfRecName(idx + 1)
460460
else "{...}.this" // TODO move underlying type to an addendum, e.g. ... z3 ... where z3: ...
461461
case tp: SkolemType =>
462-
if (homogenizedView) toText(tp.info)
463-
else if (ctx.settings.XprintTypes.value) "<" ~ toText(tp.repr) ~ ":" ~ toText(tp.info) ~ ">"
464-
else toText(tp.repr)
462+
def reprStr = toText(tp.repr) ~ hashStr(tp)
463+
if homogenizedView then
464+
toText(tp.info)
465+
else if ctx.settings.XprintTypes.value then
466+
"<" ~ reprStr ~ ":" ~ toText(tp.info) ~ ">"
467+
else
468+
reprStr
465469
}
466470
}
467471

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,10 @@ object SpaceEngine {
570570
// Case unapplySeq:
571571
// 1. return the type `List[T]` where `T` is the element type of the unapplySeq return type `Seq[T]`
572572

573-
val resTp = wildApprox(ctx.typeAssigner.safeSubstMethodParams(mt, scrutineeTp :: Nil).finalResultType)
573+
var resTp0 = mt.resultType
574+
if mt.isResultDependent then
575+
resTp0 = ctx.typeAssigner.safeSubstParam(resTp0, mt.paramRefs.head, scrutineeTp)
576+
val resTp = wildApprox(resTp0.finalResultType)
574577

575578
val sig =
576579
if (resTp.isRef(defn.BooleanClass))

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

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Decorators._
1313
import config.Printers.{gadts, typr}
1414
import annotation.tailrec
1515
import reporting.*
16+
import TypeAssigner.SkolemizedArgs
1617
import collection.mutable
1718
import scala.annotation.internal.sharable
1819

@@ -839,22 +840,33 @@ trait Inferencing { this: Typer =>
839840
if tvar.origin.paramName.is(NameKinds.DepParamName) then
840841
representedParamRef(tvar.origin) match
841842
case ref: TermParamRef =>
842-
def findArg(tree: Tree)(using Context): Tree = tree match
843-
case Apply(fn, args) =>
843+
def findArg(tree: Tree)(using Context): Option[(Tree, Apply)] = tree match
844+
case app @ Apply(fn, args) =>
844845
if fn.tpe.widen eq ref.binder then
845-
if ref.paramNum < args.length then args(ref.paramNum)
846-
else EmptyTree
846+
if ref.paramNum < args.length then Some((args(ref.paramNum), app))
847+
else None
847848
else findArg(fn)
848849
case TypeApply(fn, _) => findArg(fn)
849850
case Block(_, expr) => findArg(expr)
850851
case Inlined(_, _, expr) => findArg(expr)
851-
case _ => EmptyTree
852-
853-
val arg = findArg(call)
854-
if !arg.isEmpty then
855-
var argType = arg.tpe.widenIfUnstable
856-
if !argType.isSingleton then argType = SkolemType(argType)
857-
argType <:< tvar
852+
case _ => None
853+
854+
findArg(call) match
855+
case Some((arg, app)) =>
856+
var argType = arg.tpe.widenIfUnstable
857+
if !argType.isSingleton then
858+
argType = app.getAttachment(SkolemizedArgs) match
859+
case Some(mapping) =>
860+
mapping.get(arg) match
861+
case Some(sk @ SkolemType(at)) =>
862+
assert(argType frozen_=:= at)
863+
sk
864+
case _ =>
865+
SkolemType(argType)
866+
case _ =>
867+
SkolemType(argType)
868+
argType <:< tvar
869+
case _ =>
858870
case _ =>
859871
end constrainIfDependentParamRef
860872
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2247,8 +2247,9 @@ class Namer { typer: Typer =>
22472247
// it would be erased to BoxedUnit.
22482248
def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp
22492249

2250-
def cookedRhsType = dealiasIfUnit(rhsType).deskolemized
2250+
def cookedRhsType = dealiasIfUnit(rhsType)
22512251
def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.srcPos)
2252+
.deskolemized
22522253
//if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType")
22532254
if (inherited.exists)
22542255
if sym.isInlineVal || isTracked then lhsType else inherited

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

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import collection.mutable
1212
import reporting.*
1313
import Checking.{checkNoPrivateLeaks, checkNoWildcard}
1414
import cc.CaptureSet
15+
import util.Property
1516
import transform.Splicer
1617

1718
trait TypeAssigner {
@@ -270,36 +271,43 @@ trait TypeAssigner {
270271
untpd.cpy.Super(tree)(qual, tree.mix)
271272
.withType(superType(qual.tpe, tree.mix, mixinClass, tree.srcPos))
272273

274+
private type SkolemBuffer = mutable.ListBuffer[(Tree, SkolemType)]
275+
273276
/** Substitute argument type `argType` for parameter `pref` in type `tp`,
274277
* skolemizing the argument type if it is not stable and `pref` occurs in `tp`.
278+
* If skolemization happens the new SkolemType is passed to `recordSkolem`
279+
* provided the latter is non-null.
275280
*/
276-
def safeSubstParam(tp: Type, pref: ParamRef, argType: Type)(using Context): Type = {
281+
def safeSubstParam(tp: Type, pref: ParamRef, argType: Type,
282+
recordSkolem: (SkolemType => Unit) | Null = null)(using Context): Type =
277283
val tp1 = tp.substParam(pref, argType)
278-
if ((tp1 eq tp) || argType.isStable) tp1
279-
else tp.substParam(pref, SkolemType(argType.widen))
280-
}
284+
if (tp1 eq tp) || argType.isStable then tp1
285+
else
286+
val narrowed = SkolemType(argType.widen)
287+
if recordSkolem != null then recordSkolem(narrowed)
288+
tp.substParam(pref, narrowed)
281289

282290
/** Substitute types of all arguments `args` for corresponding `params` in `tp`.
283291
* The number of parameters `params` may exceed the number of arguments.
284292
* In this case, only the common prefix is substituted.
293+
* Skolems generated by `safeSubstParam` are stored in `skolems`.
285294
*/
286-
def safeSubstParams(tp: Type, params: List[ParamRef], argTypes: List[Type])(using Context): Type = argTypes match {
287-
case argType :: argTypes1 =>
288-
val tp1 = safeSubstParam(tp, params.head, argType)
289-
safeSubstParams(tp1, params.tail, argTypes1)
295+
private def safeSubstParams(tp: Type, params: List[ParamRef],
296+
args: List[Tree], skolems: SkolemBuffer)(using Context): Type = args match
297+
case arg :: args1 =>
298+
val tp1 = safeSubstParam(tp, params.head, arg.tpe, sk => skolems += ((arg, sk)))
299+
safeSubstParams(tp1, params.tail, args1, skolems)
290300
case Nil =>
291301
tp
292-
}
293-
294-
def safeSubstMethodParams(mt: MethodType, argTypes: List[Type])(using Context): Type =
295-
if mt.isResultDependent then safeSubstParams(mt.resultType, mt.paramRefs, argTypes)
296-
else mt.resultType
297302

298303
def assignType(tree: untpd.Apply, fn: Tree, args: List[Tree])(using Context): Apply = {
304+
var skolems: SkolemBuffer | Null = null
299305
val ownType = fn.tpe.widen match {
300306
case fntpe: MethodType =>
301307
if fntpe.paramInfos.hasSameLengthAs(args) || ctx.phase.prev.relaxedTyping then
302-
if fntpe.isResultDependent then safeSubstMethodParams(fntpe, args.tpes)
308+
if fntpe.isResultDependent then
309+
skolems = new mutable.ListBuffer()
310+
safeSubstParams(fntpe.resultType, fntpe.paramRefs, args, skolems.nn)
303311
else fntpe.resultType // fast path optimization
304312
else
305313
val erroringPhase =
@@ -312,7 +320,13 @@ trait TypeAssigner {
312320
if (ctx.settings.Ydebug.value) new FatalError("").printStackTrace()
313321
errorType(err.takesNoParamsMsg(fn, ""), tree.srcPos)
314322
}
315-
ConstFold.Apply(tree.withType(ownType))
323+
val app = tree.withType(ownType)
324+
if skolems != null
325+
&& skolems.nn.nonEmpty // @notional why is `.nn` needed here?
326+
&& skolems.nn.size == skolems.nn.toSet.size // each skolemized argument is unique
327+
then
328+
app.putAttachment(SkolemizedArgs, skolems.nn.toMap)
329+
ConstFold.Apply(app)
316330
}
317331

318332
def assignType(tree: untpd.TypeApply, fn: Tree, args: List[Tree])(using Context): TypeApply = {
@@ -570,6 +584,12 @@ trait TypeAssigner {
570584
}
571585

572586
object TypeAssigner extends TypeAssigner:
587+
588+
/** An attachment on an application indicating a map from arguments to the skolem types
589+
* that were created in safeSubstParams.
590+
*/
591+
private[typer] val SkolemizedArgs = new Property.Key[Map[tpd.Tree, SkolemType]]
592+
573593
def seqLitType(tree: untpd.SeqLiteral, elemType: Type)(using Context) = tree match
574594
case tree: untpd.JavaSeqLiteral => defn.ArrayOf(elemType)
575595
case _ => if ctx.erasedTypes then defn.SeqType else defn.SeqType.appliedTo(elemType)

tests/pos/i23489.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import scala.language.experimental.modularity
2+
3+
class Box1[T <: Singleton](val x: T)
4+
class Box2[T : Singleton](x: => T)
5+
def id(x: Int): x.type = x
6+
def readInt(): Int = ???
7+
8+
def Test = ()
9+
val x = Box1(id(readInt()))
10+
11+
val _: Box1[? <: Int] = x
12+
13+
val y = Box2(id(readInt()))

0 commit comments

Comments
 (0)