Skip to content

Reject postfix ops already in Parser #14677

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 3 commits into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/backend/jvm/BytecodeWriters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ trait BytecodeWriters {
super.writeClass(label, jclassName, jclassBytes, outfile)

val segments = jclassName.split("[./]")
val asmpFile = segments.foldLeft(baseDir: Path)(_ / _) changeExtension "asmp" toFile;
val asmpFile = segments.foldLeft(baseDir: Path)(_ / _).changeExtension("asmp").toFile

asmpFile.parent.createDirectory()
emitAsmp(jclassBytes, asmpFile)
Expand Down Expand Up @@ -131,7 +131,7 @@ trait BytecodeWriters {
super.writeClass(label, jclassName, jclassBytes, outfile)

val pathName = jclassName
val dumpFile = pathName.split("[./]").foldLeft(baseDir: Path) (_ / _) changeExtension "class" toFile;
val dumpFile = pathName.split("[./]").foldLeft(baseDir: Path) (_ / _).changeExtension("class").toFile
dumpFile.parent.createDirectory()
val outstream = new DataOutputStream(new FileOutputStream(dumpFile.path))

Expand Down
16 changes: 2 additions & 14 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1722,28 +1722,16 @@ object desugar {
// This is a deliberate departure from scalac, where StringContext is not rooted (See #4732)
Apply(Select(Apply(scalaDot(nme.StringContext), strs), id).withSpan(tree.span), elems)
case PostfixOp(t, op) =>
if ((ctx.mode is Mode.Type) && !isBackquoted(op) && op.name == tpnme.raw.STAR) {
if (ctx.mode is Mode.Type) && !isBackquoted(op) && op.name == tpnme.raw.STAR then
if ctx.isJava then
AppliedTypeTree(ref(defn.RepeatedParamType), t)
else
Annotated(
AppliedTypeTree(ref(defn.SeqType), t),
New(ref(defn.RepeatedAnnot.typeRef), Nil :: Nil))
}
else {
else
assert(ctx.mode.isExpr || ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive), ctx.mode)
if (!enabled(nme.postfixOps)) {
report.error(
s"""postfix operator `${op.name}` needs to be enabled
|by making the implicit value scala.language.postfixOps visible.
|----
|This can be achieved by adding the import clause 'import scala.language.postfixOps'
|or by setting the compiler option -language:postfixOps.
|See the Scaladoc for value scala.language.postfixOps for a discussion
|why the feature needs to be explicitly enabled.""".stripMargin, t.srcPos)
}
Select(t, op.name)
}
case PrefixOp(op, t) =>
val nspace = if (ctx.mode.is(Mode.Type)) tpnme else nme
Select(t, nspace.UNARY_PREFIX ++ op.name)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/PathResolver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ object PathResolver {
def ppcp(s: String): String = split(s) match {
case Nil => ""
case Seq(x) => x
case xs => xs map ("\n" + _) mkString
case xs => xs.map("\n" + _).mkString
}

/** Values found solely by inspecting environment or property variables.
Expand Down
27 changes: 14 additions & 13 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ object Parsers {
enum ParamOwner:
case Class, Type, TypeParam, Def

enum ParseKind:
case Expr, Type, Pattern

type StageKind = Int
object StageKind {
val None = 0
Expand Down Expand Up @@ -939,18 +942,19 @@ object Parsers {
def infixOps(
first: Tree, canStartOperand: Token => Boolean, operand: Location => Tree,
location: Location,
isType: Boolean,
isOperator: => Boolean,
maybePostfix: Boolean = false): Tree = {
kind: ParseKind,
isOperator: => Boolean): Tree =
val base = opStack

def recur(top: Tree): Tree =
val isType = kind == ParseKind.Type
if (isIdent && isOperator) {
val op = if (isType) typeIdent() else termIdent()
val op = if isType then typeIdent() else termIdent()
val top1 = reduceStack(base, top, precedence(op.name), !op.name.isRightAssocOperatorName, op.name, isType)
opStack = OpInfo(top1, op, in.offset) :: opStack
colonAtEOLOpt()
newLineOptWhenFollowing(canStartOperand)
val maybePostfix = kind == ParseKind.Expr && in.postfixOpsEnabled
if (maybePostfix && !canStartOperand(in.token)) {
val topInfo = opStack.head
opStack = opStack.tail
Expand All @@ -973,7 +977,7 @@ object Parsers {
else top

recur(first)
}
end infixOps

/* -------- IDENTIFIERS AND LITERALS ------------------------------------------- */

Expand Down Expand Up @@ -1525,8 +1529,7 @@ object Parsers {
def infixType(): Tree = infixTypeRest(refinedType())

def infixTypeRest(t: Tree): Tree =
infixOps(t, canStartTypeTokens, refinedTypeFn, Location.ElseWhere,
isType = true,
infixOps(t, canStartTypeTokens, refinedTypeFn, Location.ElseWhere, ParseKind.Type,
isOperator = !followingIsVararg())

/** RefinedType ::= WithType {[nl] Refinement}
Expand Down Expand Up @@ -2218,10 +2221,8 @@ object Parsers {
t

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

@pikinier20 pikinier20 Mar 14, 2022

Choose a reason for hiding this comment

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

I wonder whether just changing there the value of maybePostfix to in.postfixOpsEnabled wouldn't do the same as all these parser changes.

I mean, this is the only place where we set ParseKind.Expr so instead of checking if the postfix is enabled in infixOps, we can do it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, since even with postfixOpsEnabled we do not allow postfix ops in patterns and types.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, but for these cases the maybePostfix parameter was already by default false.

infixOps(t, in.canStartExprTokens, prefixExpr, location, ParseKind.Expr,
isOperator = !(location.inArgs && followingIsVararg()))

/** PrefixExpr ::= [PrefixOperator'] SimpleExpr
* PrefixOperator ::= ‘-’ | ‘+’ | ‘~’ | ‘!’
Expand Down Expand Up @@ -2708,8 +2709,8 @@ object Parsers {
/** InfixPattern ::= SimplePattern {id [nl] SimplePattern}
*/
def infixPattern(): Tree =
infixOps(simplePattern(), in.canStartExprTokens, simplePatternFn, Location.InPattern,
isType = false,
infixOps(
simplePattern(), in.canStartExprTokens, simplePatternFn, Location.InPattern, ParseKind.Pattern,
isOperator = in.name != nme.raw.BAR && !followingIsVararg())

/** SimplePattern ::= PatVar
Expand Down
9 changes: 9 additions & 0 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,15 @@ object Scanners {
fewerBracesEnabledCtx = myLanguageImportContext
fewerBracesEnabledCache

private var postfixOpsEnabledCache = false
private var postfixOpsEnabledCtx: Context = NoContext

def postfixOpsEnabled =
if postfixOpsEnabledCtx ne myLanguageImportContext then
postfixOpsEnabledCache = featureEnabled(nme.postfixOps)
postfixOpsEnabledCtx = myLanguageImportContext
postfixOpsEnabledCache

/** All doc comments kept by their end position in a `Map`.
*
* Note: the map is necessary since the comments are looked up after an
Expand Down
2 changes: 0 additions & 2 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ class CompilationTests {
compileFile("tests/pos-special/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
compileFile("tests/pos-special/kind-projector-underscores.scala", defaultOptions.and("-Ykind-projector:underscores")),
compileFile("tests/run/i5606.scala", defaultOptions.and("-Yretain-trees")),
compileFile("tests/pos-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"),
compileFile("tests/pos-custom-args/i8875.scala", defaultOptions.and("-Xprint:getters")),
compileFile("tests/pos-custom-args/i9267.scala", defaultOptions.and("-Ystop-after:erasure")),
compileFile("tests/pos-special/extend-java-enum.scala", defaultOptions.and("-source", "3.0-migration")),
Expand Down Expand Up @@ -180,7 +179,6 @@ class CompilationTests {
compileFile("tests/neg-custom-args/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
compileFile("tests/neg-custom-args/kind-projector-underscores.scala", defaultOptions.and("-Ykind-projector:underscores")),
compileFile("tests/neg-custom-args/typeclass-derivation2.scala", defaultOptions.and("-language:experimental.erasedDefinitions")),
compileFile("tests/neg-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"),
compileFile("tests/neg-custom-args/deptypes.scala", defaultOptions.and("-language:experimental.dependent")),
compileFile("tests/neg-custom-args/matchable.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
compileFile("tests/neg-custom-args/i7314.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
Expand Down
4 changes: 2 additions & 2 deletions compiler/test/dotty/tools/repl/TabcompleteTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class TabcompleteTests extends ReplTest {
| case dot_product_*
| case __system
|
|Foo."""stripMargin))
|Foo.""".stripMargin))
}


Expand All @@ -192,7 +192,7 @@ class TabcompleteTests extends ReplTest {
| case dot_product_*
| case __system
|
|Foo.`bac"""stripMargin))
|Foo.`bac""".stripMargin))
}

@Test def commands = initially {
Expand Down
2 changes: 1 addition & 1 deletion compiler/test/dotty/tools/vulpix/TestConfiguration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ object TestConfiguration {

val yCheckOptions = Array("-Ycheck:all")

val commonOptions = Array("-indent", "-language:postfixOps") ++ checkOptions ++ noCheckOptions ++ yCheckOptions
val commonOptions = Array("-indent") ++ checkOptions ++ noCheckOptions ++ yCheckOptions
val defaultOptions = TestFlags(basicClasspath, commonOptions)
val unindentOptions = TestFlags(basicClasspath, Array("-no-indent") ++ checkOptions ++ noCheckOptions ++ yCheckOptions)
val withCompilerOptions =
Expand Down
2 changes: 1 addition & 1 deletion project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ object Build {
"-unchecked",
"-Xfatal-warnings",
"-encoding", "UTF8",
"-language:existentials,higherKinds,implicitConversions,postfixOps"
"-language:existentials,higherKinds,implicitConversions"
),

(Compile / compile / javacOptions) ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ final class Parser(
stack.length > 0 && char != endOfText
}) do {}

list mkString
list.mkString
}

def getInline(isInlineEnd: => Boolean, textTransform: String => String = identity): Inline = {
Expand Down
20 changes: 0 additions & 20 deletions tests/neg-custom-args/i5498-postfixOps.check

This file was deleted.

1 change: 1 addition & 0 deletions tests/neg/i12361.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import language.postfixOps
object Test {
foo = macro Impls . foo [ U ] += // error // error
}
17 changes: 17 additions & 0 deletions tests/neg/i14564.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- [E018] Syntax Error: tests/neg/i14564.scala:5:28 --------------------------------------------------------------------
5 |def test = sum"${ List(42)* }" // error // error
| ^
| expression expected but '}' found
|
| longer explanation available when compiling with `-explain`
-- [E008] Not Found Error: tests/neg/i14564.scala:5:26 -----------------------------------------------------------------
5 |def test = sum"${ List(42)* }" // error // error
| ^^^^^^^^^
| value * is not a member of List[Int], but could be made available as an extension method.
|
| One of the following imports might make progress towards fixing the problem:
|
| import math.Fractional.Implicits.infixFractionalOps
| import math.Integral.Implicits.infixIntegralOps
| import math.Numeric.Implicits.infixNumericOps
|
6 changes: 6 additions & 0 deletions tests/neg/i14564.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import language.postfixOps as _

extension (sc: StringContext) def sum(xs: Int*): String = xs.sum.toString

def test = sum"${ List(42)* }" // error // error

2 changes: 1 addition & 1 deletion tests/neg/i1672.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
class Test {
implicit def compareComparables[T](x: T)(implicit ord: Ordering[T]) = // error: result type of implicit definition needs to be given explicitly
new ord.OrderingOps(x)
class Bippy { def compare(y: Bippy) = util Random }
class Bippy { def compare(y: Bippy) = util.Random }
() < () // error: value `<` is not a member of Unit
}
1 change: 1 addition & 0 deletions tests/neg/i1707.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import language.postfixOps
object DepBug {
class A {
class B
Expand Down
16 changes: 16 additions & 0 deletions tests/neg/i5498-postfixOps.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- [E018] Syntax Error: tests/neg/i5498-postfixOps.scala:4:10 ----------------------------------------------------------
4 | 1 second // error: usage of postfix operator
| ^
| expression expected but end of statement found
|
| longer explanation available when compiling with `-explain`
-- [E018] Syntax Error: tests/neg/i5498-postfixOps.scala:6:37 ----------------------------------------------------------
6 | Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator // error
| ^
| expression expected but ')' found
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg/i5498-postfixOps.scala:6:0 -------------------------------------------------------------------------
6 | Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator // error
|^
|no implicit argument of type scala.concurrent.duration.DurationConversions.Classifier[Null] was found for parameter ev of method second in trait DurationConversions
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import scala.concurrent.duration.*
def test() = {
1 second // error: usage of postfix operator

Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator
Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator // error
}
1 change: 1 addition & 0 deletions tests/neg/i7438.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import language.postfixOps
type Tr[+A]
extension [A, B](tr: Tr[A]) inline def map(f: A => B): Tr[B] = ???

Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i9344.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

import language.postfixOps
object Test {
val I1: Int = 0 * * * 8 * 1 - 1 + 1 + // error
}
1 change: 1 addition & 0 deletions tests/neg/parser-stability-7.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import language.postfixOps
class x0[x1] {
def x2: x1
}
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/t6124.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

import language.postfixOps
trait T {
def f = 3_14_E-2 // error
def e = 3_14E-_2 // error
Expand Down
9 changes: 0 additions & 9 deletions tests/pos-custom-args/i5498-postfixOps.scala

This file was deleted.

7 changes: 4 additions & 3 deletions tests/pos/i5498-postfixOps.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import scala.concurrent.duration.*

import scala.language.postfixOps

def test() = {
// only OK since the defaultOptions in the TestConfiguration includes -language:postfixOps
1 second

Seq(1, 2).filter(List(1,2) contains)
}
Seq(1, 2) filter (List(1,2) contains) toList
}
1 change: 1 addition & 0 deletions tests/pos/test-desugar.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import language.postfixOps
object desugar {

// variables
Expand Down
2 changes: 1 addition & 1 deletion tests/run/t8346.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ object Test extends App {
inits foreach {case (name, singleton) =>
print(s"${name}: ")
val one = singleton(1)
println(Seq(2,3,4).scanLeft(one)(_ + _) map sVarInfo toList)
println(Seq(2,3,4).scanLeft(one)(_ + _).map(sVarInfo).toList)
}

println(s"ValueSet: ${sVarInfo(SomeEnum.values)}")
Expand Down