Skip to content

Commit 41728c4

Browse files
committed
Turn TypeOps into an object
Also, move `avoid` from TypeAssigner to TypeOps, since we need to refer to it in Types. Based on #8867
1 parent fc99c11 commit 41728c4

13 files changed

+175
-172
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ object Contexts {
7878
abstract class Context(val base: ContextBase)
7979
extends Periods
8080
with Substituters
81-
with TypeOps
8281
with Phases
8382
with Printers
8483
with Symbols

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package core
55
import Types._, Contexts._, Symbols._, Flags._, Names._, NameOps._, Denotations._
66
import Decorators._
77
import StdNames.nme
8+
import TypeOps.refineUsingParent
89
import collection.mutable
910
import util.Stats
1011
import config.Config
@@ -2306,7 +2307,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
23062307
* denote the same set of values.
23072308
*/
23082309
def decompose(sym: Symbol, tp: Type): List[Type] =
2309-
sym.children.map(x => ctx.refineUsingParent(tp, x)).filter(_.exists)
2310+
sym.children.map(x => refineUsingParent(tp, x)).filter(_.exists)
23102311

23112312
(tp1.dealias, tp2.dealias) match {
23122313
case (tp1: TypeRef, tp2: TypeRef) if tp1.symbol == defn.SingletonClass || tp2.symbol == defn.SingletonClass =>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package core
55
import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._
66
import Flags.JavaDefined
77
import Uniques.unique
8+
import TypeOps.makePackageObjPrefixExplicit
89
import transform.ExplicitOuter._
910
import transform.ValueClasses._
1011
import transform.TypeUtils._
@@ -166,7 +167,7 @@ object TypeErasure {
166167
def erasedRef(tp: Type)(implicit ctx: Context): Type = tp match {
167168
case tp: TermRef =>
168169
assert(tp.symbol.exists, tp)
169-
val tp1 = ctx.makePackageObjPrefixExplicit(tp)
170+
val tp1 = makePackageObjPrefixExplicit(tp)
170171
if (tp1 ne tp) erasedRef(tp1)
171172
else TermRef(erasedRef(tp.prefix), tp.symbol.asTerm)
172173
case tp: ThisType =>

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

Lines changed: 151 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ import typer.Inferencing.isFullyDefined
2222
import typer.IfBottom
2323

2424
import scala.annotation.internal.sharable
25+
import scala.annotation.threadUnsafe
2526

26-
trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
27+
object TypeOps:
28+
29+
@sharable var track: Boolean = false // for debugging
2730

2831
/** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec
2932
* for what this means.
3033
*/
31-
final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = {
34+
final def asSeenFrom(tp: Type, pre: Type, cls: Symbol)(using Context): Type = {
3235
pre match {
3336
case pre: QualSkolemType =>
3437
// When a selection has an unstable qualifier, the qualifier type gets
@@ -53,7 +56,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
5356
}
5457

5558
/** The TypeMap handling the asSeenFrom */
56-
class AsSeenFromMap(pre: Type, cls: Symbol) extends ApproximatingTypeMap {
59+
class AsSeenFromMap(pre: Type, cls: Symbol)(using Context) extends ApproximatingTypeMap {
5760
/** Set to true when the result of `apply` was approximated to avoid an unstable prefix. */
5861
var approximated: Boolean = false
5962

@@ -62,7 +65,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
6265
/** Map a `C.this` type to the right prefix. If the prefix is unstable, and
6366
* the current variance is <= 0, return a range.
6467
*/
65-
def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ trace.conditionally(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)", show = true) /*<|<*/ {
68+
def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ trace.conditionally(track, s"toPrefix($pre, $cls, $thiscls)", show = true) /*<|<*/ {
6669
if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass))
6770
tp
6871
else pre match {
@@ -89,7 +92,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
8992
}
9093
}
9194

92-
trace.conditionally(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) { // !!! DEBUG
95+
trace.conditionally(track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) { // !!! DEBUG
9396
// All cases except for ThisType are the same as in Map. Inlined for performance
9497
// TODO: generalize the inlining trick?
9598
tp match {
@@ -117,11 +120,11 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
117120
}
118121
}
119122

120-
def isLegalPrefix(pre: Type)(implicit ctx: Context): Boolean =
123+
def isLegalPrefix(pre: Type)(using Context): Boolean =
121124
pre.isStable || !ctx.phase.isTyper
122125

123126
/** Implementation of Types#simplified */
124-
final def simplify(tp: Type, theMap: SimplifyMap): Type = {
127+
def simplify(tp: Type, theMap: SimplifyMap)(using Context): Type = {
125128
def mapOver = (if (theMap != null) theMap else new SimplifyMap).mapOver(tp)
126129
tp match {
127130
case tp: NamedType =>
@@ -135,20 +138,20 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
135138
}
136139
case tp: TypeParamRef =>
137140
if (tp.paramName.is(DepParamName)) {
138-
val bounds = thisCtx.typeComparer.bounds(tp)
141+
val bounds = ctx.typeComparer.bounds(tp)
139142
if (bounds.lo.isRef(defn.NothingClass)) bounds.hi else bounds.lo
140143
}
141144
else {
142-
val tvar = typerState.constraint.typeVarOfParam(tp)
145+
val tvar = ctx.typerState.constraint.typeVarOfParam(tp)
143146
if (tvar.exists) tvar else tp
144147
}
145148
case _: ThisType | _: BoundType =>
146149
tp
147150
case tp: AliasingBounds =>
148151
tp.derivedAlias(simplify(tp.alias, theMap))
149-
case AndType(l, r) if !thisCtx.mode.is(Mode.Type) =>
152+
case AndType(l, r) if !ctx.mode.is(Mode.Type) =>
150153
simplify(l, theMap) & simplify(r, theMap)
151-
case OrType(l, r) if !thisCtx.mode.is(Mode.Type) =>
154+
case OrType(l, r) if !ctx.mode.is(Mode.Type) =>
152155
simplify(l, theMap) | simplify(r, theMap)
153156
case _: AppliedType | _: MatchType =>
154157
val normed = tp.tryNormalize
@@ -158,7 +161,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
158161
}
159162
}
160163

161-
class SimplifyMap extends TypeMap {
164+
class SimplifyMap(using Context) extends TypeMap {
162165
def apply(tp: Type): Type = simplify(tp, this)
163166
}
164167

@@ -178,7 +181,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
178181
* in a "best effort", ad-hoc way by selectively widening types in `T1, ..., Tn`
179182
* and stopping if the resulting union simplifies to a type that is not a disjunction.
180183
*/
181-
def orDominator(tp: Type): Type = {
184+
def orDominator(tp: Type)(using Context): Type = {
182185

183186
/** a faster version of cs1 intersect cs2 */
184187
def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = {
@@ -193,7 +196,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
193196
val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu
194197
if (cs == c.baseClasses) accu1 else dominators(rest, accu1)
195198
case Nil => // this case can happen because after erasure we do not have a top class anymore
196-
assert(thisCtx.erasedTypes || thisCtx.reporter.errorsReported)
199+
assert(ctx.erasedTypes || ctx.reporter.errorsReported)
197200
defn.ObjectClass :: Nil
198201
}
199202

@@ -217,7 +220,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
217220
case AppliedType(tycon2, args2) =>
218221
tp1.derivedAppliedType(
219222
mergeRefinedOrApplied(tycon1, tycon2),
220-
thisCtx.typeComparer.lubArgs(args1, args2, tycon1.typeParams))
223+
ctx.typeComparer.lubArgs(args1, args2, tycon1.typeParams))
221224
case _ => fallback
222225
}
223226
case tp1 @ TypeRef(pre1, _) =>
@@ -327,12 +330,128 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
327330
}
328331
}
329332

333+
/** An abstraction of a class info, consisting of
334+
* - the intersection of its parents,
335+
* - refined by all non-private fields, methods, and type members,
336+
* - abstracted over all type parameters (into a type lambda)
337+
* - where all references to `this` of the class are closed over in a RecType.
338+
*/
339+
def classBound(info: ClassInfo)(using Context): Type = {
340+
val cls = info.cls
341+
val parentType = info.parents.reduceLeft(ctx.typeComparer.andType(_, _))
342+
343+
def addRefinement(parent: Type, decl: Symbol) = {
344+
val inherited =
345+
parentType.findMember(decl.name, cls.thisType,
346+
required = EmptyFlags, excluded = Private
347+
).suchThat(decl.matches(_))
348+
val inheritedInfo = inherited.info
349+
val isPolyFunctionApply = decl.name == nme.apply && (parent <:< defn.PolyFunctionType)
350+
if isPolyFunctionApply
351+
|| inheritedInfo.exists
352+
&& !decl.isClass
353+
&& decl.info.widenExpr <:< inheritedInfo.widenExpr
354+
&& !(inheritedInfo.widenExpr <:< decl.info.widenExpr)
355+
then
356+
val r = RefinedType(parent, decl.name, decl.info)
357+
typr.println(i"add ref $parent $decl --> " + r)
358+
r
359+
else
360+
parent
361+
}
362+
363+
def close(tp: Type) = RecType.closeOver { rt =>
364+
tp.subst(cls :: Nil, rt.recThis :: Nil).substThis(cls, rt.recThis)
365+
}
366+
367+
def isRefinable(sym: Symbol) = !sym.is(Private) && !sym.isConstructor
368+
val refinableDecls = info.decls.filter(isRefinable)
369+
val raw = refinableDecls.foldLeft(parentType)(addRefinement)
370+
HKTypeLambda.fromParams(cls.typeParams, raw) match {
371+
case tl: HKTypeLambda => tl.derivedLambdaType(resType = close(tl.resType))
372+
case tp => close(tp)
373+
}
374+
}
375+
376+
/** An upper approximation of the given type `tp` that does not refer to any symbol in `symsToAvoid`.
377+
* We need to approximate with ranges:
378+
*
379+
* term references to symbols in `symsToAvoid`,
380+
* term references that have a widened type of which some part refers
381+
* to a symbol in `symsToAvoid`,
382+
* type references to symbols in `symsToAvoid`,
383+
* this types of classes in `symsToAvoid`.
384+
*
385+
* Type variables that would be interpolated to a type that
386+
* needs to be widened are replaced by the widened interpolation instance.
387+
*/
388+
def avoid(tp: Type, symsToAvoid: => List[Symbol])(using Context): Type = {
389+
val widenMap = new ApproximatingTypeMap {
390+
@threadUnsafe lazy val forbidden = symsToAvoid.toSet
391+
def toAvoid(sym: Symbol) = !sym.isStatic && forbidden.contains(sym)
392+
def partsToAvoid = new NamedPartsAccumulator(tp => toAvoid(tp.symbol))
393+
def apply(tp: Type): Type = tp match {
394+
case tp: TermRef
395+
if toAvoid(tp.symbol) || partsToAvoid(mutable.Set.empty, tp.info).nonEmpty =>
396+
tp.info.widenExpr.dealias match {
397+
case info: SingletonType => apply(info)
398+
case info => range(defn.NothingType, apply(info))
399+
}
400+
case tp: TypeRef if toAvoid(tp.symbol) =>
401+
tp.info match {
402+
case info: AliasingBounds =>
403+
apply(info.alias)
404+
case TypeBounds(lo, hi) =>
405+
range(atVariance(-variance)(apply(lo)), apply(hi))
406+
case info: ClassInfo =>
407+
range(defn.NothingType, apply(classBound(info)))
408+
case _ =>
409+
emptyRange // should happen only in error cases
410+
}
411+
case tp: ThisType if toAvoid(tp.cls) =>
412+
range(defn.NothingType, apply(classBound(tp.cls.classInfo)))
413+
case tp: SkolemType if partsToAvoid(mutable.Set.empty, tp.info).nonEmpty =>
414+
range(defn.NothingType, apply(tp.info))
415+
case tp: TypeVar if mapCtx.typerState.constraint.contains(tp) =>
416+
val lo = mapCtx.typeComparer.instanceType(
417+
tp.origin, fromBelow = variance > 0 || variance == 0 && tp.hasLowerBound)
418+
val lo1 = apply(lo)
419+
if (lo1 ne lo) lo1 else tp
420+
case _ =>
421+
mapOver(tp)
422+
}
423+
424+
/** Three deviations from standard derivedSelect:
425+
* 1. We first try a widening conversion to the type's info with
426+
* the original prefix. Since the original prefix is known to
427+
* be a subtype of the returned prefix, this can improve results.
428+
* 2. Then, if the approximation result is a singleton reference C#x.type, we
429+
* replace by the widened type, which is usually more natural.
430+
* 3. Finally, we need to handle the case where the prefix type does not have a member
431+
* named `tp.name` anymmore. In that case, we need to fall back to Bot..Top.
432+
*/
433+
override def derivedSelect(tp: NamedType, pre: Type) =
434+
if (pre eq tp.prefix)
435+
tp
436+
else tryWiden(tp, tp.prefix).orElse {
437+
if (tp.isTerm && variance > 0 && !pre.isSingleton)
438+
apply(tp.info.widenExpr)
439+
else if (upper(pre).member(tp.name).exists)
440+
super.derivedSelect(tp, pre)
441+
else
442+
range(defn.NothingType, defn.AnyType)
443+
}
444+
}
445+
446+
widenMap(tp)
447+
}
448+
330449
/** If `tpe` is of the form `p.x` where `p` refers to a package
331450
* but `x` is not owned by a package, expand it to
332451
*
333452
* p.package.x
334453
*/
335-
def makePackageObjPrefixExplicit(tpe: NamedType): Type = {
454+
def makePackageObjPrefixExplicit(tpe: NamedType)(using Context): Type = {
336455
def tryInsert(pkgClass: SymDenotation): Type = pkgClass match {
337456
case pkg: PackageClassDenotation =>
338457
val pobj = pkg.packageObjFor(tpe.symbol)
@@ -370,9 +489,12 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
370489
* In fact the current treatment for this sitiuation can so far only be classified as "not obviously wrong",
371490
* (maybe it still needs to be revised).
372491
*/
373-
def boundsViolations(args: List[Tree], boundss: List[TypeBounds],
374-
instantiate: (Type, List[Type]) => Type, app: Type)(
375-
implicit ctx: Context): List[BoundsViolation] = {
492+
def boundsViolations(
493+
args: List[Tree],
494+
boundss: List[TypeBounds],
495+
instantiate: (Type, List[Type]) => Type,
496+
app: Type)(
497+
using Context): List[BoundsViolation] = {
376498
val argTypes = args.tpes
377499

378500
/** Replace all wildcards in `tps` with `<app>#<tparam>` where `<tparam>` is the
@@ -465,9 +587,6 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
465587
violations.toList
466588
}
467589

468-
/** Are we in an inline method body? */
469-
def inInlineMethod: Boolean = owner.ownersIterator.exists(_.isInlineMethod)
470-
471590
/** Refine child based on parent
472591
*
473592
* In child class definition, we have:
@@ -487,7 +606,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
487606
* If the subtyping is true, the instantiated type `p.child[Vs]` is
488607
* returned. Otherwise, `NoType` is returned.
489608
*/
490-
def refineUsingParent(parent: Type, child: Symbol)(implicit ctx: Context): Type = {
609+
def refineUsingParent(parent: Type, child: Symbol)(using Context): Type = {
491610
if (child.isTerm && child.is(Case, butNot = Module)) return child.termRef // enum vals always match
492611

493612
// <local child> is a place holder from Scalac, it is hopeless to instantiate it.
@@ -503,7 +622,8 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
503622

504623
val childTp = if (child.isTerm) child.termRef else child.typeRef
505624

506-
instantiateToSubType(childTp, parent)(ctx.fresh.setNewTyperState()).dealias
625+
instantiateToSubType(childTp, parent)(using ctx.fresh.setNewTyperState())
626+
.dealias
507627
}
508628

509629
/** Instantiate type `tp1` to be a subtype of `tp2`
@@ -513,7 +633,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
513633
*
514634
* Otherwise, return NoType.
515635
*/
516-
private def instantiateToSubType(tp1: NamedType, tp2: Type)(implicit ctx: Context): Type = {
636+
private def instantiateToSubType(tp1: NamedType, tp2: Type)(using Context): Type = {
517637
/** expose abstract type references to their bounds or tvars according to variance */
518638
class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap {
519639
def expose(lo: Type, hi: Type): Type =
@@ -594,8 +714,9 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
594714
// we manually patch subtyping check instead of changing TypeComparer.
595715
// See tests/patmat/i3645b.scala
596716
def parentQualify = tp1.widen.classSymbol.info.parents.exists { parent =>
597-
implicit val ictx = ctx.fresh.setNewTyperState()
598-
parent.argInfos.nonEmpty && minTypeMap.apply(parent) <:< maxTypeMap.apply(tp2)
717+
inContext(ctx.fresh.setNewTyperState()) {
718+
parent.argInfos.nonEmpty && minTypeMap.apply(parent) <:< maxTypeMap.apply(tp2)
719+
}
599720
}
600721

601722
if (protoTp1 <:< tp2)
@@ -612,13 +733,8 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object.
612733
}
613734
}
614735
}
615-
}
616736

617-
object TypeOps {
618-
@sharable var track: Boolean = false // !!!DEBUG
619-
620-
// TODO: Move other typeops here. It's a bit weird that they are a part of `ctx`
621-
622-
def nestedPairs(ts: List[Type])(implicit ctx: Context): Type =
737+
def nestedPairs(ts: List[Type])(using Context): Type =
623738
ts.foldRight(defn.UnitType: Type)(defn.PairClass.typeRef.appliedTo(_, _))
624-
}
739+
740+
end TypeOps

0 commit comments

Comments
 (0)