Skip to content

Commit 54fb992

Browse files
Merge pull request #6179 from dotty-staging/fix-#6150
Fix #6150: Emit error when calling Scala 2 macro
2 parents aa59bef + 89412a0 commit 54fb992

File tree

18 files changed

+97
-18
lines changed

18 files changed

+97
-18
lines changed

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class ScalaSettings extends Settings.SettingGroup {
7777
val XreplLineWidth: Setting[Int] = IntSetting("-Xrepl-line-width", "Maximal number of columns per line for REPL output", 390)
7878
val XfatalWarnings: Setting[Boolean] = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.")
7979
val XverifySignatures: Setting[Boolean] = BooleanSetting("-Xverify-signatures", "Verify generic signatures in generated bytecode.")
80+
val XignoreScala2Macros: Setting[Boolean] = BooleanSetting("-Xignore-scala2-macros", "Ignore errors when compiling code that calls Scala2 macros, these will fail at runtime.")
8081

8182
val XmixinForceForwarders = ChoiceSetting(
8283
name = "-Xmixin-force-forwarders",

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,10 +631,17 @@ class Definitions {
631631
def StringContextS(implicit ctx: Context): Symbol = StringContextSR.symbol
632632
lazy val StringContextRawR: TermRef = StringContextClass.requiredMethodRef(nme.raw_)
633633
def StringContextRaw(implicit ctx: Context): Symbol = StringContextRawR.symbol
634+
lazy val StringContext_fR: TermRef = StringContextClass.requiredMethodRef(nme.f)
635+
def StringContext_f(implicit ctx: Context): Symbol = StringContext_fR.symbol
634636
def StringContextModule(implicit ctx: Context): Symbol = StringContextClass.companionModule
635637
lazy val StringContextModule_applyR: TermRef = StringContextModule.requiredMethodRef(nme.apply)
636638
def StringContextModule_apply(implicit ctx: Context): Symbol = StringContextModule_applyR.symbol
637639

640+
lazy val InternalStringContextModuleR: TermRef = ctx.requiredModuleRef("dotty.internal.StringContext")
641+
def InternalStringContextModule(implicit ctx: Context): Symbol = InternalStringContextModuleR.termSymbol
642+
lazy val InternalStringContextModule_fR: TermRef = InternalStringContextModule.requiredMethodRef(nme.f)
643+
def InternalStringContextModule_f(implicit ctx: Context): Symbol = InternalStringContextModule_fR.symbol
644+
638645
lazy val PartialFunctionType: TypeRef = ctx.requiredClassRef("scala.PartialFunction")
639646
def PartialFunctionClass(implicit ctx: Context): ClassSymbol = PartialFunctionType.symbol.asClass
640647
lazy val PartialFunction_isDefinedAtR: TermRef = PartialFunctionClass.requiredMethodRef(nme.isDefinedAt)

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -661,9 +661,6 @@ object Flags {
661661
/** Is a default parameter in Scala 2*/
662662
final val DefaultParameter: FlagConjunction = allOf(Param, DefaultParameterized)
663663

664-
/** A Scala 2 Macro */
665-
final val Scala2Macro: FlagConjunction = allOf(Macro, Scala2x)
666-
667664
/** A trait that does not need to be initialized */
668665
final val NoInitsTrait: FlagConjunction = allOf(Trait, NoInits)
669666

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,9 @@ object SymDenotations {
807807
// we need an inline flag on them only do that
808808
// reduceProjection gets access to their rhs
809809

810+
/** Is this a Scala 2 macro */
811+
final def isScala2Macro(implicit ctx: Context): Boolean = is(Macro) && symbol.owner.is(Scala2x)
812+
810813
/** An erased value or an inline method, excluding @forceInline annotated methods.
811814
* The latter have to be kept around to get to parity with Scala.
812815
* This is necessary at least until we have full bootstrap. Right now

compiler/src/dotty/tools/dotc/profile/Profiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ object ConsoleProfileReporter extends ProfileReporter {
228228
}
229229

230230
override def reportGc(data: GcEventData): Unit = {
231-
println(f"Profiler GC reported ${data.gcEndMillis - data.gcStartMillis}ms")
231+
println(s"Profiler GC reported ${data.gcEndMillis - data.gcStartMillis}ms")
232232
}
233233
}
234234

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,10 +400,10 @@ object RefChecks {
400400
overrideError("is an extension method, cannot override a normal method")
401401
} else if (other.is(Extension) && !member.is(Extension)) { // (1.9.2)
402402
overrideError("is a normal method, cannot override an extension method")
403-
} else if ((member.isInlineMethod || member.is(Scala2Macro)) && other.is(Deferred) &&
403+
} else if ((member.isInlineMethod || member.isScala2Macro) && other.is(Deferred) &&
404404
member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.10)
405405
overrideError("is an inline method, must override at least one concrete method")
406-
} else if (other.is(Scala2Macro) && !member.is(Scala2Macro)) { // (1.11)
406+
} else if (other.isScala2Macro && !member.isScala2Macro) { // (1.11)
407407
overrideError("cannot be used here - only Scala-2 macros can override Scala-2 macros")
408408
} else if (!compatibleTypes(memberTp(self), otherTp(self)) &&
409409
!compatibleTypes(memberTp(upwardsSelf), otherTp(upwardsSelf))) {

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2756,6 +2756,24 @@ class Typer extends Namer
27562756
tree.tpe <:< wildApprox(pt)
27572757
readaptSimplified(Inliner.inlineCall(tree, pt))
27582758
}
2759+
else if (tree.symbol.isScala2Macro) {
2760+
if (ctx.settings.XignoreScala2Macros.value) {
2761+
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)
2762+
tree
2763+
} else if (tree.symbol eq defn.StringContext_f) {
2764+
// As scala.StringContext.f is defined in the standard library which
2765+
// we currently do not bootstrap we cannot implement the macro the library.
2766+
// To overcome the current limitation we intercept the call and rewrite it into
2767+
// a call to dotty.internal.StringContext.f which we can implement using the new macros.
2768+
// As the macro is implemented in the bootstrapped library, it can only be used from the bootstrapped compiler.
2769+
val Apply(TypeApply(Select(sc, _), _), args) = tree
2770+
val newCall = ref(defn.InternalStringContextModule_f).appliedTo(sc).appliedToArgs(args)
2771+
Inliner.inlineCall(newCall, pt)
2772+
} else {
2773+
ctx.error("Scala 2 macro cannot be used in Dotty. See http://dotty.epfl.ch/docs/reference/dropped-features/macros.html", tree.sourcePos)
2774+
tree
2775+
}
2776+
}
27592777
else if (tree.tpe <:< pt) {
27602778
if (pt.hasAnnotation(defn.InlineParamAnnot))
27612779
checkInlineConformant(tree, isFinal = false, "argument to inline parameter")

compiler/test/dotty/tools/backend/jvm/AsmNode.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ sealed trait AsmNode[+T] {
1515
def attrs: List[Attribute]
1616
def visibleAnnotations: List[AnnotationNode]
1717
def invisibleAnnotations: List[AnnotationNode]
18-
def characteristics = f"$name%15s $desc%-30s$accessString$sigString"
19-
def erasedCharacteristics = f"$name%15s $desc%-30s$accessString"
18+
def characteristics = "%15s %-30s%s%s".format(name, desc, accessString, sigString)
19+
def erasedCharacteristics = "%15s %-30s%s".format(name, desc, accessString)
2020

2121
private def accessString = if (access == 0) "" else " " + Modifier.toString(access)
2222
private def sigString = if (signature == null) "" else " " + signature
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
---
22
layout: doc-page
3-
title: Dropped: Macros
3+
title: Dropped: Scala 2 Macros
44
---
55

6-
The previous, experimental macro system has been dropped. Instead,
7-
there is a cleaner, more restricted system based on two complementary
8-
concepts: `inline` and `meta`.
9-
10-
`inline` has been [implemented](../other-new-features/inline.md) in Dotty.
11-
12-
`meta` is worked on in the separate [Scalameta](http://scalameta.org) project
6+
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.
7+
`'{ ... }` 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 `${ ... }`.
8+
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 `'{ ... }`.
9+
Additionally, the contents of code can be inspected and created with a more complex reflection API (TASTy Reflect) as an extension of `'{ ... }`/`${ ... }` framework.
1310

11+
* `inline` has been [implemented](../other-new-features/inline.md) in Dotty.
12+
* Quotes `'{ ... }` and splices `${ ... }` has been [implemented](../other-new-features/principled-meta-programming.md) in Dotty.
13+
* [TASTy reflect](../other-new-features/tasty-reflect.md) provides more complex tree based APIs to inspect or create quoted code.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package dotty.internal
2+
3+
import scala.quoted._
4+
5+
object StringContext {
6+
7+
/** Implemetation of scala.StringContext.f used in Dotty while the standard library is still not bootstrapped */
8+
inline def f(sc: => scala.StringContext)(args: Any*): String = ${ fImpl('sc, 'args) }
9+
10+
private def fImpl(sc: Expr[StringContext], args: Expr[Seq[Any]]): Expr[String] = {
11+
// TODO implement f interpolation checks and generate optimal code
12+
// See https://github.com/alemannosara/f-interpolators-in-Dotty-macros
13+
'{
14+
// Purely runtime implementation of the f interpolation without any checks
15+
val parts = $sc.parts.toList
16+
assert(parts.nonEmpty, "StringContext should have non empty parts")
17+
val parts2 = parts.head :: parts.tail.map(x => if (x.startsWith("%s")) x else "%s" + x)
18+
parts2.mkString.format($args: _*)
19+
}
20+
}
21+
22+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package dotty.internal
2+
3+
object StringContext {
4+
5+
@forceInline def f(sc: => scala.StringContext)(args: Any*): String =
6+
throw new Exception("non-boostrapped library")
7+
8+
}

project/Build.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,12 @@ object Build {
568568
IO.delete(trgDir)
569569
IO.createDirectory(trgDir)
570570
IO.unzip(scalaJSIRSourcesJar, trgDir)
571+
572+
// Remove f interpolator macro call to avoid its expansion while compiling the compiler and the implementation of the f macro
573+
val utilsFile = trgDir / "org/scalajs/ir/Utils.scala"
574+
val patchedSource = IO.read(utilsFile).replace("""f"\\u$c%04x"""", """"\\u%04x".format(c)""")
575+
IO.write(utilsFile, patchedSource)
576+
571577
(trgDir ** "*.scala").get.toSet
572578
} (Set(scalaJSIRSourcesJar)).toSeq
573579
}.taskValue,

tests/neg/override-scala2-macro.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<56..56> in override-scala2-macro.scala
2+
error overriding method f in class StringContext of type [A >: Any](args: Seq[A]): String;
3+
method f of type [A >: Any](args: Seq[A]): String cannot be used here - only Scala-2 macros can override Scala-2 macros

tests/neg/override-scala2-macro.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Foo extends StringContext {
2+
override inline def f[A >: Any](args: A*): String = ??? // error
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
abc
2+
Hello world!
3+
Hello world!
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
println(f"abc")
5+
println(f"Hello ${"world"}%s!")
6+
println(f"Hello ${"world"}!")
7+
}
8+
}

0 commit comments

Comments
 (0)