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..e5af8efa5238 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,9 @@ 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 _ => // 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..83a5a2232dd9 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 = ??? @@ -35,7 +40,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 +50,15 @@ 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) } + // implemented in dotty.tools.dotc.typer.Inliner.Intrinsics + error("Compiler bug: `code` was not evaluated by the compiler") + end extension /** Checks at compiletime that the provided values is a constant after @@ -66,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("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 * 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("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 = ??? + inline def constValue[T]: T = + // implemented in dotty.tools.dotc.typer.Inliner + error("Compiler bug: `constValue` was not evaluated by the compiler") /** Given a tuple type `(X1, ..., Xn)`, returns a tuple value * `(constValue[X1], ..., constValue[Xn])`. @@ -104,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("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 690ecb1574d9..d0d5558e738f 100644 --- a/library/src/scala/compiletime/testing/package.scala +++ b/library/src/scala/compiletime/testing/package.scala @@ -11,7 +11,8 @@ 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 = - error("`typeChecks` was not checked by the compiler") + // implemented in package dotty.tools.dotc.typer.Inliner.Intrinsics + 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. @@ -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] = - error("`typeCheckErrors` was not checked by the compiler") + // implemented in package dotty.tools.dotc.typer.Inliner.Intrinsics + error("Compiler bug: `typeCheckErrors` was not checked by the compiler") } 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