From 9247e7239b7e68afe77292795eef825214527abc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 13 Mar 2020 18:25:46 +0100 Subject: [PATCH 1/7] Drop unused name tags Drop unused AvoidClashName name kind and name tags AVIDCLASH and DIRECT --- compiler/src/dotty/tools/dotc/core/NameKinds.scala | 1 - compiler/src/dotty/tools/dotc/core/NameOps.scala | 2 +- compiler/src/dotty/tools/dotc/core/NameTags.scala | 9 --------- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- library/src/scala/annotation/internal/Child.scala | 5 ++--- 6 files changed, 5 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 04767f86fe06..bddb9ed42a8a 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -353,7 +353,6 @@ 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 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..98ba3eed3f95 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. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index f332679b7f8e..993889760199 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -883,7 +883,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/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 From 28c49f77f572358f9453ee3a3586e72408e2aff6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 17 Mar 2020 15:21:04 +0100 Subject: [PATCH 2/7] Switch to implementation scheme described in PR The previous scheme was more complicated and looks more fragile, in particular if complex staging operations are applied to a retained inline method. In that case we have to rely on the fact that the rhs of a retained inline method is in fact its inlineable body, and not the retained body. --- .../src/dotty/tools/dotc/core/NameKinds.scala | 1 + .../tools/dotc/core/SymDenotations.scala | 5 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- .../ReflectionCompilerInterface.scala | 2 +- .../dotty/tools/dotc/transform/Erasure.scala | 68 ++++++++++++++++--- .../tools/dotc/transform/TreeChecker.scala | 3 +- .../src/dotty/tools/dotc/typer/Checking.scala | 1 - .../src/dotty/tools/dotc/typer/Inliner.scala | 34 +++++++++- .../src/dotty/tools/dotc/typer/ReTyper.scala | 2 +- .../dotty/tools/dotc/typer/RefChecks.scala | 8 +-- .../src/dotty/tools/dotc/typer/Typer.scala | 21 ++++-- docs/docs/reference/metaprogramming/inline.md | 38 +++++++++++ tasty/src/dotty/tools/tasty/TastyFormat.scala | 7 +- .../BigFloat/BigFloatFromDigitsImpl_1.scala | 16 +++++ .../BigFloat/BigFloat_1.scala | 18 +---- .../{neg => neg-macros}/BigFloat/Test_2.scala | 0 .../GenericNumLits/EvenFromDigitsImpl_1.scala | 18 +++++ tests/neg-macros/GenericNumLits/Even_1.scala | 18 +---- tests/neg-macros/quote-MacroOverride.scala | 4 +- .../GenericNumLits/EvenFromDigitsImpl_1.scala | 18 +++++ .../GenericNumLits/Even_1.scala | 18 +---- tests/neg/inline-abstract.scala | 11 +++ tests/neg/inlinevals.scala | 12 ++-- tests/neg/transparent-override/B_2.scala | 4 +- .../BigFloat/BigFloatFromDigitsImpl_1.scala | 16 +++++ tests/run-macros/BigFloat/BigFloat_1.scala | 18 +---- tests/run/inline-override.check | 5 ++ tests/run/inline-override.scala | 23 +++++++ tests/run/quote-MacroOverride.scala | 4 +- tests/run/typeclass-derivation2b.scala | 2 +- tests/run/typelevel-overrides.scala | 11 +-- 31 files changed, 294 insertions(+), 114 deletions(-) create mode 100644 tests/neg-macros/BigFloat/BigFloatFromDigitsImpl_1.scala rename tests/{neg => neg-macros}/BigFloat/BigFloat_1.scala (73%) rename tests/{neg => neg-macros}/BigFloat/Test_2.scala (100%) create mode 100644 tests/neg-macros/GenericNumLits/EvenFromDigitsImpl_1.scala create mode 100644 tests/neg-with-compiler/GenericNumLits/EvenFromDigitsImpl_1.scala create mode 100644 tests/neg/inline-abstract.scala create mode 100644 tests/run-macros/BigFloat/BigFloatFromDigitsImpl_1.scala create mode 100644 tests/run/inline-override.check create mode 100644 tests/run/inline-override.scala diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index bddb9ed42a8a..f1e0cf68f5dd 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -353,6 +353,7 @@ object NameKinds { val ProtectedAccessorName: PrefixNameKind = new PrefixNameKind(PROTECTEDACCESSOR, "protected$") val InlineAccessorName: PrefixNameKind = new PrefixNameKind(INLINEACCESSOR, "inline$") + 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/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 98ba3eed3f95..fad0ccf5a7c5 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -938,13 +938,16 @@ object SymDenotations { def isInlineMethod(implicit ctx: Context): Boolean = isAllOf(InlineMethod, butNot = Accessor) + def isInlineRetained(using Context): Boolean = + !is(Deferred) && 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 && !isInlineRetained /** ()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..a385a2c03c0e 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.isInlineMethod && oldSymbol.isInlineRetained 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,35 @@ object Erasure { outerParamDefs(constr) else Nil + 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.isInlineMethod && stat.symbol.isInlineRetained => + val rdef = retainerDef(stat.symbol) + def allParams(ddef: DefDef) = + (ddef.tparams ::: ddef.vparamss.flatten).map(_.symbol) + val fromParams = allParams(rdef) + val toParams = allParams(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 +935,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/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 993889760199..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)) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 284fe5f9cf2a..a48a791ab3cc 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,38 @@ object Inliner { ) } + /** For a retained inline method add a RetainedBody annotaton that + * records the tree for which code will be generated at runtime. This is + * the inline expansion of a call to the method itself with its + * parameters as arguments. Given an inline method + * + * inline def f[Ts](xs: Us) = body + * + * This sets up the call + * + * f[Ts'](xs') + * + * where the 'ed parameters are copies of the original ones. The call is + * then inline processed in a context which has a clone f' of f as owner. + * The cloning of owner and parameters is necessary since otherwise the + * inliner gets confused in various ways. The inlined body is then + * transformed back by replacing cloned versions of parameters with original + * and replacing the cloned owner f' with f. + */ + 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..8425f234af1d 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.isInlineRetained 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..b7c40d620930 100644 --- a/docs/docs/reference/metaprogramming/inline.md +++ b/docs/docs/reference/metaprogramming/inline.md @@ -142,6 +142,44 @@ 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/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 From e5bdd2e0ec708e05a640824f0aa6dc6b9ca85006 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 17 Mar 2020 16:50:31 +0100 Subject: [PATCH 3/7] Add doc comments for two key methods --- .../dotty/tools/dotc/transform/Erasure.scala | 17 ++++++++++++++++ .../src/dotty/tools/dotc/typer/Inliner.scala | 20 +++++++------------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index a385a2c03c0e..0aed3c7a94e3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -892,6 +892,23 @@ 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) => diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index a48a791ab3cc..67b568ab6033 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -124,23 +124,17 @@ object Inliner { ) } - /** For a retained inline method add a RetainedBody annotaton that - * records the tree for which code will be generated at runtime. This is - * the inline expansion of a call to the method itself with its - * parameters as arguments. Given an inline method + /** For a retained inline method, another method that keeps track of + * the body that is kept at runtime. For instance, an inline method * - * inline def f[Ts](xs: Us) = body + * inline override def f(x: T) = b * - * This sets up the call + * is complemented by the body retainer method * - * f[Ts'](xs') + * private def f$retainedBody(x: T) = f(x) * - * where the 'ed parameters are copies of the original ones. The call is - * then inline processed in a context which has a clone f' of f as owner. - * The cloning of owner and parameters is necessary since otherwise the - * inliner gets confused in various ways. The inlined body is then - * transformed back by replacing cloned versions of parameters with original - * and replacing the cloned owner f' with f. + * 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 From e6f5df63488b161e5f5c0a5206e26594ab13dc2e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 17 Mar 2020 16:55:04 +0100 Subject: [PATCH 4/7] Refactor: Move allParamSyms to TreeInfo --- compiler/src/dotty/tools/dotc/ast/TreeInfo.scala | 10 +++------- compiler/src/dotty/tools/dotc/transform/Erasure.scala | 6 ++---- .../dotty/tools/dotc/transform/HoistSuperArgs.scala | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) 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/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 0aed3c7a94e3..1f436a46ea69 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -924,10 +924,8 @@ object Erasure { stats.mapConserve { case stat: DefDef if stat.symbol.isInlineMethod && stat.symbol.isInlineRetained => val rdef = retainerDef(stat.symbol) - def allParams(ddef: DefDef) = - (ddef.tparams ::: ddef.vparamss.flatten).map(_.symbol) - val fromParams = allParams(rdef) - val toParams = allParams(stat) + val fromParams = untpd.allParamSyms(rdef) + val toParams = untpd.allParamSyms(stat) assert(fromParams.hasSameLengthAs(toParams)) val mapBody = TreeTypeMap( oldOwners = rdef.symbol :: Nil, 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 { From 051039e32f13120c7aef733c50366f201959df10 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 17 Mar 2020 17:30:04 +0100 Subject: [PATCH 5/7] Make concrete inline methods effectively final --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 4 +++- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 4 ++-- docs/docs/reference/metaprogramming/inline.md | 5 +++-- tests/neg-macros/quote-MacroOverride.scala | 4 +++- tests/run/quote-MacroOverride.scala | 7 +++---- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index fad0ccf5a7c5..27749c85913f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1094,7 +1094,9 @@ object SymDenotations { /** A symbol is effectively final if it cannot be overridden in a subclass */ final def isEffectivelyFinal(implicit ctx: Context): Boolean = - isOneOf(EffectivelyFinalFlags) || !owner.isExtensibleClass + isOneOf(EffectivelyFinalFlags) + || is(Inline, butNot = Deferred) + || !owner.isExtensibleClass /** A class is effectively sealed if has the `final` or `sealed` modifier, or it * is defined in Scala 3 and is neither abstract nor open. diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index c5a476df1328..0784dc34c06b 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -157,7 +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 O is inline, M must be inline + * 1.10 If O is inline (and deferred, otherwise O would be final), 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,7 +398,7 @@ object RefChecks { else if (other.isAllOf(ExtensionMethod) && !member.isAllOf(ExtensionMethod)) // (1.9.2) overrideError("is a normal method, cannot override an extension method") else if other.isInlineMethod && !member.isInlineMethod then // (1.10) - overrideError("is not inline, cannot override an inline method") + overrideError("is not inline, cannot implement 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/docs/docs/reference/metaprogramming/inline.md b/docs/docs/reference/metaprogramming/inline.md index b7c40d620930..1538344f3e93 100644 --- a/docs/docs/reference/metaprogramming/inline.md +++ b/docs/docs/reference/metaprogramming/inline.md @@ -144,7 +144,7 @@ funkyAssertEquals(computeActual(), computeExpected(), computeDelta()) ``` ### Rules for Overriding -Inline methods can override other methods and can themselves be overridden by other inline methods. The rules are as follows: +Inline methods can override other methods. Abstract inline methods can themselves be implemented 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 @@ -167,7 +167,8 @@ 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. +2. Inline methods can override or implement normal methods, as the previous example shows. Concrete inline methods are effectively final; they cannot be +overriden. 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 diff --git a/tests/neg-macros/quote-MacroOverride.scala b/tests/neg-macros/quote-MacroOverride.scala index 89c72b2e5007..32807360bf1f 100644 --- a/tests/neg-macros/quote-MacroOverride.scala +++ b/tests/neg-macros/quote-MacroOverride.scala @@ -3,11 +3,13 @@ object Test { abstract class A { def f(): Unit inline def g(): Unit = () + inline def h(): Unit } object B extends A { override inline def f() = () - override def g() = () // error: is not inline, cannot override an inline method + override def g() = () // error: cannot override final member + def h() = () // error: is not inline, cannot implement an inline method } } diff --git a/tests/run/quote-MacroOverride.scala b/tests/run/quote-MacroOverride.scala index 70449a91343a..5a88375d778c 100644 --- a/tests/run/quote-MacroOverride.scala +++ b/tests/run/quote-MacroOverride.scala @@ -1,8 +1,8 @@ object Test { abstract class A { - inline def f1(): String = "A.f1" - inline def f2(): String = "A.f2" + inline def f1(): String + def f2(): String def f3(): String = "A.f3" } @@ -14,8 +14,7 @@ object Test { def main(args: Array[String]): Unit = { val a: A = B - assert(a.f1() == "A.f1") - assert(a.f2() == "A.f2") + assert(a.f2() == "B.f2") assert(a.f3() == "B.f3") val b: B.type = B From 42dc0e229c6b472cd64fa9aa384c1e0cf4eac0cc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 17 Mar 2020 22:19:20 +0100 Subject: [PATCH 6/7] Revert "Make concrete inline methods effectively final" This reverts commit 051039e32f13120c7aef733c50366f201959df10. --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 4 +--- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 4 ++-- docs/docs/reference/metaprogramming/inline.md | 5 ++--- tests/neg-macros/quote-MacroOverride.scala | 4 +--- tests/run/quote-MacroOverride.scala | 7 ++++--- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 27749c85913f..fad0ccf5a7c5 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1094,9 +1094,7 @@ object SymDenotations { /** A symbol is effectively final if it cannot be overridden in a subclass */ final def isEffectivelyFinal(implicit ctx: Context): Boolean = - isOneOf(EffectivelyFinalFlags) - || is(Inline, butNot = Deferred) - || !owner.isExtensibleClass + isOneOf(EffectivelyFinalFlags) || !owner.isExtensibleClass /** A class is effectively sealed if has the `final` or `sealed` modifier, or it * is defined in Scala 3 and is neither abstract nor open. diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 0784dc34c06b..c5a476df1328 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -157,7 +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 O is inline (and deferred, otherwise O would be final), M must be inline + * 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,7 +398,7 @@ object RefChecks { else if (other.isAllOf(ExtensionMethod) && !member.isAllOf(ExtensionMethod)) // (1.9.2) overrideError("is a normal method, cannot override an extension method") else if other.isInlineMethod && !member.isInlineMethod then // (1.10) - overrideError("is not inline, cannot implement an inline method") + 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/docs/docs/reference/metaprogramming/inline.md b/docs/docs/reference/metaprogramming/inline.md index 1538344f3e93..b7c40d620930 100644 --- a/docs/docs/reference/metaprogramming/inline.md +++ b/docs/docs/reference/metaprogramming/inline.md @@ -144,7 +144,7 @@ funkyAssertEquals(computeActual(), computeExpected(), computeDelta()) ``` ### Rules for Overriding -Inline methods can override other methods. Abstract inline methods can themselves be implemented by other inline methods. The rules are as follows: +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 @@ -167,8 +167,7 @@ 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. Concrete inline methods are effectively final; they cannot be -overriden. +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 diff --git a/tests/neg-macros/quote-MacroOverride.scala b/tests/neg-macros/quote-MacroOverride.scala index 32807360bf1f..89c72b2e5007 100644 --- a/tests/neg-macros/quote-MacroOverride.scala +++ b/tests/neg-macros/quote-MacroOverride.scala @@ -3,13 +3,11 @@ object Test { abstract class A { def f(): Unit inline def g(): Unit = () - inline def h(): Unit } object B extends A { override inline def f() = () - override def g() = () // error: cannot override final member - def h() = () // error: is not inline, cannot implement an inline method + override def g() = () // error: is not inline, cannot override an inline method } } diff --git a/tests/run/quote-MacroOverride.scala b/tests/run/quote-MacroOverride.scala index 5a88375d778c..70449a91343a 100644 --- a/tests/run/quote-MacroOverride.scala +++ b/tests/run/quote-MacroOverride.scala @@ -1,8 +1,8 @@ object Test { abstract class A { - inline def f1(): String - def f2(): String + inline def f1(): String = "A.f1" + inline def f2(): String = "A.f2" def f3(): String = "A.f3" } @@ -14,7 +14,8 @@ object Test { def main(args: Array[String]): Unit = { val a: A = B - assert(a.f2() == "B.f2") + assert(a.f1() == "A.f1") + assert(a.f2() == "A.f2") assert(a.f3() == "B.f3") val b: B.type = B From 453319bb133c74f49dfa2ec5752f0ac783a5dcb6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 Mar 2020 10:37:21 +0100 Subject: [PATCH 7/7] Address review suggestions --- compiler/src/dotty/tools/dotc/core/Flags.scala | 1 + compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 7 ++++--- compiler/src/dotty/tools/dotc/transform/Erasure.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- docs/docs/reference/metaprogramming/inline.md | 5 +++-- 5 files changed, 11 insertions(+), 8 deletions(-) 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/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index fad0ccf5a7c5..53f6101f3aff 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -938,8 +938,9 @@ object SymDenotations { def isInlineMethod(implicit ctx: Context): Boolean = isAllOf(InlineMethod, butNot = Accessor) - def isInlineRetained(using Context): Boolean = - !is(Deferred) && allOverriddenSymbols.exists(!_.is(Inline)) + 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) @@ -947,7 +948,7 @@ object SymDenotations { /** An erased value or an inline method. */ def isEffectivelyErased(implicit ctx: Context): Boolean = - is(Erased) || isInlineMethod && !isInlineRetained + 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/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 1f436a46ea69..29434a210e8d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -85,7 +85,7 @@ class Erasure extends Phase with DenotTransformer { else oldFlags &~ Flags.HasDefaultParamsFlags // HasDefaultParamsFlags needs to be dropped because overriding might become overloading val oldAnnotations = ref.annotations var newAnnotations = oldAnnotations - if oldSymbol.isInlineMethod && oldSymbol.isInlineRetained then + if oldSymbol.isRetainedInlineMethod then newFlags = newFlags &~ Flags.Inline newAnnotations = newAnnotations.filterConserve(!_.isInstanceOf[BodyAnnotation]) // TODO: define derivedSymDenotation? @@ -922,7 +922,7 @@ object Erasure { (inlineMeth, stat) }.toMap stats.mapConserve { - case stat: DefDef if stat.symbol.isInlineMethod && stat.symbol.isInlineRetained => + case stat: DefDef if stat.symbol.isRetainedInlineMethod => val rdef = retainerDef(stat.symbol) val fromParams = untpd.allParamSyms(rdef) val toParams = untpd.allParamSyms(stat) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 8425f234af1d..cdea08e3889e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2421,7 +2421,7 @@ class Typer extends Namer */ protected def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] = tpd.cpy.DefDef(mdef)(rhs = Inliner.bodyToInline(mdef.symbol)) - :: (if mdef.symbol.isInlineRetained then Inliner.bodyRetainer(mdef) :: Nil else Nil) + :: (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) diff --git a/docs/docs/reference/metaprogramming/inline.md b/docs/docs/reference/metaprogramming/inline.md index b7c40d620930..b47d21bbe0cb 100644 --- a/docs/docs/reference/metaprogramming/inline.md +++ b/docs/docs/reference/metaprogramming/inline.md @@ -177,8 +177,9 @@ abstract class A { object B extends A { inline def f(): Int = 22 } -B.f() // OK -val a: A = B; a.f() // error: cannot inline f() in A. +B.f() // OK +val a: A = B +a.f() // error: cannot inline f() in A. ``` ### Relationship to @inline