diff --git a/community-build/community-projects/ScalaPB b/community-build/community-projects/ScalaPB index 329e6d037627..79ecc8db710e 160000 --- a/community-build/community-projects/ScalaPB +++ b/community-build/community-projects/ScalaPB @@ -1 +1 @@ -Subproject commit 329e6d03762799d7c565859eb8df32ea16814a2e +Subproject commit 79ecc8db710e53f07ea12d1dfef80993bbd62e96 diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 8e87031d1674..8e59b4800d4f 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 8e87031d16741022bac42d2562961ac9092ca91a +Subproject commit 8e59b4800d4fb247e78a82314afec2ea233325e8 diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 5202666b5414..b3b4b55d19fb 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -77,6 +77,7 @@ class ScalaSettings extends Settings.SettingGroup { val XreplLineWidth: Setting[Int] = IntSetting("-Xrepl-line-width", "Maximal number of columns per line for REPL output", 390) val XfatalWarnings: Setting[Boolean] = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.") val XverifySignatures: Setting[Boolean] = BooleanSetting("-Xverify-signatures", "Verify generic signatures in generated bytecode.") + val XignoreScala2Macros: Setting[Boolean] = BooleanSetting("-Xignore-scala2-macros", "Ignore errors when compiling code that calls Scala2 macros, these will fail at runtime.") val XmixinForceForwarders = ChoiceSetting( name = "-Xmixin-force-forwarders", diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5c1c678dc02c..0af0526cf542 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -631,10 +631,17 @@ class Definitions { def StringContextS(implicit ctx: Context): Symbol = StringContextSR.symbol lazy val StringContextRawR: TermRef = StringContextClass.requiredMethodRef(nme.raw_) def StringContextRaw(implicit ctx: Context): Symbol = StringContextRawR.symbol + lazy val StringContext_fR: TermRef = StringContextClass.requiredMethodRef(nme.f) + def StringContext_f(implicit ctx: Context): Symbol = StringContext_fR.symbol def StringContextModule(implicit ctx: Context): Symbol = StringContextClass.companionModule lazy val StringContextModule_applyR: TermRef = StringContextModule.requiredMethodRef(nme.apply) def StringContextModule_apply(implicit ctx: Context): Symbol = StringContextModule_applyR.symbol + lazy val InternalStringContextModuleR: TermRef = ctx.requiredModuleRef("dotty.internal.StringContext") + def InternalStringContextModule(implicit ctx: Context): Symbol = InternalStringContextModuleR.termSymbol + lazy val InternalStringContextModule_fR: TermRef = InternalStringContextModule.requiredMethodRef(nme.f) + def InternalStringContextModule_f(implicit ctx: Context): Symbol = InternalStringContextModule_fR.symbol + lazy val PartialFunctionType: TypeRef = ctx.requiredClassRef("scala.PartialFunction") def PartialFunctionClass(implicit ctx: Context): ClassSymbol = PartialFunctionType.symbol.asClass lazy val PartialFunction_isDefinedAtR: TermRef = PartialFunctionClass.requiredMethodRef(nme.isDefinedAt) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 67dc4e6fe687..4aafd2e74fb7 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -661,9 +661,6 @@ object Flags { /** Is a default parameter in Scala 2*/ final val DefaultParameter: FlagConjunction = allOf(Param, DefaultParameterized) - /** A Scala 2 Macro */ - final val Scala2Macro: FlagConjunction = allOf(Macro, Scala2x) - /** A trait that does not need to be initialized */ final val NoInitsTrait: FlagConjunction = allOf(Trait, NoInits) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index e270cd66fdd1..dae8aa99e997 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -807,6 +807,9 @@ object SymDenotations { // we need an inline flag on them only do that // reduceProjection gets access to their rhs + /** Is this a Scala 2 macro */ + final def isScala2Macro(implicit ctx: Context): Boolean = is(Macro) && symbol.owner.is(Scala2x) + /** An erased value or an inline method, excluding @forceInline annotated methods. * The latter have to be kept around to get to parity with Scala. * This is necessary at least until we have full bootstrap. Right now diff --git a/compiler/src/dotty/tools/dotc/profile/Profiler.scala b/compiler/src/dotty/tools/dotc/profile/Profiler.scala index ecb9696a7fd5..69d493b78f3f 100644 --- a/compiler/src/dotty/tools/dotc/profile/Profiler.scala +++ b/compiler/src/dotty/tools/dotc/profile/Profiler.scala @@ -228,7 +228,7 @@ object ConsoleProfileReporter extends ProfileReporter { } override def reportGc(data: GcEventData): Unit = { - println(f"Profiler GC reported ${data.gcEndMillis - data.gcStartMillis}ms") + println(s"Profiler GC reported ${data.gcEndMillis - data.gcStartMillis}ms") } } diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 1f04a4c03b6f..da7211a6294c 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -400,10 +400,10 @@ object RefChecks { overrideError("is an extension method, cannot override a normal method") } else if (other.is(Extension) && !member.is(Extension)) { // (1.9.2) overrideError("is a normal method, cannot override an extension method") - } else if ((member.isInlineMethod || member.is(Scala2Macro)) && other.is(Deferred) && + } else if ((member.isInlineMethod || member.isScala2Macro) && other.is(Deferred) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.10) overrideError("is an inline method, must override at least one concrete method") - } else if (other.is(Scala2Macro) && !member.is(Scala2Macro)) { // (1.11) + } else if (other.isScala2Macro && !member.isScala2Macro) { // (1.11) overrideError("cannot be used here - only Scala-2 macros can override Scala-2 macros") } else if (!compatibleTypes(memberTp(self), otherTp(self)) && !compatibleTypes(memberTp(upwardsSelf), otherTp(upwardsSelf))) { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1c81ef0ec665..477d4d06270d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2753,6 +2753,24 @@ class Typer extends Namer tree.tpe <:< wildApprox(pt) readaptSimplified(Inliner.inlineCall(tree, pt)) } + else if (tree.symbol.isScala2Macro) { + if (ctx.settings.XignoreScala2Macros.value) { + ctx.warning("Scala 2 macro cannot be used in Dotty, this call will crash at runtime. See http://dotty.epfl.ch/docs/reference/dropped-features/macros.html", tree.sourcePos) + tree + } else if (tree.symbol eq defn.StringContext_f) { + // As scala.StringContext.f is defined in the standard library which + // we currently do not bootstrap we cannot implement the macro the library. + // To overcome the current limitation we intercept the call and rewrite it into + // a call to dotty.internal.StringContext.f which we can implement using the new macros. + // As the macro is implemented in the bootstrapped library, it can only be used from the bootstrapped compiler. + val Apply(TypeApply(Select(sc, _), _), args) = tree + val newCall = ref(defn.InternalStringContextModule_f).appliedTo(sc).appliedToArgs(args) + Inliner.inlineCall(newCall, pt) + } else { + ctx.error("Scala 2 macro cannot be used in Dotty. See http://dotty.epfl.ch/docs/reference/dropped-features/macros.html", tree.sourcePos) + tree + } + } else if (tree.tpe <:< pt) { if (pt.hasAnnotation(defn.InlineParamAnnot)) checkInlineConformant(tree, isFinal = false, "argument to inline parameter") diff --git a/compiler/test/dotty/tools/backend/jvm/AsmNode.scala b/compiler/test/dotty/tools/backend/jvm/AsmNode.scala index ac3f34258d67..1100c9054b4d 100644 --- a/compiler/test/dotty/tools/backend/jvm/AsmNode.scala +++ b/compiler/test/dotty/tools/backend/jvm/AsmNode.scala @@ -15,8 +15,8 @@ sealed trait AsmNode[+T] { def attrs: List[Attribute] def visibleAnnotations: List[AnnotationNode] def invisibleAnnotations: List[AnnotationNode] - def characteristics = f"$name%15s $desc%-30s$accessString$sigString" - def erasedCharacteristics = f"$name%15s $desc%-30s$accessString" + def characteristics = "%15s %-30s%s%s".format(name, desc, accessString, sigString) + def erasedCharacteristics = "%15s %-30s%s".format(name, desc, accessString) private def accessString = if (access == 0) "" else " " + Modifier.toString(access) private def sigString = if (signature == null) "" else " " + signature diff --git a/docs/docs/reference/dropped-features/macros.md b/docs/docs/reference/dropped-features/macros.md index 8a3fa7cd807b..ac0b93e1d1c3 100644 --- a/docs/docs/reference/dropped-features/macros.md +++ b/docs/docs/reference/dropped-features/macros.md @@ -1,13 +1,13 @@ --- layout: doc-page -title: Dropped: Macros +title: Dropped: Scala 2 Macros --- -The previous, experimental macro system has been dropped. Instead, -there is a cleaner, more restricted system based on two complementary -concepts: `inline` and `meta`. - -`inline` has been [implemented](../other-new-features/inline.md) in Dotty. - -`meta` is worked on in the separate [Scalameta](http://scalameta.org) project +The previous, experimental macro system has been dropped. Instead, there is a cleaner, more restricted system based on two complementary concepts: `inline` and `'{ ... }`/`${ ... }` code generation. +`'{ ... }` delays the compilation of the code and produces an object containing the code, dually `${ ... }` evaluates an expression which produces code and inserts it in the surrounding `${ ... }`. +In this setting, a definition marked as inlined containing a `${ ... }` is a macro, the code inside the `${ ... }` is executed at compile-time and produces code in the form of `'{ ... }`. +Additionally, the contents of code can be inspected and created with a more complex reflection API (TASTy Reflect) as an extension of `'{ ... }`/`${ ... }` framework. +* `inline` has been [implemented](../other-new-features/inline.md) in Dotty. +* Quotes `'{ ... }` and splices `${ ... }` has been [implemented](../other-new-features/principled-meta-programming.md) in Dotty. + * [TASTy reflect](../other-new-features/tasty-reflect.md) provides more complex tree based APIs to inspect or create quoted code. diff --git a/library/src-bootstrapped/dotty/internal/StringContext.scala b/library/src-bootstrapped/dotty/internal/StringContext.scala new file mode 100644 index 000000000000..04279c602c4c --- /dev/null +++ b/library/src-bootstrapped/dotty/internal/StringContext.scala @@ -0,0 +1,22 @@ +package dotty.internal + +import scala.quoted._ + +object StringContext { + + /** Implemetation of scala.StringContext.f used in Dotty while the standard library is still not bootstrapped */ + inline def f(sc: => scala.StringContext)(args: Any*): String = ${ fImpl('sc, 'args) } + + private def fImpl(sc: Expr[StringContext], args: Expr[Seq[Any]]): Expr[String] = { + // TODO implement f interpolation checks and generate optimal code + // See https://github.com/alemannosara/f-interpolators-in-Dotty-macros + '{ + // Purely runtime implementation of the f interpolation without any checks + val parts = $sc.parts.toList + assert(parts.nonEmpty, "StringContext should have non empty parts") + val parts2 = parts.head :: parts.tail.map(x => if (x.startsWith("%s")) x else "%s" + x) + parts2.mkString.format($args: _*) + } + } + +} diff --git a/library/src-non-bootstrapped/dotty/internal/StringContext.scala b/library/src-non-bootstrapped/dotty/internal/StringContext.scala new file mode 100644 index 000000000000..c09d39b67b3f --- /dev/null +++ b/library/src-non-bootstrapped/dotty/internal/StringContext.scala @@ -0,0 +1,8 @@ +package dotty.internal + +object StringContext { + + @forceInline def f(sc: => scala.StringContext)(args: Any*): String = + throw new Exception("non-boostrapped library") + +} diff --git a/project/Build.scala b/project/Build.scala index 429bab53705d..75b2ce43322f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -568,6 +568,12 @@ object Build { IO.delete(trgDir) IO.createDirectory(trgDir) IO.unzip(scalaJSIRSourcesJar, trgDir) + + // Remove f interpolator macro call to avoid its expansion while compiling the compiler and the implementation of the f macro + val utilsFile = trgDir / "org/scalajs/ir/Utils.scala" + val patchedSource = IO.read(utilsFile).replace("""f"\\u$c%04x"""", """"\\u%04x".format(c)""") + IO.write(utilsFile, patchedSource) + (trgDir ** "*.scala").get.toSet } (Set(scalaJSIRSourcesJar)).toSeq }.taskValue, diff --git a/tests/neg/override-scala2-macro.check b/tests/neg/override-scala2-macro.check new file mode 100644 index 000000000000..ff3f0ea88768 --- /dev/null +++ b/tests/neg/override-scala2-macro.check @@ -0,0 +1,3 @@ +<56..56> in override-scala2-macro.scala +error overriding method f in class StringContext of type [A >: Any](args: Seq[A]): String; + method f of type [A >: Any](args: Seq[A]): String cannot be used here - only Scala-2 macros can override Scala-2 macros diff --git a/tests/neg/override-scala2-macro.scala b/tests/neg/override-scala2-macro.scala new file mode 100644 index 000000000000..0bb048fdb0c0 --- /dev/null +++ b/tests/neg/override-scala2-macro.scala @@ -0,0 +1,3 @@ +class Foo extends StringContext { + override inline def f[A >: Any](args: A*): String = ??? // error +} diff --git a/tests/run-with-compiler/f-interpolator.check b/tests/run-with-compiler/f-interpolator.check new file mode 100644 index 000000000000..a937551a56a4 --- /dev/null +++ b/tests/run-with-compiler/f-interpolator.check @@ -0,0 +1,3 @@ +abc +Hello world! +Hello world! diff --git a/tests/run-with-compiler/f-interpolator.scala b/tests/run-with-compiler/f-interpolator.scala new file mode 100644 index 000000000000..32a1a4d1d981 --- /dev/null +++ b/tests/run-with-compiler/f-interpolator.scala @@ -0,0 +1,8 @@ + +object Test { + def main(args: Array[String]): Unit = { + println(f"abc") + println(f"Hello ${"world"}%s!") + println(f"Hello ${"world"}!") + } +}