Skip to content

Intrinsify StringContext.f #9929

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
Show file tree
Hide file tree
Changes from 3 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
14 changes: 10 additions & 4 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,9 @@ class Definitions {
@tu lazy val Seq_length : Symbol = SeqClass.requiredMethod(nme.length)
@tu lazy val Seq_toSeq : Symbol = SeqClass.requiredMethod(nme.toSeq)

@tu lazy val StringOps: Symbol = requiredClass("scala.collection.StringOps")
@tu lazy val StringOps_format: Symbol = StringOps.requiredMethod(nme.format)

@tu lazy val ArrayType: TypeRef = requiredClassRef("scala.Array")
def ArrayClass(using Context): ClassSymbol = ArrayType.symbol.asClass
@tu lazy val Array_apply : Symbol = ArrayClass.requiredMethod(nme.apply)
Expand Down Expand Up @@ -684,7 +687,13 @@ class Definitions {
@tu lazy val SerializableType: TypeRef = JavaSerializableClass.typeRef
def SerializableClass(using Context): ClassSymbol = SerializableType.symbol.asClass

@tu lazy val JavaEnumClass: ClassSymbol = {
@tu lazy val JavaBigIntegerClass: ClassSymbol = requiredClass("java.math.BigInteger")
@tu lazy val JavaBigDecimalClass: ClassSymbol = requiredClass("java.math.BigDecimal")
@tu lazy val JavaCalendarClass: ClassSymbol = requiredClass("java.util.Calendar")
@tu lazy val JavaDateClass: ClassSymbol = requiredClass("java.util.Date")
@tu lazy val JavaFormattableClass: ClassSymbol = requiredClass("java.util.Formattable")

@tu lazy val JavaEnumClass: ClassSymbol = {
val cls = requiredClass("java.lang.Enum")
// jl.Enum has a single constructor protected(name: String, ordinal: Int).
// We remove the arguments from the primary constructor, and enter
Expand Down Expand Up @@ -733,9 +742,6 @@ class Definitions {
@tu lazy val StringContextModule_standardInterpolator: Symbol = StringContextModule.requiredMethod(nme.standardInterpolator)
@tu lazy val StringContextModule_processEscapes: Symbol = StringContextModule.requiredMethod(nme.processEscapes)

@tu lazy val InternalStringContextMacroModule: Symbol = requiredModule("dotty.internal.StringContextMacro")
@tu lazy val InternalStringContextMacroModule_f: Symbol = InternalStringContextMacroModule.requiredMethod(nme.f)

@tu lazy val PartialFunctionClass: ClassSymbol = requiredClass("scala.PartialFunction")
@tu lazy val PartialFunction_isDefinedAt: Symbol = PartialFunctionClass.requiredMethod(nme.isDefinedAt)
@tu lazy val PartialFunction_applyOrElse: Symbol = PartialFunctionClass.requiredMethod(nme.applyOrElse)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ object StdNames {
val flagsFromBits : N = "flagsFromBits"
val flatMap: N = "flatMap"
val foreach: N = "foreach"
val format: N = "format"
val fromDigits: N = "fromDigits"
val fromProduct: N = "fromProduct"
val genericArrayOps: N = "genericArrayOps"
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import dotty.tools.dotc.core.Decorators._
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.StdNames._
import dotty.tools.dotc.core.NameKinds._
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.core.Types.MethodType
import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.transform.MegaPhase.MiniPhase

/**
Expand Down Expand Up @@ -116,6 +117,7 @@ class StringInterpolatorOpt extends MiniPhase {
val sym = tree.symbol
val isInterpolatedMethod = // Test names first to avoid loading scala.StringContext if not used
(sym.name == nme.raw_ && sym.eq(defn.StringContext_raw)) ||
(sym.name == nme.f && sym.eq(defn.StringContext_f)) ||
(sym.name == nme.s && sym.eq(defn.StringContext_s))
if (isInterpolatedMethod)
tree match {
Expand All @@ -132,6 +134,11 @@ class StringInterpolatorOpt extends MiniPhase {
if (!str.const.stringValue.isEmpty) concat(str)
}
result
case Apply(intp, args :: Nil) if sym.eq(defn.StringContext_f) =>
val partsStr = StringContextChecker.checkedParts(intp, args).mkString
resolveConstructor(defn.StringOps.typeRef, List(Literal(Constant(partsStr))))
.select(nme.format)
.appliedTo(args)
// Starting with Scala 2.13, s and raw are macros in the standard
// library, so we need to expand them manually.
// sc.s(args) --> standardInterpolator(processEscapes, args, sc.parts)
Expand Down
15 changes: 2 additions & 13 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3257,21 +3257,10 @@ class Typer extends Namer
if ((inlined ne tree) && errorCount == ctx.reporter.errorCount) readaptSimplified(inlined)
else inlined
}
else if tree.symbol.name == nme.f && tree.symbol == defn.StringContext_f then
// To avoid forcing StringContext_f when compiling StingContex
// we test the name before accession symbol StringContext_f.

// As scala.StringContext.f is defined in the standard library which
// we currently do not bootstrap we cannot implement the macro in 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.InternalStringContextMacroModule_f).appliedTo(sc).appliedToArgs(args).withSpan(tree.span)
readaptSimplified(Inliner.inlineCall(newCall))
else if (tree.symbol.isScala2Macro &&
// raw and s are eliminated by the StringInterpolatorOpt phase
// `raw`, `f` and `s` are eliminated by the StringInterpolatorOpt phase
tree.symbol != defn.StringContext_raw &&
tree.symbol != defn.StringContext_f &&
tree.symbol != defn.StringContext_s)
if (ctx.settings.XignoreScala2Macros.value) {
report.warning("Scala 2 macro cannot be used in Dotty, this call will crash at runtime. See https://dotty.epfl.ch/docs/reference/dropped-features/macros.html", tree.srcPos.startPos)
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,6 @@ object Build {
)).get

++ (dir / "js/src/test/scala/org/scalajs/testsuite/javalib" ** (("*.scala": FileFilter)
-- "FormatterJSTest.scala" // compile error with the f"" interpolator
-- "ObjectJSTest.scala" // non-native JS classes
)).get

Expand Down
83 changes: 83 additions & 0 deletions tests/neg/f-interpolator-neg.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
object Test {

def numberArgumentsTests(s : String, d : Int) = {
new StringContext().f() // error
new StringContext("", " is ", "%2d years old").f(s) // error
new StringContext("", " is ", "%2d years old").f(s, d, d) // error
new StringContext("", "").f() // error
}

def interpolationMismatches(s : String, f : Double, b : Boolean) = {
f"$s%b" // error
f"$s%c" // error
f"$f%c" // error
f"$s%x" // error
f"$b%d" // error
f"$s%d" // error
f"$f%o" // error
f"$s%e" // error
f"$b%f" // error
f"$s%i" // error
}

def flagMismatches(s : String, c : Char, d : Int, f : Double, t : java.util.Date) = {
f"$s%+ 0,(s" // error
f"$c%#+ 0,(c" // error
f"$d%#d" // error
f"$d%,x" // error
f"$d%+ (x" // error
f"$f%,(a" // error
f"$t%#+ 0,(tT" // error
f"%-#+ 0,(n" // error
f"%#+ 0,(%" // error
}

def badPrecisions(c : Char, d : Int, f : Double, t : java.util.Date) = {
f"$c%.2c" // error
f"$d%.2d" // error
f"%.2%" // error
f"%.2n" // error
f"$f%.2a" // error
f"$t%.2tT" // error
}

def badIndexes() = {
f"%<s" // error
f"%<c" // error
f"%<tT" // error
f"${8}%d ${9}%d %3$$d" // error
f"${8}%d ${9}%d%0$$d" // error
}

def warnings(s : String) = {
f"${8}%d ${9}%1$$d"
f"$s%s $s%s %1$$<s"
f"$s%s $s%1$$s"
}

def badArgTypes(s : String) = {
f"$s%#s" // error
}

def misunderstoodConversions(t : java.util.Date, s : String) = {
f"$t%tG" // error
f"$t%t" // error
f"$s%10.5" // error
}

def otherBrainFailures(d : Int) = {
f"${d}random-leading-junk%d" // error
f"%1$$n"
f"%1$$d" // error
f"blablablabla %% %.2d" // error
f"blablablabla %.2b %%" // error

f"ana${3}%.2f%2${true}%bb" // error
f"ac{2c{2{c.ca "

f"b%c.%2ii%iin" // error
f"b}22%2.c<{%{" // error
f"%%bci.2${'i'}%..2c2" // error
}

}
73 changes: 0 additions & 73 deletions tests/run-macros/f-interpolator-neg/Macros_1.scala

This file was deleted.

Loading