Skip to content
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

Change syntax of splices and quotes #5918

Merged
merged 26 commits into from Feb 22, 2019
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ace1883
Use `$` for splices in syntax and parsing
odersky Feb 13, 2019
05e5309
Update bootstrap library to new syntax
odersky Feb 14, 2019
3257b9d
Update tests to new syntax
odersky Feb 14, 2019
6faa0df
Use nme.UNARY_~ directly
odersky Feb 14, 2019
a067989
Fix some tests
nicolasstucki Feb 18, 2019
eee09d4
Fix more tests
nicolasstucki Feb 18, 2019
e2086da
Fix ${ 'x } -> x and '{ $x } -> x transformation
nicolasstucki Feb 18, 2019
26ca221
Add regression tests
nicolasstucki Feb 18, 2019
d749bd9
Adapt more tests
nicolasstucki Feb 18, 2019
5bc50de
Change id syntax
odersky Feb 18, 2019
c6cc337
Drop Block(...) around quoted and spliced expressions where possible
odersky Feb 18, 2019
7a71580
Fix '{}
nicolasstucki Feb 18, 2019
6bcd70c
Readapt tests
nicolasstucki Feb 18, 2019
8b0ba22
Fix and adapt tests
nicolasstucki Feb 18, 2019
bc5e068
Rename $MaxSpecialized to MaxSpecialized
nicolasstucki Feb 19, 2019
cc279c8
Revert changes in tests/pos/t4579.scala
nicolasstucki Feb 19, 2019
7d39a1e
Update printer
nicolasstucki Feb 19, 2019
e23bf3c
Avoid accidental conversion from ${ x } to x.unary_~ and back
nicolasstucki Feb 19, 2019
94e3a03
Change use of `$` for splices in syntax and parsing
odersky Feb 19, 2019
bd17841
Update docs to new syntax
odersky Feb 19, 2019
b32a4a8
Fix quote parsing
odersky Feb 19, 2019
3846e49
Fix test
odersky Feb 19, 2019
ad3f519
Revert stdLib213 change after rebase
nicolasstucki Feb 20, 2019
b560392
Adapt i5954 tests
nicolasstucki Feb 21, 2019
2ad3310
Change `splice` member to `$splice`
nicolasstucki Feb 21, 2019
4b0c234
Adapt `splice` to `$splice` in test
nicolasstucki Feb 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions bench/tests/power-macro/PowerMacro.scala
Expand Up @@ -2,11 +2,11 @@ import scala.quoted.Expr

object PowerMacro {

inline def power(inline n: Long, x: Double) = ~powerCode(n, '(x))
inline def power(inline n: Long, x: Double) = ${powerCode(n, '{x})}

def powerCode(n: Long, x: Expr[Double]): Expr[Double] =
if (n == 0) '(1.0)
else if (n % 2 == 0) '{ val y = ~x * ~x; ~powerCode(n / 2, '(y)) }
else '{ ~x * ~powerCode(n - 1, x) }
if (n == 0) '{1.0}
else if (n % 2 == 0) '{ val y = $x * $x; ${powerCode(n / 2, '{y})} }
else '{ $x * ${powerCode(n - 1, x)} }

}
2 changes: 1 addition & 1 deletion community-build/community-projects/scalatest
Submodule scalatest updated 0 files
2 changes: 1 addition & 1 deletion community-build/community-projects/stdLib213
Submodule stdLib213 updated 2162 files
12 changes: 8 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Expand Up @@ -1313,11 +1313,15 @@ object desugar {
val desugared = tree match {
case SymbolLit(str) =>
Literal(Constant(scala.Symbol(str)))
case Quote(expr) =>
if (expr.isType)
TypeApply(ref(defn.QuotedType_applyR), List(expr))
case Quote(t) =>
if (t.isType)
TypeApply(ref(defn.QuotedType_applyR), List(t))
else
Apply(ref(defn.QuotedExpr_applyR), expr)
Apply(ref(defn.QuotedExpr_applyR), t)
case Splice(expr) =>
Select(expr, nme.splice)
case TypSplice(expr) =>
Select(expr, tpnme.splice)
case InterpolatedString(id, segments) =>
val strs = segments map {
case ts: Thicket => ts.trees.head
Expand Down
41 changes: 28 additions & 13 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Expand Up @@ -16,8 +16,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {

abstract class OpTree(implicit @constructorOnly src: SourceFile) extends Tree {
def op: Ident
override def isTerm: Boolean = op.name.isTermName
override def isType: Boolean = op.name.isTypeName
override def isTerm: Boolean = op.isTerm
override def isType: Boolean = op.isType
}

/** A typed subtree of an untyped tree needs to be wrapped in a TypedSplice
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nitpick 🙈, TypSplice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is actually a TypeSplice which is a completely different concept 🙉. That one wraps a tree that is typed inside an untyped tree.

Expand Down Expand Up @@ -84,10 +84,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {

case class InfixOp(left: Tree, op: Ident, right: Tree)(implicit @constructorOnly src: SourceFile) extends OpTree
case class PostfixOp(od: Tree, op: Ident)(implicit @constructorOnly src: SourceFile) extends OpTree
case class PrefixOp(op: Ident, od: Tree)(implicit @constructorOnly src: SourceFile) extends OpTree {
override def isType: Boolean = op.isType
override def isTerm: Boolean = op.isTerm
}
case class PrefixOp(op: Ident, od: Tree)(implicit @constructorOnly src: SourceFile) extends OpTree
case class Parens(t: Tree)(implicit @constructorOnly src: SourceFile) extends ProxyTree {
def forwardTo: Tree = t
}
Expand All @@ -96,7 +93,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
override def isType: Boolean = !isTerm
}
case class Throw(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
case class Quote(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
case class Quote(t: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
case class Splice(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
case class TypSplice(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TypTree
case class DoWhile(body: Tree, cond: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
case class ForYield(enums: List[Tree], expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
case class ForDo(enums: List[Tree], body: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
Expand Down Expand Up @@ -493,9 +492,17 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case tree: Throw if expr eq tree.expr => tree
case _ => finalize(tree, untpd.Throw(expr)(tree.source))
}
def Quote(tree: Tree)(expr: Tree)(implicit ctx: Context): TermTree = tree match {
case tree: Quote if expr eq tree.expr => tree
case _ => finalize(tree, untpd.Quote(expr)(tree.source))
def Quote(tree: Tree)(t: Tree)(implicit ctx: Context): Tree = tree match {
case tree: Quote if t eq tree.t => tree
case _ => finalize(tree, untpd.Quote(t)(tree.source))
}
def Splice(tree: Tree)(expr: Tree)(implicit ctx: Context): Tree = tree match {
case tree: Splice if expr eq tree.expr => tree
case _ => finalize(tree, untpd.Splice(expr)(tree.source))
}
def TypSplice(tree: Tree)(expr: Tree)(implicit ctx: Context): Tree = tree match {
case tree: TypSplice if expr eq tree.expr => tree
case _ => finalize(tree, untpd.TypSplice(expr)(tree.source))
}
def DoWhile(tree: Tree)(body: Tree, cond: Tree)(implicit ctx: Context): TermTree = tree match {
case tree: DoWhile if (body eq tree.body) && (cond eq tree.cond) => tree
Expand Down Expand Up @@ -557,8 +564,12 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
cpy.Tuple(tree)(transform(trees))
case Throw(expr) =>
cpy.Throw(tree)(transform(expr))
case Quote(expr) =>
cpy.Quote(tree)(transform(expr))
case Quote(t) =>
cpy.Quote(tree)(transform(t))
case Splice(expr) =>
cpy.Splice(tree)(transform(expr))
case TypSplice(expr) =>
cpy.TypSplice(tree)(transform(expr))
case DoWhile(body, cond) =>
cpy.DoWhile(tree)(transform(body), transform(cond))
case ForYield(enums, expr) =>
Expand Down Expand Up @@ -606,7 +617,11 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
this(x, trees)
case Throw(expr) =>
this(x, expr)
case Quote(expr) =>
case Quote(t) =>
this(x, t)
case Splice(expr) =>
this(x, expr)
case TypSplice(expr) =>
this(x, expr)
case DoWhile(body, cond) =>
this(this(x, body), cond)
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Expand Up @@ -707,16 +707,16 @@ class Definitions {
def QuotedExprModule(implicit ctx: Context): Symbol = QuotedExprClass.companionModule
lazy val QuotedExpr_applyR: TermRef = QuotedExprModule.requiredMethodRef(nme.apply)
def QuotedExpr_apply(implicit ctx: Context): Symbol = QuotedExpr_applyR.symbol
lazy val QuotedExpr_~ : TermSymbol = QuotedExprClass.requiredMethod(nme.UNARY_~)
lazy val QuotedExpr_splice : TermSymbol = QuotedExprClass.requiredMethod(nme.splice)

lazy val QuotedExprsModule: TermSymbol = ctx.requiredModule("scala.quoted.Exprs")
def QuotedExprsClass(implicit ctx: Context): ClassSymbol = QuotedExprsModule.asClass

lazy val QuotedTypeType: TypeRef = ctx.requiredClassRef("scala.quoted.Type")
def QuotedTypeClass(implicit ctx: Context): ClassSymbol = QuotedTypeType.symbol.asClass

lazy val QuotedType_spliceR: TypeRef = QuotedTypeClass.requiredType(tpnme.UNARY_~).typeRef
def QuotedType_~ : Symbol = QuotedType_spliceR.symbol
lazy val QuotedType_spliceR: TypeRef = QuotedTypeClass.requiredType(tpnme.splice).typeRef
def QuotedType_splice : Symbol = QuotedType_spliceR.symbol

lazy val QuotedTypeModuleType: TermRef = ctx.requiredModuleRef("scala.quoted.Type")
def QuotedTypeModule(implicit ctx: Context): Symbol = QuotedTypeModuleType.symbol
Expand Down
Expand Up @@ -26,7 +26,7 @@ object PickledQuotes {
def pickleQuote(tree: Tree)(implicit ctx: Context): scala.runtime.quoted.Unpickler.Pickled = {
if (ctx.reporter.hasErrors) Nil
else {
assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'(~x)` which should be optimized to `x`
assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'{$x}` which should be optimized to `x`
val pickled = pickle(tree)
TastyString.pickle(pickled)
}
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/parsing/CharArrayReader.scala
Expand Up @@ -122,6 +122,8 @@ abstract class CharArrayReader { self =>
/** A new reader that takes off at the current character position */
def lookaheadReader(): CharArrayLookaheadReader = new CharArrayLookaheadReader

def lookaheadChar(): Char = lookaheadReader().getc()

class CharArrayLookaheadReader extends CharArrayReader {
val buf: Array[Char] = self.buf
charOffset = self.charOffset
Expand Down
60 changes: 45 additions & 15 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Expand Up @@ -918,15 +918,39 @@ object Parsers {
})
else t

/** The block in a quote or splice */
def stagedBlock(isQuote: Boolean) = {
val saved = in.inQuote
in.inQuote = isQuote
inDefScopeBraces {
try
block() match {
case t @ Block(Nil, expr) =>
if (expr.isEmpty) t else expr
case t => t
}
finally in.inQuote = saved
}
}

/** SimpleEpxr ::= ‘$’ (id | ‘{’ Block ‘}’)
* SimpleType ::= ‘$’ (id | ‘{’ Block ‘}’)
*/
def splice(isType: Boolean): Tree =
atSpan(in.skipToken()) {
val expr = if (isIdent) termIdent() else stagedBlock(isQuote = false)
if (isType) TypSplice(expr) else Splice(expr)
}

/** SimpleType ::= SimpleType TypeArgs
* | SimpleType `#' id
* | StableId
* | ['~'] StableId
* | Path `.' type
* | `(' ArgTypes `)'
* | `_' TypeBounds
* | Refinement
* | Literal
* | ‘$’ (id | ‘{’ Block ‘}’)
*/
def simpleType(): Tree = simpleTypeRest {
if (in.token == LPAREN)
Expand All @@ -940,8 +964,8 @@ object Parsers {
val start = in.skipToken()
typeBounds().withSpan(Span(start, in.lastOffset, start))
}
else if (isIdent(nme.raw.TILDE) && in.lookaheadIn(BitSet(IDENTIFIER, BACKQUOTED_IDENT)))
atSpan(in.offset) { PrefixOp(typeIdent(), path(thisOK = true)) }
else if (in.token == SPLICE)
splice(isType = true)
else path(thisOK = false, handleSingletonType) match {
case r @ SingletonTypeTree(_) => r
case r => convertToTypeId(r)
Expand Down Expand Up @@ -1402,9 +1426,9 @@ object Parsers {

/** SimpleExpr ::= ‘new’ (ConstrApp [TemplateBody] | TemplateBody)
* | BlockExpr
* | ‘'{’ BlockExprContents ‘}’
* | ‘'(’ ExprsInParens ‘)
* | ‘'[’ Type ‘]’
* | ‘'’ (id | ‘{’ Block ‘}’)
* | ‘'’ ‘[’ Type ‘]
* | ‘$’ (id | ‘{’ Block ‘}’)
* | SimpleExpr1 [`_']
* SimpleExpr1 ::= literal
* | xmlLiteral
Expand Down Expand Up @@ -1433,15 +1457,21 @@ object Parsers {
case LBRACE =>
canApply = false
blockExpr()
case QPAREN =>
in.token = LPAREN
atSpan(in.offset)(Quote(simpleExpr()))
case QBRACE =>
in.token = LBRACE
atSpan(in.offset)(Quote(simpleExpr()))
case QBRACKET =>
in.token = LBRACKET
atSpan(in.offset)(Quote(inBrackets(typ())))
case QUOTE =>
atSpan(in.skipToken()) {
Quote {
if (in.token == LBRACKET) {
val saved = in.inQuote
in.inQuote = true
inBrackets {
try typ() finally in.inQuote = saved
}
}
else stagedBlock(isQuote = true)
}
}
case SPLICE =>
splice(isType = false)
case NEW =>
canApply = false
newExpr()
Expand Down
66 changes: 34 additions & 32 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Expand Up @@ -184,6 +184,8 @@ object Scanners {
/** Return a list of all the comment positions */
def commentSpans: List[Span] = commentPosBuf.toList

var inQuote = false

private[this] def addComment(comment: Comment): Unit = {
val lookahead = lookaheadReader()
def nextPos: Int = (lookahead.getc(): @switch) match {
Expand Down Expand Up @@ -415,7 +417,7 @@ object Scanners {
'K' | 'L' | 'M' | 'N' | 'O' |
'P' | 'Q' | 'R' | 'S' | 'T' |
'U' | 'V' | 'W' | 'X' | 'Y' |
'Z' | '$' | '_' |
'Z' | '_' |
'a' | 'b' | 'c' | 'd' | 'e' |
'f' | 'g' | 'h' | 'i' | 'j' |
'k' | 'l' | 'm' | 'n' | 'o' |
Expand All @@ -424,9 +426,15 @@ object Scanners {
'z' =>
putChar(ch)
nextChar()
getIdentRest()
if (ch == '"' && token == IDENTIFIER)
token = INTERPOLATIONID
finishIdent()
case '$' =>
putChar(ch)
nextChar()
if (inQuote) {
token = SPLICE
litBuf.clear()
}
else finishIdent()
case '<' => // is XMLSTART?
def fetchLT() = {
val last = if (charOffset >= 2) buf(charOffset - 2) else ' '
Expand Down Expand Up @@ -523,19 +531,13 @@ object Scanners {
charLitOr { getIdentRest(); SYMBOLLIT }
else if (isOperatorPart(ch) && (ch != '\\'))
charLitOr { getOperatorRest(); SYMBOLLIT }
else if (ch == '(' || ch == '{' || ch == '[') {
val tok = quote(ch)
charLitOr(tok)
}
else {
getLitChar()
if (ch == '\'') {
nextChar()
token = CHARLIT
setStrVal()
} else {
error("unclosed character literal")
}
else ch match {
case '{' | '[' | ' ' | '\t' if lookaheadChar() != '\'' =>
token = QUOTE
case _ =>
getLitChar()
if (ch == '\'') finishCharLit()
else error("unclosed character literal")
}
}
fetchSingleQuote()
Expand Down Expand Up @@ -716,6 +718,11 @@ object Scanners {
}
}

def finishIdent(): Unit = {
getIdentRest()
if (ch == '"' && token == IDENTIFIER) token = INTERPOLATIONID
}

private def getOperatorRest(): Unit = (ch: @switch) match {
case '~' | '!' | '@' | '#' | '%' |
'^' | '*' | '+' | '-' | '<' |
Expand Down Expand Up @@ -965,9 +972,8 @@ object Scanners {
}
token = INTLIT
if (base == 10 && ch == '.') {
val lookahead = lookaheadReader()
lookahead.nextChar()
if ('0' <= lookahead.ch && lookahead.ch <= '9') {
val lch = lookaheadChar()
if ('0' <= lch && lch <= '9') {
putChar('.'); nextChar(); getFraction()
}
} else (ch: @switch) match {
Expand All @@ -981,30 +987,26 @@ object Scanners {
setStrVal()
}

private def finishCharLit(): Unit = {
nextChar()
token = CHARLIT
setStrVal()
}

/** Parse character literal if current character is followed by \',
* or follow with given op and return a symbol literal token
*/
def charLitOr(op: => Token): Unit = {
putChar(ch)
nextChar()
if (ch == '\'') {
nextChar()
token = CHARLIT
setStrVal()
} else {
if (ch == '\'') finishCharLit()
else {
token = op
strVal = if (name != null) name.toString else null
litBuf.clear()
}
}

/** The opening quote bracket token corresponding to `c` */
def quote(c: Char): Token = c match {
case '(' => QPAREN
case '{' => QBRACE
case '[' => QBRACKET
}

override def toString: String =
showTokenDetailed(token) + {
if ((identifierTokens contains token) || (literalTokens contains token)) " " + name
Expand Down