Skip to content

Commit 4cd0547

Browse files
committed
Add experimental Scala 2 macro compat
1 parent 66f335c commit 4cd0547

File tree

13 files changed

+123
-17
lines changed

13 files changed

+123
-17
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,10 @@ object desugar {
237237
cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs))
238238
}
239239

240-
val meth1 = addEvidenceParams(
241-
cpy.DefDef(meth)(name = methName, tparams = tparams1), epbuf.toList)
240+
val meth1 =
241+
rhs match
242+
case MacroTree(call) => cpy.DefDef(meth)(rhs = call).withMods(mods | Macro | Erased)
243+
case _ => addEvidenceParams(cpy.DefDef(meth)(name = methName, tparams = tparams1), epbuf.toList)
242244

243245
/** The longest prefix of parameter lists in vparamss whose total length does not exceed `n` */
244246
def takeUpTo(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match {

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
112112
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree
113113
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree
114114
case class Export(expr: Tree, selectors: List[ImportSelector])(implicit @constructorOnly src: SourceFile) extends Tree
115+
case class MacroTree(call: Tree)(implicit @constructorOnly src: SourceFile) extends Tree
115116

116117
case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree {
117118
// TODO: Make bound a typed tree?
@@ -736,6 +737,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
736737
x
737738
case TypedSplice(splice) =>
738739
this(x, splice)
740+
case MacroTree(call) =>
741+
this(x, call)
739742
case _ =>
740743
super.foldMoreCases(x, tree)
741744
}

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ class ScalaSettings extends Settings.SettingGroup {
168168
val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.")
169169
val YerasedTerms: Setting[Boolean] = BooleanSetting("-Yerased-terms", "Allows the use of erased terms.")
170170
val YcheckInit: Setting[Boolean] = BooleanSetting("-Ycheck-init", "Check initialization of objects")
171+
val YScala2ExperimentalCompat: Setting[Boolean] = BooleanSetting("-Yscala2-experimental-compat", "Allows Scala 2 macro definitions (no checks performed)")
171172

172173
/** Area-specific debug output */
173174
val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,11 @@ object SymDenotations {
995995
&& allOverriddenSymbols.exists(!_.is(Inline))
996996

997997
/** Is this a Scala 2 macro */
998-
final def isScala2Macro(implicit ctx: Context): Boolean = is(Macro) && symbol.owner.is(Scala2x)
998+
final def isScala2Macro(implicit ctx: Context): Boolean =
999+
is(Macro, butNot = Inline) && (
1000+
is(Erased) // Scala 2 macro defined in Scala 3
1001+
|| symbol.owner.is(Scala2x) // Scala 2 macro defined in Scala 2
1002+
)
9991003

10001004
/** An erased value or an inline method.
10011005
*/

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,15 +2248,20 @@ object Parsers {
22482248
newExpr()
22492249
case MACRO =>
22502250
val start = in.skipToken()
2251-
val call = ident()
2252-
// The standard library uses "macro ???" to denote "fast track" macros
2253-
// hardcoded in the compiler, don't issue an error for those macros
2254-
// since we want to be able to compile the standard library.
2255-
if (call `ne` nme.???)
2256-
syntaxError(
2257-
"Scala 2 macros are not supported, see https://dotty.epfl.ch/docs/reference/dropped-features/macros.html",
2258-
start)
2259-
unimplementedExpr
2251+
val call = simpleExpr()
2252+
call match
2253+
case Ident(name) if name eq nme.??? =>
2254+
// The standard library uses "macro ???" to denote "fast track" macros
2255+
// hardcoded in the compiler, don't issue an error for those macros
2256+
// since we want to be able to compile the standard library.
2257+
MacroTree(call)
2258+
case _ if ctx.settings.YScala2ExperimentalCompat.value =>
2259+
MacroTree(call)
2260+
case _ =>
2261+
syntaxError(
2262+
"Scala 2 macros are not supported, see https://dotty.epfl.ch/docs/reference/dropped-features/macros.html",
2263+
start)
2264+
unimplementedExpr
22602265
case COLONEOL =>
22612266
syntaxError("':' not allowed here")
22622267
in.nextToken()

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
632632
toText(tree.app) ~ Str("(with integrated type args)").provided(printDebug)
633633
case Thicket(trees) =>
634634
"Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
635+
case MacroTree(call) =>
636+
keywordStr("macro ") ~ toTextGlobal(call)
635637
case _ =>
636638
tree.fallbackToText(this)
637639
}
@@ -786,7 +788,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
786788

787789
addVparamssText(prefix ~ tparamsText(tree.tparams), vparamss) ~
788790
optAscription(tree.tpt) ~
789-
optText(tree.rhs)(" = " ~ _)
791+
optText(tree.rhs)(" = " ~ keywordText("macro ").provided(tree.symbol.isScala2Macro) ~ _)
790792
}
791793
}
792794
}

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ class Namer { typer: Typer =>
435435
* package members are not entered twice in the same run.
436436
*/
437437
def enterSymbol(sym: Symbol)(using Context): Symbol = {
438-
if (sym.exists) {
438+
if (sym.exists && !sym.isScala2Macro) {
439439
typr.println(s"entered: $sym in ${ctx.owner}")
440440
ctx.enter(sym)
441441
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package dotty.tools.dotc.typer
2+
3+
import dotty.tools.dotc.ast._
4+
import dotty.tools.dotc.ast.Trees._
5+
import dotty.tools.dotc.core._
6+
import dotty.tools.dotc.core.Contexts._
7+
import dotty.tools.dotc.core.Decorators._
8+
import dotty.tools.dotc.core.Symbols._
9+
10+
trait Scala2MacroDeclatration { self: Typer =>
11+
import tpd._
12+
13+
/** Types the body Scala 2 macro declaration `def f = macro <body>` */
14+
def typedScala2MacroBody(call: untpd.Tree)(using Context): Tree =
15+
// TODO check that call is to a method with valid signature
16+
call match
17+
case rhs0: Ident =>
18+
typedIdent(rhs0, defn.AnyType)
19+
case rhs0: Select =>
20+
typedSelect(rhs0, defn.AnyType)
21+
case rhs0: TypeApply =>
22+
typedTypeApply(rhs0, defn.AnyType)
23+
case _ =>
24+
ctx.error("Invalid Scala 2 macro", call.sourcePos)
25+
EmptyTree
26+
27+
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class Typer extends Namer
9191
with Dynamic
9292
with Checking
9393
with QuotesAndSplices
94+
with Scala2MacroDeclatration
9495
with Deriving {
9596

9697
import Typer._
@@ -1862,12 +1863,14 @@ class Typer extends Namer
18621863
}
18631864
}
18641865
}
1865-
18661866
if (sym.isInlineMethod) rhsCtx.addMode(Mode.InlineableBody)
1867-
val rhs1 = typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx)
1868-
val rhsToInline = PrepareInlineable.wrapRHS(ddef, tpt1, rhs1)
1867+
1868+
val rhs1 =
1869+
if sym.isScala2Macro then typedScala2MacroBody(ddef.rhs)(using rhsCtx)
1870+
else typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx)
18691871

18701872
if (sym.isInlineMethod)
1873+
val rhsToInline = PrepareInlineable.wrapRHS(ddef, tpt1, rhs1)
18711874
PrepareInlineable.registerInlineInfo(sym, rhsToInline)
18721875

18731876
if (sym.isConstructor && !sym.isPrimaryConstructor) {

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ class CompilationTests extends ParallelTesting {
6565
compileFile("tests/pos-special/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
6666
compileFile("tests/run/i5606.scala", defaultOptions.and("-Yretain-trees")),
6767
compileFile("tests/pos-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"),
68+
compileFile("tests/pos-custom-args/scala2-macro-compat-1.scala", defaultOptions.and("-Yscala2-experimental-compat")),
69+
compileFile("tests/pos-custom-args/scala2-macro-compat-2.scala", defaultOptions.and("-Yscala2-experimental-compat")),
70+
6871
).checkCompile()
6972
}
7073

tests/neg-macro-compat/Macro1.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import scala.language.experimental.macros
2+
import scala.reflect.macros._
3+
4+
object Macros {
5+
6+
def foo0(x: Int): Int = macro foo0Impl // error
7+
def foo1(x: Int): Int = macro foo1Impl // error
8+
def foo2(x: Int, y: String): Int = macro foo2Impl // error
9+
def foo3(x: Int): Int = macro foo3Impl // error
10+
11+
def foo0Impl: Nothing = ???
12+
def foo1Impl(context: scala.reflect.macros.Context)(x: context.Expr[String]): context.Expr[Int] = ???
13+
def foo2Impl(context: scala.reflect.macros.Context)(x: context.Expr[Int], y: context.Expr[String]): Nothing = ???
14+
def foo3Impl(x: Int): Int = ???
15+
16+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
3+
object Macro1 {
4+
import scala.language.experimental.macros
5+
def lineNumber: Int = macro LineNumberMacro2.thisLineNumberImpl
6+
inline def lineNumber: Int = ${ LineNumberMacro3.thisLineNumberExpr }
7+
}
8+
9+
object LineNumberMacro2 {
10+
class Context: // Dummy scala.reflect.macros.Context
11+
type Expr[+T]
12+
13+
def thisLineNumberImpl(context: Context): context.Expr[Int] = {
14+
// val lineNumber = context.enclosingPosition.line
15+
// context.literal(lineNumber)
16+
???
17+
}
18+
}
19+
20+
object LineNumberMacro3 {
21+
import scala.quoted._
22+
def thisLineNumberExpr(using qctx: QuoteContext): Expr[Int] = {
23+
import qctx.tasty.{_, given _}
24+
Expr(rootPosition.startLine + 1)
25+
}
26+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.language.experimental.macros
2+
3+
class Context: // Dummy scala.reflect.macros.Context
4+
type Expr[T]
5+
6+
object Macros {
7+
8+
def foo1(x: Int): Int = macro foo1Impl
9+
def foo2(x: Int, y: String): Int = macro foo2Impl
10+
11+
def foo1Impl(context: Context)(x: context.Expr[Int]): context.Expr[Int] = ???
12+
def foo2Impl(context: Context)(x: context.Expr[Int], y: context.Expr[String]): context.Expr[Int] = ???
13+
14+
}

0 commit comments

Comments
 (0)