From cffd862c95e10a49aaff1ea6798e7c4c6b2f22ce Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 7 May 2020 15:53:32 +0200 Subject: [PATCH 1/2] Turn TypeOps into an object Also, move `avoid` from TypeAssigner to TypeOps, since we need to refer to it in Types. Based on #8867 --- .../src/dotty/tools/dotc/core/Contexts.scala | 1 - .../dotty/tools/dotc/core/TypeComparer.scala | 3 +- .../dotty/tools/dotc/core/TypeErasure.scala | 3 +- .../src/dotty/tools/dotc/core/TypeOps.scala | 186 ++++++++++++++---- .../src/dotty/tools/dotc/core/Types.scala | 8 +- .../tools/dotc/transform/ReifyQuotes.scala | 2 +- .../tools/dotc/transform/patmat/Space.scala | 2 +- .../src/dotty/tools/dotc/typer/Checking.scala | 6 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 6 +- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- .../tools/dotc/typer/PrepareInlineable.scala | 2 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 124 +----------- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 13 files changed, 175 insertions(+), 172 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index e1f507737277..b54f6d320693 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -78,7 +78,6 @@ object Contexts { abstract class Context(val base: ContextBase) extends Periods with Substituters - with TypeOps with Phases with Printers with Symbols diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 9a6a53ad43e5..53a24765c96b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -5,6 +5,7 @@ package core import Types._, Contexts._, Symbols._, Flags._, Names._, NameOps._, Denotations._ import Decorators._ import StdNames.nme +import TypeOps.refineUsingParent import collection.mutable import util.Stats import config.Config @@ -2306,7 +2307,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w * denote the same set of values. */ def decompose(sym: Symbol, tp: Type): List[Type] = - sym.children.map(x => ctx.refineUsingParent(tp, x)).filter(_.exists) + sym.children.map(x => refineUsingParent(tp, x)).filter(_.exists) (tp1.dealias, tp2.dealias) match { case (tp1: TypeRef, tp2: TypeRef) if tp1.symbol == defn.SingletonClass || tp2.symbol == defn.SingletonClass => diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 8906a07beb00..fef52118809b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -5,6 +5,7 @@ package core import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._ import Flags.JavaDefined import Uniques.unique +import TypeOps.makePackageObjPrefixExplicit import transform.ExplicitOuter._ import transform.ValueClasses._ import transform.TypeUtils._ @@ -166,7 +167,7 @@ object TypeErasure { def erasedRef(tp: Type)(implicit ctx: Context): Type = tp match { case tp: TermRef => assert(tp.symbol.exists, tp) - val tp1 = ctx.makePackageObjPrefixExplicit(tp) + val tp1 = makePackageObjPrefixExplicit(tp) if (tp1 ne tp) erasedRef(tp1) else TermRef(erasedRef(tp.prefix), tp.symbol.asTerm) case tp: ThisType => diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index de64afffd7b8..9897d244d069 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -22,13 +22,16 @@ import typer.Inferencing.isFullyDefined import typer.IfBottom import scala.annotation.internal.sharable +import scala.annotation.threadUnsafe -trait TypeOps { thisCtx: Context => // TODO: Make standalone object. +object TypeOps: + + @sharable var track: Boolean = false // for debugging /** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec * for what this means. */ - final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = { + final def asSeenFrom(tp: Type, pre: Type, cls: Symbol)(using Context): Type = { pre match { case pre: QualSkolemType => // When a selection has an unstable qualifier, the qualifier type gets @@ -53,7 +56,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. } /** The TypeMap handling the asSeenFrom */ - class AsSeenFromMap(pre: Type, cls: Symbol) extends ApproximatingTypeMap { + class AsSeenFromMap(pre: Type, cls: Symbol)(using Context) extends ApproximatingTypeMap { /** Set to true when the result of `apply` was approximated to avoid an unstable prefix. */ var approximated: Boolean = false @@ -62,7 +65,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. /** Map a `C.this` type to the right prefix. If the prefix is unstable, and * the current variance is <= 0, return a range. */ - def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ trace.conditionally(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)", show = true) /*<|<*/ { + def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ trace.conditionally(track, s"toPrefix($pre, $cls, $thiscls)", show = true) /*<|<*/ { if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass)) tp else pre match { @@ -89,7 +92,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. } } - trace.conditionally(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) { // !!! DEBUG + trace.conditionally(track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) { // !!! DEBUG // All cases except for ThisType are the same as in Map. Inlined for performance // TODO: generalize the inlining trick? tp match { @@ -117,11 +120,11 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. } } - def isLegalPrefix(pre: Type)(implicit ctx: Context): Boolean = + def isLegalPrefix(pre: Type)(using Context): Boolean = pre.isStable || !ctx.phase.isTyper /** Implementation of Types#simplified */ - final def simplify(tp: Type, theMap: SimplifyMap): Type = { + def simplify(tp: Type, theMap: SimplifyMap)(using Context): Type = { def mapOver = (if (theMap != null) theMap else new SimplifyMap).mapOver(tp) tp match { case tp: NamedType => @@ -135,20 +138,20 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. } case tp: TypeParamRef => if (tp.paramName.is(DepParamName)) { - val bounds = thisCtx.typeComparer.bounds(tp) + val bounds = ctx.typeComparer.bounds(tp) if (bounds.lo.isRef(defn.NothingClass)) bounds.hi else bounds.lo } else { - val tvar = typerState.constraint.typeVarOfParam(tp) + val tvar = ctx.typerState.constraint.typeVarOfParam(tp) if (tvar.exists) tvar else tp } case _: ThisType | _: BoundType => tp case tp: AliasingBounds => tp.derivedAlias(simplify(tp.alias, theMap)) - case AndType(l, r) if !thisCtx.mode.is(Mode.Type) => + case AndType(l, r) if !ctx.mode.is(Mode.Type) => simplify(l, theMap) & simplify(r, theMap) - case OrType(l, r) if !thisCtx.mode.is(Mode.Type) => + case OrType(l, r) if !ctx.mode.is(Mode.Type) => simplify(l, theMap) | simplify(r, theMap) case _: AppliedType | _: MatchType => val normed = tp.tryNormalize @@ -158,7 +161,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. } } - class SimplifyMap extends TypeMap { + class SimplifyMap(using Context) extends TypeMap { def apply(tp: Type): Type = simplify(tp, this) } @@ -178,7 +181,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. * in a "best effort", ad-hoc way by selectively widening types in `T1, ..., Tn` * and stopping if the resulting union simplifies to a type that is not a disjunction. */ - def orDominator(tp: Type): Type = { + def orDominator(tp: Type)(using Context): Type = { /** a faster version of cs1 intersect cs2 */ def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = { @@ -193,7 +196,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu if (cs == c.baseClasses) accu1 else dominators(rest, accu1) case Nil => // this case can happen because after erasure we do not have a top class anymore - assert(thisCtx.erasedTypes || thisCtx.reporter.errorsReported) + assert(ctx.erasedTypes || ctx.reporter.errorsReported) defn.ObjectClass :: Nil } @@ -217,7 +220,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. case AppliedType(tycon2, args2) => tp1.derivedAppliedType( mergeRefinedOrApplied(tycon1, tycon2), - thisCtx.typeComparer.lubArgs(args1, args2, tycon1.typeParams)) + ctx.typeComparer.lubArgs(args1, args2, tycon1.typeParams)) case _ => fallback } case tp1 @ TypeRef(pre1, _) => @@ -327,12 +330,128 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. } } + /** An abstraction of a class info, consisting of + * - the intersection of its parents, + * - refined by all non-private fields, methods, and type members, + * - abstracted over all type parameters (into a type lambda) + * - where all references to `this` of the class are closed over in a RecType. + */ + def classBound(info: ClassInfo)(using Context): Type = { + val cls = info.cls + val parentType = info.parents.reduceLeft(ctx.typeComparer.andType(_, _)) + + def addRefinement(parent: Type, decl: Symbol) = { + val inherited = + parentType.findMember(decl.name, cls.thisType, + required = EmptyFlags, excluded = Private + ).suchThat(decl.matches(_)) + val inheritedInfo = inherited.info + val isPolyFunctionApply = decl.name == nme.apply && (parent <:< defn.PolyFunctionType) + if isPolyFunctionApply + || inheritedInfo.exists + && !decl.isClass + && decl.info.widenExpr <:< inheritedInfo.widenExpr + && !(inheritedInfo.widenExpr <:< decl.info.widenExpr) + then + val r = RefinedType(parent, decl.name, decl.info) + typr.println(i"add ref $parent $decl --> " + r) + r + else + parent + } + + def close(tp: Type) = RecType.closeOver { rt => + tp.subst(cls :: Nil, rt.recThis :: Nil).substThis(cls, rt.recThis) + } + + def isRefinable(sym: Symbol) = !sym.is(Private) && !sym.isConstructor + val refinableDecls = info.decls.filter(isRefinable) + val raw = refinableDecls.foldLeft(parentType)(addRefinement) + HKTypeLambda.fromParams(cls.typeParams, raw) match { + case tl: HKTypeLambda => tl.derivedLambdaType(resType = close(tl.resType)) + case tp => close(tp) + } + } + + /** An upper approximation of the given type `tp` that does not refer to any symbol in `symsToAvoid`. + * We need to approximate with ranges: + * + * term references to symbols in `symsToAvoid`, + * term references that have a widened type of which some part refers + * to a symbol in `symsToAvoid`, + * type references to symbols in `symsToAvoid`, + * this types of classes in `symsToAvoid`. + * + * Type variables that would be interpolated to a type that + * needs to be widened are replaced by the widened interpolation instance. + */ + def avoid(tp: Type, symsToAvoid: => List[Symbol])(using Context): Type = { + val widenMap = new ApproximatingTypeMap { + @threadUnsafe lazy val forbidden = symsToAvoid.toSet + def toAvoid(sym: Symbol) = !sym.isStatic && forbidden.contains(sym) + def partsToAvoid = new NamedPartsAccumulator(tp => toAvoid(tp.symbol)) + def apply(tp: Type): Type = tp match { + case tp: TermRef + if toAvoid(tp.symbol) || partsToAvoid(mutable.Set.empty, tp.info).nonEmpty => + tp.info.widenExpr.dealias match { + case info: SingletonType => apply(info) + case info => range(defn.NothingType, apply(info)) + } + case tp: TypeRef if toAvoid(tp.symbol) => + tp.info match { + case info: AliasingBounds => + apply(info.alias) + case TypeBounds(lo, hi) => + range(atVariance(-variance)(apply(lo)), apply(hi)) + case info: ClassInfo => + range(defn.NothingType, apply(classBound(info))) + case _ => + emptyRange // should happen only in error cases + } + case tp: ThisType if toAvoid(tp.cls) => + range(defn.NothingType, apply(classBound(tp.cls.classInfo))) + case tp: SkolemType if partsToAvoid(mutable.Set.empty, tp.info).nonEmpty => + range(defn.NothingType, apply(tp.info)) + case tp: TypeVar if mapCtx.typerState.constraint.contains(tp) => + val lo = mapCtx.typeComparer.instanceType( + tp.origin, fromBelow = variance > 0 || variance == 0 && tp.hasLowerBound) + val lo1 = apply(lo) + if (lo1 ne lo) lo1 else tp + case _ => + mapOver(tp) + } + + /** Three deviations from standard derivedSelect: + * 1. We first try a widening conversion to the type's info with + * the original prefix. Since the original prefix is known to + * be a subtype of the returned prefix, this can improve results. + * 2. Then, if the approximation result is a singleton reference C#x.type, we + * replace by the widened type, which is usually more natural. + * 3. Finally, we need to handle the case where the prefix type does not have a member + * named `tp.name` anymmore. In that case, we need to fall back to Bot..Top. + */ + override def derivedSelect(tp: NamedType, pre: Type) = + if (pre eq tp.prefix) + tp + else tryWiden(tp, tp.prefix).orElse { + if (tp.isTerm && variance > 0 && !pre.isSingleton) + apply(tp.info.widenExpr) + else if (upper(pre).member(tp.name).exists) + super.derivedSelect(tp, pre) + else + range(defn.NothingType, defn.AnyType) + } + } + + widenMap(tp) + } + /** If `tpe` is of the form `p.x` where `p` refers to a package * but `x` is not owned by a package, expand it to * * p.package.x */ - def makePackageObjPrefixExplicit(tpe: NamedType): Type = { + def makePackageObjPrefixExplicit(tpe: NamedType)(using Context): Type = { def tryInsert(pkgClass: SymDenotation): Type = pkgClass match { case pkg: PackageClassDenotation => val pobj = pkg.packageObjFor(tpe.symbol) @@ -370,9 +489,12 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. * In fact the current treatment for this sitiuation can so far only be classified as "not obviously wrong", * (maybe it still needs to be revised). */ - def boundsViolations(args: List[Tree], boundss: List[TypeBounds], - instantiate: (Type, List[Type]) => Type, app: Type)( - implicit ctx: Context): List[BoundsViolation] = { + def boundsViolations( + args: List[Tree], + boundss: List[TypeBounds], + instantiate: (Type, List[Type]) => Type, + app: Type)( + using Context): List[BoundsViolation] = { val argTypes = args.tpes /** Replace all wildcards in `tps` with `#` where `` is the @@ -465,9 +587,6 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. violations.toList } - /** Are we in an inline method body? */ - def inInlineMethod: Boolean = owner.ownersIterator.exists(_.isInlineMethod) - /** Refine child based on parent * * In child class definition, we have: @@ -487,7 +606,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. * If the subtyping is true, the instantiated type `p.child[Vs]` is * returned. Otherwise, `NoType` is returned. */ - def refineUsingParent(parent: Type, child: Symbol)(implicit ctx: Context): Type = { + def refineUsingParent(parent: Type, child: Symbol)(using Context): Type = { if (child.isTerm && child.is(Case, butNot = Module)) return child.termRef // enum vals always match // is a place holder from Scalac, it is hopeless to instantiate it. @@ -503,7 +622,8 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. val childTp = if (child.isTerm) child.termRef else child.typeRef - instantiateToSubType(childTp, parent)(ctx.fresh.setNewTyperState()).dealias + instantiateToSubType(childTp, parent)(using ctx.fresh.setNewTyperState()) + .dealias } /** Instantiate type `tp1` to be a subtype of `tp2` @@ -513,7 +633,7 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. * * Otherwise, return NoType. */ - private def instantiateToSubType(tp1: NamedType, tp2: Type)(implicit ctx: Context): Type = { + private def instantiateToSubType(tp1: NamedType, tp2: Type)(using Context): Type = { /** expose abstract type references to their bounds or tvars according to variance */ class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap { def expose(lo: Type, hi: Type): Type = @@ -594,8 +714,9 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. // we manually patch subtyping check instead of changing TypeComparer. // See tests/patmat/i3645b.scala def parentQualify = tp1.widen.classSymbol.info.parents.exists { parent => - implicit val ictx = ctx.fresh.setNewTyperState() - parent.argInfos.nonEmpty && minTypeMap.apply(parent) <:< maxTypeMap.apply(tp2) + inContext(ctx.fresh.setNewTyperState()) { + parent.argInfos.nonEmpty && minTypeMap.apply(parent) <:< maxTypeMap.apply(tp2) + } } if (protoTp1 <:< tp2) @@ -612,13 +733,8 @@ trait TypeOps { thisCtx: Context => // TODO: Make standalone object. } } } -} -object TypeOps { - @sharable var track: Boolean = false // !!!DEBUG - - // TODO: Move other typeops here. It's a bit weird that they are a part of `ctx` - - def nestedPairs(ts: List[Type])(implicit ctx: Context): Type = + def nestedPairs(ts: List[Type])(using Context): Type = ts.foldRight(defn.UnitType: Type)(defn.PairClass.typeRef.appliedTo(_, _)) -} + +end TypeOps diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 16edb3e435c9..f8e3c28be546 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -898,7 +898,7 @@ object Types { final def asSeenFrom(pre: Type, cls: Symbol)(implicit ctx: Context): Type = { record("asSeenFrom") if (!cls.membersNeedAsSeenFrom(pre)) this - else ctx.asSeenFrom(this, pre, cls) + else TypeOps.asSeenFrom(this, pre, cls) } // ----- Subtype-related -------------------------------------------- @@ -1668,7 +1668,7 @@ object Types { * after the type variables are instantiated. Finally, it * maps poly params in the current constraint set back to their type vars. */ - def simplified(implicit ctx: Context): Type = ctx.simplify(this, null) + def simplified(implicit ctx: Context): Type = TypeOps.simplify(this, null) /** Compare `this == that`, assuming corresponding binders in `bs` are equal. * The normal `equals` should be equivalent to `equals(that, null`)`. @@ -2948,7 +2948,7 @@ object Types { /** Replace or type by the closest non-or type above it */ def join(implicit ctx: Context): Type = { if (myJoinPeriod != ctx.period) { - myJoin = ctx.orDominator(this) + myJoin = TypeOps.orDominator(this) core.println(i"join of $this == $myJoin") assert(myJoin != this) myJoinPeriod = ctx.period @@ -4165,7 +4165,7 @@ object Types { val problems = problemSyms(Set.empty, tp) if problems.isEmpty then tp else - val atp = ctx.typer.avoid(tp, problems.toList) + val atp = TypeOps.avoid(tp, problems.toList) def msg = i"Inaccessible variables captured in instantation of type variable $this.\n$tp was fixed to $atp" typr.println(msg) val bound = ctx.typeComparer.fullUpperBound(origin) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 22cfd2d588c1..634cd15568dc 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -76,7 +76,7 @@ class ReifyQuotes extends MacroTransform { override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { - case tree: RefTree if !ctx.inInlineMethod => + case tree: RefTree if !Inliner.inInlineMethod => assert(!tree.symbol.isQuote) assert(!tree.symbol.isSplice) case _ : TypeDef => diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index bc74d9542a61..48fce0271a98 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -605,7 +605,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { case tp => val parts = children.map { sym => val sym1 = if (sym.is(ModuleClass)) sym.sourceModule else sym - val refined = ctx.refineUsingParent(tp, sym1) + val refined = TypeOps.refineUsingParent(tp, sym1) def inhabited(tp: Type): Boolean = tp.dealias match { diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 32294f6c5fc4..178d8b429fe0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -69,7 +69,7 @@ object Checking { errorTree(arg, showInferred(MissingTypeParameterInTypeApp(arg.tpe), app, tpt)) } - for (arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate, app) do + for (arg, which, bound) <- TypeOps.boundsViolations(args, boundss, instantiate, app) do ctx.error( showInferred(DoesNotConformToBound(arg.tpe, which, bound), app, tpt), @@ -884,7 +884,7 @@ trait Checking { /** Check that `tree` can be right hand-side or argument to `inline` value or parameter. */ def checkInlineConformant(tpt: Tree, tree: Tree, sym: Symbol)(using Context): Unit = { - if sym.is(Inline, butNot = DeferredOrTermParamOrAccessor) && !ctx.erasedTypes && !ctx.inInlineMethod then + if sym.is(Inline, butNot = DeferredOrTermParamOrAccessor) && !ctx.erasedTypes && !Inliner.inInlineMethod then // final vals can be marked inline even if they're not pure, see Typer#patchFinalVals val purityLevel = if (sym.is(Final)) Idempotent else Pure tpt.tpe.widenTermRefExpr.dealias.normalized match @@ -1088,7 +1088,7 @@ trait Checking { /** Check that we are in an inline context (inside an inline method or in inline code) */ def checkInInlineContext(what: String, posd: Positioned)(using Context): Unit = - if !ctx.inInlineMethod && !ctx.isInlineContext then + if !Inliner.inInlineMethod && !ctx.isInlineContext then ctx.error(em"$what can only be used in an inline method", posd.sourcePos) /** 1. Check that all case classes that extend `scala.Enum` are `enum` cases diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d60123e510ab..aea9129959d1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -48,9 +48,13 @@ object Inliner { else EmptyTree + /** Are we in an inline method body? */ + def inInlineMethod(using Context): Boolean = + ctx.owner.ownersIterator.exists(_.isInlineMethod) + /** Should call to method `meth` be inlined in this context? */ def isInlineable(meth: Symbol)(using Context): Boolean = - meth.is(Inline) && meth.hasAnnotation(defn.BodyAnnot) && !ctx.inInlineMethod + meth.is(Inline) && meth.hasAnnotation(defn.BodyAnnot) && !inInlineMethod /** Should call be inlined in this context? */ def isInlineable(tree: Tree)(using Context): Boolean = tree match { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 8e71aedc6c52..b60fb34e68c0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1510,7 +1510,7 @@ class Namer { typer: Typer => // are better ways to achieve this. It would be good if we could get rid of this code. // It seems at least partially redundant with the nesting level checking on TypeVar // instantiation. - val hygienicType = avoid(rhsType, paramss.flatten) + val hygienicType = TypeOps.avoid(rhsType, paramss.flatten) if (!hygienicType.isValueType || !(hygienicType <:< tpt.tpe)) ctx.error(i"return type ${tpt.tpe} of lambda cannot be made hygienic;\n" + i"it is not a supertype of the hygienic type $hygienicType", mdef.sourcePos) diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index cf7286be340f..82d123d0980b 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -244,7 +244,7 @@ object PrepareInlineable { def checkInlineMethod(inlined: Symbol, body: Tree)(using Context): Unit = { if (inlined.owner.isClass && inlined.owner.seesOpaques) ctx.error(em"Implementation restriction: No inline methods allowed where opaque type aliases are in scope", inlined.sourcePos) - if (ctx.outer.inInlineMethod) + if Inliner.inInlineMethod(using ctx.outer) then ctx.error(ex"Implementation restriction: nested inline methods are not supported", inlined.sourcePos) if (inlined.is(Macro) && !ctx.isAfterTyper) { diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 35eb5b534c35..1162257f0411 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -16,8 +16,6 @@ import collection.mutable import reporting.messages._ import Checking.{checkNoPrivateLeaks, checkNoWildcard} -import scala.annotation.threadUnsafe - trait TypeAssigner { import tpd._ @@ -41,124 +39,8 @@ trait TypeAssigner { } } - /** An abstraction of a class info, consisting of - * - the intersection of its parents, - * - refined by all non-private fields, methods, and type members, - * - abstracted over all type parameters (into a type lambda) - * - where all references to `this` of the class are closed over in a RecType. - */ - def classBound(info: ClassInfo)(using Context): Type = { - val cls = info.cls - val parentType = info.parents.reduceLeft(ctx.typeComparer.andType(_, _)) - - def addRefinement(parent: Type, decl: Symbol) = { - val inherited = - parentType.findMember(decl.name, cls.thisType, - required = EmptyFlags, excluded = Private - ).suchThat(decl.matches(_)) - val inheritedInfo = inherited.info - val isPolyFunctionApply = decl.name == nme.apply && (parent <:< defn.PolyFunctionType) - if isPolyFunctionApply - || inheritedInfo.exists - && !decl.isClass - && decl.info.widenExpr <:< inheritedInfo.widenExpr - && !(inheritedInfo.widenExpr <:< decl.info.widenExpr) - then - val r = RefinedType(parent, decl.name, decl.info) - typr.println(i"add ref $parent $decl --> " + r) - r - else - parent - } - - def close(tp: Type) = RecType.closeOver { rt => - tp.subst(cls :: Nil, rt.recThis :: Nil).substThis(cls, rt.recThis) - } - - def isRefinable(sym: Symbol) = !sym.is(Private) && !sym.isConstructor - val refinableDecls = info.decls.filter(isRefinable) - val raw = refinableDecls.foldLeft(parentType)(addRefinement) - HKTypeLambda.fromParams(cls.typeParams, raw) match { - case tl: HKTypeLambda => tl.derivedLambdaType(resType = close(tl.resType)) - case tp => close(tp) - } - } - - /** An upper approximation of the given type `tp` that does not refer to any symbol in `symsToAvoid`. - * We need to approximate with ranges: - * - * term references to symbols in `symsToAvoid`, - * term references that have a widened type of which some part refers - * to a symbol in `symsToAvoid`, - * type references to symbols in `symsToAvoid`, - * this types of classes in `symsToAvoid`. - * - * Type variables that would be interpolated to a type that - * needs to be widened are replaced by the widened interpolation instance. - */ - def avoid(tp: Type, symsToAvoid: => List[Symbol])(using Context): Type = { - val widenMap = new ApproximatingTypeMap { - @threadUnsafe lazy val forbidden = symsToAvoid.toSet - def toAvoid(sym: Symbol) = !sym.isStatic && forbidden.contains(sym) - def partsToAvoid = new NamedPartsAccumulator(tp => toAvoid(tp.symbol)) - def apply(tp: Type): Type = tp match { - case tp: TermRef - if toAvoid(tp.symbol) || partsToAvoid(mutable.Set.empty, tp.info).nonEmpty => - tp.info.widenExpr.dealias match { - case info: SingletonType => apply(info) - case info => range(defn.NothingType, apply(info)) - } - case tp: TypeRef if toAvoid(tp.symbol) => - tp.info match { - case info: AliasingBounds => - apply(info.alias) - case TypeBounds(lo, hi) => - range(atVariance(-variance)(apply(lo)), apply(hi)) - case info: ClassInfo => - range(defn.NothingType, apply(classBound(info))) - case _ => - emptyRange // should happen only in error cases - } - case tp: ThisType if toAvoid(tp.cls) => - range(defn.NothingType, apply(classBound(tp.cls.classInfo))) - case tp: SkolemType if partsToAvoid(mutable.Set.empty, tp.info).nonEmpty => - range(defn.NothingType, apply(tp.info)) - case tp: TypeVar if mapCtx.typerState.constraint.contains(tp) => - val lo = mapCtx.typeComparer.instanceType( - tp.origin, fromBelow = variance > 0 || variance == 0 && tp.hasLowerBound) - val lo1 = apply(lo) - if (lo1 ne lo) lo1 else tp - case _ => - mapOver(tp) - } - - /** Three deviations from standard derivedSelect: - * 1. We first try a widening conversion to the type's info with - * the original prefix. Since the original prefix is known to - * be a subtype of the returned prefix, this can improve results. - * 2. Then, if the approximation result is a singleton reference C#x.type, we - * replace by the widened type, which is usually more natural. - * 3. Finally, we need to handle the case where the prefix type does not have a member - * named `tp.name` anymmore. In that case, we need to fall back to Bot..Top. - */ - override def derivedSelect(tp: NamedType, pre: Type) = - if (pre eq tp.prefix) - tp - else tryWiden(tp, tp.prefix).orElse { - if (tp.isTerm && variance > 0 && !pre.isSingleton) - apply(tp.info.widenExpr) - else if (upper(pre).member(tp.name).exists) - super.derivedSelect(tp, pre) - else - range(defn.NothingType, defn.AnyType) - } - } - - widenMap(tp) - } - def avoidingType(expr: Tree, bindings: List[Tree])(using Context): Type = - avoid(expr.tpe, localSyms(bindings).filter(_.isTerm)) + TypeOps.avoid(expr.tpe, localSyms(bindings).filter(_.isTerm)) def avoidPrivateLeaks(sym: Symbol)(using Context): Type = if sym.owner.isClass && !sym.isOneOf(JavaOrPrivateOrSynthetic) @@ -228,7 +110,7 @@ trait TypeAssigner { else errorType(ex"$whatCanNot be accessed as a member of $pre$where.$whyNot", pos) } } - else ctx.makePackageObjPrefixExplicit(tpe withDenot d) + else TypeOps.makePackageObjPrefixExplicit(tpe withDenot d) case _ => tpe } @@ -241,7 +123,7 @@ trait TypeAssigner { * @see QualSkolemType, TypeOps#asSeenFrom */ def maybeSkolemizePrefix(qualType: Type, name: Name)(using Context): Type = - if (name.isTermName && !ctx.isLegalPrefix(qualType)) + if (name.isTermName && !TypeOps.isLegalPrefix(qualType)) QualSkolemType(qualType) else qualType diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 38df14d16116..6fb7f5336f89 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -885,7 +885,7 @@ class Typer extends Namer if (noLeaks(tree)) tree else { fullyDefinedType(tree.tpe, "block", tree.span) - var avoidingType = avoid(tree.tpe, localSyms) + var avoidingType = TypeOps.avoid(tree.tpe, localSyms) val ptDefined = isFullyDefined(pt, ForceDegree.none) if (ptDefined && !(avoidingType.widenExpr <:< pt)) avoidingType = pt val tree1 = ascribeType(tree, avoidingType) From f2f082defecded84d63bc3ae553469d54e701684 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 7 May 2020 16:44:19 +0200 Subject: [PATCH 2/2] Fix rebase breakage --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6fb7f5336f89..ddb7b7b5e6db 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3083,7 +3083,7 @@ class Typer extends Namer checkEqualityEvidence(tree, pt) tree } - else if (methPart(tree).symbol.isAllOf(Inline | Deferred) && !ctx.inInlineMethod) then + else if (methPart(tree).symbol.isAllOf(Inline | Deferred) && !Inliner.inInlineMethod) then errorTree(tree, i"Deferred inline ${methPart(tree).symbol.showLocated} cannot be invoked") else if (Inliner.isInlineable(tree) && !ctx.settings.YnoInline.value &&