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

Use p* for vararg splices #11240

Merged
merged 7 commits into from
Feb 3, 2021
Merged
Show file tree
Hide file tree
Changes from 6 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
132 changes: 74 additions & 58 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -376,13 +376,13 @@ object Parsers {
in.nextToken() // needed to ensure progress; otherwise we might cycle forever
accept(SEMI)

def rewriteNotice(additionalOption: String = "") = {
def rewriteNotice(version: String = "3.0", additionalOption: String = "") = {
val optionStr = if (additionalOption.isEmpty) "" else " " ++ additionalOption
i"\nThis construct can be rewritten automatically under$optionStr -rewrite -source 3.0-migration."
i"\nThis construct can be rewritten automatically under$optionStr -rewrite -source $version-migration."
}

def syntaxVersionError(option: String, span: Span) =
syntaxError(em"""This construct is not allowed under $option.${rewriteNotice(option)}""", span)
syntaxError(em"""This construct is not allowed under $option.${rewriteNotice("3.0", option)}""", span)

def rewriteToNewSyntax(span: Span = Span(in.offset)): Boolean = {
if (in.newSyntax) {
Expand Down Expand Up @@ -919,7 +919,20 @@ object Parsers {
val next = in.lookahead.token
next == LBRACKET || next == LPAREN

/* --------- OPERAND/OPERATOR STACK --------------------------------------- */
/** Is current ident a `*`, and is it followed by a `)` or `, )`? */
def followingIsVararg(): Boolean =
in.isIdent(nme.raw.STAR) && {
val lookahead = in.LookaheadScanner()
lookahead.nextToken()
lookahead.token == RPAREN
|| lookahead.token == COMMA
&& {
lookahead.nextToken()
lookahead.token == RPAREN
}
}

/* --------- OPERAND/OPERATOR STACK --------------------------------------- */

var opStack: List[OpInfo] = Nil

Expand Down Expand Up @@ -956,8 +969,8 @@ object Parsers {
*/
def infixOps(
first: Tree, canStartOperand: Token => Boolean, operand: () => Tree,
isType: Boolean = false,
isOperator: => Boolean = true,
isType: Boolean,
isOperator: => Boolean,
maybePostfix: Boolean = false): Tree = {
val base = opStack

Expand Down Expand Up @@ -1522,15 +1535,9 @@ object Parsers {
*/
def infixType(): Tree = infixTypeRest(refinedType())

/** Is current ident a `*`, and is it followed by a `)` or `,`? */
def isPostfixStar: Boolean =
in.name == nme.raw.STAR && {
val nxt = in.lookahead.token
nxt == RPAREN || nxt == COMMA
}

def infixTypeRest(t: Tree): Tree =
infixOps(t, canStartTypeTokens, refinedType, isType = true, isOperator = !isPostfixStar)
infixOps(t, canStartTypeTokens, refinedType, isType = true,
isOperator = !followingIsVararg())

/** RefinedType ::= WithType {[nl] Refinement}
*/
Expand Down Expand Up @@ -2046,7 +2053,7 @@ object Parsers {
case t =>
syntaxError(em"`inline` must be followed by an `if` or a `match`", start)
t
else expr1Rest(postfixExpr(), location)
else expr1Rest(postfixExpr(location), location)
end expr1

def expr1Rest(t: Tree, location: Location): Tree = in.token match
Expand All @@ -2068,22 +2075,25 @@ object Parsers {

def ascription(t: Tree, location: Location): Tree = atSpan(startOffset(t)) {
in.token match {
case USCORE =>
case USCORE if in.lookahead.isIdent(nme.raw.STAR) =>
val uscoreStart = in.skipToken()
if isIdent(nme.raw.STAR) then
in.nextToken()
if !(location.inArgs && in.token == RPAREN) then
if opStack.nonEmpty then
report.errorOrMigrationWarning(
em"""`_*` can be used only for last argument of method application.
|It is no longer allowed in operands of infix operations.""",
in.sourcePos(uscoreStart))
else
syntaxError(SeqWildcardPatternPos(), uscoreStart)
Typed(t, atSpan(uscoreStart) { Ident(tpnme.WILDCARD_STAR) })
val isVarargSplice = location.inArgs && followingIsVararg()
in.nextToken()
if isVarargSplice then
if sourceVersion.isAtLeast(`3.1`) then
report.errorOrMigrationWarning(
em"The syntax `x: _*` is no longer supported for vararg splices; use `x*` instead${rewriteNotice("3.1")}",
in.sourcePos(uscoreStart))
if sourceVersion == `3.1-migration` then
patch(source, Span(t.span.end, in.lastOffset), " *")
else if opStack.nonEmpty then
report.errorOrMigrationWarning(
em"""`_*` can be used only for last argument of method application.
|It is no longer allowed in operands of infix operations.""",
in.sourcePos(uscoreStart))
else
syntaxErrorOrIncomplete(IncorrectRepeatedParameterSyntax())
t
syntaxError(SeqWildcardPatternPos(), uscoreStart)
Typed(t, atSpan(uscoreStart) { Ident(tpnme.WILDCARD_STAR) })
case AT if !location.inPattern =>
annotations().foldLeft(t)(Annotated)
case _ =>
Expand Down Expand Up @@ -2152,7 +2162,7 @@ object Parsers {
// Don't error in non-strict mode, as the alternative syntax "implicit (x: T) => ... "
// is not supported by Scala2.x
report.errorOrMigrationWarning(
s"This syntax is no longer supported; parameter needs to be enclosed in (...)${rewriteNotice()}",
s"This syntax is no longer supported; parameter needs to be enclosed in (...)${rewriteNotice("3.1")}",
source.atSpan(Span(start, in.lastOffset)))
in.nextToken()
val t = infixType()
Expand Down Expand Up @@ -2200,10 +2210,18 @@ object Parsers {
* | InfixExpr id [nl] InfixExpr
* | InfixExpr MatchClause
*/
def postfixExpr(): Tree = postfixExprRest(prefixExpr())
def postfixExpr(location: Location = Location.ElseWhere): Tree =
val t = postfixExprRest(prefixExpr(), location)
if location.inArgs && followingIsVararg() then
Typed(t, atSpan(in.skipToken()) { Ident(tpnme.WILDCARD_STAR) })
else
t

def postfixExprRest(t: Tree): Tree =
infixOps(t, in.canStartExprTokens, prefixExpr, maybePostfix = true)
def postfixExprRest(t: Tree, location: Location = Location.ElseWhere): Tree =
infixOps(t, in.canStartExprTokens, prefixExpr,
isType = false,
isOperator = !(location.inArgs && followingIsVararg()),
maybePostfix = true)

/** PrefixExpr ::= [`-' | `+' | `~' | `!'] SimpleExpr
*/
Expand Down Expand Up @@ -2331,7 +2349,7 @@ object Parsers {
if (in.token == RPAREN) Nil else commaSeparated(exprInParens)

/** ParArgumentExprs ::= `(' [‘using’] [ExprsInParens] `)'
* | `(' [ExprsInParens `,'] PostfixExpr `:' `_' `*' ')'
* | `(' [ExprsInParens `,'] PostfixExpr `*' ')'
*/
def parArgumentExprs(): (List[Tree], Boolean) = inParens {
if in.token == RPAREN then
Expand Down Expand Up @@ -2610,37 +2628,44 @@ object Parsers {
ascription(p, location)
else p

/** Pattern2 ::= [id `as'] InfixPattern
/** Pattern2 ::= InfixPattern [‘*’]
odersky marked this conversation as resolved.
Show resolved Hide resolved
*/
def pattern3(): Tree =
val p = infixPattern()
if followingIsVararg() then
atSpan(in.skipToken()) {
Typed(p, Ident(tpnme.WILDCARD_STAR))
}
else p

/** Pattern2 ::= [id `@'] Pattern3
*/
val pattern2: () => Tree = () => infixPattern() match {
val pattern2: () => Tree = () => pattern3() match
case p @ Ident(name) if in.token == AT =>
val offset = in.skipToken()
infixPattern() match {
case pt @ Ident(tpnme.WILDCARD_STAR) => // compatibility for Scala2 `x @ _*` syntax
warnMigration(p)
atSpan(startOffset(p), offset) { Typed(p, pt) }
pattern3() match {
case pt @ Bind(nme.WILDCARD, pt1: Typed) if pt.mods.is(Given) =>
atSpan(startOffset(p), 0) { Bind(name, pt1).withMods(pt.mods) }
case Typed(Ident(nme.WILDCARD), pt @ Ident(tpnme.WILDCARD_STAR)) =>
atSpan(startOffset(p), 0) { Typed(p, pt) }
case pt =>
atSpan(startOffset(p), 0) { Bind(name, pt) }
}
case p @ Ident(tpnme.WILDCARD_STAR) =>
warnMigration(p)
atSpan(startOffset(p)) { Typed(Ident(nme.WILDCARD), p) }
case p =>
p
}

private def warnMigration(p: Tree) =
private def warnStarMigration(p: Tree) =
if sourceVersion.isAtLeast(`3.1`) then
report.errorOrMigrationWarning(
"The syntax `x @ _*` is no longer supported; use `x : _*` instead",
em"The syntax `x: _*` is no longer supported for vararg splices; use `x*` instead",
in.sourcePos(startOffset(p)))

/** InfixPattern ::= SimplePattern {id [nl] SimplePattern}
*/
def infixPattern(): Tree =
infixOps(simplePattern(), in.canStartExprTokens, simplePattern, isOperator = in.name != nme.raw.BAR)
infixOps(simplePattern(), in.canStartExprTokens, simplePattern,
isType = false,
isOperator = in.name != nme.raw.BAR && !followingIsVararg())

/** SimplePattern ::= PatVar
* | Literal
Expand All @@ -2659,16 +2684,7 @@ object Parsers {
case id @ Ident(nme.raw.MINUS) if isNumericLit => literal(startOffset(id))
case t => simplePatternRest(t)
case USCORE =>
val wildIdent = wildcardIdent()

// compatibility for Scala2 `x @ _*` and `_*` syntax
// `x: _*' is parsed in `ascription'
if (isIdent(nme.raw.STAR)) {
in.nextToken()
if (in.token != RPAREN) syntaxError(SeqWildcardPatternPos(), wildIdent.span)
atSpan(wildIdent.span) { Ident(tpnme.WILDCARD_STAR) }
}
else wildIdent
wildcardIdent()
case LPAREN =>
atSpan(in.offset) { makeTupleOrParens(inParens(patternsOpt())) }
case QUOTE =>
Expand Down Expand Up @@ -2710,7 +2726,7 @@ object Parsers {
if (in.token == RPAREN) Nil else patterns(location)

/** ArgumentPatterns ::= ‘(’ [Patterns] ‘)’
* | ‘(’ [Patterns ‘,’] Pattern2 ‘:’ ‘_’ ‘*’ ‘)’
* | ‘(’ [Patterns ‘,’] Pattern2 ‘*’ ‘)’
*/
def argumentPatterns(): List[Tree] =
inParens(patternsOpt(Location.InPatternArgs))
Expand Down
18 changes: 14 additions & 4 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -415,10 +415,20 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
}
case Typed(expr, tpt) =>
changePrec(InfixPrec) {
val exprText = toText(expr)
val line = exprText.lastLine
val colon = if (!line.isEmpty && isOperatorPart(line.last)) " :" else ":"
exprText ~ colon ~ toText(tpt) }
if isWildcardStarArg(tree) then
expr match
case Ident(nme.WILDCARD_STAR) =>
// `_*` is used as a wildcard name to indicate a vararg splice pattern;
// avoid the double `*` in this case.
toText(expr)
case _ =>
toText(expr) ~ "*"
else
val exprText = toText(expr)
val line = exprText.lastLine
val colon = if !line.isEmpty && isOperatorPart(line.last) then " :" else ":"
exprText ~ colon ~ toText(tpt)
}
case NamedArg(name, arg) =>
toText(name) ~ " = " ~ toText(arg)
case Assign(lhs, rhs) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
IllegalVariableInPatternAlternativeID,
IdentifierExpectedID,
AuxConstructorNeedsNonImplicitParameterID,
IncorrectRepeatedParameterSyntaxID,
VarArgsParamMustComeLastID,
IllegalLiteralID,
PatternMatchExhaustivityID,
MatchCaseUnreachableID,
Expand Down
32 changes: 3 additions & 29 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -779,32 +779,6 @@ import transform.SymUtils._
|"""
}

class IncorrectRepeatedParameterSyntax()(using Context)
extends SyntaxMsg(IncorrectRepeatedParameterSyntaxID) {
def msg = "'*' expected"
def explain =
em"""|Expected * in ${hl("_*")} operator.
|
|The ${hl("_*")} operator can be used to supply a sequence-based argument
|to a method with a variable-length or repeated parameter. It is used
|to expand the sequence to a variable number of arguments, such that:
|${hl("func(args: _*)")} would expand to ${hl("func(arg1, arg2 ... argN)")}.
|
|Below is an example of how a method with a variable-length
|parameter can be declared and used.
|
|Squares the arguments of a variable-length parameter:
|${hl("def square(args: Int*) = args.map(a => a * a)")}
|
|Usage:
|${hl("square(1, 2, 3) // res0: List[Int] = List(1, 4, 9)")}
|
|Secondary Usage with ${hl("_*")}:
|${hl("val ints = List(2, 3, 4) // ints: List[Int] = List(2, 3, 4)")}
|${hl("square(ints: _*) // res1: List[Int] = List(4, 9, 16)")}
|""".stripMargin
}

class IllegalLiteral()(using Context)
extends SyntaxMsg(IllegalLiteralID) {
def msg = "Illegal literal"
Expand Down Expand Up @@ -865,11 +839,11 @@ import transform.SymUtils._

class SeqWildcardPatternPos()(using Context)
extends SyntaxMsg(SeqWildcardPatternPosID) {
def msg = em"""${hl("_*")} can be used only for last argument"""
def msg = em"""${hl("*")} can be used only for last argument"""
def explain = {
val code =
"""def sumOfTheFirstTwo(list: List[Int]): Int = list match {
| case List(first, second, x:_*) => first + second
| case List(first, second, x*) => first + second
| case _ => 0
|}"""
em"""|Sequence wildcard pattern is expected at the end of an argument list.
Expand Down Expand Up @@ -1274,7 +1248,7 @@ import transform.SymUtils._
}

class VarArgsParamMustComeLast()(using Context)
extends SyntaxMsg(IncorrectRepeatedParameterSyntaxID) {
extends SyntaxMsg(VarArgsParamMustComeLastID) {
def msg = em"""${hl("varargs")} parameter must come last"""
def explain =
em"""|The ${hl("varargs")} field must be the last field in the method signature.
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ ExprsInParens ::= ExprInParens {‘,’ ExprInParens}
ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here
| Expr
ParArgumentExprs ::= ‘(’ [‘using’] ExprsInParens ‘)’ exprs
| ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘:’ ‘_’ ‘*’ ‘)’ exprs :+ Typed(expr, Ident(wildcardStar))
| ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ exprs :+ Typed(expr, Ident(wildcardStar))
ArgumentExprs ::= ParArgumentExprs
| BlockExpr
BlockExpr ::= <<< (CaseClauses | Block) >>>
Expand Down Expand Up @@ -297,7 +297,7 @@ PatVar ::= varid
| ‘_’
Patterns ::= Pattern {‘,’ Pattern}
ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ Apply(fn, pats)
| ‘(’ [Patterns ‘,’] Pattern2 ‘:’ ‘_’ ‘*’ ‘)’
| ‘(’ [Patterns ‘,’] Pattern2 ‘*’ ‘)’
Copy link
Contributor

Choose a reason for hiding this comment

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

This syntax deviates from Scala 2.

I think it would be good to change it to:

Suggested change
| ‘(’ [Patterns ‘,’] Pattern2 ‘*’ ‘)’
| ‘(’ [Patterns ‘,’] id ‘*’ ‘)’

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that's orthogonal to this PR. I am not sure about that. It's the same as for other ascriptions in patterns. I think they are useful to have.

```

### Type and Value Parameters
Expand Down
42 changes: 0 additions & 42 deletions docs/docs/reference/changed-features/vararg-patterns.md

This file was deleted.

Loading