Skip to content

mixed left and right assoc #2007

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
Feb 20, 2017
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
17 changes: 8 additions & 9 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -404,14 +404,13 @@ object Parsers {

var opStack: List[OpInfo] = Nil

def checkAssoc(offset: Int, op: Name, leftAssoc: Boolean) =
if (isLeftAssoc(op) != leftAssoc)
syntaxError(
"left- and right-associative operators with same precedence may not be mixed", offset)
def checkAssoc(offset: Token, op1: Name, op2: Name, op2LeftAssoc: Boolean): Unit =
if (isLeftAssoc(op1) != op2LeftAssoc)
syntaxError(MixedLeftAndRightAssociativeOps(op1, op2, op2LeftAssoc), offset)

def reduceStack(base: List[OpInfo], top: Tree, prec: Int, leftAssoc: Boolean): Tree = {
def reduceStack(base: List[OpInfo], top: Tree, prec: Int, leftAssoc: Boolean, op2: Name): Tree = {
if (opStack != base && precedence(opStack.head.operator.name) == prec)
checkAssoc(opStack.head.offset, opStack.head.operator.name, leftAssoc)
checkAssoc(opStack.head.offset, opStack.head.operator.name, op2, leftAssoc)
def recur(top: Tree): Tree = {
if (opStack == base) top
else {
Expand Down Expand Up @@ -445,20 +444,20 @@ object Parsers {
var top = first
while (isIdent && in.name != notAnOperator) {
val op = if (isType) typeIdent() else termIdent()
top = reduceStack(base, top, precedence(op.name), isLeftAssoc(op.name))
top = reduceStack(base, top, precedence(op.name), isLeftAssoc(op.name), op.name)
opStack = OpInfo(top, op, in.offset) :: opStack
newLineOptWhenFollowing(canStartOperand)
if (maybePostfix && !canStartOperand(in.token)) {
val topInfo = opStack.head
opStack = opStack.tail
val od = reduceStack(base, topInfo.operand, 0, true)
val od = reduceStack(base, topInfo.operand, 0, true, in.name)
return atPos(startOffset(od), topInfo.offset) {
PostfixOp(od, topInfo.operator)
}
}
top = operand()
}
reduceStack(base, top, 0, true)
reduceStack(base, top, 0, true, in.name)
}

/* -------- IDENTIFIERS AND LITERALS ------------------------------------------- */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public enum ErrorMessageID {
OverridesNothingID,
OverridesNothingButNameExistsID,
ForwardReferenceExtendsOverDefinitionID,
ExpectedTokenButFoundID;
ExpectedTokenButFoundID,
MixedLeftAndRightAssociativeOpsID;

public int errorNumber() {
return ordinal() - 2;
Expand Down
34 changes: 34 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1093,4 +1093,38 @@ object messages {
|""".stripMargin
}

case class MixedLeftAndRightAssociativeOps(op1: Name, op2: Name, op2LeftAssoc: Boolean)(implicit ctx: Context)
extends Message(MixedLeftAndRightAssociativeOpsID) {
val kind = "Syntax"
val op1Asso = if (op2LeftAssoc) "which is right-associative" else "which is left-associative"
val op2Asso = if (op2LeftAssoc) "which is left-associative" else "which is right-associative"
val msg = s"`${op1}` (${op1Asso}) and `${op2}` ($op2Asso) have same precedence and may not be mixed"
val explanation =
s"""|The operators ${op1} and ${op2} are used as infix operators in the same expression,
|but they bind to different sides:
|${op1} is applied to the operand to its ${if (op2LeftAssoc) "right" else "left"}
|${op2} is applied to the operand to its ${if (op2LeftAssoc) "left" else "right"}
|As both have the same precedence the compiler can't decide which to apply first.
|
|You may use parenthesis to make the application order explicit,
|or use method application syntax `operand1.${op1}(operand2)`.
|
|Operators ending in a colon `:` are right-associative. All other operators are left-associative.
|
|Infix operator precedence is determined by the operator's first character. Characters are listed
|below in increasing order of precedence, with characters on the same line having the same precedence.
| (all letters)
| |
| ^
| &
| = !
| < >
| :
| + -
| * / %
| (all other special characters)
|Operators starting with a letter have lowest precedence, followed by operators starting with `|`, etc.
|""".stripMargin
}

}
23 changes: 23 additions & 0 deletions compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,27 @@ class ErrorMessagesTests extends ErrorMessagesTest {
""".stripMargin
}
.expectNoErrors

@Test def leftAndRightAssociative =
checkMessagesAfter("frontend") {
"""
|object Ops {
| case class I(j: Int) {
| def +-(i: Int) = i
| def +:(i: Int) = i
| }
| val v = I(1) +- I(4) +: I(4)
|}
""".stripMargin
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
val defn = ictx.definitions

assertMessageCount(1, messages)
val MixedLeftAndRightAssociativeOps(op1, op2, op2LeftAssoc) :: Nil = messages
assertEquals("+-", op1.show)
assertEquals("+:", op2.show)
assertFalse(op2LeftAssoc)
}
}