Skip to content

Commit 7f7ff4e

Browse files
committed
Add Scala 2 macro compat to Scala2Compat mode
1 parent 46aec62 commit 7f7ff4e

File tree

16 files changed

+158
-15
lines changed

16 files changed

+158
-15
lines changed

Foo.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object Foo {
2+
def foo(): Int = macro Bar.fooImpl
3+
}
4+
5+
object Bar {
6+
def fooImpl(x: Int): Int = ???
7+
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,12 @@ object desugar {
259259
case _ =>
260260
}
261261

262+
meth1.rhs match {
263+
case MacroTree(call) =>
264+
meth1 = cpy.DefDef(meth1)(rhs = call).withMods(meth1.mods | Macro | Erased)
265+
case _ =>
266+
}
267+
262268
/** The longest prefix of parameter lists in vparamss whose total length does not exceed `n` */
263269
def takeUpTo(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match {
264270
case vparams :: vparamss1 =>
@@ -684,7 +690,7 @@ object desugar {
684690
val mods = constr1.mods
685691
mods.is(Private) || (!mods.is(Protected) && mods.hasPrivateWithin)
686692
}
687-
693+
688694
/** Does one of the parameter's types (in the first param clause)
689695
* mention a preceding parameter?
690696
*/

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

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

115116
case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree {
116117
// TODO: Make bound a typed tree?
@@ -732,6 +733,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
732733
x
733734
case TypedSplice(splice) =>
734735
this(x, splice)
736+
case MacroTree(call) =>
737+
this(x, call)
735738
case _ =>
736739
super.foldMoreCases(x, tree)
737740
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,7 @@ object SymDenotations {
933933
isAllOf(InlineMethod, butNot = Accessor)
934934

935935
/** Is this a Scala 2 macro */
936-
final def isScala2Macro(implicit ctx: Context): Boolean = is(Macro) && symbol.owner.is(Scala2x)
936+
final def isScala2Macro(implicit ctx: Context): Boolean = is(Macro, butNot = Inline)
937937

938938
/** An erased value or an inline method.
939939
*/

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2225,15 +2225,20 @@ object Parsers {
22252225
newExpr()
22262226
case MACRO =>
22272227
val start = in.skipToken()
2228-
val call = ident()
2229-
// The standard library uses "macro ???" to denote "fast track" macros
2230-
// hardcoded in the compiler, don't issue an error for those macros
2231-
// since we want to be able to compile the standard library.
2232-
if (call `ne` nme.???)
2233-
syntaxError(
2234-
"Scala 2 macros are not supported, see https://dotty.epfl.ch/docs/reference/dropped-features/macros.html",
2235-
start)
2236-
unimplementedExpr
2228+
val call = simpleExpr()
2229+
if (ctx.scala2CompatMode)
2230+
MacroTree(call)
2231+
else
2232+
call match
2233+
case Ident(name) if call.eq(nme.???) =>
2234+
// The standard library uses "macro ???" to denote "fast track" macros
2235+
// hardcoded in the compiler, don't issue an error for those macros
2236+
// since we want to be able to compile the standard library.
2237+
case _ =>
2238+
syntaxError(
2239+
"Scala 2 macros are not supported, see https://dotty.epfl.ch/docs/reference/dropped-features/macros.html",
2240+
start)
2241+
unimplementedExpr
22372242
case COLONEOL =>
22382243
syntaxError("':' not allowed here")
22392244
in.nextToken()

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
634634
toText(tree.app) ~ Str("(with integrated type args)").provided(printDebug)
635635
case Thicket(trees) =>
636636
"Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
637+
case MacroTree(call) =>
638+
keywordStr("macro ") ~ toTextGlobal(call)
637639
case _ =>
638640
tree.fallbackToText(this)
639641
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ class Namer { typer: Typer =>
437437
* package members are not entered twice in the same run.
438438
*/
439439
def enterSymbol(sym: Symbol)(implicit ctx: Context): Symbol = {
440-
if (sym.exists) {
440+
if (sym.exists && !sym.isScala2Macro) {
441441
typr.println(s"entered: $sym in ${ctx.owner}")
442442
ctx.enter(sym)
443443
}

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

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1675,8 +1675,61 @@ class Typer extends Namer
16751675
}
16761676
}
16771677

1678+
def typeScala2MacroBody(tree: untpd.Tree)(implicit ctx: Context): Tree =
1679+
assert(ctx.scala2CompatMode)
1680+
val rhs1: Tree = ddef.rhs match
1681+
case rhs0: Ident => typedIdent(rhs0, defn.AnyType)
1682+
case rhs0: Select => typedSelect(rhs0, defn.AnyType)
1683+
case rhs0: TypeApply => typedTypeApply(rhs0, defn.AnyType)
1684+
if ctx.phase.isTyper then
1685+
val reflectWContextType = ctx.requiredClassRef("scala.reflect.macros.whitebox.Context")
1686+
val reflectBContextType = ctx.requiredClassRef("scala.reflect.macros.blackbox.Context")
1687+
def checkResType(res: Type, cParam: TermParamRef): Unit =
1688+
res match
1689+
case AppliedType(expr, res) if expr <:< cParam.select(tpnme.Expr) =>
1690+
// TODO check res type
1691+
case _ =>
1692+
ctx.error(s"Expected macro implementation to return a ${cParam.paramName}.Expr", tree.sourcePos)
1693+
1694+
def checkParams(tpe: Type, paramss: List[List[ValDef]], cParam: TermParamRef): Unit = {
1695+
tpe match
1696+
case mt: MethodType =>
1697+
paramss match
1698+
case params :: paramss2 =>
1699+
if mt.paramInfos.size != params.size then
1700+
ctx.error("Wrong number of macro arguments", tree.sourcePos)
1701+
else
1702+
val expected = params.map(x => cParam.select(tpnme.Expr).appliedTo(x.tpt.tpe))
1703+
for ((e, a) <- expected.zip(mt.paramInfos))
1704+
if !(a =:= e) then ctx.error("Expected macro implementation to match parameters", tree.sourcePos)
1705+
checkParams(mt.resType, paramss2, cParam)
1706+
case Nil =>
1707+
ctx.error("Expected macro implementation has too many parameter groups", tree.sourcePos)
1708+
1709+
case res =>
1710+
if paramss.isEmpty then checkResType(res, cParam)
1711+
else ctx.error("Expected macro implementation to not have parameters after Context", tree.sourcePos)
1712+
}
1713+
rhs1.tpe.widenDealias match
1714+
case mt: PolyType =>
1715+
// TODO
1716+
ctx.error("Scala 2 macro definitions with type parameters not supported yet", tree.sourcePos)
1717+
case mt: MethodType =>
1718+
mt.paramInfos match
1719+
case c :: Nil if c <:< reflectWContextType || c <:< reflectBContextType =>
1720+
checkParams(mt.resType, vparamss1, mt.paramRefs(0))
1721+
case _ =>
1722+
ctx.error("Expected macro implementation first parameter to be Context parameter", tree.sourcePos)
1723+
case _ =>
1724+
ctx.error("Expected macro implementation to have at least a Context parameter", tree.sourcePos)
1725+
1726+
rhs1
1727+
1728+
16781729
if (sym.isInlineMethod) rhsCtx.addMode(Mode.InlineableBody)
1679-
val rhs1 = typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(rhsCtx)
1730+
val rhs1 =
1731+
if sym.isScala2Macro then typeScala2MacroBody(ddef.rhs)(rhsCtx) // Scala 2 macro definition
1732+
else typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(rhsCtx)
16801733

16811734
if (sym.isInlineMethod)
16821735
PrepareInlineable.registerInlineInfo(sym, _ => rhs1)

compiler/test/dotty/Properties.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ object Properties {
6868
/** scala-asm jar */
6969
def scalaAsm: String = sys.props("dotty.tests.classes.scalaAsm")
7070

71+
/** scala-reflect jar */
72+
def scalaReflect: String = sys.props("dotty.tests.classes.scalaReflect")
73+
7174
/** jline-terminal jar */
7275
def jlineTerminal: String = sys.props("dotty.tests.classes.jlineTerminal")
7376

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ class BootstrappedOnlyCompilationTests extends ParallelTesting {
3232

3333
@Test def posMacros: Unit = {
3434
implicit val testGroup: TestGroup = TestGroup("compilePosMacros")
35-
compileFilesInDir("tests/pos-macros", defaultOptions).checkCompile()
35+
aggregateTests(
36+
compileFilesInDir("tests/pos-macros", defaultOptions),
37+
compileFilesInDir("tests/pos-macro-compat", withReflectOptions),
38+
).checkCompile()
3639
}
3740

3841
@Test def posWithCompiler: Unit = {
@@ -100,7 +103,10 @@ class BootstrappedOnlyCompilationTests extends ParallelTesting {
100103

101104
@Test def negMacros: Unit = {
102105
implicit val testGroup: TestGroup = TestGroup("compileNegWithCompiler")
103-
compileFilesInDir("tests/neg-macros", defaultOptions).checkExpectedErrors()
106+
aggregateTests(
107+
compileFilesInDir("tests/neg-macros", defaultOptions),
108+
compileFilesInDir("tests/neg-macro-compat", withReflectOptions),
109+
).checkExpectedErrors()
104110
}
105111

106112
@Test def negWithCompiler: Unit = {

compiler/test/dotty/tools/vulpix/TestConfiguration.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ object TestConfiguration {
2424
Properties.dottyLibrary
2525
))
2626

27+
val withReflectClasspath = mkClasspath(List(
28+
Properties.scalaLibrary,
29+
Properties.dottyLibrary,
30+
Properties.scalaReflect,
31+
))
32+
2733
val withCompilerClasspath = mkClasspath(List(
2834
Properties.scalaLibrary,
2935
Properties.scalaAsm,
@@ -55,6 +61,8 @@ object TestConfiguration {
5561
val defaultOptions = TestFlags(basicClasspath, commonOptions)
5662
val withCompilerOptions =
5763
defaultOptions.withClasspath(withCompilerClasspath).withRunClasspath(withCompilerClasspath)
64+
lazy val withReflectOptions =
65+
defaultOptions.withClasspath(withReflectClasspath).and("-language:Scala2Compat")
5866
lazy val withStagingOptions =
5967
defaultOptions.withClasspath(withStagingClasspath).withRunClasspath(withStagingClasspath)
6068
lazy val withTastyInspectorOptions =

project/Build.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ object Build {
536536
"-Ddotty.tests.classes.compilerInterface=" + findArtifactPath(externalDeps, "compiler-interface"),
537537
"-Ddotty.tests.classes.scalaLibrary=" + findArtifactPath(externalDeps, "scala-library"),
538538
"-Ddotty.tests.classes.scalaAsm=" + findArtifactPath(externalDeps, "scala-asm"),
539+
"-Ddotty.tests.classes.scalaReflect=" + findArtifactPath(externalDeps, "scala-reflect"),
539540
"-Ddotty.tests.classes.jlineTerminal=" + findArtifactPath(externalDeps, "jline-terminal"),
540541
"-Ddotty.tests.classes.jlineReader=" + findArtifactPath(externalDeps, "jline-reader"),
541542
)
@@ -726,6 +727,7 @@ object Build {
726727
"-Ddotty.tests.classes.dottyTastyInspector=" + jars("dotty-tasty-inspector"),
727728
)
728729
},
730+
libraryDependencies += "org.scala-lang" % "scala-reflect" % "2.13.1",
729731
packageAll := {
730732
packageAll.in(`dotty-compiler`).value ++ Seq(
731733
"dotty-compiler" -> packageBin.in(Compile).value.getAbsolutePath,

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+
}

tests/pos-macro-compat/Macro1.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
object Macro1 {
2+
import scala.language.experimental.macros
3+
def lineNumber: Int = macro LineNumberMacro.thisLineNumberImpl
4+
inline def lineNumber: Int = ${ LineNumberMacro.thisLineNumberExpr }
5+
}
6+
7+
object LineNumberMacro {
8+
import scala.reflect.macros._
9+
def thisLineNumberImpl(context: Context): context.Expr[Int] = {
10+
val lineNumber = context.enclosingPosition.line
11+
context.literal(lineNumber)
12+
}
13+
14+
import scala.quoted._
15+
def thisLineNumberExpr(using qctx: QuoteContext): Expr[Int] = {
16+
// import qctx.tasty.{_, given _}
17+
// Expr(rootPosition.startLine + 1)
18+
Expr(3)
19+
}
20+
}

tests/pos-macro-compat/Macro2.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import scala.language.experimental.macros
2+
import scala.reflect.macros._
3+
4+
object Macros {
5+
6+
def foo1(x: Int): Int = macro foo1Impl
7+
def foo2(x: Int, y: String): Int = macro foo2Impl
8+
9+
def foo1Impl(context: scala.reflect.macros.Context)(x: context.Expr[Int]): context.Expr[Int] = ???
10+
def foo2Impl(context: scala.reflect.macros.Context)(x: context.Expr[Int], y: context.Expr[String]): context.Expr[Int] = ???
11+
12+
}

0 commit comments

Comments
 (0)