Skip to content

Commit bfa02e2

Browse files
committed
Refinement: Make scheme work also if arguments are duplicated
The new scheme works also if argument trees are not unique. Instead of storing skolem types directly in attachment of arguments, we store a map from arguments to their skolems in the enclosing application node. But we check then that the same argument tree does not appear several times in an application. If it does appear several times, we don't generate the map. Furthermore, we are also safe if the whole application tree is used several times, since for each call we generate new skolems and store them in the map. So different calls get different skolems.
1 parent 31cd178 commit bfa02e2

File tree

4 files changed

+54
-45
lines changed

4 files changed

+54
-45
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ object SpaceEngine {
573573
var resTp0 = mt.resultType
574574
if mt.isResultDependent then
575575
resTp0 = ctx.typeAssigner.safeSubstParam(resTp0, mt.paramRefs.head, scrutineeTp)
576-
val resTp = wildApprox(resTp0).finalResultType
576+
val resTp = wildApprox(resTp0.finalResultType)
577577

578578
val sig =
579579
if (resTp.isRef(defn.BooleanClass))

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

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

@@ -840,23 +840,33 @@ trait Inferencing { this: Typer =>
840840
if tvar.origin.paramName.is(NameKinds.DepParamName) then
841841
representedParamRef(tvar.origin) match
842842
case ref: TermParamRef =>
843-
def findArg(tree: Tree)(using Context): Tree = tree match
844-
case Apply(fn, args) =>
843+
def findArg(tree: Tree)(using Context): Option[(Tree, Apply)] = tree match
844+
case app @ Apply(fn, args) =>
845845
if fn.tpe.widen eq ref.binder then
846-
if ref.paramNum < args.length then args(ref.paramNum)
847-
else EmptyTree
846+
if ref.paramNum < args.length then Some((args(ref.paramNum), app))
847+
else None
848848
else findArg(fn)
849849
case TypeApply(fn, _) => findArg(fn)
850850
case Block(_, expr) => findArg(expr)
851851
case Inlined(_, _, expr) => findArg(expr)
852-
case _ => EmptyTree
853-
854-
val arg = findArg(call)
855-
if !arg.isEmpty then
856-
var argType = arg.tpe.widenIfUnstable
857-
if !argType.isSingleton then
858-
argType = skolemizeArgType(argType, arg)
859-
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 _ =>
860870
case _ =>
861871
end constrainIfDependentParamRef
862872
}

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

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -271,35 +271,43 @@ trait TypeAssigner {
271271
untpd.cpy.Super(tree)(qual, tree.mix)
272272
.withType(superType(qual.tpe, tree.mix, mixinClass, tree.srcPos))
273273

274+
private type SkolemBuffer = mutable.ListBuffer[(Tree, SkolemType)]
275+
274276
/** Substitute argument type `argType` for parameter `pref` in type `tp`,
275277
* 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.
276280
*/
277-
def safeSubstParam(tp: Type, pref: ParamRef, argType: Type, arg: Tree | Null = null)(using Context): Type = {
281+
def safeSubstParam(tp: Type, pref: ParamRef, argType: Type,
282+
recordSkolem: (SkolemType => Unit) | Null = null)(using Context): Type =
278283
val tp1 = tp.substParam(pref, argType)
279284
if (tp1 eq tp) || argType.isStable then tp1
280-
else tp.substParam(pref, skolemizeArgType(argType.widen, arg))
281-
}
285+
else
286+
val narrowed = SkolemType(argType.widen)
287+
if recordSkolem != null then recordSkolem(narrowed)
288+
tp.substParam(pref, narrowed)
282289

283290
/** Substitute types of all arguments `args` for corresponding `params` in `tp`.
284291
* The number of parameters `params` may exceed the number of arguments.
285292
* In this case, only the common prefix is substituted.
293+
* Skolems generated by `safeSubstParam` are stored in `skolems`.
286294
*/
287-
def safeSubstParams(tp: Type, params: List[ParamRef], args: List[Tree])(using Context): Type = args match
295+
private def safeSubstParams(tp: Type, params: List[ParamRef],
296+
args: List[Tree], skolems: SkolemBuffer)(using Context): Type = args match
288297
case arg :: args1 =>
289-
val tp1 = safeSubstParam(tp, params.head, arg.tpe, arg)
290-
safeSubstParams(tp1, params.tail, args1)
298+
val tp1 = safeSubstParam(tp, params.head, arg.tpe, sk => skolems += ((arg, sk)))
299+
safeSubstParams(tp1, params.tail, args1, skolems)
291300
case Nil =>
292301
tp
293302

294-
def safeSubstMethodParams(mt: MethodType, args: List[Tree])(using Context): Type =
295-
if mt.isResultDependent then safeSubstParams(mt.resultType, mt.paramRefs, args)
296-
else mt.resultType
297-
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)
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 = {
@@ -571,25 +585,10 @@ trait TypeAssigner {
571585

572586
object TypeAssigner extends TypeAssigner:
573587

574-
/** An attachment on an argument in an application indicating that the argument's
575-
* type was converted to the given skolem type.
588+
/** An attachment on an application indicating a map from arguments to the skolem types
589+
* that were created in safeSubstParams.
576590
*/
577-
private val Skolemized = new Property.StickyKey[SkolemType]
578-
579-
/** A skolem type wrapping `argType`, associated with `arg` if it is non-null.
580-
* Skolem types for the same arguments with equal underlying `argType`s are re-used.
581-
*/
582-
def skolemizeArgType(argType: Type, arg: tpd.Tree | Null)(using Context): Type =
583-
if arg == null then
584-
SkolemType(argType)
585-
else
586-
arg.getAttachment(Skolemized) match
587-
case Some(sk @ SkolemType(tp)) if argType frozen_=:= tp =>
588-
sk
589-
case _ =>
590-
val sk = SkolemType(argType)
591-
arg.putAttachment(Skolemized, sk)
592-
sk
591+
private[typer] val SkolemizedArgs = new Property.Key[Map[tpd.Tree, SkolemType]]
593592

594593
def seqLitType(tree: untpd.SeqLiteral, elemType: Type)(using Context) = tree match
595594
case tree: untpd.JavaSeqLiteral => defn.ArrayOf(elemType)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4355,7 +4355,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
43554355
def inferArgsAfter(leading: Tree) =
43564356
val formals2 =
43574357
if wtp.isParamDependent && leading.tpe.exists then
4358-
formals1.mapconserve(f1 => safeSubstParam(f1, wtp.paramRefs(argIndex), leading.tpe, leading))
4358+
formals1.mapconserve(f1 => safeSubstParam(f1, wtp.paramRefs(argIndex), leading.tpe))
43594359
else formals1
43604360
implicitArgs(formals2, argIndex + 1, pt)
43614361

0 commit comments

Comments
 (0)