Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement inline override #8543

Merged
merged 7 commits into from
Mar 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 3 additions & 7 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,9 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
case arg => arg.typeOpt.widen.isRepeatedParam
}

/** If this tree has type parameters, those. Otherwise Nil.
def typeParameters(tree: Tree): List[TypeDef] = tree match {
case DefDef(_, _, tparams, _, _, _) => tparams
case ClassDef(_, _, tparams, _) => tparams
case TypeDef(_, _, tparams, _) => tparams
case _ => Nil
}*/
/** All type and value parameter symbols of this DefDef */
def allParamSyms(ddef: DefDef)(using Context): List[Symbol] =
(ddef.tparams ::: ddef.vparamss.flatten).map(_.symbol)

/** Does this argument list end with an argument of the form <expr> : _* ? */
def isWildcardStarArgList(trees: List[Tree])(implicit ctx: Context): Boolean =
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ object Flags {
val AbstractSealed: FlagSet = Abstract | Sealed
val AbstractOrTrait: FlagSet = Abstract | Trait
val EffectivelyOpenFlags = Abstract | JavaDefined | Open | Scala2x | Trait
val AccessorOrDeferred: FlagSet = Accessor | Deferred
val PrivateAccessor: FlagSet = Accessor | Private
val AccessorOrSynthetic: FlagSet = Accessor | Synthetic
val EnumCase: FlagSet = Case | Enum
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/NameKinds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ object NameKinds {
val ProtectedAccessorName: PrefixNameKind = new PrefixNameKind(PROTECTEDACCESSOR, "protected$")
val InlineAccessorName: PrefixNameKind = new PrefixNameKind(INLINEACCESSOR, "inline$")

val AvoidClashName: SuffixNameKind = new SuffixNameKind(AVOIDCLASH, "$_avoid_name_clash_$")
val BodyRetainerName: SuffixNameKind = new SuffixNameKind(BODYRETAINER, "$retainedBody")
val FieldName: SuffixNameKind = new SuffixNameKind(FIELD, "$$local") {
override def mkString(underlying: TermName, info: ThisInfo) = underlying.toString
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ object NameOps {
/** If flags is a ModuleClass but not a Package, add module class suffix */
def adjustIfModuleClass(flags: FlagSet): N = likeSpacedN {
if (flags.is(ModuleClass, butNot = Package)) name.asTypeName.moduleClassName
else name.toTermName.exclude(AvoidClashName)
else name.toTermName
}

/** The expanded name.
Expand Down
9 changes: 0 additions & 9 deletions compiler/src/dotty/tools/dotc/core/NameTags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,6 @@ object NameTags extends TastyFormat.NameTags {

final val INITIALIZER = 26 // A mixin initializer method

final val AVOIDCLASH = 27 // Adds a suffix to avoid a name clash;
// Used in FirstTransform for synthesized companion objects of classes
// if they would clash with another value.

final val DIRECT = 28 // Used by ShortCutImplicits for the name of methods that
// implement implicit function result types directly.

final val FIELD = 29 // Used by Memoize to tag the name of a class member field.

final val EXTMETH = 30 // Used by ExtensionMethods for the name of an extension method
Expand Down Expand Up @@ -51,8 +44,6 @@ object NameTags extends TastyFormat.NameTags {
case INLINEACCESSOR => "INLINEACCESSOR"
case PROTECTEDACCESSOR => "PROTECTEDACCESSOR"
case INITIALIZER => "INITIALIZER"
case AVOIDCLASH => "AVOIDCLASH"
case DIRECT => "DIRECT"
case FIELD => "FIELD"
case EXTMETH => "EXTMETH"
case IMPLMETH => "IMPLMETH"
Expand Down
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ object SymDenotations {
*/
def effectiveName(implicit ctx: Context): Name =
if (this.is(ModuleClass)) name.stripModuleClassSuffix
else name.exclude(AvoidClashName)
else name

/** The privateWithin boundary, NoSymbol if no boundary is given.
*/
Expand Down Expand Up @@ -938,13 +938,17 @@ object SymDenotations {
def isInlineMethod(implicit ctx: Context): Boolean =
isAllOf(InlineMethod, butNot = Accessor)

def isRetainedInlineMethod(using Context): Boolean =
isAllOf(InlineMethod, butNot = AccessorOrDeferred)
&& allOverriddenSymbols.exists(!_.is(Inline))

/** Is this a Scala 2 macro */
final def isScala2Macro(implicit ctx: Context): Boolean = is(Macro) && symbol.owner.is(Scala2x)

/** An erased value or an inline method.
*/
def isEffectivelyErased(implicit ctx: Context): Boolean =
is(Erased) || isInlineMethod
is(Erased) || isInlineMethod && !isRetainedInlineMethod

/** ()T and => T types should be treated as equivalent for this symbol.
* Note: For the moment, we treat Scala-2 compiled symbols as loose matching,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ class TreeUnpickler(reader: TastyReader,
def readRhs(implicit ctx: Context): LazyTree =
if (nothingButMods(end))
EmptyTree
else if (sym.isInlineMethod)
else if sym.isInlineMethod && !sym.is(Deferred) then
// The body of an inline method is stored in an annotation, so no need to unpickle it again
new Trees.Lazy[Tree] {
def complete(implicit ctx: Context) = typer.Inliner.bodyToInline(sym)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1699,7 +1699,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
}
def Symbol_annots(self: Symbol)(using ctx: Context): List[Term] =
self.annotations.flatMap {
case _: core.Annotations.LazyBodyAnnotation => Nil
case _: core.Annotations.BodyAnnotation => Nil
case annot => annot.tree :: Nil
}

Expand Down
83 changes: 73 additions & 10 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@ import core.Types._
import core.Names._
import core.StdNames._
import core.NameOps._
import core.NameKinds.AdaptedClosureName
import core.NameKinds.{AdaptedClosureName, BodyRetainerName}
import core.Decorators._
import core.Constants._
import core.Definitions._
import core.Annotations.BodyAnnotation
import typer.{NoChecking, LiftErased}
import typer.Inliner
import typer.ProtoTypes._
import core.TypeErasure._
import core.Decorators._
import dotty.tools.dotc.ast.{tpd, untpd}
import ast.Trees._
import ast.TreeTypeMap
import dotty.tools.dotc.core.{Constants, Flags}
import ValueClasses._
import TypeUtils._
Expand Down Expand Up @@ -78,17 +80,32 @@ class Erasure extends Phase with DenotTransformer {
val oldInfo = ref.info
val newInfo = transformInfo(oldSymbol, oldInfo)
val oldFlags = ref.flags
val newFlags =
var newFlags =
if (oldSymbol.is(Flags.TermParam) && isCompacted(oldSymbol.owner)) oldFlags &~ Flags.Param
else oldFlags &~ Flags.HasDefaultParamsFlags // HasDefaultParamsFlags needs to be dropped because overriding might become overloading

val oldAnnotations = ref.annotations
var newAnnotations = oldAnnotations
if oldSymbol.isRetainedInlineMethod then
newFlags = newFlags &~ Flags.Inline
newAnnotations = newAnnotations.filterConserve(!_.isInstanceOf[BodyAnnotation])
// TODO: define derivedSymDenotation?
if ((oldSymbol eq newSymbol) && (oldOwner eq newOwner) && (oldName eq newName) && (oldInfo eq newInfo) && (oldFlags == newFlags))
if (oldSymbol eq newSymbol)
&& (oldOwner eq newOwner)
&& (oldName eq newName)
&& (oldInfo eq newInfo)
&& (oldFlags == newFlags)
&& (oldAnnotations eq newAnnotations)
then
ref
else {
else
assert(!ref.is(Flags.PackageClass), s"trans $ref @ ${ctx.phase} oldOwner = $oldOwner, newOwner = $newOwner, oldInfo = $oldInfo, newInfo = $newInfo ${oldOwner eq newOwner} ${oldInfo eq newInfo}")
ref.copySymDenotation(symbol = newSymbol, owner = newOwner, name = newName, initFlags = newFlags, info = newInfo)
}
ref.copySymDenotation(
symbol = newSymbol,
owner = newOwner,
name = newName,
initFlags = newFlags,
info = newInfo,
annotations = newAnnotations)
}
case ref: JointRefDenotation =>
new UniqueRefDenotation(
Expand Down Expand Up @@ -813,7 +830,8 @@ object Erasure {
* parameter of type `[]Object`.
*/
override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context): Tree =
if (sym.isEffectivelyErased) erasedDef(sym)
if sym.isEffectivelyErased || sym.name.is(BodyRetainerName) then
erasedDef(sym)
else
val restpe = if sym.isConstructor then defn.UnitType else sym.info.resultType
var vparams = outerParamDefs(sym)
Expand Down Expand Up @@ -874,6 +892,50 @@ object Erasure {
outerParamDefs(constr)
else Nil

/** For all statements in stats: given a retained inline method and
* its retainedBody method such as
*
* inline override def f(x: T) = body1
* private def f$retainedBody(x: T) = body2
nicolasstucki marked this conversation as resolved.
Show resolved Hide resolved
*
* return the runtime version of `f` as
*
* override def f(x: T) = body2
*
* Here, the owner of body2 is changed to f and all references
* to parameters of f$retainedBody are changed to references of
* corresponding parameters in f.
*
* `f$retainedBody` is subseqently mapped to the empty tree in `typedDefDef`
* which is then dropped in `typedStats`.
*/
private def addRetainedInlineBodies(stats: List[untpd.Tree])(using ctx: Context): List[untpd.Tree] =
lazy val retainerDef: Map[Symbol, DefDef] = stats.collect {
case stat: DefDef if stat.symbol.name.is(BodyRetainerName) =>
val retainer = stat.symbol
val origName = retainer.name.asTermName.exclude(BodyRetainerName)
val inlineMeth = ctx.atPhase(ctx.typerPhase) {
retainer.owner.info.decl(origName)
.matchingDenotation(retainer.owner.thisType, stat.symbol.info)
.symbol
}
(inlineMeth, stat)
}.toMap
stats.mapConserve {
case stat: DefDef if stat.symbol.isRetainedInlineMethod =>
val rdef = retainerDef(stat.symbol)
val fromParams = untpd.allParamSyms(rdef)
val toParams = untpd.allParamSyms(stat)
assert(fromParams.hasSameLengthAs(toParams))
val mapBody = TreeTypeMap(
oldOwners = rdef.symbol :: Nil,
newOwners = stat.symbol :: Nil,
substFrom = fromParams,
substTo = toParams)
cpy.DefDef(stat)(rhs = mapBody.transform(rdef.rhs))
case stat => stat
}

override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = {
val xxl = defn.isXXLFunctionClass(tree.typeOpt.typeSymbol)
var implClosure = super.typedClosure(tree, pt).asInstanceOf[Closure]
Expand All @@ -888,9 +950,10 @@ object Erasure {
typed(tree.arg, pt)

override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): (List[Tree], Context) = {
val stats0 = addRetainedInlineBodies(stats)(using preErasureCtx)
val stats1 =
if (takesBridges(ctx.owner)) new Bridges(ctx.owner.asClass, erasurePhase).add(stats)
else stats
if (takesBridges(ctx.owner)) new Bridges(ctx.owner.asClass, erasurePhase).add(stats0)
else stats0
val (stats2, finalCtx) = super.typedStats(stats1, exprOwner)
(stats2.filter(!_.isEmpty), finalCtx)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase
if (constr == cls.primaryConstructor)
cls.info.decls.filter(d => d.is(TypeParam) || d.is(ParamAccessor))
else
(cdef.tparams ::: cdef.vparamss.flatten).map(_.symbol)
allParamSyms(cdef)

/** The parameter references defined by the constructor info */
def allParamRefs(tp: Type): List[ParamRef] = tp match {
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ class TreeChecker extends Phase with SymTransformer {
val badDeferredAndPrivate =
sym.is(Method) && sym.is(Deferred) && sym.is(Private)
&& !sym.hasAnnotation(defn.NativeAnnot)
&& !sym.is(Erased)
&& !sym.isEffectivelyErased

assert(!badDeferredAndPrivate, i"$sym is both Deferred and Private")

checkCompanion(symd)
Expand Down
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,6 @@ object Checking {
fail(OnlyClassesCanHaveDeclaredButUndefinedMembers(sym))
checkWithDeferred(Private)
checkWithDeferred(Final)
checkWithDeferred(Inline)
}
if (sym.isValueClass && sym.is(Trait) && !sym.isRefinementClass)
fail(CannotExtendAnyVal(sym))
Expand Down Expand Up @@ -883,7 +882,7 @@ trait Checking {
typr.println(i"check no double declarations $cls")

def checkDecl(decl: Symbol): Unit = {
for (other <- seen(decl.name) if (!decl.isAbsent() && !other.isAbsent())) {
for (other <- seen(decl.name) if !decl.isAbsent() && !other.isAbsent()) {
typr.println(i"conflict? $decl $other")
def javaFieldMethodPair =
decl.is(JavaDefined) && other.is(JavaDefined) &&
Expand Down
28 changes: 27 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import StdNames._
import transform.SymUtils._
import Contexts.Context
import Names.{Name, TermName}
import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName}
import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName, BodyRetainerName}
import ProtoTypes.selectionProto
import SymDenotations.SymDenotation
import Inferencing.isFullyDefined
Expand Down Expand Up @@ -124,6 +124,32 @@ object Inliner {
)
}

/** For a retained inline method, another method that keeps track of
* the body that is kept at runtime. For instance, an inline method
*
* inline override def f(x: T) = b
*
* is complemented by the body retainer method
*
* private def f$retainedBody(x: T) = f(x)
*
* where the call `f(x)` is inline-expanded. This body is then transferred
* back to `f` at erasure, using method addRetainedInlineBodies.
*/
def bodyRetainer(mdef: DefDef)(using ctx: Context): DefDef =
val meth = mdef.symbol.asTerm

val retainer = meth.copy(
name = BodyRetainerName(meth.name),
flags = meth.flags &~ (Inline | Override) | Private,
coord = mdef.rhs.span.startPos).asTerm
polyDefDef(retainer, targs => prefss =>
inlineCall(
ref(meth).appliedToTypes(targs).appliedToArgss(prefss)
.withSpan(mdef.rhs.span.startPos))(
using ctx.withOwner(retainer)))
.reporting(i"retainer for $meth: $result", inlining)

/** Replace `Inlined` node by a block that contains its bindings and expansion */
def dropInlined(inlined: Inlined)(implicit ctx: Context): Tree =
if (enclosingInlineds.nonEmpty) inlined // Remove in the outer most inlined call
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/ReTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class ReTyper extends Typer with ReChecking {
throw ex
}

override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): Tree = mdef
override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] = mdef :: Nil

override def inferView(from: Tree, to: Type)(implicit ctx: Context): Implicits.SearchResult =
Implicits.NoMatchingImplicitsFailure
Expand Down
8 changes: 3 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,7 @@ object RefChecks {
* 1.8.3 M is of type ()S, O is of type []T and S <: T, or
* 1.9.1 If M is erased, O is erased. If O is erased, M is erased or inline.
* 1.9.2 If M or O are extension methods, they must both be extension methods.
* 1.10 If M is an inline or Scala-2 macro method, O cannot be deferred unless
* there's also a concrete method that M overrides.
* 1.10 If O is inline, M must be inline
odersky marked this conversation as resolved.
Show resolved Hide resolved
* 1.11. If O is a Scala-2 macro, M must be a Scala-2 macro.
* 2. Check that only abstract classes have deferred members
* 3. Check that concrete classes do not have deferred definitions
Expand Down Expand Up @@ -398,9 +397,8 @@ object RefChecks {
overrideError("is an extension method, cannot override a normal method")
else if (other.isAllOf(ExtensionMethod) && !member.isAllOf(ExtensionMethod)) // (1.9.2)
overrideError("is a normal method, cannot override an extension method")
else if ((member.isInlineMethod || member.isScala2Macro) && other.is(Deferred) &&
member.extendedOverriddenSymbols.forall(_.is(Deferred))) // (1.10)
overrideError("is an inline method, must override at least one concrete method")
else if other.isInlineMethod && !member.isInlineMethod then // (1.10)
overrideError("is not inline, cannot override an inline method")
else if (other.isScala2Macro && !member.isScala2Macro) // (1.11)
overrideError("cannot be used here - only Scala-2 macros can override Scala-2 macros")
else if (!compatibleTypes(memberTp(self), otherTp(self)) &&
Expand Down
21 changes: 14 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2336,8 +2336,9 @@ class Typer extends Namer
val newCtx = if (ctx.owner.isTerm && adaptCreationContext(mdef)) ctx
else ctx.withNotNullInfos(initialNotNullInfos)
typed(mdef)(using newCtx) match {
case mdef1: DefDef if !Inliner.bodyToInline(mdef1.symbol).isEmpty =>
buf += inlineExpansion(mdef1)
case mdef1: DefDef
if mdef1.symbol.is(Inline, butNot = Deferred) && !Inliner.bodyToInline(mdef1.symbol).isEmpty =>
buf ++= inlineExpansion(mdef1)
// replace body with expansion, because it will be used as inlined body
// from separately compiled files - the original BodyAnnotation is not kept.
case mdef1: TypeDef if mdef1.symbol.is(Enum, butNot = Case) =>
Expand Down Expand Up @@ -2414,11 +2415,13 @@ class Typer extends Namer
}

/** Given an inline method `mdef`, the method rewritten so that its body
* uses accessors to access non-public members.
* uses accessors to access non-public members. Also, if the inline method
* is retained, add a method to record the retained version of the body.
* Overwritten in Retyper to return `mdef` unchanged.
*/
protected def inlineExpansion(mdef: DefDef)(implicit ctx: Context): Tree =
protected def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] =
tpd.cpy.DefDef(mdef)(rhs = Inliner.bodyToInline(mdef.symbol))
:: (if mdef.symbol.isRetainedInlineMethod then Inliner.bodyRetainer(mdef) :: Nil else Nil)

def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree =
typed(tree, pt)(ctx retractMode Mode.PatternOrTypeBits)
Expand Down Expand Up @@ -2936,9 +2939,13 @@ class Typer extends Namer
!suppressInline) {
tree.tpe <:< wildApprox(pt)
val errorCount = ctx.reporter.errorCount
val inlined = Inliner.inlineCall(tree)
if ((inlined ne tree) && errorCount == ctx.reporter.errorCount) readaptSimplified(inlined)
else inlined
val meth = methPart(tree).symbol
if meth.is(Deferred) then
errorTree(tree, i"Deferred inline ${meth.showLocated} cannot be invoked")
else
val inlined = Inliner.inlineCall(tree)
if ((inlined ne tree) && errorCount == ctx.reporter.errorCount) readaptSimplified(inlined)
else inlined
}
else if (tree.symbol.isScala2Macro &&
// raw and s are eliminated by the StringInterpolatorOpt phase
Expand Down