From a8ecfe5fd10a8954f2b22f9aa4eb379a9c57ffe7 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 12 Nov 2020 16:50:13 +0100 Subject: [PATCH 1/4] Intrinsify scala.compiletime.code --- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../src/dotty/tools/dotc/typer/Inliner.scala | 28 +++++++++++++++++++ .../dotty/internal/CompileTimeMacros.scala | 11 -------- .../dotty/internal/CompileTimeMacros.scala | 7 ----- library/src/scala/compiletime/package.scala | 10 ++++--- .../beta-reduce-inline-result.check | 4 +-- tests/run-macros/i6622.check | 6 ++++ tests/run-macros/i6622.scala | 12 ++++---- 8 files changed, 49 insertions(+), 30 deletions(-) delete mode 100644 library/src-bootstrapped/dotty/internal/CompileTimeMacros.scala delete mode 100644 library/src-non-bootstrapped/dotty/internal/CompileTimeMacros.scala create mode 100644 tests/run-macros/i6622.check diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 26581fb60302..6860b3ae3735 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -224,6 +224,7 @@ class Definitions { @tu lazy val ScalaXmlPackageClass: Symbol = getPackageClassIfDefined("scala.xml") @tu lazy val CompiletimePackageObject: Symbol = requiredModule("scala.compiletime.package") + @tu lazy val Compiletime_code: Symbol = CompiletimePackageObject.requiredMethod("code") @tu lazy val Compiletime_erasedValue : Symbol = CompiletimePackageObject.requiredMethod("erasedValue") @tu lazy val Compiletime_error : Symbol = CompiletimePackageObject.requiredMethod(nme.error) @tu lazy val Compiletime_requireConst: Symbol = CompiletimePackageObject.requiredMethod("requireConst") diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 1bbfcbd378c5..dc30b3a8c47d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -332,6 +332,32 @@ object Inliner { def typeCheckErrors(tree: Tree)(using Context): Tree = val errors = compileForErrors(tree, false) packErrors(errors) + + def code(strCtx: Tree, argsTree: Tree, pos: SrcPos)(using Context): Tree = + object Consts: + def unapply(trees: List[Tree]): Option[List[String]] = + trees match + case Nil => Some(Nil) + case Literal(Constant(part: String)) :: Consts(rest) => Some(part :: rest) + case _ => None + strCtx match + case Apply(stringContextApply, List(Typed(SeqLiteral(Consts(parts), _), _))) + if stringContextApply.symbol == defn.StringContextModule_apply => + argsTree match + case Typed(SeqLiteral(args, _), _) => + if parts.size == args.size + 1 then + val argStrs = args.map(_.show) + val showed = StringContext(parts: _*).s(argStrs: _*) + Literal(Constant(showed)).withSpan(pos.span) + else + report.error("Wrong number of arguments for StringContext.s", strCtx.srcPos) + ref(defn.Predef_undefined) + case _ => + report.error("Exprected explicit arguments to code", strCtx.srcPos) + ref(defn.Predef_undefined) + case _ => + report.error("Exprected StringContext.apply with explicit `parts` arguments", strCtx.srcPos) + ref(defn.Predef_undefined) } } @@ -622,6 +648,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { arg match case ConstantValue(_) | Inlined(_, Nil, Typed(ConstantValue(_), _)) => // ok case _ => report.error(em"expected a constant value but found: $arg", arg.srcPos) + case (strCtx :: Nil) :: (args :: Nil) :: Nil if inlinedMethod == defn.Compiletime_code => + return Intrinsics.code(strCtx, args, call.srcPos) case _ => // Special handling of `constValue[T]` and `constValueOpt[T]` diff --git a/library/src-bootstrapped/dotty/internal/CompileTimeMacros.scala b/library/src-bootstrapped/dotty/internal/CompileTimeMacros.scala deleted file mode 100644 index 2e2d0cc6f224..000000000000 --- a/library/src-bootstrapped/dotty/internal/CompileTimeMacros.scala +++ /dev/null @@ -1,11 +0,0 @@ -package dotty.internal - -import scala.quoted._ - -object CompileTimeMacros: - def codeExpr(using qctx: QuoteContext)(sc: Expr[StringContext], args: Expr[Seq[Any]]): Expr[String] = - (sc, args) match - case (Expr.StringContext(Consts(parts)), Varargs(args2)) => - Expr(StringContext(parts: _*).s(args2.map(_.show): _*)) - case _ => - report.throwError("compiletime.code must be used as a string interpolator `code\"...\"`") diff --git a/library/src-non-bootstrapped/dotty/internal/CompileTimeMacros.scala b/library/src-non-bootstrapped/dotty/internal/CompileTimeMacros.scala deleted file mode 100644 index 0643f1db8cfe..000000000000 --- a/library/src-non-bootstrapped/dotty/internal/CompileTimeMacros.scala +++ /dev/null @@ -1,7 +0,0 @@ -package dotty.internal - -import scala.quoted._ - -object CompileTimeMacros: - def codeExpr(using qctx: QuoteContext)(sc: Expr[StringContext], args: Expr[Seq[Any]]): Expr[String] = - throw new Exception("Non bootstrapped library") diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 372bc4fbfaa1..570e7a3ed856 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -35,7 +35,7 @@ package object compiletime { /** Returns the string representation of interpolated elaborated code: * * ```scala - * inline def logged(p1: => Any) = { + * inline def logged(inline p1: Any) = { * val c = code"code: $p1" * val res = p1 * (c, p1) @@ -45,11 +45,13 @@ package object compiletime { * // ("code: scala.Predef.identity("foo")", identity("foo")) * ``` * - * @note only by-name arguments will be displayed as "code". + * The formatting of the code is not stable across version of the compiler. + * + * @note only `inline` arguments will be displayed as "code". * Other values may display unintutively. */ - transparent inline def code (inline args: Any*): String = - ${ dotty.internal.CompileTimeMacros.codeExpr('self, 'args) } + transparent inline def code (inline args: Any*): String = ??? + end extension /** Checks at compiletime that the provided values is a constant after diff --git a/tests/run-macros/beta-reduce-inline-result.check b/tests/run-macros/beta-reduce-inline-result.check index e237698be3a2..f1c651a532ba 100644 --- a/tests/run-macros/beta-reduce-inline-result.check +++ b/tests/run-macros/beta-reduce-inline-result.check @@ -1,6 +1,6 @@ -compile-time: (4: scala.Int) +compile-time: 4:Int run-time: 4 -compile-time: (1: scala.Int) +compile-time: 1:Int run-time: 1 run-time: 5 run-time: 7 diff --git a/tests/run-macros/i6622.check b/tests/run-macros/i6622.check new file mode 100644 index 000000000000..951a493b2f45 --- /dev/null +++ b/tests/run-macros/i6622.check @@ -0,0 +1,6 @@ +abc println(34) ... +abc println(34) +println(34) ... +println(34) +... + diff --git a/tests/run-macros/i6622.scala b/tests/run-macros/i6622.scala index 449a7c64b72c..566877fcc8ca 100644 --- a/tests/run-macros/i6622.scala +++ b/tests/run-macros/i6622.scala @@ -3,12 +3,12 @@ import scala.compiletime._ object Test { def main(args: Array[String]): Unit = { - assert(code"abc ${println(34)} ..." == "abc scala.Predef.println(34) ...") - assert(code"abc ${println(34)}" == "abc scala.Predef.println(34)") - assert(code"${println(34)} ..." == "scala.Predef.println(34) ...") - assert(code"${println(34)}" == "scala.Predef.println(34)") - assert(code"..." == "...") - assert(testConstant(code"") == "") + println(code"abc ${println(34)} ...") + println(code"abc ${println(34)}") + println(code"${println(34)} ...") + println(code"${println(34)}") + println(code"...") + println(testConstant(code"")) } inline def testConstant(inline msg: String): String = msg From d0917c531c3902f40e6fa433c377420f77a94ce4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 13 Nov 2020 08:49:56 +0100 Subject: [PATCH 2/4] Add comments and better error messages in case of compiler bug --- library/src/scala/compiletime/package.scala | 25 ++++++++++++++----- .../scala/compiletime/testing/package.scala | 2 ++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 570e7a3ed856..937ae956b672 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -28,6 +28,11 @@ package object compiletime { * ```scala * error(code"My error of this code: ${println("foo")}") * ``` + * or + * ```scala + * inline def errorOnThisCode(inline x: Any) = + * error(code"My error of this code: $x") + * ``` */ inline def error(inline msg: String): Nothing = ??? @@ -50,7 +55,9 @@ package object compiletime { * @note only `inline` arguments will be displayed as "code". * Other values may display unintutively. */ - transparent inline def code (inline args: Any*): String = ??? + transparent inline def code (inline args: Any*): String = + // implemented in dotty.tools.dotc.typer.Inliner.Intrinsics + error("`code` was not evaluated by the compiler") end extension @@ -68,18 +75,24 @@ package object compiletime { * twice(m) // error: expected a constant value but found: m * ``` */ - inline def requireConst(inline x: Boolean | Byte | Short | Int | Long | Float | Double | Char | String): Unit = () + inline def requireConst(inline x: Boolean | Byte | Short | Int | Long | Float | Double | Char | String): Unit = + // implemented in dotty.tools.dotc.typer.Inliner + error("`requireConst` was not evaluated by the compiler") /** Same as `constValue` but returns a `None` if a constant value * cannot be constructed from the provided type. Otherwise returns * that value wrapped in `Some`. */ - inline def constValueOpt[T]: Option[T] = ??? + inline def constValueOpt[T]: Option[T] = + // implemented in dotty.tools.dotc.typer.Inliner + error("`constValueOpt` was not evaluated by the compiler") /** Given a constant, singleton type `T`, convert it to a value * of the same singleton type. For example: `assert(constValue[1] == 1)`. */ - inline def constValue[T]: T = ??? + inline def constValue[T]: T = + // implemented in dotty.tools.dotc.typer.Inliner + error("`constValue` was not evaluated by the compiler") /** Given a tuple type `(X1, ..., Xn)`, returns a tuple value * `(constValue[X1], ..., constValue[Xn])`. @@ -106,8 +119,8 @@ package object compiletime { * * the returned value would be `2`. */ - transparent inline def summonFrom[T](f: Nothing => T): T = ??? - + transparent inline def summonFrom[T](f: Nothing => T): T = + error("`summonFrom` was not evaluated by the compiler") /** Summon a given value of type `T`. Usually, the argument is not passed explicitly. * The summoning is delayed until the call has been fully inlined. diff --git a/library/src/scala/compiletime/testing/package.scala b/library/src/scala/compiletime/testing/package.scala index 690ecb1574d9..9d5cb0934e67 100644 --- a/library/src/scala/compiletime/testing/package.scala +++ b/library/src/scala/compiletime/testing/package.scala @@ -11,6 +11,7 @@ package object testing { * The code should be a sequence of expressions or statements that may appear in a block. */ inline def typeChecks(inline code: String): Boolean = + // implemented in package dotty.tools.dotc.typer.Inliner.Intrinsics error("`typeChecks` was not checked by the compiler") /** Whether the code type checks in the current context? If not, @@ -27,5 +28,6 @@ package object testing { * The code should be a sequence of expressions or statements that may appear in a block. */ inline def typeCheckErrors(inline code: String): List[Error] = + // implemented in package dotty.tools.dotc.typer.Inliner.Intrinsics error("`typeCheckErrors` was not checked by the compiler") } From c561b118338cd84cfe69039f08ca827d2ed77e8f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 13 Nov 2020 13:02:00 +0100 Subject: [PATCH 3/4] Fix requireConst code inlining --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index dc30b3a8c47d..e5af8efa5238 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -648,6 +648,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { arg match case ConstantValue(_) | Inlined(_, Nil, Typed(ConstantValue(_), _)) => // ok case _ => report.error(em"expected a constant value but found: $arg", arg.srcPos) + return Literal(Constant(())).withSpan(sourcePos.span) case (strCtx :: Nil) :: (args :: Nil) :: Nil if inlinedMethod == defn.Compiletime_code => return Intrinsics.code(strCtx, args, call.srcPos) case _ => From ee00a1d8697e7fdc7c12f422501f24f579652cc4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 13 Nov 2020 17:37:40 +0100 Subject: [PATCH 4/4] Indicate the the errors are compiler bugs --- library/src/scala/compiletime/package.scala | 10 +++++----- library/src/scala/compiletime/testing/package.scala | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 937ae956b672..83a5a2232dd9 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -57,7 +57,7 @@ package object compiletime { */ transparent inline def code (inline args: Any*): String = // implemented in dotty.tools.dotc.typer.Inliner.Intrinsics - error("`code` was not evaluated by the compiler") + error("Compiler bug: `code` was not evaluated by the compiler") end extension @@ -77,7 +77,7 @@ package object compiletime { */ inline def requireConst(inline x: Boolean | Byte | Short | Int | Long | Float | Double | Char | String): Unit = // implemented in dotty.tools.dotc.typer.Inliner - error("`requireConst` was not evaluated by the compiler") + error("Compiler bug: `requireConst` was not evaluated by the compiler") /** Same as `constValue` but returns a `None` if a constant value * cannot be constructed from the provided type. Otherwise returns @@ -85,14 +85,14 @@ package object compiletime { */ inline def constValueOpt[T]: Option[T] = // implemented in dotty.tools.dotc.typer.Inliner - error("`constValueOpt` was not evaluated by the compiler") + error("Compiler bug: `constValueOpt` was not evaluated by the compiler") /** Given a constant, singleton type `T`, convert it to a value * of the same singleton type. For example: `assert(constValue[1] == 1)`. */ inline def constValue[T]: T = // implemented in dotty.tools.dotc.typer.Inliner - error("`constValue` was not evaluated by the compiler") + error("Compiler bug: `constValue` was not evaluated by the compiler") /** Given a tuple type `(X1, ..., Xn)`, returns a tuple value * `(constValue[X1], ..., constValue[Xn])`. @@ -120,7 +120,7 @@ package object compiletime { * the returned value would be `2`. */ transparent inline def summonFrom[T](f: Nothing => T): T = - error("`summonFrom` was not evaluated by the compiler") + error("Compiler bug: `summonFrom` was not evaluated by the compiler") /** Summon a given value of type `T`. Usually, the argument is not passed explicitly. * The summoning is delayed until the call has been fully inlined. diff --git a/library/src/scala/compiletime/testing/package.scala b/library/src/scala/compiletime/testing/package.scala index 9d5cb0934e67..d0d5558e738f 100644 --- a/library/src/scala/compiletime/testing/package.scala +++ b/library/src/scala/compiletime/testing/package.scala @@ -12,7 +12,7 @@ package object testing { */ inline def typeChecks(inline code: String): Boolean = // implemented in package dotty.tools.dotc.typer.Inliner.Intrinsics - error("`typeChecks` was not checked by the compiler") + error("Compiler bug: `typeChecks` was not checked by the compiler") /** Whether the code type checks in the current context? If not, * returns a list of errors encountered on compilation. @@ -29,5 +29,5 @@ package object testing { */ inline def typeCheckErrors(inline code: String): List[Error] = // implemented in package dotty.tools.dotc.typer.Inliner.Intrinsics - error("`typeCheckErrors` was not checked by the compiler") + error("Compiler bug: `typeCheckErrors` was not checked by the compiler") }