diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 7eec52abf285..d9b385b00469 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -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 : _* ? */ def isWildcardStarArgList(trees: List[Tree])(implicit ctx: Context): Boolean = diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 73cfd29bd237..50002e5e2fea 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 04767f86fe06..f1e0cf68f5dd 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index b2ac2f6429eb..2faf8d28f404 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -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. diff --git a/compiler/src/dotty/tools/dotc/core/NameTags.scala b/compiler/src/dotty/tools/dotc/core/NameTags.scala index d58ec050cab2..299cba7b6948 100644 --- a/compiler/src/dotty/tools/dotc/core/NameTags.scala +++ b/compiler/src/dotty/tools/dotc/core/NameTags.scala @@ -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 @@ -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" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 394d880ab13b..53f6101f3aff 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -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. */ @@ -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, diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index e4f2772ed960..42dec06f89c2 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala index 98b16a2d55fb..2bc76ff073c5 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index a2c71f74187c..29434a210e8d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -12,10 +12,11 @@ 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._ @@ -23,6 +24,7 @@ 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._ @@ -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( @@ -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) @@ -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 + * + * 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] @@ -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) } diff --git a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala index d72f0895ca49..3d13ab89211b 100644 --- a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala +++ b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 3bd6c91b4068..ac48a6bb49eb 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index f332679b7f8e..c8527132915e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -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)) @@ -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) && diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 284fe5f9cf2a..67b568ab6033 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -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 @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 431d1699ee41..b12bbe101a70 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index c22eca3996c5..c5a476df1328 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -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 * 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 @@ -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)) && diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0968c7881317..cdea08e3889e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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) => @@ -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) @@ -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 diff --git a/docs/docs/reference/metaprogramming/inline.md b/docs/docs/reference/metaprogramming/inline.md index 055265a1d482..b47d21bbe0cb 100644 --- a/docs/docs/reference/metaprogramming/inline.md +++ b/docs/docs/reference/metaprogramming/inline.md @@ -142,6 +142,45 @@ funkyAssertEquals(computeActual(), computeExpected(), computeDelta()) // if (actual - expected).abs > computeDelta() then // throw new AssertionError(s"difference between ${expected} and ${actual} was larger than ${computeDelta()}") ``` +### Rules for Overriding + +Inline methods can override other methods and can themselves be overridden by other inline methods. The rules are as follows: + +1. If an inline method `f` implements or overrides another, non-inline method, the inline method can also be invoked at runtime. For instance, consider the scenario: +```scala +abstract class A { + def f(): Int + def g(): Int = f() +} +class B extends A { + inline def f() = 22 + override inline def g() = f() + 11 +} +val b = B() +val a: A = b +// inlined invocatons +assert(b.f() == 22) +assert(b.g() == 33) +// dynamic invocations +assert(a.f() == 22) +assert(a.g() == 33) +``` +The inlined invocations and the dynamically dispatched invocations give the same results. + +2. Inline methods can override or implement normal methods, as the previous example shows. Inline methods can be overridden only by other inline methods. + +3. Inline methods can also be abstract. An abstract inline method can be implemented only by other inline methods. It cannot be invoked directly: +```scala +abstract class A { + inline def f(): Int +} +object B extends A { + inline def f(): Int = 22 +} +B.f() // OK +val a: A = B +a.f() // error: cannot inline f() in A. +``` ### Relationship to @inline diff --git a/library/src/scala/annotation/internal/Child.scala b/library/src/scala/annotation/internal/Child.scala index 78a6be5d2b36..b0d380e2460d 100644 --- a/library/src/scala/annotation/internal/Child.scala +++ b/library/src/scala/annotation/internal/Child.scala @@ -1,6 +1,5 @@ -package scala.annotation.internal - -import scala.annotation.Annotation +package scala.annotation +package internal /** An annotation to indicate a child class or object of the annotated class. * E.g. if we have diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 379606d26588..ae40e14022e5 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -59,8 +59,8 @@ Standard-Section: "ASTs" TopLevelStat* TYPEDEF Length NameRef (type_Term | Template) Modifier* -- modifiers type name (= type | bounds) | moifiers class name template IMPORT Length qual_Term Selector* -- import qual selectors ValOrDefDef = VALDEF Length NameRef type_Term rhs_Term? Modifier* -- modifiers val name : type (= rhs)? - DEFDEF Length NameRef TypeParam* Params* returnType_Term rhs_Term? - Modifier* -- modifiers def name [typeparams] paramss : returnType (= rhs)? + DEFDEF Length NameRef TypeParam* Params* returnType_Term + rhs_Term? Modifier* -- modifiers def name [typeparams] paramss : returnType (= rhs)? Selector = IMPORTED name_NameRef -- name, "_" for normal wildcards, "" for given wildcards RENAMED to_NameRef -- => name BOUNDED type_Term -- type bound @@ -279,6 +279,9 @@ object TastyFormat { final val INLINEACCESSOR = 21 // The name of an inline accessor `inline$name` + final val BODYRETAINER = 22 // The name of a synthetic method that retains the runtime + // body of an inline method + final val OBJECTCLASS = 23 // The name of an object class (or: module class) `$`. final val SIGNED = 63 // A pair of a name and a signature, used to identify diff --git a/tests/neg-macros/BigFloat/BigFloatFromDigitsImpl_1.scala b/tests/neg-macros/BigFloat/BigFloatFromDigitsImpl_1.scala new file mode 100644 index 000000000000..839e556a7c2f --- /dev/null +++ b/tests/neg-macros/BigFloat/BigFloatFromDigitsImpl_1.scala @@ -0,0 +1,16 @@ +package test +import scala.util.FromDigits +import scala.quoted._ + +object BigFloatFromDigitsImpl: + def apply(digits: Expr[String])(using ctx: QuoteContext): Expr[BigFloat] = + digits match + case Const(ds) => + try + val BigFloat(m, e) = BigFloat(ds) + '{BigFloat(${Expr(m)}, ${Expr(e)})} + catch case ex: FromDigits.FromDigitsException => + ctx.error(ex.getMessage) + '{BigFloat(0, 0)} + case digits => + '{BigFloat($digits)} diff --git a/tests/neg/BigFloat/BigFloat_1.scala b/tests/neg-macros/BigFloat/BigFloat_1.scala similarity index 73% rename from tests/neg/BigFloat/BigFloat_1.scala rename to tests/neg-macros/BigFloat/BigFloat_1.scala index aae96aa5e3f9..61308fa93c45 100644 --- a/tests/neg/BigFloat/BigFloat_1.scala +++ b/tests/neg-macros/BigFloat/BigFloat_1.scala @@ -30,29 +30,13 @@ object BigFloat extends App { BigFloat(BigInt(intPart), exponent) } - private def fromDigitsImpl(digits: Expr[String])(using ctx: QuoteContext): Expr[BigFloat] = - digits match { - case Const(ds) => - try { - val BigFloat(m, e) = apply(ds) - '{BigFloat(${Expr(m)}, ${Expr(e)})} - } - catch { - case ex: FromDigits.FromDigitsException => - ctx.error(ex.getMessage) - '{BigFloat(0, 0)} - } - case digits => - '{apply($digits)} - } - class BigFloatFromDigits extends FromDigits.Floating[BigFloat] { def fromDigits(digits: String) = apply(digits) } given BigFloatFromDigits { override inline def fromDigits(digits: String) = ${ - fromDigitsImpl('digits) + BigFloatFromDigitsImpl('digits) } } diff --git a/tests/neg/BigFloat/Test_2.scala b/tests/neg-macros/BigFloat/Test_2.scala similarity index 100% rename from tests/neg/BigFloat/Test_2.scala rename to tests/neg-macros/BigFloat/Test_2.scala diff --git a/tests/neg-macros/GenericNumLits/EvenFromDigitsImpl_1.scala b/tests/neg-macros/GenericNumLits/EvenFromDigitsImpl_1.scala new file mode 100644 index 000000000000..6733abc99a62 --- /dev/null +++ b/tests/neg-macros/GenericNumLits/EvenFromDigitsImpl_1.scala @@ -0,0 +1,18 @@ +import scala.util.FromDigits +import scala.quoted._ +import Even._ + +object EvenFromDigitsImpl: + def apply(digits: Expr[String])(using ctx: QuoteContext): Expr[Even] = digits match { + case Const(ds) => + val ev = + try evenFromDigits(ds) + catch { + case ex: FromDigits.FromDigitsException => + ctx.error(ex.getMessage) + Even(0) + } + '{Even(${Expr(ev.n)})} + case _ => + '{evenFromDigits($digits)} + } diff --git a/tests/neg-macros/GenericNumLits/Even_1.scala b/tests/neg-macros/GenericNumLits/Even_1.scala index fcb3288dd71e..37da76205719 100644 --- a/tests/neg-macros/GenericNumLits/Even_1.scala +++ b/tests/neg-macros/GenericNumLits/Even_1.scala @@ -5,33 +5,19 @@ import scala.quoted._ case class Even(n: Int) object Even { - private def evenFromDigits(digits: String): Even = { + def evenFromDigits(digits: String): Even = { val intValue = FromDigits.intFromDigits(digits) if (intValue % 2 == 0) Even(intValue) else throw FromDigits.MalformedNumber(s"$digits is odd") } - private def evenFromDigitsImpl(digits: Expr[String])(using ctx: QuoteContext): Expr[Even] = digits match { - case Const(ds) => - val ev = - try evenFromDigits(ds) - catch { - case ex: FromDigits.FromDigitsException => - ctx.error(ex.getMessage) - Even(0) - } - '{Even(${Expr(ev.n)})} - case _ => - '{evenFromDigits($digits)} - } - class EvenFromDigits extends FromDigits[Even] { def fromDigits(digits: String) = evenFromDigits(digits) } given EvenFromDigits { override inline def fromDigits(digits: String) = ${ - evenFromDigitsImpl('digits) + EvenFromDigitsImpl('digits) } } } diff --git a/tests/neg-macros/quote-MacroOverride.scala b/tests/neg-macros/quote-MacroOverride.scala index e6d19fcf8cc8..89c72b2e5007 100644 --- a/tests/neg-macros/quote-MacroOverride.scala +++ b/tests/neg-macros/quote-MacroOverride.scala @@ -6,8 +6,8 @@ object Test { } object B extends A { - override inline def f() = () // error: method f of type (): Unit is an inline method, must override at least one concrete method - override def g() = () + override inline def f() = () + override def g() = () // error: is not inline, cannot override an inline method } } diff --git a/tests/neg-with-compiler/GenericNumLits/EvenFromDigitsImpl_1.scala b/tests/neg-with-compiler/GenericNumLits/EvenFromDigitsImpl_1.scala new file mode 100644 index 000000000000..6733abc99a62 --- /dev/null +++ b/tests/neg-with-compiler/GenericNumLits/EvenFromDigitsImpl_1.scala @@ -0,0 +1,18 @@ +import scala.util.FromDigits +import scala.quoted._ +import Even._ + +object EvenFromDigitsImpl: + def apply(digits: Expr[String])(using ctx: QuoteContext): Expr[Even] = digits match { + case Const(ds) => + val ev = + try evenFromDigits(ds) + catch { + case ex: FromDigits.FromDigitsException => + ctx.error(ex.getMessage) + Even(0) + } + '{Even(${Expr(ev.n)})} + case _ => + '{evenFromDigits($digits)} + } diff --git a/tests/neg-with-compiler/GenericNumLits/Even_1.scala b/tests/neg-with-compiler/GenericNumLits/Even_1.scala index fcb3288dd71e..37da76205719 100644 --- a/tests/neg-with-compiler/GenericNumLits/Even_1.scala +++ b/tests/neg-with-compiler/GenericNumLits/Even_1.scala @@ -5,33 +5,19 @@ import scala.quoted._ case class Even(n: Int) object Even { - private def evenFromDigits(digits: String): Even = { + def evenFromDigits(digits: String): Even = { val intValue = FromDigits.intFromDigits(digits) if (intValue % 2 == 0) Even(intValue) else throw FromDigits.MalformedNumber(s"$digits is odd") } - private def evenFromDigitsImpl(digits: Expr[String])(using ctx: QuoteContext): Expr[Even] = digits match { - case Const(ds) => - val ev = - try evenFromDigits(ds) - catch { - case ex: FromDigits.FromDigitsException => - ctx.error(ex.getMessage) - Even(0) - } - '{Even(${Expr(ev.n)})} - case _ => - '{evenFromDigits($digits)} - } - class EvenFromDigits extends FromDigits[Even] { def fromDigits(digits: String) = evenFromDigits(digits) } given EvenFromDigits { override inline def fromDigits(digits: String) = ${ - evenFromDigitsImpl('digits) + EvenFromDigitsImpl('digits) } } } diff --git a/tests/neg/inline-abstract.scala b/tests/neg/inline-abstract.scala new file mode 100644 index 000000000000..2f4596f38ea6 --- /dev/null +++ b/tests/neg/inline-abstract.scala @@ -0,0 +1,11 @@ +class A: + inline def f(): Int + +class B extends A: + inline def f() = 1 + +def Test = + val b = B() + println(b.f()) // ok + val a: A = b + println(a.f()) // error: Deferred inline method f in class A cannot be invoked diff --git a/tests/neg/inlinevals.scala b/tests/neg/inlinevals.scala index eeac86c525d3..d3bd16df8ac1 100644 --- a/tests/neg/inlinevals.scala +++ b/tests/neg/inlinevals.scala @@ -1,6 +1,6 @@ object Test { - def power0(x: Double, inline n: Int): Double = ??? // error + def power0(x: Double, inline n: Int): Double = ??? // error: inline modifier can only be used for parameters of inline methods inline def power(x: Double, inline n: Int): Double = // ok inline if n == 0 then ??? else ??? @@ -10,13 +10,13 @@ object Test { inline inline val twice = 30 // error: repeated modifier - class C(inline x: Int, private inline val y: Int) { // error // error - inline val foo: Int // error: abstract member may not be inline - inline def bar: Int // error: abstract member may not be inline + class C(inline x: Int, private inline val y: Int) { // error // error inline modifier can only be used for parameters of inline methods (both) + inline val foo: Int + inline def bar: Int } power(2.0, N) // ok, since it's a by-name parameter - power(2.0, X) // error: argument to inline parameter must be a constant expression + power(2.0, X) // error: cannot reduce inline if inline val M = X // error: rhs must be constant expression @@ -24,7 +24,7 @@ object Test { inline def foo(x: Int) = { - def f(inline xs: List[Int]) = xs // error + def f(inline xs: List[Int]) = xs // error: inline modifier can only be used for parameters of inline methods inline val y = { println("hi"); 1 } // ok inline val z = x // ok diff --git a/tests/neg/transparent-override/B_2.scala b/tests/neg/transparent-override/B_2.scala index 5c83c7f5e75b..0fb58fd853f1 100644 --- a/tests/neg/transparent-override/B_2.scala +++ b/tests/neg/transparent-override/B_2.scala @@ -1,9 +1,9 @@ class B extends A { - inline def f(x: Int): Int = inline x match { // error + inline def f(x: Int): Int = inline x match { // OK case 0 => 1 case _ => x } - def g(x: Int): Int = 1 // error + override def g(x: Int): Int = 1 // error: is not inline, cannot override an inline methiod } diff --git a/tests/run-macros/BigFloat/BigFloatFromDigitsImpl_1.scala b/tests/run-macros/BigFloat/BigFloatFromDigitsImpl_1.scala new file mode 100644 index 000000000000..839e556a7c2f --- /dev/null +++ b/tests/run-macros/BigFloat/BigFloatFromDigitsImpl_1.scala @@ -0,0 +1,16 @@ +package test +import scala.util.FromDigits +import scala.quoted._ + +object BigFloatFromDigitsImpl: + def apply(digits: Expr[String])(using ctx: QuoteContext): Expr[BigFloat] = + digits match + case Const(ds) => + try + val BigFloat(m, e) = BigFloat(ds) + '{BigFloat(${Expr(m)}, ${Expr(e)})} + catch case ex: FromDigits.FromDigitsException => + ctx.error(ex.getMessage) + '{BigFloat(0, 0)} + case digits => + '{BigFloat($digits)} diff --git a/tests/run-macros/BigFloat/BigFloat_1.scala b/tests/run-macros/BigFloat/BigFloat_1.scala index aae96aa5e3f9..61308fa93c45 100644 --- a/tests/run-macros/BigFloat/BigFloat_1.scala +++ b/tests/run-macros/BigFloat/BigFloat_1.scala @@ -30,29 +30,13 @@ object BigFloat extends App { BigFloat(BigInt(intPart), exponent) } - private def fromDigitsImpl(digits: Expr[String])(using ctx: QuoteContext): Expr[BigFloat] = - digits match { - case Const(ds) => - try { - val BigFloat(m, e) = apply(ds) - '{BigFloat(${Expr(m)}, ${Expr(e)})} - } - catch { - case ex: FromDigits.FromDigitsException => - ctx.error(ex.getMessage) - '{BigFloat(0, 0)} - } - case digits => - '{apply($digits)} - } - class BigFloatFromDigits extends FromDigits.Floating[BigFloat] { def fromDigits(digits: String) = apply(digits) } given BigFloatFromDigits { override inline def fromDigits(digits: String) = ${ - fromDigitsImpl('digits) + BigFloatFromDigitsImpl('digits) } } diff --git a/tests/run/inline-override.check b/tests/run/inline-override.check new file mode 100644 index 000000000000..60d76e340238 --- /dev/null +++ b/tests/run/inline-override.check @@ -0,0 +1,5 @@ +inline 22 +inline 22 +inline 22 +inline 22 +inline 22 diff --git a/tests/run/inline-override.scala b/tests/run/inline-override.scala new file mode 100644 index 000000000000..e62405f3f687 --- /dev/null +++ b/tests/run/inline-override.scala @@ -0,0 +1,23 @@ +abstract class A: + def f(x: Int) = s"dynamic $x" + def h(x: Int): String + inline def i(x: Int): String + +class B extends A: + inline override def f(x: Int) = g(x) + inline def g(x: Int) = s"inline $x" + inline def h(x: Int) = g(x) + inline def i(x: Int) = g(x) + +@main def Test = + val b = B() + println(b.f(22)) + println(b.h(22)) + println(b.i(22)) + val a: A = b + println(a.f(22)) + println(a.h(22)) +// println(a.i(22)) + + + diff --git a/tests/run/quote-MacroOverride.scala b/tests/run/quote-MacroOverride.scala index b2d7b732069b..70449a91343a 100644 --- a/tests/run/quote-MacroOverride.scala +++ b/tests/run/quote-MacroOverride.scala @@ -7,7 +7,7 @@ object Test { } object B extends A { - override def f1(): String = "B.f1" + override inline def f1(): String = "B.f1" override inline def f2(): String = "B.f2" override inline def f3(): String = "B.f3" } @@ -16,7 +16,7 @@ object Test { val a: A = B assert(a.f1() == "A.f1") assert(a.f2() == "A.f2") - assert(a.f3() == "A.f3") + assert(a.f3() == "B.f3") val b: B.type = B assert(b.f1() == "B.f1") diff --git a/tests/run/typeclass-derivation2b.scala b/tests/run/typeclass-derivation2b.scala index a2f96ecf206b..3ea06ed4a751 100644 --- a/tests/run/typeclass-derivation2b.scala +++ b/tests/run/typeclass-derivation2b.scala @@ -26,7 +26,7 @@ object TypeLevel { abstract class GenericSum[S] extends Generic[S] { def ordinal(x: S): Int - def alternative(n: Int): GenericProduct[_ <: S] = ??? + inline def alternative(n: Int): GenericProduct[_ <: S] } abstract class GenericProduct[P] extends Generic[P] { diff --git a/tests/run/typelevel-overrides.scala b/tests/run/typelevel-overrides.scala index c8f5f3b5f9df..e894749633b3 100644 --- a/tests/run/typelevel-overrides.scala +++ b/tests/run/typelevel-overrides.scala @@ -10,20 +10,23 @@ class A extends T { class B extends A { override inline def f(x: Int) = inline x match { case 0 => 0 - case x => x + case x => x + 2 } } class C extends A with U { override inline def f(x: Int) = inline x match { case 0 => 0 - case x => x + case x => x + 2 } } object Test extends App { val a: A = new B - assert(a.f(0) == 0) + assert(a.f(0) == 2, a.f(0)) + assert(a.f(1) == 3) val b: B = new B assert(b.f(0) == 0) + assert(b.f(1) == 3) val c: A = new C - assert(c.f(0) == 1, c.f(0)) + assert(c.f(0) == 2, c.f(0)) + assert(c.f(1) == 3, c.f(1)) } \ No newline at end of file