Skip to content

Fix #6150: Emit error when calling Scala 2 macro #6179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion community-build/community-projects/ScalaPB
Submodule ScalaPB updated 1 files
+1 −1 build.sbt
2 changes: 1 addition & 1 deletion community-build/community-projects/scalatest
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 0 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/profile/Profiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))) {
Expand Down
18 changes: 18 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions compiler/test/dotty/tools/backend/jvm/AsmNode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions docs/docs/reference/dropped-features/macros.md
Original file line number Diff line number Diff line change
@@ -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.
22 changes: 22 additions & 0 deletions library/src-bootstrapped/dotty/internal/StringContext.scala
Original file line number Diff line number Diff line change
@@ -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: _*)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dotty.internal

object StringContext {

@forceInline def f(sc: => scala.StringContext)(args: Any*): String =
throw new Exception("non-boostrapped library")

}
6 changes: 6 additions & 0 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions tests/neg/override-scala2-macro.check
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions tests/neg/override-scala2-macro.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Foo extends StringContext {
override inline def f[A >: Any](args: A*): String = ??? // error
}
3 changes: 3 additions & 0 deletions tests/run-with-compiler/f-interpolator.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
abc
Hello world!
Hello world!
8 changes: 8 additions & 0 deletions tests/run-with-compiler/f-interpolator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

object Test {
def main(args: Array[String]): Unit = {
println(f"abc")
println(f"Hello ${"world"}%s!")
println(f"Hello ${"world"}!")
}
}