Skip to content

Commit 2f86033

Browse files
Move refineUsingParent to Typer
1 parent 5b6cb1a commit 2f86033

File tree

3 files changed

+152
-159
lines changed

3 files changed

+152
-159
lines changed

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1898,11 +1898,8 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
18981898
* It should hold that `tp` and `decompose(tp).reduce(_ or _)`
18991899
* denote the same set of values.
19001900
*/
1901-
def decompose(sym: Symbol, tp: Type): List[Type] = {
1902-
import dotty.tools.dotc.transform.patmat.SpaceEngine
1903-
val se = new SpaceEngine
1904-
sym.children.map(x => se.refine(tp, x)).filter(_.exists)
1905-
}
1901+
def decompose(sym: Symbol, tp: Type): List[Type] =
1902+
sym.children.map(x => ctx.typer.refineUsingParent(tp, x)).filter(_.exists)
19061903

19071904
(tp1.dealias, tp2.dealias) match {
19081905
case (tp1: ConstantType, tp2: ConstantType) =>

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

Lines changed: 2 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -466,10 +466,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
466466
children.map(sym => Typ(sym.termRef, true))
467467
case tp =>
468468
val parts = children.map { sym =>
469-
if (sym.is(ModuleClass))
470-
refine(tp, sym.sourceModule)
471-
else
472-
refine(tp, sym)
469+
val sym1 = if (sym.is(ModuleClass)) sym.sourceModule else sym
470+
ctx.typer.refineUsingParent(tp, sym1)
473471
} filter(_.exists)
474472

475473
debug.println(s"${tp.show} decomposes to [${parts.map(_.show).mkString(", ")}]")
@@ -478,156 +476,6 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
478476
}
479477
}
480478

