diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e09b7ca98955..18fbf977bbc2 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -827,7 +827,9 @@ class Definitions { @tu lazy val QuoteUnpicklerClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteUnpickler") + @tu lazy val QuoteUnpickler_unpickleExpr: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExpr") @tu lazy val QuoteUnpickler_unpickleExprV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExprV2") + @tu lazy val QuoteUnpickler_unpickleType: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleType") @tu lazy val QuoteUnpickler_unpickleTypeV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleTypeV2") @tu lazy val QuoteMatchingClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteMatching") diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 2e0454c3a7aa..b3a4783a783c 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -50,7 +50,7 @@ object PickledQuotes { /** `typeHole`/`types` argument of `QuoteUnpickler.{unpickleExpr,unpickleExprV2,unpickleType,unpickleTypeV2}` */ enum TypeHole: /** `termHole` argument of `QuoteUnpickler.{unpickleExpr, unpickleType}`. - * From code compiled with Scala 3.0.x and 3.1.x. + * From code compiled with Scala 3.0.x and 3.1.x or with -scala-output-version 3.0 and 3.1 * Note: For `unpickleType` it will always be `null`. */ case V1(evalHole: Null | ((Int, Seq[scala.quoted.Type[?]]) => scala.quoted.Type[?])) @@ -65,7 +65,7 @@ object PickledQuotes { enum ExprHole: /** `termHole` argument of `QuoteUnpickler.{unpickleExpr, unpickleType}`. - * From code compiled with Scala 3.0.x and 3.1.x. + * From code compiled with Scala 3.0.x and 3.1.x or with -scala-output-version 3.0 and 3.1 * Note: For `unpickleType` it will always be `null`. */ case V1(evalHole: Null | ((Int, Seq[ExprHole.ArgV1], scala.quoted.Quotes) => scala.quoted.Expr[?])) diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index a12ef6d50bed..40e3526861d0 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -291,6 +291,9 @@ object PickleQuotes { * this closure is always applied directly to the actual context and the BetaReduce phase removes it. */ def pickleAsTasty() = { + + val unpickleV1 = ctx.scalaRelease <= Release3_1 + val pickleQuote = PickledQuotes.pickleQuote(body) val pickledQuoteStrings = pickleQuote match case x :: Nil => Literal(Constant(x)) @@ -303,8 +306,26 @@ object PickleQuotes { // This and all closures in typeSplices are removed by the BetaReduce phase val types = - if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible - else SeqLiteral(typeSplices.map(_._1), TypeTree(defn.QuotedTypeClass.typeRef.appliedTo(WildcardType))) + if unpickleV1 then + if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible + else + Lambda( + MethodType( + List(nme.idx, nme.contents).map(name => UniqueName.fresh(name).toTermName), + List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType)), + defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)), + args => { + val cases = typeSplices.map { case (splice, idx) => + CaseDef(Literal(Constant(idx)), EmptyTree, splice) + } + cases match + case CaseDef(_, _, rhs) :: Nil => rhs + case _ => Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) + } + ) + else // if unpickleV2 then + if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible + else SeqLiteral(typeSplices.map(_._1), TypeTree(defn.QuotedTypeClass.typeRef.appliedTo(WildcardType))) // This and all closures in termSplices are removed by the BetaReduce phase val termHoles = @@ -320,7 +341,12 @@ object PickleQuotes { val defn.FunctionOf(argTypes, defn.FunctionOf(quotesType :: _, _, _, _), _, _) = splice.tpe val rhs = { val spliceArgs = argTypes.zipWithIndex.map { (argType, i) => - args(1).select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argType) + val argi = args(1).select(nme.apply).appliedTo(Literal(Constant(i))) + if unpickleV1 && argType.derivesFrom(defn.QuotedExprClass) then + val argType1 = defn.FunctionType(1).appliedTo(defn.QuotesClass.typeRef, argType) + argi.asInstance(argType1).select(nme.apply).appliedTo(args(2)) + else + argi.asInstance(argType) } val Block(List(ddef: DefDef), _) = splice // TODO: beta reduce inner closure? Or wait until BetaReduce phase? @@ -337,10 +363,14 @@ object PickleQuotes { val quotedType = quoteClass.typeRef.appliedTo(originalTp) val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, quotedType) val unpickleMeth = - if isType then defn.QuoteUnpickler_unpickleTypeV2 - else defn.QuoteUnpickler_unpickleExprV2 + if unpickleV1 then + if isType then defn.QuoteUnpickler_unpickleType + else defn.QuoteUnpickler_unpickleExpr + else // if unpickleV2 then + if isType then defn.QuoteUnpickler_unpickleTypeV2 + else defn.QuoteUnpickler_unpickleExprV2 val unpickleArgs = - if isType then List(pickledQuoteStrings, types) + if isType && !unpickleV1 then List(pickledQuoteStrings, types) else List(pickledQuoteStrings, types, termHoles) quotes .asInstance(defn.QuoteUnpicklerClass.typeRef) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index fe0c233173b7..7f99ad57b2d0 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -135,7 +135,13 @@ class Splicing extends MacroTransform: val hole = tpd.Hole(false, holeIdx, Nil, ref(qual), TypeTree(tp)) typeHoles.put(qual.symbol, hole) hole - cpy.TypeDef(tree)(rhs = hole) + val rhs = + if ctx.scalaRelease <= Release3_1 then + val secondHoleIdx = numHoles + numHoles += 1 + TypeBoundsTree(hole, cpy.Hole(hole)(idx = secondHoleIdx)) + else hole + cpy.TypeDef(tree)(rhs = rhs) case Apply(Select(Apply(TypeApply(fn,_), List(code)),nme.apply),List(quotes)) if fn.symbol == defn.QuotedRuntime_exprQuote => super.transform(tree)(using quoteContext) diff --git a/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala b/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala new file mode 100644 index 000000000000..ce7bf1b45b13 --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala @@ -0,0 +1,17 @@ +package app + +import lib.* + +def powerTest(x: Double): Unit = + power(x, 0) + power(x, 1) + power(x, 5) + power(x, 10) + +def letTest: Unit = + let(0) { _ + 1 } + let(0) { _.toString } + let((4, 'a')) { _.swap } + let(new Foo) { _.hashCode } + +class Foo diff --git a/sbt-test/scala3-compat/macros-forward-3.0/build.sbt b/sbt-test/scala3-compat/macros-forward-3.0/build.sbt new file mode 100644 index 000000000000..974fd6cff6cc --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/build.sbt @@ -0,0 +1,14 @@ +lazy val checkOptions = Seq("-Xcheck-macros", "-Ycheck:all", "-Yno-double-bindings") + +lazy val lib = project.in(file("lib")) + .settings( + scalacOptions ++= Seq("-scala-output-version", "3.0") ++ checkOptions + ) + +lazy val app = project.in(file("app")) + .dependsOn(lib) + .settings( + scalaVersion := "3.0.2", + scalacOptions ++= checkOptions, + dependencyOverrides += scalaOrganization.value %% "scala3-library" % scalaVersion.value, + ) diff --git a/sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala b/sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala new file mode 100644 index 000000000000..12e69a6ce4fd --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala @@ -0,0 +1,20 @@ +package lib + +import scala.quoted.* + +inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } + +private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + unrolledPowerCode(x, n.valueOrError) + +private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + if n == 0 then '{ 1.0 } // tests simple quotes without splices + else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices + else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture + + +inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } + +private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = + // tests use of Type + '{ val y: T = $x; $body(y): U } diff --git a/sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/scala3-compat/macros-forward-3.0/test b/sbt-test/scala3-compat/macros-forward-3.0/test new file mode 100644 index 000000000000..19aca297fdcf --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/test @@ -0,0 +1 @@ +> app/compile diff --git a/tests/disabled/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala b/tests/disabled/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala new file mode 100644 index 000000000000..fb06e93f91c0 --- /dev/null +++ b/tests/disabled/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala @@ -0,0 +1,20 @@ +import scala.quoted.* + +object Macros: + + inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } + + private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + unrolledPowerCode(x, n.valueOrError) + + private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + if n == 0 then '{ 1.0 } // tests simple quotes without splices + else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices + else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture + + + inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } + + private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = + // tests use of Type + '{ val y: T = $x; $body(y): U } diff --git a/tests/disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala b/tests/disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala new file mode 100644 index 000000000000..8c0a8004b9cf --- /dev/null +++ b/tests/disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala @@ -0,0 +1,15 @@ +import Macros.* + +def powerTest(x: Double): Unit = + power(x, 0) + power(x, 1) + power(x, 5) + power(x, 10) + +def letTest: Unit = + let(0) { _ + 1 } + let(0) { _.toString } + let((4, 'a')) { _.swap } + let(new Foo) { _.hashCode } + +class Foo diff --git a/tests/disabled/pos-macros/forwardCompat-3.0/why.md b/tests/disabled/pos-macros/forwardCompat-3.0/why.md new file mode 100644 index 000000000000..efb05ec1e0f4 --- /dev/null +++ b/tests/disabled/pos-macros/forwardCompat-3.0/why.md @@ -0,0 +1,3 @@ +Fails `testCompilation` as if the release flag was not set. But it was and the compile used it. + +Manual tests show that this does work. diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala b/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala new file mode 100644 index 000000000000..fb06e93f91c0 --- /dev/null +++ b/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala @@ -0,0 +1,20 @@ +import scala.quoted.* + +object Macros: + + inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } + + private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + unrolledPowerCode(x, n.valueOrError) + + private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + if n == 0 then '{ 1.0 } // tests simple quotes without splices + else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices + else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture + + + inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } + + private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = + // tests use of Type + '{ val y: T = $x; $body(y): U } diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala b/tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala new file mode 100644 index 000000000000..8c0a8004b9cf --- /dev/null +++ b/tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala @@ -0,0 +1,15 @@ +import Macros.* + +def powerTest(x: Double): Unit = + power(x, 0) + power(x, 1) + power(x, 5) + power(x, 10) + +def letTest: Unit = + let(0) { _ + 1 } + let(0) { _.toString } + let((4, 'a')) { _.swap } + let(new Foo) { _.hashCode } + +class Foo diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/why.md b/tests/disabled/pos-macros/forwardCompat-3.1/why.md new file mode 100644 index 000000000000..f281f9c08662 --- /dev/null +++ b/tests/disabled/pos-macros/forwardCompat-3.1/why.md @@ -0,0 +1 @@ +Disabled until https://github.com/lampepfl/dotty/issues/14306 is fixed