Skip to content

Commit e7f6049

Browse files
Move refineUsingParent to TypeOps
1 parent ffa8acf commit e7f6049

File tree

5 files changed

+157
-151
lines changed

5 files changed

+157
-151
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1899,7 +1899,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
18991899
* denote the same set of values.
19001900
*/
19011901
def decompose(sym: Symbol, tp: Type): List[Type] =
1902-
sym.children.map(x => ctx.typer.refineUsingParent(tp, x)).filter(_.exists)
1902+
sym.children.map(x => ctx.refineUsingParent(tp, x)).filter(_.exists)
19031903

19041904
(tp1.dealias, tp2.dealias) match {
19051905
case (tp1: ConstantType, tp2: ConstantType) =>

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

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import collection.mutable
1313
import ast.tpd._
1414
import reporting.trace
1515
import reporting.diagnostic.Message
16+
import config.Printers.{gadts, typr}
17+
import typer.Applications._
18+
import typer.ProtoTypes._
19+
import typer.ForceDegree
20+
import typer.Inferencing.isFullyDefined
1621

1722
import scala.annotation.internal.sharable
1823

@@ -334,6 +339,154 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
334339
* This test is used when we are too early in the pipeline to consider imports.
335340
*/
336341
def scala2Setting = ctx.settings.language.value.contains(nme.Scala2.toString)
342+
343+
/** Refine child based on parent
344+
*
345+
* In child class definition, we have:
346+
*
347+
* class Child[Ts] extends path.Parent[Us] with Es
348+
* object Child extends path.Parent[Us] with Es
349+
* val child = new path.Parent[Us] with Es // enum values
350+
*
351+
* Given a parent type `parent` and a child symbol `child`, we infer the prefix
352+
* and type parameters for the child:
353+
*
354+
* prefix.child[Vs] <:< parent
355+
*
356+
* where `Vs` are fresh type variables and `prefix` is the symbol prefix with all
357+
* non-module and non-package `ThisType` replaced by fresh type variables.
358+
*
359+
* If the subtyping is true, the instantiated type `p.child[Vs]` is
360+
* returned. Otherwise, `NoType` is returned.
361+
*/
362+
def refineUsingParent(parent: Type, child: Symbol)(implicit ctx: Context): Type = {
363+
if (child.isTerm && child.is(Case, butNot = Module)) return child.termRef // enum vals always match
364+
365+
// <local child> is a place holder from Scalac, it is hopeless to instantiate it.
366+
//
367+
// Quote from scalac (from nsc/symtab/classfile/Pickler.scala):
368+
//
369+
// ...When a sealed class/trait has local subclasses, a single
370+
// <local child> class symbol is added as pickled child
371+
// (instead of a reference to the anonymous class; that was done
372+
// initially, but seems not to work, ...).
373+
//
374+
if (child.name == tpnme.LOCAL_CHILD) return child.typeRef
375+
376+
val childTp = if (child.isTerm) child.termRef else child.typeRef
377+
378+
instantiate(childTp, parent)(ctx.fresh.setNewTyperState()).dealias
379+
}
380+
381+
/** Instantiate type `tp1` to be a subtype of `tp2`
382+
*
383+
* Return the instantiated type if type parameters and this type
384+
* in `tp1` can be instantiated such that `tp1 <:< tp2`.
385+
*
386+
* Otherwise, return NoType.
387+
*/
388+
private def instantiate(tp1: NamedType, tp2: Type)(implicit ctx: Context): Type = {
389+
/** expose abstract type references to their bounds or tvars according to variance */
390+
class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap {
391+
def expose(lo: Type, hi: Type): Type =
392+
if (variance == 0)
393+
newTypeVar(TypeBounds(lo, hi))
394+
else if (variance == 1)
395+
if (maximize) hi else lo
396+
else
397+
if (maximize) lo else hi
398+
399+
def apply(tp: Type): Type = tp match {
400+
case tp: TypeRef if isBounds(tp.underlying) =>
401+
val lo = this(tp.info.loBound)
402+
val hi = this(tp.info.hiBound)
403+
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala tests/patmat/t9657.scala
404+
val exposed = expose(lo, hi)
405+
typr.println(s"$tp exposed to =====> $exposed")
406+
exposed
407+
408+
case AppliedType(tycon: TypeRef, args) if isBounds(tycon.underlying) =>
409+
val args2 = args.map(this)
410+
val lo = this(tycon.info.loBound).applyIfParameterized(args2)
411+
val hi = this(tycon.info.hiBound).applyIfParameterized(args2)
412+
val exposed = expose(lo, hi)
413+
typr.println(s"$tp exposed to =====> $exposed")
414+
exposed
415+
416+
case _ =>
417+
mapOver(tp)
418+
}
419+
}
420+
421+
def minTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = false)
422+
def maxTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = true)
423+
424+
// Fix subtype checking for child instantiation,
425+
// such that `Foo(Test.this.foo) <:< Foo(Foo.this)`
426+
// See tests/patmat/i3938.scala
427+
class RemoveThisMap extends TypeMap {
428+
var prefixTVar: Type = null
429+
def apply(tp: Type): Type = tp match {
430+
case ThisType(tref: TypeRef) if !tref.symbol.isStaticOwner =>
431+
if (tref.symbol.is(Module))
432+
TermRef(this(tref.prefix), tref.symbol.sourceModule)
433+
else if (prefixTVar != null)
434+
this(tref)
435+
else {
436+
prefixTVar = WildcardType // prevent recursive call from assigning it
437+
prefixTVar = newTypeVar(TypeBounds.upper(this(tref)))
438+
prefixTVar
439+
}
440+
case tp => mapOver(tp)
441+
}
442+
}
443+
444+
// replace uninstantiated type vars with WildcardType, check tests/patmat/3333.scala
445+
def instUndetMap(implicit ctx: Context) = new TypeMap {
446+
def apply(t: Type): Type = t match {
447+
case tvar: TypeVar if !tvar.isInstantiated => WildcardType(tvar.origin.underlying.bounds)
448+
case _ => mapOver(t)
449+
}
450+
}
451+
452+
val removeThisType = new RemoveThisMap
453+
val tvars = tp1.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
454+
val protoTp1 = removeThisType.apply(tp1).appliedTo(tvars)
455+
456+
val force = new ForceDegree.Value(
457+
tvar =>
458+
!(ctx.typerState.constraint.entry(tvar.origin) `eq` tvar.origin.underlying) ||
459+
(tvar `eq` removeThisType.prefixTVar),
460+
minimizeAll = false,
461+
allowBottom = false
462+
)
463+
464+
// If parent contains a reference to an abstract type, then we should
465+
// refine subtype checking to eliminate abstract types according to
466+
// variance. As this logic is only needed in exhaustivity check,
467+
// we manually patch subtyping check instead of changing TypeComparer.
468+
// See tests/patmat/i3645b.scala
469+
def parentQualify = tp1.widen.classSymbol.info.parents.exists { parent =>
470+
implicit val ictx = ctx.fresh.setNewTyperState()
471+
parent.argInfos.nonEmpty && minTypeMap.apply(parent) <:< maxTypeMap.apply(tp2)
472+
}
473+
474+
if (protoTp1 <:< tp2) {
475+
if (isFullyDefined(protoTp1, force)) protoTp1
476+
else instUndetMap.apply(protoTp1)
477+
}
478+
else {
479+
val protoTp2 = maxTypeMap.apply(tp2)
480+
if (protoTp1 <:< protoTp2 || parentQualify) {
481+
if (isFullyDefined(AndType(protoTp1, protoTp2), force)) protoTp1
482+
else instUndetMap.apply(protoTp1)
483+
}
484+
else {
485+
typr.println(s"$protoTp1 <:< $protoTp2 = false")
486+
NoType
487+
}
488+
}
489+
}
337490
}
338491

339492
object TypeOps {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package dotty.tools.dotc
1+
package dotty.tools
2+
package dotc
23
package transform
34

45
import core._

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
467467
case tp =>
468468
val parts = children.map { sym =>
469469
val sym1 = if (sym.is(ModuleClass)) sym.sourceModule else sym
470-
val refined = ctx.typer.refineUsingParent(tp, sym1)
470+
val refined = ctx.refineUsingParent(tp, sym1)
471471
val inhabited = new TypeAccumulator[Boolean] {
472472
override def apply(x: Boolean, tp: Type) = x && {
473473
tp match {

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

Lines changed: 0 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -2929,152 +2929,4 @@ 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-
}
30802932
}

0 commit comments

Comments
 (0)