481-
/** Refine child based on parent
482-
*
483-
* In child class definition, we have:
484-
*
485-
* class Child[Ts] extends path.Parent[Us] with Es
486-
* object Child extends path.Parent[Us] with Es
487-
* val child = new path.Parent[Us] with Es // enum values
488-
*
489-
* Given a parent type `parent` and a child symbol `child`, we infer the prefix
490-
* and type parameters for the child:
491-
*
492-
* prefix.child[Vs] <:< parent
493-
*
494-
* where `Vs` are fresh type variables and `prefix` is the symbol prefix with all
495-
* non-module and non-package `ThisType` replaced by fresh type variables.
496-
*
497-
* If the subtyping is true, the instantiated type `p.child[Vs]` is
498-
* returned. Otherwise, `NoType` is returned.
499-
*
500-
*/
501-
def refine(parent: Type, child: Symbol): Type = {
502-
if (child.isTerm && child.is(Case, butNot = Module)) return child.termRef // enum vals always match
503-
504-
// <local child> is a place holder from Scalac, it is hopeless to instantiate it.
505-
//
506-
// Quote from scalac (from nsc/symtab/classfile/Pickler.scala):
507-
//
508-
// ...When a sealed class/trait has local subclasses, a single
509-
// <local child> class symbol is added as pickled child
510-
// (instead of a reference to the anonymous class; that was done
511-
// initially, but seems not to work, ...).
512-
//
513-
if (child.name == tpnme.LOCAL_CHILD) return child.typeRef
514-
515-
val childTp = if (child.isTerm) child.termRef else child.typeRef
516-
517-
instantiate(childTp, parent)(ctx.fresh.setNewTyperState()).dealias
518-
}
519-
520-
/** expose abstract type references to their bounds or tvars according to variance */
521-
private class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap {
522-
def expose(lo: Type, hi: Type): Type =
523-
if (variance == 0)
524-
newTypeVar(TypeBounds(lo, hi))
525-
else if (variance == 1)
526-
if (maximize) hi else lo
527-
else
528-
if (maximize) lo else hi
529-
530-
def apply(tp: Type): Type = tp match {
531-
case tp: TypeRef if isBounds(tp.underlying) =>
532-
val lo = this(tp.info.loBound)
533-
val hi = this(tp.info.hiBound)
534-
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala tests/patmat/t9657.scala
535-
val exposed = expose(lo, hi)
536-
debug.println(s"$tp exposed to =====> $exposed")
537-
exposed
538-
539-
case AppliedType(tycon: TypeRef, args) if isBounds(tycon.underlying) =>
540-
val args2 = args.map(this)
541-
val lo = this(tycon.info.loBound).applyIfParameterized(args2)
542-
val hi = this(tycon.info.hiBound).applyIfParameterized(args2)
543-
val exposed = expose(lo, hi)
544-
debug.println(s"$tp exposed to =====> $exposed")
545-
exposed
546-
547-
case _ =>
548-
mapOver(tp)
549-
}
550-
}
551-
552-
private def minTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = false)
553-
private def maxTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = true)
554-
555-
/** Instantiate type `tp1` to be a subtype of `tp2`
556-
*
557-
* Return the instantiated type if type parameters and this type
558-
* in `tp1` can be instantiated such that `tp1 <:< tp2`.
559-
*
560-
* Otherwise, return NoType.
561-
*
562-
*/
563-
def instantiate(tp1: NamedType, tp2: Type)(implicit ctx: Context): Type = {
564-
// Fix subtype checking for child instantiation,
565-
// such that `Foo(Test.this.foo) <:< Foo(Foo.this)`
566-
// See tests/patmat/i3938.scala
567-
class RemoveThisMap extends TypeMap {
568-
var prefixTVar: Type = null
569-
def apply(tp: Type): Type = tp match {
570-
case ThisType(tref: TypeRef) if !tref.symbol.isStaticOwner =>
571-
if (tref.symbol.is(Module))
572-
TermRef(this(tref.prefix), tref.symbol.sourceModule)
573-
else if (prefixTVar != null)
574-
this(tref)
575-
else {
576-
prefixTVar = WildcardType // prevent recursive call from assigning it
577-
prefixTVar = newTypeVar(TypeBounds.upper(this(tref)))
578-
prefixTVar
579-
}
580-
case tp => mapOver(tp)
581-
}
582-
}
583-
584-
// replace uninstantiated type vars with WildcardType, check tests/patmat/3333.scala
585-
def instUndetMap(implicit ctx: Context) = new TypeMap {
586-
def apply(t: Type): Type = t match {
587-
case tvar: TypeVar if !tvar.isInstantiated => WildcardType(tvar.origin.underlying.bounds)
588-
case _ => mapOver(t)
589-
}
590-
}
591-
592-
val removeThisType = new RemoveThisMap
593-
val tvars = tp1.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
594-
val protoTp1 = removeThisType.apply(tp1).appliedTo(tvars)
595-
596-
val force = new ForceDegree.Value(
597-
tvar =>
598-
!(ctx.typerState.constraint.entry(tvar.origin) `eq` tvar.origin.underlying) ||
599-
(tvar `eq` removeThisType.prefixTVar),
600-
minimizeAll = false,
601-
allowBottom = false
602-
)
603-
604-
// If parent contains a reference to an abstract type, then we should
605-
// refine subtype checking to eliminate abstract types according to
606-
// variance. As this logic is only needed in exhaustivity check,
607-
// we manually patch subtyping check instead of changing TypeComparer.
608-
// See tests/patmat/i3645b.scala
609-
def parentQualify = tp1.widen.classSymbol.info.parents.exists { parent =>
610-
implicit val ictx = ctx.fresh.setNewTyperState()
611-
parent.argInfos.nonEmpty && minTypeMap.apply(parent) <:< maxTypeMap.apply(tp2)
612-
}
613-
614-
if (protoTp1 <:< tp2) {
615-
if (isFullyDefined(protoTp1, force)) protoTp1
616-
else instUndetMap.apply(protoTp1)
617-
}
618-
else {
619-
val protoTp2 = maxTypeMap.apply(tp2)
620-
if (protoTp1 <:< protoTp2 || parentQualify) {
621-
if (isFullyDefined(AndType(protoTp1, protoTp2), force)) protoTp1
622-
else instUndetMap.apply(protoTp1)
623-
}
624-
else {
625-
debug.println(s"$protoTp1 <:< $protoTp2 = false")
626-
NoType
627-
}
628-
}
629-
}
630-
631479
/** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */
632480
def canDecompose(tp: Type): Boolean = {
633481
val dealiasedTp = tp.dealias

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

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2929,4 +2929,152 @@ class Typer extends Namer
29292929
!tree.tpe.isRef(defn.UnitClass) && !isSelfOrSuperConstrCall(tree))
29302930
ctx.warning(PureExpressionInStatementPosition(original, exprOwner), original.sourcePos)
29312931
}
2932+
2933+
/** Refine child based on parent
2934+
*
2935+
* In child class definition, we have:
2936+
*
2937+
* class Child[Ts] extends path.Parent[Us] with Es
2938+
* object Child extends path.Parent[Us] with Es
2939+
* val child = new path.Parent[Us] with Es // enum values
2940+
*
2941+
* Given a parent type `parent` and a child symbol `child`, we infer the prefix
2942+
* and type parameters for the child:
2943+
*
2944+
* prefix.child[Vs] <:< parent
2945+
*
2946+
* where `Vs` are fresh type variables and `prefix` is the symbol prefix with all
2947+
* non-module and non-package `ThisType` replaced by fresh type variables.
2948+
*
2949+
* If the subtyping is true, the instantiated type `p.child[Vs]` is
2950+
* returned. Otherwise, `NoType` is returned.
2951+
*/
2952+
def refineUsingParent(parent: Type, child: Symbol)(implicit ctx: Context): Type = {
2953+
if (child.isTerm && child.is(Case, butNot = Module)) return child.termRef // enum vals always match
2954+
2955+
// <local child> is a place holder from Scalac, it is hopeless to instantiate it.
2956+
//
2957+
// Quote from scalac (from nsc/symtab/classfile/Pickler.scala):
2958+
//
2959+
// ...When a sealed class/trait has local subclasses, a single
2960+
// <local child> class symbol is added as pickled child
2961+
// (instead of a reference to the anonymous class; that was done
2962+
// initially, but seems not to work, ...).
2963+
//
2964+
if (child.name == tpnme.LOCAL_CHILD) return child.typeRef
2965+
2966+
val childTp = if (child.isTerm) child.termRef else child.typeRef
2967+
2968+
instantiate(childTp, parent)(ctx.fresh.setNewTyperState()).dealias
2969+
}
2970+
2971+
/** Instantiate type `tp1` to be a subtype of `tp2`
2972+
*
2973+
* Return the instantiated type if type parameters and this type
2974+
* in `tp1` can be instantiated such that `tp1 <:< tp2`.
2975+
*
2976+
* Otherwise, return NoType.
2977+
*/
2978+
private def instantiate(tp1: NamedType, tp2: Type)(implicit ctx: Context): Type = {
2979+
/** expose abstract type references to their bounds or tvars according to variance */
2980+
class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap {
2981+
def expose(lo: Type, hi: Type): Type =
2982+
if (variance == 0)
2983+
newTypeVar(TypeBounds(lo, hi))
2984+
else if (variance == 1)
2985+
if (maximize) hi else lo
2986+
else
2987+
if (maximize) lo else hi
2988+
2989+
def apply(tp: Type): Type = tp match {
2990+
case tp: TypeRef if isBounds(tp.underlying) =>
2991+
val lo = this(tp.info.loBound)
2992+
val hi = this(tp.info.hiBound)
2993+
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala tests/patmat/t9657.scala
2994+
val exposed = expose(lo, hi)
2995+
typr.println(s"$tp exposed to =====> $exposed")
2996+
exposed
2997+
2998+
case AppliedType(tycon: TypeRef, args) if isBounds(tycon.underlying) =>
2999+
val args2 = args.map(this)
3000+
val lo = this(tycon.info.loBound).applyIfParameterized(args2)
3001+
val hi = this(tycon.info.hiBound).applyIfParameterized(args2)
3002+
val exposed = expose(lo, hi)
3003+
typr.println(s"$tp exposed to =====> $exposed")
3004+
exposed
3005+
3006+
case _ =>
3007+
mapOver(tp)
3008+
}
3009+
}
3010+
3011+
def minTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = false)
3012+
def maxTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = true)
3013+
3014+
// Fix subtype checking for child instantiation,
3015+
// such that `Foo(Test.this.foo) <:< Foo(Foo.this)`
3016+
// See tests/patmat/i3938.scala
3017+
class RemoveThisMap extends TypeMap {
3018+
var prefixTVar: Type = null
3019+
def apply(tp: Type): Type = tp match {
3020+
case ThisType(tref: TypeRef) if !tref.symbol.isStaticOwner =>
3021+
if (tref.symbol.is(Module))
3022+
TermRef(this(tref.prefix), tref.symbol.sourceModule)
3023+
else if (prefixTVar != null)
3024+
this(tref)
3025+
else {
3026+
prefixTVar = WildcardType // prevent recursive call from assigning it
3027+
prefixTVar = newTypeVar(TypeBounds.upper(this(tref)))
3028+
prefixTVar
3029+
}
3030+
case tp => mapOver(tp)
3031+
}
3032+
}
3033+
3034+
// replace uninstantiated type vars with WildcardType, check tests/patmat/3333.scala
3035+
def instUndetMap(implicit ctx: Context) = new TypeMap {
3036+
def apply(t: Type): Type = t match {
3037+
case tvar: TypeVar if !tvar.isInstantiated => WildcardType(tvar.origin.underlying.bounds)
3038+
case _ => mapOver(t)
3039+
}
3040+
}
3041+
3042+
val removeThisType = new RemoveThisMap
3043+
val tvars = tp1.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
3044+
val protoTp1 = removeThisType.apply(tp1).appliedTo(tvars)
3045+
3046+
val force = new ForceDegree.Value(
3047+
tvar =>
3048+
!(ctx.typerState.constraint.entry(tvar.origin) `eq` tvar.origin.underlying) ||
3049+
(tvar `eq` removeThisType.prefixTVar),
3050+
minimizeAll = false,
3051+
allowBottom = false
3052+
)
3053+
3054+
// If parent contains a reference to an abstract type, then we should
3055+
// refine subtype checking to eliminate abstract types according to
3056+
// variance. As this logic is only needed in exhaustivity check,
3057+
// we manually patch subtyping check instead of changing TypeComparer.
3058+
// See tests/patmat/i3645b.scala
3059+
def parentQualify = tp1.widen.classSymbol.info.parents.exists { parent =>
3060+
implicit val ictx = ctx.fresh.setNewTyperState()
3061+
parent.argInfos.nonEmpty && minTypeMap.apply(parent) <:< maxTypeMap.apply(tp2)
3062+
}
3063+
3064+
if (protoTp1 <:< tp2) {
3065+
if (isFullyDefined(protoTp1, force)) protoTp1
3066+
else instUndetMap.apply(protoTp1)
3067+
}
3068+
else {
3069+
val protoTp2 = maxTypeMap.apply(tp2)
3070+
if (protoTp1 <:< protoTp2 || parentQualify) {
3071+
if (isFullyDefined(AndType(protoTp1, protoTp2), force)) protoTp1
3072+
else instUndetMap.apply(protoTp1)
3073+
}
3074+
else {
3075+
typr.println(s"$protoTp1 <:< $protoTp2 = false")
3076+
NoType
3077+
}
3078+
}
3079+
}
29323080
}

0 commit comments

Comments
 (0)