diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 85c436a29c2f..f8f9e76567e8 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -350,6 +350,9 @@ object Flags { /** An opaque type alias or a class containing one */ val (Opaque @ _, _, _) = newFlags(43, "opaque") + /** An transparent inline method */ + val (_, Transparent @ _, _) = newFlags(44, "transparent") + // ------------ Flags following this one are not pickled ---------------------------------- @@ -426,7 +429,7 @@ object Flags { CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open val TermSourceModifierFlags: FlagSet = - CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Erased + CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Erased | Transparent /** Flags representing modifiers that can appear in trees */ val ModifierFlags: FlagSet = @@ -443,7 +446,7 @@ object Flags { Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic, OuterOrCovariant, LabelOrContravariant, CaseAccessor, Extension, NonMember, Implicit, Given, Permanent, Synthetic, - SuperParamAliasOrScala2x, Inline, Macro) + SuperParamAliasOrScala2x, Inline, Macro, Transparent) /** Flags that are not (re)set when completing the denotation, or, if symbol is * a top-level class or object, when completing the denotation once the class diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 470ce6b13822..db71e61a151b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -699,6 +699,7 @@ class TreePickler(pickler: TastyPickler) { if (flags.is(ParamAccessor)) writeModTag(PARAMsetter) if (flags.is(SuperParamAlias)) writeModTag(PARAMalias) if (flags.is(Exported)) writeModTag(EXPORTED) + if (flags.is(Transparent)) writeModTag(TRANSPARENT) assert(!(flags.is(Label))) } else { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index c4efc746de31..2871e321ecc3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -635,6 +635,7 @@ class TreeUnpickler(reader: TastyReader, case INLINE => addFlag(Inline) case INLINEPROXY => addFlag(InlineProxy) case MACRO => addFlag(Macro) + case TRANSPARENT => addFlag(Transparent) case OPAQUE => addFlag(Opaque) case STATIC => addFlag(JavaStatic) case OBJECT => addFlag(Module) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 7baf74ec90c9..72366b905d9c 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -195,8 +195,7 @@ class ReifyQuotes extends MacroTransform { case splice: Select => cpy.Select(splice)(body1, splice.name) } } - else { - assert(level == 1, "unexpected top splice outside quote") + else if (level == 1) { val (body1, quotes) = nested(isQuote = false).splitSplice(body)(spliceContext) val tpe = outer.embedded.getHoleType(body, splice) val hole = makeHole(splice.isTerm, body1, quotes, tpe).withSpan(splice.span) @@ -206,6 +205,10 @@ class ReifyQuotes extends MacroTransform { // For example we can have a lifted tree containing the LHS of an assignment (see tests/run-with-compiler/quote-var.scala). if (splice.isType || outer.embedded.isLiftedSymbol(body.symbol)) hole else Inlined(EmptyTree, Nil, hole).withSpan(splice.span) + } else { + assert(level == 0, "unexpected splice insiced top splice") + val newBody = Inliner.expandMacro(body, splice.span) + transform(newBody) } /** Transforms the contents of a nested splice diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d0d03532b977..ff8215312f6c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -325,6 +325,27 @@ object Inliner { val errors = compileForErrors(tree, false) packErrors(errors) } + + def expandMacro(body: Tree, span: Span)(implicit ctx: Context) = { + assert(level == 0) + val inlinedFrom = enclosingInlineds.last + val ctx1 = tastyreflect.MacroExpansion.context(inlinedFrom) + + val evaluatedSplice = inContext(tastyreflect.MacroExpansion.context(inlinedFrom)) { + Splicer.splice(body, inlinedFrom.sourcePos, MacroClassLoader.fromContext) + } + + val inlinedNormailizer = new TreeMap { + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { + case Inlined(EmptyTree, Nil, expr) if enclosingInlineds.isEmpty => transform(expr) + case _ => super.transform(tree) + } + } + val normalizedSplice = inlinedNormailizer.transform(evaluatedSplice) + if (normalizedSplice.isEmpty) normalizedSplice + else normalizedSplice.withSpan(span) + } + } /** Produces an inlined version of `call` via its `inlined` method. @@ -1233,7 +1254,14 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { case res: Apply if res.symbol == defn.InternalQuoted_exprSplice && level == 0 && !suppressInline => - expandMacro(res.args.head, tree.span) + val body = res.args.head + checkMacroDependencies(body, call.sourcePos) + if call.symbol.is(Transparent) then + expandMacro(res.args.head, tree.span) + else + // Blackbox macros expanded later in ReifyQuotes + ctx.compilationUnit.needsStaging = true + res case res => res } @@ -1391,31 +1419,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { } } - private def expandMacro(body: Tree, span: Span)(using Context) = { + private def checkMacroDependencies(body: Tree, callPos: SourcePosition)(implicit ctx: Context): Unit = { assert(level == 0) - val inlinedFrom = enclosingInlineds.last val dependencies = macroDependencies(body) - if dependencies.nonEmpty && !ctx.reporter.errorsReported then for sym <- dependencies do if ctx.compilationUnit.source.file == sym.associatedFile then - ctx.error(em"Cannot call macro $sym defined in the same source file", call.sourcePos) + ctx.error(em"Cannot call macro $sym defined in the same source file", callPos) if (ctx.settings.XprintSuspension.value) - ctx.echo(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}", call.sourcePos) + ctx.echo(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}", callPos) ctx.compilationUnit.suspend() // this throws a SuspendException - - val evaluatedSplice = inContext(tastyreflect.MacroExpansion.context(inlinedFrom)) { - Splicer.splice(body, inlinedFrom.sourcePos, MacroClassLoader.fromContext) - } - val inlinedNormailizer = new TreeMap { - override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { - case Inlined(EmptyTree, Nil, expr) if enclosingInlineds.isEmpty => transform(expr) - case _ => super.transform(tree) - } - } - val normalizedSplice = inlinedNormailizer.transform(evaluatedSplice) - if (normalizedSplice.isEmpty) normalizedSplice - else normalizedSplice.withSpan(span) } /** Return the set of symbols that are referred at level -1 by the tree and defined in the current run. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index aabd00731e82..89de1bc34548 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1917,6 +1917,9 @@ class Typer extends Namer if (sym.isInlineMethod) val rhsToInline = PrepareInlineable.wrapRHS(ddef, tpt1, rhs1) + rhsToInline match + case _: Typed => + case _ => sym.setFlag(Transparent) // FIXME Tag whitebox macros (do it in desugar) PrepareInlineable.registerInlineInfo(sym, rhsToInline) if (sym.isConstructor && !sym.isPrimaryConstructor) { diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 4d8e4354f420..fc9cfeaf63b4 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -185,6 +185,7 @@ Standard-Section: "ASTs" TopLevelStat* OPAQUE -- opaque, also used for classes containing opaque aliases INLINE -- inline MACRO -- Inline method containing toplevel splices + TRANSPARENT -- Transparent inline method INLINEPROXY -- Symbol of binding with an argument to an inline method as rhs (TODO: do we still need this?) STATIC -- Mapped to static Java member OBJECT -- An object or its class @@ -254,7 +255,7 @@ Standard Section: "Comments" Comment* object TastyFormat { final val header: Array[Int] = Array(0x5C, 0xA1, 0xAB, 0x1F) - val MajorVersion: Int = 23 + val MajorVersion: Int = 24 val MinorVersion: Int = 0 /** Tags used to serialize names, should update [[nameTagToString]] if a new constant is added */ @@ -351,16 +352,17 @@ object TastyFormat { final val HASDEFAULT = 31 final val STABLE = 32 final val MACRO = 33 - final val ERASED = 34 - final val OPAQUE = 35 - final val EXTENSION = 36 - final val GIVEN = 37 - final val PARAMsetter = 38 - final val EXPORTED = 39 - final val OPEN = 40 - final val PARAMEND = 41 - final val PARAMalias = 42 - final val SUPERTRAIT = 43 + final val TRANSPARENT = 34 + final val ERASED = 35 + final val OPAQUE = 36 + final val EXTENSION = 37 + final val GIVEN = 38 + final val PARAMsetter = 39 + final val EXPORTED = 40 + final val OPEN = 41 + final val PARAMEND = 42 + final val PARAMalias = 43 + final val SUPERTRAIT = 44 // Cat. 2: tag Nat @@ -500,6 +502,7 @@ object TastyFormat { | INLINE | INLINEPROXY | MACRO + | TRANSPARENT | OPAQUE | STATIC | OBJECT @@ -561,6 +564,7 @@ object TastyFormat { case INLINE => "INLINE" case INLINEPROXY => "INLINEPROXY" case MACRO => "MACRO" + case TRANSPARENT => "TRANSPARENT" case OPAQUE => "OPAQUE" case STATIC => "STATIC" case OBJECT => "OBJECT" diff --git a/tests/neg-macros/i6530.scala b/tests/neg-macros/i6530.scala index 4fd703b7a4de..cab1078daffa 100644 --- a/tests/neg-macros/i6530.scala +++ b/tests/neg-macros/i6530.scala @@ -1,4 +1,7 @@ object Macros { inline def q : Int = ${ '[ Int ] } // error val x : Int = 1 + q // error + + transparent inline def q2: Int = ${ '[ Int ] } // error + val y : Int = 1 + q2 // error } diff --git a/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check b/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check index 11668d95952e..f487bcdcb6c2 100644 --- a/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check +++ b/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check @@ -1,3 +1,3 @@ -DefDef("foo", Nil, Nil, Inferred(), None) -ValDef("bar", Inferred(), None) +DefDef("foo", Nil, Nil, TypeIdent("Int"), Some(Apply(Select(Literal(Constant(1)), "+"), List(Literal(Constant(2)))))) +ValDef("bar", TypeIdent("Int"), Some(Apply(Select(Literal(Constant(2)), "+"), List(Literal(Constant(3)))))) Bind("x", Ident("_")) diff --git a/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check b/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check index 5e16ce08121f..8b123859496b 100644 --- a/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check +++ b/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check @@ -1,5 +1,5 @@ foo -ValDef("macro", Inferred(), None) +DefDef("main", Nil, List(List(ValDef("args", Applied(TypeIdent("Array"), List(TypeIdent("String"))), None))), TypeIdent("Unit"), Some(Block(Nil, Inlined(Some(Projection(Inferred(), "Macros$")), Nil, Typed(Apply(TypeApply(Ident("exprSplice"), List(Inferred())), List(Block(List(DefDef("$anonfun", Nil, List(List(ValDef("evidence$1", Inferred(), None))), Inferred(), Some(Apply(Apply(TypeApply(Ident("impl"), List(Inferred())), List(Apply(Select(Apply(TypeApply(Ident("exprQuote"), List(Inferred())), List(Inlined(None, Nil, Block(List(DefDef("foo", Nil, Nil, Inferred(), Some(Block(List(DefDef("bar", Nil, Nil, Inferred(), Some(Literal(Constant(1)))), ValDef("bar2", Inferred(), Some(Literal(Constant(2))))), Typed(Ident("bar"), Inferred())))), ValDef("foo2", Inferred(), Some(Block(List(DefDef("baz", Nil, Nil, Inferred(), Some(Literal(Constant(3)))), ValDef("baz2", Inferred(), Some(Literal(Constant(4))))), Typed(Ident("baz"), Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeIdent("Int")), DefDef("b", Nil, Nil, Inferred(), Some(Literal(Constant(5)))), ValDef("b2", Inferred(), Some(Literal(Constant(6))))))), Literal(Constant(())))))), "apply"), List(Ident("evidence$1"))))), List(Ident("evidence$1")))))), Closure(Ident("$anonfun"), None)))), TypeIdent("Unit")))))) bar DefDef("foo", Nil, Nil, Inferred(), None) @@ -8,7 +8,7 @@ bar2 DefDef("foo", Nil, Nil, Inferred(), None) foo2 -ValDef("macro", Inferred(), None) +DefDef("main", Nil, List(List(ValDef("args", Applied(TypeIdent("Array"), List(TypeIdent("String"))), None))), TypeIdent("Unit"), Some(Block(Nil, Inlined(Some(Projection(Inferred(), "Macros$")), Nil, Typed(Apply(TypeApply(Ident("exprSplice"), List(Inferred())), List(Block(List(DefDef("$anonfun", Nil, List(List(ValDef("evidence$1", Inferred(), None))), Inferred(), Some(Apply(Apply(TypeApply(Ident("impl"), List(Inferred())), List(Apply(Select(Apply(TypeApply(Ident("exprQuote"), List(Inferred())), List(Inlined(None, Nil, Block(List(DefDef("foo", Nil, Nil, Inferred(), Some(Block(List(DefDef("bar", Nil, Nil, Inferred(), Some(Literal(Constant(1)))), ValDef("bar2", Inferred(), Some(Literal(Constant(2))))), Typed(Ident("bar"), Inferred())))), ValDef("foo2", Inferred(), Some(Block(List(DefDef("baz", Nil, Nil, Inferred(), Some(Literal(Constant(3)))), ValDef("baz2", Inferred(), Some(Literal(Constant(4))))), Typed(Ident("baz"), Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeIdent("Int")), DefDef("b", Nil, Nil, Inferred(), Some(Literal(Constant(5)))), ValDef("b2", Inferred(), Some(Literal(Constant(6))))))), Literal(Constant(())))))), "apply"), List(Ident("evidence$1"))))), List(Ident("evidence$1")))))), Closure(Ident("$anonfun"), None)))), TypeIdent("Unit")))))) baz ValDef("foo2", Inferred(), None) diff --git a/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check b/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check index 6030480e8861..83c5fcea1695 100644 --- a/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check +++ b/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check @@ -1,2 +1,2 @@ -DefDef("foo", Nil, Nil, Inferred(), None) -ValDef("bar", Inferred(), None) +DefDef("foo", Nil, Nil, TypeIdent("Int"), Some(Apply(Select(Literal(Constant(1)), "+"), List(Literal(Constant(2)))))) +ValDef("bar", TypeIdent("Int"), Some(Apply(Select(Literal(Constant(2)), "+"), List(Literal(Constant(3)))))) diff --git a/tests/run-macros/box/Macro_1.scala b/tests/run-macros/box/Macro_1.scala new file mode 100644 index 000000000000..1551102f9ad9 --- /dev/null +++ b/tests/run-macros/box/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +object Macros { + + inline def blackbox: Int = ${one} + + transparent inline def whitebox: Int = ${one} + + private def one(using QuoteContext): Expr[Int] = Expr(1) + +} diff --git a/tests/run-macros/box/Test_2.scala b/tests/run-macros/box/Test_2.scala new file mode 100644 index 000000000000..07f6bea5319b --- /dev/null +++ b/tests/run-macros/box/Test_2.scala @@ -0,0 +1,12 @@ +import scala.quoted._ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + val a: Int = blackbox + val b: 1 = whitebox + + assert(a == 1) + assert(b == 1) + } +} diff --git a/tests/run-macros/reflect-inline/assert_1.scala b/tests/run-macros/reflect-inline/assert_1.scala index 54f4cc444863..ae472b29b423 100644 --- a/tests/run-macros/reflect-inline/assert_1.scala +++ b/tests/run-macros/reflect-inline/assert_1.scala @@ -1,13 +1,13 @@ import scala.quoted._ object api { - inline def (inline x: String) stripMargin: String = + transparent inline def (inline x: String) stripMargin: String = ${ stripImpl('x) } private def stripImpl(x: Expr[String])(using qctx: QuoteContext): Expr[String] = Expr(augmentString(x.unliftOrError).stripMargin) - inline def typeChecks(inline x: String): Boolean = + transparent inline def typeChecks(inline x: String): Boolean = ${ typeChecksImpl('{scala.compiletime.testing.typeChecks(x)}) } private def typeChecksImpl(b: Expr[Boolean])(using qctx: QuoteContext): Expr[Boolean] = {