Skip to content

Add experimental Scala 2 macro compat #8811

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,10 @@ object desugar {
cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs))
}

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

/** The longest prefix of parameter lists in vparamss whose total length does not exceed `n` */
def takeUpTo(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match {
Expand Down
9 changes: 9 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree
case class Export(expr: Tree, selectors: List[ImportSelector])(implicit @constructorOnly src: SourceFile) extends Tree
case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree

case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree {
// TODO: Make bound a typed tree?
Expand Down Expand Up @@ -623,6 +624,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case tree: TypedSplice if splice `eq` tree.splice => tree
case _ => finalize(tree, untpd.TypedSplice(splice)(ctx))
}
def MacroTree(tree: Tree)(expr: Tree)(implicit ctx: Context): Tree = tree match {
case tree: MacroTree if expr `eq` tree.expr => tree
case _ => finalize(tree, untpd.MacroTree(expr)(tree.source))
}
}

abstract class UntypedTreeMap(cpy: UntypedTreeCopier = untpd.cpy) extends TreeMap(cpy) {
Expand Down Expand Up @@ -677,6 +682,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
cpy.ImportSelector(tree)(transformSub(imported), transform(renamed), transform(bound))
case Number(_, _) | TypedSplice(_) =>
tree
case MacroTree(expr) =>
cpy.MacroTree(tree)(transform(expr))
case _ =>
super.transformMoreCases(tree)
}
Expand Down Expand Up @@ -736,6 +743,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
x
case TypedSplice(splice) =>
this(x, splice)
case MacroTree(expr) =>
this(x, expr)
case _ =>
super.foldMoreCases(x, tree)
}
Expand Down
7 changes: 6 additions & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -995,7 +995,12 @@ object SymDenotations {
&& allOverriddenSymbols.exists(!_.is(Inline))

/** Is this a Scala 2 macro */
final def isScala2Macro(implicit ctx: Context): Boolean = is(Macro) && symbol.owner.is(Scala2x)
final def isScala2Macro(implicit ctx: Context): Boolean =
isScala2MacroInScala3 || (is(Macro) && symbol.owner.is(Scala2x))

/** Is this a Scala 2 macro defined */
final def isScala2MacroInScala3(implicit ctx: Context): Boolean =
is(Macro, butNot = Inline) && is(Erased)

/** An erased value or an inline method.
*/
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -580,8 +580,9 @@ class TreeUnpickler(reader: TastyReader,
}
sym.annotations = annotFns.map(_(sym))
if sym.isOpaqueAlias then sym.setFlag(Deferred)
val isScala2MacroDefinedInScala3 = flags.is(Macro, butNot = Inline) && flags.is(Erased)
ctx.owner match {
case cls: ClassSymbol => cls.enter(sym)
case cls: ClassSymbol if !isScala2MacroDefinedInScala3 => cls.enter(sym)
case _ =>
}
registerSym(start, sym)
Expand Down
10 changes: 1 addition & 9 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2248,15 +2248,7 @@ object Parsers {
newExpr()
case MACRO =>
val start = in.skipToken()
val call = ident()
// The standard library uses "macro ???" to denote "fast track" macros
// hardcoded in the compiler, don't issue an error for those macros
// since we want to be able to compile the standard library.
if (call `ne` nme.???)
syntaxError(
"Scala 2 macros are not supported, see https://dotty.epfl.ch/docs/reference/dropped-features/macros.html",
start)
unimplementedExpr
MacroTree(simpleExpr())
case COLONEOL =>
syntaxError("':' not allowed here")
in.nextToken()
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
toText(tree.app) ~ Str("(with integrated type args)").provided(printDebug)
case Thicket(trees) =>
"Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
case MacroTree(call) =>
keywordStr("macro ") ~ toTextGlobal(call)
case _ =>
tree.fallbackToText(this)
}
Expand Down Expand Up @@ -786,7 +788,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {

addVparamssText(prefix ~ tparamsText(tree.tparams), vparamss) ~
optAscription(tree.tpt) ~
optText(tree.rhs)(" = " ~ _)
optText(tree.rhs)(" = " ~ keywordText("macro ").provided(tree.symbol.isScala2Macro) ~ _)
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
tree match
case tree: ValOrDefDef if !tree.symbol.is(Synthetic) =>
checkInferredWellFormed(tree.tpt)
val sym = tree.symbol
if sym.isScala2Macro && !ctx.settings.XignoreScala2Macros.value then
if !sym.owner.unforcedDecls.exists(p => !p.isScala2Macro && p.name == sym.name && p.signature == sym.signature) then
ctx.error("No Scala 3 implementation found for this Scala 2 macro.", tree.sourcePos)
case _ =>
processMemberDef(tree)

Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,8 @@ class Namer { typer: Typer =>
* package members are not entered twice in the same run.
*/
def enterSymbol(sym: Symbol)(using Context): Symbol = {
if (sym.exists) {
// We do not enter Scala 2 macros defined in Scala 3 as they have an equivalent Scala 3 inline method.
if (sym.exists && !sym.isScala2MacroInScala3) {
typr.println(s"entered: $sym in ${ctx.owner}")
ctx.enter(sym)
}
Expand Down
21 changes: 19 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1864,10 +1864,12 @@ class Typer extends Namer
}

if (sym.isInlineMethod) rhsCtx.addMode(Mode.InlineableBody)
val rhs1 = typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx)
val rhsToInline = PrepareInlineable.wrapRHS(ddef, tpt1, rhs1)
val rhs1 =
if sym.isScala2Macro then typedScala2MacroBody(ddef.rhs)(using rhsCtx)
else typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx)

if (sym.isInlineMethod)
val rhsToInline = PrepareInlineable.wrapRHS(ddef, tpt1, rhs1)
PrepareInlineable.registerInlineInfo(sym, rhsToInline)

if (sym.isConstructor && !sym.isPrimaryConstructor) {
Expand Down Expand Up @@ -3442,4 +3444,19 @@ class Typer extends Namer
if (!tree.tpe.isErroneous && !ctx.isAfterTyper && isPureExpr(tree) &&
!tree.tpe.isRef(defn.UnitClass) && !isSelfOrSuperConstrCall(tree))
ctx.warning(PureExpressionInStatementPosition(original, exprOwner), original.sourcePos)

/** Types the body Scala 2 macro declaration `def f = macro <body>` */
private def typedScala2MacroBody(call: untpd.Tree)(using Context): Tree =
// TODO check that call is to a method with valid signature
call match
case rhs0: untpd.Ident =>
typedIdent(rhs0, defn.AnyType)
case rhs0: untpd.Select =>
typedSelect(rhs0, defn.AnyType)
case rhs0: untpd.TypeApply =>
typedTypeApply(rhs0, defn.AnyType)
case _ =>
ctx.error("Invalid Scala 2 macro", call.sourcePos)
EmptyTree

}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class SemanticdbTests:
// "-Xprint:extractSemanticDB",
"-sourceroot", expectSrc.toString,
"-classpath", target.toString,
"-Xignore-scala2-macros",
"-usejavacp"
) ++ inputFiles().map(_.toString)
val exit = Main.process(args)
Expand Down
15 changes: 15 additions & 0 deletions tests/neg/scala2-macro-compat-no-scala3-implementation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
object Macro1 {
import scala.language.experimental.macros
def lineNumber: Int = macro LineNumberMacro2.thisLineNumberImpl // error: No Scala 3 implementation found for this Scala 2 macro.
}

object LineNumberMacro2 {
class Context: // Dummy scala.reflect.macros.Context
type Expr[+T]

def thisLineNumberImpl(context: Context): context.Expr[Int] = {
// val lineNumber = context.enclosingPosition.line
// context.literal(lineNumber)
???
}
}
1 change: 1 addition & 0 deletions tests/pos/macro.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
class A {
def foo: Int = macro ???
inline def foo: Int = ???
}
26 changes: 26 additions & 0 deletions tests/pos/scala2-macro-compat-1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@


object Macro1 {
import scala.language.experimental.macros
def lineNumber: Int = macro LineNumberMacro2.thisLineNumberImpl
inline def lineNumber: Int = ${ LineNumberMacro3.thisLineNumberExpr }
}

object LineNumberMacro2 {
class Context: // Dummy scala.reflect.macros.Context
type Expr[+T]

def thisLineNumberImpl(context: Context): context.Expr[Int] = {
// val lineNumber = context.enclosingPosition.line
// context.literal(lineNumber)
???
}
}

object LineNumberMacro3 {
import scala.quoted._
def thisLineNumberExpr(using qctx: QuoteContext): Expr[Int] = {
import qctx.tasty.{_, given _}
Expr(rootPosition.startLine + 1)
}
}
17 changes: 17 additions & 0 deletions tests/pos/scala2-macro-compat-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import scala.language.experimental.macros

class Context: // Dummy scala.reflect.macros.Context
type Expr[T]

object Macros {

def foo1(x: Int): Int = macro foo1Impl
def foo1(x: Int): Int = ???

def foo2(x: Int, y: String): Int = macro foo2Impl
def foo2(x: Int, y: String): Int = ???

def foo1Impl(context: Context)(x: context.Expr[Int]): context.Expr[Int] = ???
def foo2Impl(context: Context)(x: context.Expr[Int], y: context.Expr[String]): context.Expr[Int] = ???

}
23 changes: 23 additions & 0 deletions tests/pos/scala2-macro-compat-3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@


object Macro1 {
import scala.language.experimental.macros
def lineNumber: Int = macro LineNumberMacro2.thisLineNumberImpl
inline def lineNumber: Int = 4
}

object Macro1Consume {
val test = Macro1.lineNumber
}

object LineNumberMacro2 {
class Context: // Dummy scala.reflect.macros.Context
type Expr[+T]

def thisLineNumberImpl(context: Context): context.Expr[Int] = {
// val lineNumber = context.enclosingPosition.line
// context.literal(lineNumber)
???
}
}

2 changes: 1 addition & 1 deletion tests/semanticdb/expect/Annotations.expect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class B/*<-annot::B#*/ @ConstructorAnnotation/*->com::javacp::annot::Constructor
@ObjectAnnotation/*->com::javacp::annot::ObjectAnnotation#*/
object M/*<-annot::M.*/ {
@MacroAnnotation/*->com::javacp::annot::MacroAnnotation#*/
def m/*<-annot::M.m().*/[TT/*<-annot::M.m().[TT]*/]: Int/*->scala::Int#*//*->scala::Predef.`???`().*/ = macro ???
def m/*<-annot::M.m().*/[TT/*<-annot::M.m().[TT]*/]: Int/*->scala::Int#*/ = macro ???/*->scala::Predef.`???`().*/
}

@TraitAnnotation/*->com::javacp::annot::TraitAnnotation#*/
Expand Down
2 changes: 1 addition & 1 deletion tests/semanticdb/expect/semanticdb-Flags.expect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package object p {
p/*<-flags::p::package.*/rivate lazy val x/*<-flags::p::package.x.*/ = 1
protected implicit var y/*<-flags::p::package.y().*/: Int/*->scala::Int#*/ = 2
def z/*<-flags::p::package.z().*/(pp/*<-flags::p::package.z().(pp)*/: Int/*->scala::Int#*/) = 3
def m/*<-flags::p::package.m().*/[TT/*<-flags::p::package.m().[TT]*/]: Int/*->scala::Int#*//*->scala::Predef.`???`().*/ = macro ???
def m/*<-flags::p::package.m().*/[TT/*<-flags::p::package.m().[TT]*/]: Int/*->scala::Int#*/ = macro ???/*->scala::Predef.`???`().*/
abstract class C/*<-flags::p::package.C#*/[+T/*<-flags::p::package.C#[T]*/, -U/*<-flags::p::package.C#[U]*/, V/*<-flags::p::package.C#[V]*/](x/*<-flags::p::package.C#x.*/: T/*->flags::p::package.C#[T]*/, y/*<-flags::p::package.C#y.*/: U/*->flags::p::package.C#[U]*/, z/*<-flags::p::package.C#z.*/: V/*->flags::p::package.C#[V]*/) {
def this()/*<-flags::p::package.C#`<init>`(+1).*/ = this(???/*->scala::Predef.`???`().*/, ???/*->scala::Predef.`???`().*/, ???/*->scala::Predef.`???`().*/)
def this(t/*<-flags::p::package.C#`<init>`(+2).*//*<-flags::p::package.C#`<init>`(+2).(t)*/: T/*->flags::p::package.C#[T]*/) = this(t/*->flags::p::package.C#`<init>`(+2).(t)*/, ???/*->scala::Predef.`???`().*/, ???/*->scala::Predef.`???`().*/)
Expand Down
8 changes: 4 additions & 4 deletions tests/semanticdb/metac.expect
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ annot/B#`<init>`().(x) => param x
annot/B#`<init>`(+1). => ctor <init>
annot/B#x. => val method x
annot/M. => final object M
annot/M.m(). => method m
annot/M.m(). => macro m
annot/M.m().[TT] => typeparam TT
annot/T# => trait T
annot/T#`<init>`(). => primary ctor <init>
Expand Down Expand Up @@ -262,7 +262,7 @@ Occurrences:
[29:6..29:7): m <- annot/M.m().
[29:8..29:10): TT <- annot/M.m().[TT]
[29:13..29:16): Int -> scala/Int#
[29:16..29:16): -> scala/Predef.`???`().
[29:25..29:28): ??? -> scala/Predef.`???`().
[32:0..32:0): <- annot/T#`<init>`().
[32:1..32:16): TraitAnnotation -> com/javacp/annot/TraitAnnotation#
[33:6..33:7): T <- annot/T#
Expand Down Expand Up @@ -2986,7 +2986,7 @@ flags/p/package.Z# => sealed trait Z
flags/p/package.Z#`<init>`(). => primary ctor <init>
flags/p/package.`y_=`(). => var method y_=
flags/p/package.`y_=`().(x$1) => param x$1
flags/p/package.m(). => method m
flags/p/package.m(). => macro m
flags/p/package.m().[TT] => typeparam TT
flags/p/package.x. => lazy val method x
flags/p/package.xs1. => val method xs1
Expand All @@ -3013,7 +3013,7 @@ Occurrences:
[8:6..8:7): m <- flags/p/package.m().
[8:8..8:10): TT <- flags/p/package.m().[TT]
[8:13..8:16): Int -> scala/Int#
[8:16..8:16): -> scala/Predef.`???`().
[8:25..8:28): ??? -> scala/Predef.`???`().
[9:17..9:18): C <- flags/p/package.C#
[9:18..9:47): <- flags/p/package.C#`<init>`().
[9:20..9:21): T <- flags/p/package.C#[T]
Expand Down