Skip to content

Inline traits for specialization in Scala 3 (v2) #20254

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ class CompilationUnit protected (val source: SourceFile, val info: CompilationUn
*/
var needsInlining: Boolean = false

/** TODO */
var needsTraitInlining: Boolean = false

var hasMacroAnnotations: Boolean = false

/** Set to `true` if inliner added anonymous mirrors that need to be completed */
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class Compiler {
protected def picklerPhases: List[List[Phase]] =
List(new Pickler) :: // Generate TASTY info
List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks
List(new TraitInlining) :: // Generate inline trait members
List(new Inlining) :: // Inline and execute macros
List(new PostInlining) :: // Add mirror support for inlined code
List(new CheckUnused.PostInlining) :: // Check for unused elements
Expand All @@ -66,6 +67,7 @@ class Compiler {
new CookComments, // Cook the comments: expand variables, doc, etc.
new CheckLoopingImplicits, // Check that implicit defs do not call themselves in an infinite loop
new BetaReduce, // Reduce closure applications
new Devirtualize, // Devirtualize method calls
new InlineVals, // Check right hand-sides of an `inline val`s
new ExpandSAMs, // Expand single abstract method closures to anonymous classes
new ElimRepeated, // Rewrite vararg parameters and arguments
Expand Down Expand Up @@ -93,6 +95,7 @@ class Compiler {
new StringInterpolatorOpt, // Optimizes raw and s and f string interpolators by rewriting them to string concatenations or formats
new DropBreaks) :: // Optimize local Break throws by rewriting them
List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions
new DeferInlineTraits, // Defer all members in inline traits
new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_`
new InlinePatterns, // Remove placeholders of inlined patterns
new VCInlineMethods, // Inlines calls to value class methods
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -446,13 +446,13 @@ object Flags {

/** Flags representing source modifiers */
private val CommonSourceModifierFlags: FlagSet =
commonFlags(Private, Protected, Final, Case, Implicit, Given, Override, JavaStatic, Transparent, Erased)
commonFlags(Private, Protected, Final, Case, Implicit, Given, Override, JavaStatic, Transparent, Erased, Inline)

val TypeSourceModifierFlags: FlagSet =
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open

val TermSourceModifierFlags: FlagSet =
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy
CommonSourceModifierFlags.toTermFlags | AbsOverride | Lazy

/** Flags representing modifiers that can appear in trees */
val ModifierFlags: FlagSet =
Expand Down Expand Up @@ -584,6 +584,7 @@ object Flags {
val LazyGiven: FlagSet = Given | Lazy
val InlineOrProxy: FlagSet = Inline | InlineProxy // An inline method or inline argument proxy */
val InlineMethod: FlagSet = Inline | Method
val InlineTrait: FlagSet = Inline | Trait
val InlineImplicitMethod: FlagSet = Implicit | InlineMethod
val InlineParam: FlagSet = Inline | Param
val InlineByNameProxy: FlagSet = InlineProxy | Method
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,9 @@ object SymDenotations {
def isInlineMethod(using Context): Boolean =
isAllOf(InlineMethod, butNot = Accessor)

def isInlineTrait(using Context): Boolean =
isAllOf(InlineTrait)

/** Does this method or field need to be retained at runtime */
def isRetainedInline(using Context): Boolean =
is(Inline, butNot = Deferred)
Expand Down
195 changes: 195 additions & 0 deletions compiler/src/dotty/tools/dotc/inlines/InlineTraits.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package dotty.tools.dotc.inlines

import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.Trees.*
import dotty.tools.dotc.core.Constants.*
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Decorators.*
import dotty.tools.dotc.core.Flags.*
import dotty.tools.dotc.core.NameOps.*
import dotty.tools.dotc.core.Names.TypeName
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.core.Scopes.newScope
import dotty.tools.dotc.report
import dotty.tools.dotc.util.SrcPos
import dotty.tools.dotc.util.Spans.Span

object InlineTraits:
import tpd.*

def adaptNoInit(cls: ClassSymbol, parents1: List[Tree])(using Context): Unit =
if parents1.exists(parent => parent.symbol.isInlineTrait && !parent.symbol.is(NoInits)) then
cls.resetFlag(NoInits)

def needsTraitInlining(cls: ClassSymbol)(using Context): Boolean =
!cls.isInlineTrait
&& cls.info.parents.exists(parent => parent.typeSymbol.isInlineTrait)

/** Generate all inlined definitions for all inline parents of `cls`.
* New definition symbol are not entered in the class `cls`.
*/
def inlinedMemberSymbols(cls: ClassSymbol)(using Context): List[Symbol] =
assert(!cls.isInlineTrait, cls)
for
denot <- cls.typeRef.allMembers.toList
sym = denot.symbol
if isInlinableMember(sym)
yield
val traitTargs = parentTargs(cls, sym)
if sym.isClass then inlinedSymbolClassDef(cls, sym.asClass, traitTargs)
else inlinedSymbolValOrDef(cls, sym, traitTargs)
end inlinedMemberSymbols

def inlinedPrivateMemberSymbols(cls: ClassSymbol)(using Context): List[Symbol] =
assert(!cls.isInlineTrait, cls)
println(" ")
for
parent <- cls.info.parents
parentSym = parent.typeSymbol
if parentSym.isAllOf(InlineTrait)
sym <- parentSym.info.decls.toList
if sym.isTerm && sym.is(Private)
yield
val traitTargs = parentTargs(cls, sym)
inlinedSymbolPrivateValOrDef(cls, sym, traitTargs)



private def isInlinableMember(sym: Symbol)(using Context): Boolean =
(sym.isTerm || sym.isClass)
&& !sym.isConstructor && !sym.is(ParamAccessor)
&& sym.owner.isInlineTrait

private def parentTargs(cls: ClassSymbol, inlinableDecl: Symbol)(using Context): List[Type] =
val baseClass = inlinableDecl.owner.asClass
mixinParentTypeOf(cls, baseClass).baseType(baseClass) match
case AppliedType(_, targs) => targs
case _ => Nil

private def inlinedSymbolValOrDef(cls: ClassSymbol, inlinableDecl: Symbol, traitTargs: List[Type])(using Context): Symbol =
val flags = inlinableDecl.flags | Override | Synthetic
val info = inlinableDecl.info
.substThis(inlinableDecl.owner.asClass, ThisType.raw(cls.typeRef))
.subst(inlinableDecl.owner.typeParams, traitTargs)
val privateWithin = inlinableDecl.privateWithin // TODO what should `privateWithin` be?
newSymbol(cls, inlinableDecl.name, flags, info, privateWithin, cls.span)

private def inlinedSymbolPrivateValOrDef(cls: ClassSymbol, inlinableDecl: Symbol, traitTargs: List[Type])(using Context): Symbol =
val name = atPhase(ctx.phase.next) { inlinableDecl.name }
val flags = inlinableDecl.flags | Synthetic
val info = inlinableDecl.info
.substThis(inlinableDecl.owner.asClass, ThisType.raw(cls.typeRef))
.subst(inlinableDecl.owner.typeParams, traitTargs)
val privateWithin = inlinableDecl.privateWithin // TODO what should `privateWithin` be?
newSymbol(cls, name, flags, info, privateWithin, cls.span)

private def inlinedSymbolClassDef(cls: ClassSymbol, inlinableDecl: ClassSymbol, traitTargs: List[Type])(using Context): ClassSymbol =
def infoFn(cls1: ClassSymbol) =
inlinableDecl.info.asInstanceOf[ClassInfo].derivedClassInfo(
prefix = cls.typeRef,
declaredParents = defn.ObjectType :: cls.thisType.select(inlinableDecl) :: Nil,
decls = newScope,
// selfInfo = ,
)

val newCls = newClassSymbol(
owner = cls,
name = inlinableDecl.name.toTypeName,
flags = inlinableDecl.flags | Synthetic,
infoFn = infoFn,
privateWithin = NoSymbol,
coord = cls.coord
)

newConstructor(
newCls,
flags = EmptyFlags,
paramNames = Nil,
paramTypes = Nil,
privateWithin = NoSymbol,
coord = newCls.coord
).entered

for
decl <- inlinableDecl.info.decls.toList
if decl.isTerm && !decl.isConstructor && !decl.is(ParamAccessor)
do
inlinedSymbolValOrDef(newCls, decl, traitTargs).entered

newCls
end inlinedSymbolClassDef

def inlinedDefs(cls: ClassSymbol)(using Context): List[Tree] =
atPhase(ctx.phase.next) { cls.info.decls.toList }
.filter(sym => sym.is(Synthetic) && (atPhase(ctx.phase.next) { sym.nextOverriddenSymbol }.maybeOwner.isInlineTrait))
.map { sym =>
if sym.isClass then inlinedClassDefs(cls, sym.asClass)
else inlinedValOrDefDefs(cls, sym)
}

private def inlinedValOrDefDefs(cls: ClassSymbol, inlinedDecl: Symbol)(using Context): Tree =
val inlinableDecl = inlinedDecl.allOverriddenSymbols.find { sym =>
!sym.is(Deferred) && sym.owner.isInlineTrait
}.getOrElse(inlinedDecl.nextOverriddenSymbol)
val parent = mixinParentTypeOf(inlinedDecl.owner.asClass, inlinableDecl.owner.asClass).typeSymbol.name.asTypeName
valOrDefDefInlineOverride(cls, inlinedDecl, parent, inlinableDecl)

private def inlinedClassDefs(cls: ClassSymbol, inlinedDecl: ClassSymbol)(using Context): Tree =
val parent = inlinedDecl.info.parents.last.typeSymbol
val members = parent.info.decls.toList.filterNot(_.is(ParamAccessor)).zip(inlinedDecl.info.decls.toList).collect {
case (overridden, decl) if decl.isTerm && !decl.isConstructor && !decl.is(ParamAccessor) =>
assert(overridden.name == decl.name, (overridden, decl)) // TODO find better wy to recover `overridden` from `decl`
val parent = mixinParentTypeOf(decl.owner.asClass, overridden.owner.asClass).typeSymbol.name.asTypeName
valOrDefDefInlineOverride(cls, decl, parent, overridden)
}
ClassDef(
inlinedDecl.asClass,
DefDef(inlinedDecl.primaryConstructor.asTerm),
body = members,
superArgs = List.empty[Tree]
).withSpan(inlinedDecl.span)

private def valOrDefDefInlineOverride(cls: ClassSymbol, decl: Symbol, parent: TypeName, overridden: Symbol)(using Context): Tree =
def rhs(argss: List[List[Tree]])(using Context) =
if decl.is(Deferred) then EmptyTree
else if decl.is(Mutable) && decl.name.isSetterName then Literal(Constant(()))
else
ctx.compilationUnit.needsInlining = true
Super(This(ctx.owner.owner.asClass), parent).select(overridden).appliedToArgss(argss)

if decl.is(Method) then DefDef(decl.asTerm, rhs(_)(using ctx.withOwner(decl))).withSpan(cls.span)
else ValDef(decl.asTerm, rhs(Nil)(using ctx.withOwner(decl))).withSpan(cls.span)

private def mixinParentTypeOf(cls: ClassSymbol, baseClass: ClassSymbol)(using Context): Type =
cls.info.parents.findLast(parent => parent.typeSymbol.derivesFrom(baseClass)).get

/** Register inline members RHS in `@bodyAnnotation`s */
def registerInlineTraitInfo(stats: List[Tree])(using Context): Unit =
for stat <- stats do
stat match
case stat: ValOrDefDef if !stat.symbol.is(Inline) && !stat.symbol.is(Deferred) =>
// TODO? val rhsToInline = PrepareInlineable.wrapRHS(stat, stat.tpt, stat.rhs)
PrepareInlineable.registerInlineInfo(stat.symbol, stat.rhs/*TODO? rhsToInline*/)
case TypeDef(_, rhs: Template) =>
registerInlineTraitInfo(rhs.body)
case _ =>

/** Checks if members are supported in inline traits */
def checkValidInlineTraitMember(stats: List[Tree])(using Context): Unit =
for stat <- stats do
val sym = stat.symbol
stat match
case stat: ValOrDefDef =>
if sym.is(Module) then report.error(em"Implementation restriction: object cannot be defined in inline traits", stat.srcPos)
// else if sym.is(Private) then report.error(em"Implementation restriction: private ${sym.kindString} cannot be defined in inline traits", stat.srcPos)
else () // Ok
case stat: TypeDef =>
if sym.isClass && !sym.is(Trait) then report.error(em"Implementation restriction: ${sym.kindString} cannot be defined in inline traits", stat.srcPos)
else () // OK
case _: Import =>
report.error(em"Implementation restriction: import cannot be defined in inline traits", stat.srcPos)
case _: Export =>
report.error(em"Implementation restriction: export cannot be defined in inline traits", stat.srcPos)
case _ =>
report.error(em"Implementation restriction: statements cannot be added to inline traits", stat.srcPos)
24 changes: 13 additions & 11 deletions compiler/src/dotty/tools/dotc/inlines/Inlines.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,29 @@ object Inlines:
*/
private[dotc] class MissingInlineInfo extends Exception

/** `sym` is an inline method with a known body to inline.
*/
def hasBodyToInline(sym: SymDenotation)(using Context): Boolean =
sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot)

/** The body to inline for method `sym`, or `EmptyTree` if none exists.
* @pre hasBodyToInline(sym)
*/
def bodyToInline(sym: SymDenotation)(using Context): Tree =
if hasBodyToInline(sym) then
sym.getAnnotation(defn.BodyAnnot).get.tree
else
EmptyTree
sym.getAnnotation(defn.BodyAnnot).map(_.tree).getOrElse(EmptyTree)

/** Are we in an inline method body? */
def inInlineMethod(using Context): Boolean =
ctx.owner.ownersIterator.exists(_.isInlineMethod)

/** Are we in an inline method or trait body? */
def inInlineContext(using Context): Boolean =
ctx.owner.ownersIterator.exists(sym => sym.isInlineMethod || sym.isInlineTrait)

/** Can a call to method `meth` be inlined? */
def isInlineable(meth: Symbol)(using Context): Boolean =
meth.is(Inline) && meth.hasAnnotation(defn.BodyAnnot) && !inInlineMethod
def isSuperCallInInlineTraitGeneratedMethod =
meth.ownersIterator.exists(_.isInlineTrait)
&& ctx.owner.is(Synthetic)
&& ctx.owner.owner.isClass
&& ctx.owner.overriddenSymbol(meth.owner.asClass) == meth
(meth.is(Inline) || isSuperCallInInlineTraitGeneratedMethod)
&& meth.hasAnnotation(defn.BodyAnnot)
&& !inInlineContext

/** Should call be inlined in this context? */
def needsInlining(tree: Tree)(using Context): Boolean = tree match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ object PrepareInlineable {
isLocalOrParam(sym, inlineMethod) && !(sym.is(Param) && sym.owner == inlineMethod)

/** The type ascription `rhs: tpt`, unless `original` is `transparent`. */
def wrapRHS(original: untpd.DefDef, tpt: Tree, rhs: Tree)(using Context): Tree =
def wrapRHS(original: untpd.ValOrDefDef, tpt: Tree, rhs: Tree)(using Context): Tree =
if original.mods.is(Transparent) then rhs else Typed(rhs, tpt)

/** Return result of evaluating `op`, but drop `Inline` flag and `Body` annotation
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3924,7 +3924,7 @@ object Parsers {
}
}

/** TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
/** TmplDef ::= ([‘case’] ‘class’ | [‘inline’] ‘trait’) ClassDef
* | [‘case’] ‘object’ ObjectDef
* | ‘enum’ EnumDef
* | ‘given’ GivenDef
Expand Down
47 changes: 47 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/DeferInlineTraits.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package dotty.tools.dotc
package transform

import core._
import Contexts._
import DenotTransformers.SymTransformer
import Flags._
import SymDenotations._
import Symbols._
import MegaPhase.MiniPhase
import ast.tpd

class DeferInlineTraits extends MiniPhase with SymTransformer:
import tpd._
import DeferInlineTraits._

override def phaseName: String = DeferInlineTraits.name

override def description: String = DeferInlineTraits.description

override def transformSym(sym: SymDenotation)(using Context): SymDenotation =
if isEraseable(sym) then sym.copySymDenotation(initFlags = sym.flags &~ Final | Deferred)
else sym

override def transformValDef(tree: ValDef)(using Context): ValDef =
if isEraseable(tree.symbol) then cpy.ValDef(tree)(rhs = EmptyTree)
else tree

override def transformDefDef(tree: DefDef)(using Context): DefDef =
if isEraseable(tree.symbol) then cpy.DefDef(tree)(rhs = EmptyTree)
else tree

private def isEraseable(sym: SymDenotation)(using Context): Boolean =
sym.isTerm
&& !sym.isConstructor
&& !sym.is(Param)
&& !sym.is(ParamAccessor)
&& !sym.is(Private)
&& !sym.isLocalDummy
&& sym.ownersIterator.exists(_.isInlineTrait)


object DeferInlineTraits:
import tpd._

val name: String = "deferInlineTraits"
val description: String = "defer all members in inline traits"
Loading
Loading