diff --git a/community-build/community-projects/stdLib213 b/community-build/community-projects/stdLib213 index 00c8764bf289..e7b881ee61a4 160000 --- a/community-build/community-projects/stdLib213 +++ b/community-build/community-projects/stdLib213 @@ -1 +1 @@ -Subproject commit 00c8764bf289396c0c6f8496c907f9a37f0415cb +Subproject commit e7b881ee61a49bb5435ae54c2d23340b352c4802 diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 75d49c7d5f75..c2517145c935 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index bb8da0d59d72..3b4a52f176c7 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -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? @@ -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) { @@ -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) } @@ -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) } diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index db639d961b23..1414385273b8 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -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. */ diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index c81982ba77fd..0f39262eacca 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 7c1b2c522895..6a6d62f09856 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -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() diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 98d231de643c..4a199dd74f89 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -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) } @@ -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) ~ _) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 37e0be2b7789..e5a0c28dd86d 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 9c6e83af0522..5b2fd6bea11b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -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) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5ef55d44f0ed..730b6f5c9450 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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) { @@ -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
` */ + 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 + } diff --git a/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala b/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala index 3ff1b1d2f59f..3baf9145cde5 100644 --- a/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala +++ b/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala @@ -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) diff --git a/tests/neg/scala2-macro-compat-no-scala3-implementation.scala b/tests/neg/scala2-macro-compat-no-scala3-implementation.scala new file mode 100644 index 000000000000..406b8fd754ae --- /dev/null +++ b/tests/neg/scala2-macro-compat-no-scala3-implementation.scala @@ -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) + ??? + } +} diff --git a/tests/pos/macro.scala b/tests/pos/macro.scala index e040ab6c7cee..8db1f0751997 100644 --- a/tests/pos/macro.scala +++ b/tests/pos/macro.scala @@ -1,3 +1,4 @@ class A { def foo: Int = macro ??? + inline def foo: Int = ??? } diff --git a/tests/pos/scala2-macro-compat-1.scala b/tests/pos/scala2-macro-compat-1.scala new file mode 100644 index 000000000000..99050d9018e2 --- /dev/null +++ b/tests/pos/scala2-macro-compat-1.scala @@ -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) + } +} diff --git a/tests/pos/scala2-macro-compat-2.scala b/tests/pos/scala2-macro-compat-2.scala new file mode 100644 index 000000000000..3eeb0c8302b0 --- /dev/null +++ b/tests/pos/scala2-macro-compat-2.scala @@ -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] = ??? + +} diff --git a/tests/pos/scala2-macro-compat-3.scala b/tests/pos/scala2-macro-compat-3.scala new file mode 100644 index 000000000000..264afbe04f7a --- /dev/null +++ b/tests/pos/scala2-macro-compat-3.scala @@ -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) + ??? + } +} + diff --git a/tests/semanticdb/expect/Annotations.expect.scala b/tests/semanticdb/expect/Annotations.expect.scala index 08d5fc54806d..f36f02686f8f 100644 --- a/tests/semanticdb/expect/Annotations.expect.scala +++ b/tests/semanticdb/expect/Annotations.expect.scala @@ -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#*/ diff --git a/tests/semanticdb/expect/semanticdb-Flags.expect.scala b/tests/semanticdb/expect/semanticdb-Flags.expect.scala index be6660f99c4e..f36f0899c1ea 100644 --- a/tests/semanticdb/expect/semanticdb-Flags.expect.scala +++ b/tests/semanticdb/expect/semanticdb-Flags.expect.scala @@ -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#`