Skip to content

Commit

Permalink
Fix quote parsing
Browse files Browse the repository at this point in the history
Correctly handle 'this, 'true, 'false, 'null.
  • Loading branch information
odersky committed Feb 19, 2019
1 parent c594dff commit e21dce5
Show file tree
Hide file tree
Showing 118 changed files with 226 additions and 206 deletions.
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala
Expand Up @@ -3,6 +3,7 @@ package dotc
package parsing

import core.Contexts._
import core.Names.SimpleName
import Scanners._
import util.SourceFile
import JavaTokens._
Expand All @@ -13,8 +14,10 @@ object JavaScanners {

class JavaScanner(source: SourceFile, override val startFrom: Offset = 0)(implicit ctx: Context) extends ScannerCommon(source)(ctx) {

def toToken(idx: Int): Token =
def toToken(name: SimpleName): Token = {
val idx = name.start
if (idx >= 0 && idx <= lastKeywordStart) kwArray(idx) else IDENTIFIER
}

private class JavaTokenData0 extends TokenData

Expand Down
62 changes: 39 additions & 23 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Expand Up @@ -703,25 +703,50 @@ object Parsers {
def qualId(): Tree = dotSelectors(termIdent())

/** SimpleExpr ::= literal
* | symbol
* | 'id | 'this | 'true | 'false | 'null
* | null
* @param negOffset The offset of a preceding `-' sign, if any.
* If the literal is not negated, negOffset = in.offset.
*/
def literal(negOffset: Int = in.offset, inPattern: Boolean = false): Tree = {
def finish(value: Any): Tree = {
val t = atSpan(negOffset) { Literal(Constant(value)) }
in.nextToken()
t

def literalOf(token: Token): Literal = {
val isNegated = negOffset < in.offset
val value = token match {
case CHARLIT => in.charVal
case INTLIT => in.intVal(isNegated).toInt
case LONGLIT => in.intVal(isNegated)
case FLOATLIT => in.floatVal(isNegated).toFloat
case DOUBLELIT => in.floatVal(isNegated)
case STRINGLIT | STRINGPART => in.strVal
case TRUE => true
case FALSE => false
case NULL => null
case _ =>
syntaxErrorOrIncomplete(IllegalLiteral())
null
}
Literal(Constant(value))
}
val isNegated = negOffset < in.offset

atSpan(negOffset) {
if (in.token == QUOTEID) {
if ((staged & StageKind.Spliced) != 0 && isIdentifierStart(in.name(1)))
Quote(atSpan(in.offset + 1)(Ident(in.name.drop(1))))
if ((staged & StageKind.Spliced) != 0 && isIdentifierStart(in.name(0))) {
val t = atSpan(in.offset + 1) {
val tok = in.toToken(in.name)
tok match {
case TRUE | FALSE | NULL => literalOf(tok)
case THIS => This(EmptyTypeIdent)
case _ => Ident(in.name)
}
}
in.nextToken()
Quote(t)
}
else {
migrationWarningOrError(em"""symbol literal '${in.name} is no longer supported,
|use a string literal "${in.name}" or an application Symbol("${in.name}") instead.""")
|use a string literal "${in.name}" or an application Symbol("${in.name}") instead,
|or enclose in braces '{${in.name}} if you want a quoted expression.""")
if (in.isScala2Mode) {
patch(source, Span(in.offset, in.offset + 1), "Symbol(\"")
patch(source, Span(in.charOffset - 1), "\")")
Expand All @@ -730,20 +755,11 @@ object Parsers {
}
}
else if (in.token == INTERPOLATIONID) interpolatedString(inPattern)
else finish(in.token match {
case CHARLIT => in.charVal
case INTLIT => in.intVal(isNegated).toInt
case LONGLIT => in.intVal(isNegated)
case FLOATLIT => in.floatVal(isNegated).toFloat
case DOUBLELIT => in.floatVal(isNegated)
case STRINGLIT | STRINGPART => in.strVal
case TRUE => true
case FALSE => false
case NULL => null
case _ =>
syntaxErrorOrIncomplete(IllegalLiteral())
null
})
else {
val t = literalOf(in.token)
in.nextToken()
t
}
}
}

Expand Down
13 changes: 7 additions & 6 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Expand Up @@ -100,13 +100,12 @@ object Scanners {
def finishNamed(idtoken: Token = IDENTIFIER, target: TokenData = this): Unit = {
target.name = termName(flushBuf(litBuf))
target.token = idtoken
if (idtoken == IDENTIFIER) {
val idx = target.name.start
target.token = toToken(idx)
}
if (idtoken == IDENTIFIER)
target.token = toToken(target.name)
}

def toToken(idx: Int): Token
/** The token for given `name`. Either IDENTIFIER or a keyword. */
def toToken(name: SimpleName): Token

/** Clear buffer and set string */
def setStrVal(): Unit =
Expand Down Expand Up @@ -215,9 +214,11 @@ object Scanners {
IDENTIFIER
}

def toToken(idx: Int): Token =
def toToken(name: SimpleName): Token = {
val idx = name.start
if (idx >= 0 && idx <= lastKeywordStart) handleMigration(kwArray(idx))
else IDENTIFIER
}

private class TokenData0 extends TokenData

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/other-new-features/tasty-reflect.md
Expand Up @@ -24,7 +24,7 @@ To provide reflection capabilities in macros we need to add an implicit paramete
import scala.quoted._
import scala.tasty._

inline def natConst(x: Int): Int = ~natConstImpl('{x})
inline def natConst(x: Int): Int = ~natConstImpl('x)

def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = {
import reflection._
Expand Down
2 changes: 1 addition & 1 deletion library/src-bootstrapped/scala/StagedTuple.scala
Expand Up @@ -253,7 +253,7 @@ object StagedTuple {

def bind[T: Type](in: Expr[U] => Expr[T]): Expr[T] = '{
val t: U = $expr
${in('{t})}
${in('t)}
}

}
Expand Down
20 changes: 10 additions & 10 deletions library/src-bootstrapped/scala/Tuple.scala
Expand Up @@ -32,7 +32,7 @@ sealed trait Tuple extends Any {
}

inline def stagedToArray: Array[Object] =
${ StagedTuple.toArrayStaged('{this}, constValueOpt[BoundedSize[this.type]]) }
${ StagedTuple.toArrayStaged('this, constValueOpt[BoundedSize[this.type]]) }

inline def *: [H] (x: H): H *: this.type =
if (stageIt) stagedCons[H](x)
Expand Down Expand Up @@ -60,7 +60,7 @@ sealed trait Tuple extends Any {
}

inline def stagedCons[H] (x: H): H *: this.type =
${ StagedTuple.stagedCons('{this}, '{x}, constValueOpt[BoundedSize[this.type]]) }
${ StagedTuple.stagedCons('this, 'x, constValueOpt[BoundedSize[this.type]]) }

inline def ++(that: Tuple): Concat[this.type, that.type] =
if (stageIt) stagedConcat(that)
Expand Down Expand Up @@ -104,8 +104,8 @@ sealed trait Tuple extends Any {
}

inline def stagedConcat(that: Tuple): Concat[this.type, that.type] =
${ StagedTuple.stagedConcat('{this}, constValueOpt[BoundedSize[this.type]],
'{that}, constValueOpt[BoundedSize[that.type]]) }
${ StagedTuple.stagedConcat('this, constValueOpt[BoundedSize[this.type]],
'that, constValueOpt[BoundedSize[that.type]]) }

inline def genericConcat[T <: Tuple](xs: Tuple, ys: Tuple): Tuple =
fromArray[T](xs.toArray ++ ys.toArray)
Expand All @@ -121,7 +121,7 @@ sealed trait Tuple extends Any {
}

inline def stagedSize: Size[this.type] =
${ StagedTuple.sizeStaged[Size[this.type]]('{this}, constValueOpt[BoundedSize[this.type]]) }
${ StagedTuple.sizeStaged[Size[this.type]]('this, constValueOpt[BoundedSize[this.type]]) }
}

object Tuple {
Expand Down Expand Up @@ -217,7 +217,7 @@ object Tuple {
}

inline def stagedFromArray[T <: Tuple](xs: Array[Object]): T =
${ StagedTuple.fromArrayStaged[T]('{xs}, constValueOpt[BoundedSize[this.type]]) }
${ StagedTuple.fromArrayStaged[T]('xs, constValueOpt[BoundedSize[this.type]]) }

def dynamicFromArray[T <: Tuple](xs: Array[Object]): T = xs.length match {
case 0 => ().asInstanceOf[T]
Expand Down Expand Up @@ -340,7 +340,7 @@ sealed trait NonEmptyTuple extends Tuple {
}

inline def stagedHead: Head[this.type] =
${ StagedTuple.headStaged[this.type]('{this}, constValueOpt[BoundedSize[this.type]]) }
${ StagedTuple.headStaged[this.type]('this, constValueOpt[BoundedSize[this.type]]) }

inline def tail: Tail[this.type] =
if (stageIt) stagedTail
Expand Down Expand Up @@ -369,7 +369,7 @@ sealed trait NonEmptyTuple extends Tuple {
}

inline def stagedTail: Tail[this.type] =
${ StagedTuple.tailStaged[this.type]('{this}, constValueOpt[BoundedSize[this.type]]) }
${ StagedTuple.tailStaged[this.type]('this, constValueOpt[BoundedSize[this.type]]) }

inline def fallbackApply(n: Int) =
inline constValueOpt[n.type] match {
Expand Down Expand Up @@ -430,8 +430,8 @@ sealed trait NonEmptyTuple extends Tuple {

inline def stagedApply(n: Int): Elem[this.type, n.type] =
${ StagedTuple.applyStaged[this.type, n.type](
'{this}, constValueOpt[Size[this.type]],
'{n}, constValueOpt[n.type]) }
'this, constValueOpt[Size[this.type]],
'n, constValueOpt[n.type]) }
}

object NonEmptyTuple {
Expand Down
Expand Up @@ -22,7 +22,7 @@ trait TreeUtils {
/** */
private def let[T: quoted.Type, U: quoted.Type](rhs: Expr[T])(in: Expr[T] => Expr[U]): Expr[U] = '{
val x = $rhs
${in('{x})}
${in('x)}
}

}
2 changes: 1 addition & 1 deletion tests/disabled/run/i4803d/App_2.scala
Expand Up @@ -11,7 +11,7 @@ object Test {
}

inline def power2(x: Double) = {
inline def power(x: Double, inline n: Long) = ${ PowerMacro.powerCode('{x}, n) }
inline def power(x: Double, inline n: Long) = ${ PowerMacro.powerCode('x, n) }
power(x, 2)
}
}
2 changes: 1 addition & 1 deletion tests/disabled/run/i4803d/Macro_1.scala
Expand Up @@ -3,6 +3,6 @@ import scala.quoted._
object PowerMacro {
def powerCode(x: Expr[Double], n: Long): Expr[Double] =
if (n == 0) '{1.0}
else if (n % 2 == 0) '{ val y = $x * $x; ${powerCode('{y}, n / 2)} }
else if (n % 2 == 0) '{ val y = $x * $x; ${powerCode('y, n / 2)} }
else '{ $x * ${powerCode(x, n - 1)} }
}
2 changes: 1 addition & 1 deletion tests/disabled/run/xml-interpolation-3/XmlQuote_1.scala
Expand Up @@ -9,7 +9,7 @@ object XmlQuote {

implicit object SCOps {
inline def xml(this inline ctx: StringContext)(args: => Any*): Xml =
${XmlQuote.impl(ctx, '{args})}
${XmlQuote.impl(ctx, 'args)}
}

def impl(receiver: StringContext, args: Expr[Seq[Any]]): Expr[Xml] = {
Expand Down
Expand Up @@ -3,7 +3,7 @@ import scala.quoted._
import scala.quoted.Toolbox.Default._

object Macros {
inline def foo(i: => Int): Int = ${ fooImpl('{i}) }
inline def foo(i: => Int): Int = ${ fooImpl('i) }
def fooImpl(i: Expr[Int]): Expr[Int] = {
val y: Int = i.run
y.toExpr
Expand Down
Expand Up @@ -3,7 +3,7 @@ import scala.quoted._
import scala.quoted.Toolbox.Default._

object Macros {
inline def foo(i: => Int): Int = ${ fooImpl('{i}) }
inline def foo(i: => Int): Int = ${ fooImpl('i) }
def fooImpl(i: Expr[Int]): Expr[Int] = {
val y: Int = i.run
y.toExpr
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/i4433.scala
@@ -1,7 +1,7 @@

object Foo {
inline def g(inline p: Int => Boolean): Boolean = ${ // error
if(p(5)) '{true}
else '{false}
if(p(5)) 'true
else 'false
}
}
2 changes: 1 addition & 1 deletion tests/neg/i4774b.scala
Expand Up @@ -5,7 +5,7 @@ object Test {
def loop[T](x: Expr[T])(implicit t: Type[T]): Expr[T] = '{
val y: $t = $x;
${loop[$t]( // error
'{y}
'y
)}
}
}
2 changes: 1 addition & 1 deletion tests/neg/i4890.scala
Expand Up @@ -3,6 +3,6 @@ import scala.quoted._
object Test {
def toExpr(x: Option[String]): Expr[String] = x match {
case Some(s) =>
'{s} // error
's // error
}
}
6 changes: 3 additions & 3 deletions tests/neg/quote-interpolator-core-old.scala
Expand Up @@ -5,9 +5,9 @@ import scala.quoted._
object FInterpolation {

implicit class FInterpolatorHelper(val sc: StringContext) extends AnyVal {
inline def ff(arg1: Any): String = ${fInterpolation(sc, Seq('{arg1}))} // error: Inline macro method must be a static method
inline def ff(arg1: Any, arg2: Any): String = ${fInterpolation(sc, Seq('{arg1}, '{arg2}))} // error: Inline macro method must be a static method
inline def ff(arg1: Any, arg2: Any, arg3: Any): String = ${fInterpolation(sc, Seq('{arg1}, '{arg2}, '{arg3}))} // error: Inline macro method must be a static method
inline def ff(arg1: Any): String = ${fInterpolation(sc, Seq('arg1))} // error: Inline macro method must be a static method
inline def ff(arg1: Any, arg2: Any): String = ${fInterpolation(sc, Seq('arg1, 'arg2))} // error: Inline macro method must be a static method
inline def ff(arg1: Any, arg2: Any, arg3: Any): String = ${fInterpolation(sc, Seq('arg1, 'arg2, 'arg3))} // error: Inline macro method must be a static method
// ...
}

Expand Down
2 changes: 1 addition & 1 deletion tests/neg/quote-macro-complex-arg-0.scala
@@ -1,6 +1,6 @@
import scala.quoted._

object Macros {
inline def foo(inline i: Int, dummy: Int, j: Int): Int = ${ bar(i + 1, '{j}) } // error: i + 1 is not a parameter or field reference
inline def foo(inline i: Int, dummy: Int, j: Int): Int = ${ bar(i + 1, 'j) } // error: i + 1 is not a parameter or field reference
def bar(x: Int, y: Expr[Int]): Expr[Int] = '{ ${x.toExpr} + $y }
}
2 changes: 1 addition & 1 deletion tests/neg/quote-macro-splice.scala
Expand Up @@ -14,7 +14,7 @@ object Test {

inline def foo3: Int = { // error
val a = 1
${ impl('{a}) }
${ impl('a) }
}

inline def foo4: Int = { // error
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/quote-pcp-in-arg.scala
@@ -1,6 +1,6 @@
import scala.quoted._

object Foo {
inline def foo(x: Int): Int = ${ bar('{ '{x}; x }) } // error
inline def foo(x: Int): Int = ${ bar('{ 'x; x }) } // error
def bar(i: Expr[Int]): Expr[Int] = i
}
4 changes: 2 additions & 2 deletions tests/neg/quote-splice-interpret-1.scala
Expand Up @@ -3,7 +3,7 @@ import scala.quoted._

object Macros {
inline def isZero(inline n: Int): Boolean = ${ // error
if (n == 0) '{true}
else '{false}
if (n == 0) 'true
else 'false
}
}
4 changes: 2 additions & 2 deletions tests/neg/quote-this.scala
Expand Up @@ -12,11 +12,11 @@ class Foo {
}

inline def i(): Unit = ${ Foo.impl[Any]('{
'{this} // error
'this // error
}) }

inline def j(that: Foo): Unit = ${ Foo.impl[Any]('{
'{that} // error
'that // error
}) }

inline def k(): Unit = ${ Foo.impl[Any](this) } // error
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/tasty-macro-assert/quoted_1.scala
Expand Up @@ -12,7 +12,7 @@ object Asserts {
object Ops

inline def macroAssert(cond: => Boolean): Unit =
${impl('{cond})}
${impl('cond)}

def impl(cond: Expr[Boolean])(implicit reflect: Reflection): Expr[Unit] = {
import reflect._
Expand Down

0 comments on commit e21dce5

Please sign in to comment.