Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -471,13 +471,13 @@ object Parsers {
syntaxError(MixedLeftAndRightAssociativeOps(op1, op2, op2LeftAssoc), offset)

def reduceStack(base: List[OpInfo], top: Tree, prec: Int, leftAssoc: Boolean, op2: Name, isType: Boolean): Tree = {
if (opStack != base && precedence(opStack.head.operator.name) == prec)
if (opStack != base && precedence(opStack.head.operator.name, isType) == prec)
checkAssoc(opStack.head.offset, opStack.head.operator.name, op2, leftAssoc)
def recur(top: Tree): Tree = {
if (opStack == base) top
else {
val opInfo = opStack.head
val opPrec = precedence(opInfo.operator.name)
val opPrec = precedence(opInfo.operator.name, isType)
if (prec < opPrec || leftAssoc && prec == opPrec) {
opStack = opStack.tail
recur {
Expand Down Expand Up @@ -514,7 +514,7 @@ object Parsers {
var top = first
while (isIdent && isOperator) {
val op = if (isType) typeIdent() else termIdent()
top = reduceStack(base, top, precedence(op.name), isLeftAssoc(op.name), op.name, isType)
top = reduceStack(base, top, precedence(op.name, isType), isLeftAssoc(op.name), op.name, isType)
opStack = OpInfo(top, op, in.offset) :: opStack
newLineOptWhenFollowing(canStartOperand)
if (maybePostfix && !canStartOperand(in.token)) {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import core.NameOps._

package object parsing {

def precedence(operator: Name): Int =
def precedence(operator: Name, isType: Boolean = false): Int =
Copy link
Contributor

Choose a reason for hiding this comment

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

isType unused?

if (operator eq nme.ERROR) -1
else {
val firstCh = operator.firstPart.head
Expand Down
15 changes: 10 additions & 5 deletions compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,15 @@ class PlainPrinter(_ctx: Context) extends Printer {
(refinementNameString(rt) ~ toTextRHS(rt.refinedInfo)).close

protected def argText(arg: Type): Text = homogenizeArg(arg) match {
case arg: TypeBounds => "_" ~ toTextGlobal(arg)
case arg => toTextGlobal(arg)
case arg: TypeBounds => "_" ~ toText(arg)
case arg => toText(arg)
}

/** Pretty-print comma-separated type arguments for a constructor to be inserted among parentheses or brackets
* (hence with `GlobalPrec` precedence).
*/
protected def argsText(args: List[Type]): Text = atPrec(GlobalPrec) { Text(args.map(arg => argText(arg) ), ", ") }

/** The longest sequence of refinement types, starting at given type
* and following parents.
*/
Expand Down Expand Up @@ -144,7 +149,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
case tp: SingletonType =>
toTextLocal(tp.underlying) ~ "(" ~ toTextRef(tp) ~ ")"
case AppliedType(tycon, args) =>
(toTextLocal(tycon) ~ "[" ~ Text(args map argText, ", ") ~ "]").close
(toTextLocal(tycon) ~ "[" ~ argsText(args) ~ "]").close
case tp: RefinedType =>
val parent :: (refined: List[RefinedType @unchecked]) =
refinementChain(tp).reverse
Expand All @@ -156,9 +161,9 @@ class PlainPrinter(_ctx: Context) extends Printer {
}
finally openRecs = openRecs.tail
case AndType(tp1, tp2) =>
changePrec(AndPrec) { toText(tp1) ~ " & " ~ toText(tp2) }
changePrec(AndTypePrec) { toText(tp1) ~ " & " ~ atPrec(AndTypePrec + 1) { toText(tp2) } }
case OrType(tp1, tp2) =>
changePrec(OrPrec) { toText(tp1) ~ " | " ~ toText(tp2) }
changePrec(OrTypePrec) { toText(tp1) ~ " | " ~ atPrec(OrTypePrec + 1) { toText(tp2) } }
case tp: ErrorType =>
s"<error ${tp.msg.msg}>"
case tp: WildcardType =>
Expand Down
39 changes: 37 additions & 2 deletions compiler/src/dotty/tools/dotc/printing/Printer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,24 @@ abstract class Printer {

private[this] var prec: Precedence = GlobalPrec

/** The current precedence level */
/** The current precedence level.
* WHen pretty-printing arguments of operator `op`, `currentPrecedence` must equal `op`'s precedence level,
Copy link
Contributor

@OlivierBlanvillain OlivierBlanvillain Aug 15, 2018

Choose a reason for hiding this comment

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

s/WHen/When/

* so that pretty-printing expressions using lower-precedence operators can insert parentheses automatically
* by calling `changePrec`.
*/
def currentPrecedence = prec

/** Generate text using `op`, assuming a given precedence level `prec`. */
/** Generate text using `op`, assuming a given precedence level `prec`.
*
* ### `atPrec` vs `changePrec`
*
* This is to be used when changing precedence inside some sort of parentheses:
* for instance, to print T[A]` use
* `toText(T) ~ '[' ~ atPrec(GlobalPrec) { toText(A) } ~ ']'`.
*
* If the presence of the parentheses depends on precedence, inserting them manually is most certainly a bug.
* Use `changePrec` instead to generate them exactly when needed.
*/
def atPrec(prec: Precedence)(op: => Text): Text = {
val outerPrec = this.prec
this.prec = prec
Expand All @@ -30,6 +44,27 @@ abstract class Printer {

/** Generate text using `op`, assuming a given precedence level `prec`.
* If new level `prec` is lower than previous level, put text in parentheses.
*
* ### `atPrec` vs `changePrec`
*
* To pretty-print `A op B`, you need something like
* `changePrec(parsing.precedence(op, isType)) { toText(a) ~ op ~ toText(b) }` // BUGGY
* that will insert parentheses around `A op B` if, for instance, the
* preceding operator has higher precedence.
*
* But that does not handle infix operators with left- or right- associativity.
*
* If op and op' have the same precedence and associativity,
* A op B op' C parses as (A op B) op' C if op and op' are left-associative, and as
* A op (B op' C) if they're right-associative, so we need respectively
* ```scala
* val isType = ??? // is this a term or type operator?
* val prec = parsing.precedence(op, isType)
* // either:
* changePrec(prec) { toText(a) ~ op ~ atPrec(prec + 1) { toText(b) } } // for left-associative op and op'
* // or:
* changePrec(prec) { atPrec(prec + 1) { toText(a) } ~ op ~ toText(b) } // for right-associative op and op'
* ```
*/
def changePrec(prec: Precedence)(op: => Text): Text =
if (prec < this.prec) atPrec(prec) ("(" ~ op ~ ")") else atPrec(prec)(op)
Expand Down
52 changes: 36 additions & 16 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {

override def toText(tp: Type): Text = controlled {
def toTextTuple(args: List[Type]): Text =
"(" ~ Text(args.map(argText), ", ") ~ ")"
"(" ~ argsText(args) ~ ")"

def toTextFunction(args: List[Type], isImplicit: Boolean, isErased: Boolean): Text =
changePrec(GlobalPrec) {
Expand All @@ -155,17 +155,24 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
case _ => false
}

def toTextInfixType(op: Type, args: List[Type]): Text = {
/* SLS 3.2.8: all infix types have the same precedence.
* In A op B op' C, op and op' need the same associativity.
* Therefore, if op is left associative, anything on its right
* needs to be parenthesized if it's an infix type, and vice versa. */
val l :: r :: Nil = args
val isRightAssoc = op.typeSymbol.name.endsWith(":")
val leftArg = if (isRightAssoc && isInfixType(l)) "(" ~ argText(l) ~ ")" else argText(l)
val rightArg = if (!isRightAssoc && isInfixType(r)) "(" ~ argText(r) ~ ")" else argText(r)

leftArg ~ " " ~ simpleNameString(op.classSymbol) ~ " " ~ rightArg
def tyconName(tp: Type): Name = tp.typeSymbol.name
def checkAssocMismatch(tp: Type, isRightAssoc: Boolean) = tp match {
case AppliedType(tycon, _) => isInfixType(tp) && tyconName(tycon).endsWith(":") != isRightAssoc
case AndType(_, _) => isRightAssoc
case OrType(_, _) => isRightAssoc
case _ => false
}

def toTextInfixType(opName: Name, l: Type, r: Type)(op: => Text): Text = {
val isRightAssoc = opName.endsWith(":")
val opPrec = parsing.precedence(opName, true)

changePrec(opPrec) {
val leftPrec = if (isRightAssoc || checkAssocMismatch(l, isRightAssoc)) opPrec + 1 else opPrec
val rightPrec = if (!isRightAssoc || checkAssocMismatch(r, isRightAssoc)) opPrec + 1 else opPrec

atPrec(leftPrec) { argText(l) } ~ " " ~ op ~ " " ~ atPrec(rightPrec) { argText(r) }
}
}

homogenize(tp) match {
Expand All @@ -174,7 +181,20 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
if (tycon.isRepeatedParam) return toTextLocal(args.head) ~ "*"
if (defn.isFunctionClass(cls)) return toTextFunction(args, cls.name.isImplicitFunction, cls.name.isErasedFunction)
if (defn.isTupleClass(cls)) return toTextTuple(args)
if (isInfixType(tp)) return toTextInfixType(tycon, args)
if (isInfixType(tp)) {
val l :: r :: Nil = args
val opName = tyconName(tycon)

return toTextInfixType(tyconName(tycon), l, r) { simpleNameString(tycon.typeSymbol) }
}

// Since RefinedPrinter, unlike PlainPrinter, can output right-associative type-operators, we must override handling
// of AndType and OrType to account for associativity
case AndType(tp1, tp2) =>
return toTextInfixType(tpnme.raw.AMP, tp1, tp2) { toText(tpnme.raw.AMP) }
case OrType(tp1, tp2) =>
return toTextInfixType(tpnme.raw.BAR, tp1, tp2) { toText(tpnme.raw.BAR) }

case EtaExpansion(tycon) =>
return toText(tycon)
case tp: RefinedType if defn.isFunctionType(tp) =>
Expand All @@ -201,7 +221,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
// (they don't need to because we keep the original type tree with
// the original annotation anyway. Therefore, there will always be
// one version of the annotation tree that has the correct positions).
withoutPos(super.toText(tp))
return withoutPos(super.toText(tp))
case tp: SelectionProto =>
return "?{ " ~ toText(tp.name) ~
(Str(" ") provided !tp.name.toSimpleName.last.isLetterOrDigit) ~
Expand Down Expand Up @@ -375,9 +395,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
case SingletonTypeTree(ref) =>
toTextLocal(ref) ~ "." ~ keywordStr("type")
case AndTypeTree(l, r) =>
changePrec(AndPrec) { toText(l) ~ " & " ~ toText(r) }
changePrec(AndTypePrec) { toText(l) ~ " & " ~ atPrec(AndTypePrec + 1) { toText(r) } }
case OrTypeTree(l, r) =>
changePrec(OrPrec) { toText(l) ~ " | " ~ toText(r) }
changePrec(OrTypePrec) { toText(l) ~ " | " ~ atPrec(OrTypePrec + 1) { toText(r) } }
case RefinedTypeTree(tpt, refines) =>
toTextLocal(tpt) ~ " " ~ blockText(refines)
case AppliedTypeTree(tpt, args) =>
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/Texts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import language.implicitConversions

object Texts {

abstract class Text {
sealed abstract class Text {

protected def indentMargin = 2

Expand Down Expand Up @@ -46,9 +46,11 @@ object Texts {
case Str(s1, lines2) => Str(s1 + s2, lines1 union lines2)
case Fluid(Str(s1, lines2) :: prev) => Fluid(Str(s1 + s2, lines1 union lines2) :: prev)
case Fluid(relems) => Fluid(that :: relems)
case Vertical(_) => throw new IllegalArgumentException("Unexpected Vertical.appendToLastLine")
}
case Fluid(relems) =>
(this /: relems.reverse)(_ appendToLastLine _)
case Vertical(_) => throw new IllegalArgumentException("Unexpected Text.appendToLastLine(Vertical(...))")
}

private def appendIndented(that: Text)(width: Int): Text =
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/printing/package.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dotty.tools.dotc

import core.StdNames.nme
import core.StdNames.{nme,tpnme}
import parsing.{precedence, minPrec, maxPrec, minInfixPrec}
import util.Property.Key

Expand All @@ -9,7 +9,8 @@ package object printing {
type Precedence = Int

val DotPrec = parsing.maxPrec
val AndPrec = parsing.precedence(nme.raw.AMP)
val AndTypePrec = parsing.precedence(tpnme.raw.AMP, true)
val OrTypePrec = parsing.precedence(tpnme.raw.BAR, true)
val OrPrec = parsing.precedence(nme.raw.BAR)
val InfixPrec = parsing.minInfixPrec
val GlobalPrec = parsing.minPrec
Expand Down
8 changes: 8 additions & 0 deletions compiler/test-resources/type-printer/functions
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
scala> val toInt: Any => Int = new { def apply(a: Any) = 1; override def toString() = "<func1>" }
val toInt: Any => Int = <func1>
scala> val hoFun: (Int => Int) => Int = new { def apply(a: Int => Int) = 1; override def toString() = "<func2>" }
val hoFun: (Int => Int) => Int = <func2>
scala> val curriedFun: Int => (Int => Int) = new { def apply(a: Int) = _ => 1; override def toString() = "<func3>" }
val curriedFun: Int => Int => Int = <func3>
scala> val tupFun: ((Int, Int)) => Int = new { def apply(a: (Int, Int)) = 1; override def toString() = "<func4>" }
val tupFun: ((Int, Int)) => Int = <func4>
scala> val binFun: (Int, Int) => Int = new { def apply(a: Int, b: Int) = 1; override def toString() = "<func5>" }
val binFun: (Int, Int) => Int = <func5>
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we rewrite these tests as:

- scala> val toInt: Any => Int = new { def apply(a: Any) = 1; override def toString() = "<func1>" }                                                                                                               
- val toInt: Any => Int = <func1>
+ scala> def toInt: Any => Int = ???
+ def toInt: Any => Int

There is too much noise with vals

36 changes: 35 additions & 1 deletion compiler/test-resources/type-printer/infix
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,46 @@ scala> def foo: (Int && String) &: Boolean = ???
def foo: (Int && String) &: Boolean
scala> def foo: Int && (Boolean &: String) = ???
def foo: Int && (Boolean &: String)
scala> def foo: (Int &: String) && Boolean = ???
def foo: (Int &: String) && Boolean
scala> def foo: Int &: (Boolean && String) = ???
def foo: Int &: (Boolean && String)
scala> def foo: (Int & String) &: Boolean = ???
def foo: (Int & String) &: Boolean
scala> def foo: Int & (Boolean &: String) = ???
def foo: Int & (Boolean &: String)
scala> def foo: (Int &: String) & Boolean = ???
def foo: (Int &: String) & Boolean
scala> def foo: Int &: (Boolean & String) = ???
def foo: Int &: (Boolean & String)
scala> import scala.annotation.showAsInfix
scala> @scala.annotation.showAsInfix class Mappy[T,U]
// defined class Mappy
scala> def foo: (Int Mappy Boolean) && String = ???
def foo: (Int Mappy Boolean) && String
scala> def foo: Int Mappy Boolean && String = ???
def foo: Int Mappy Boolean && String
scala> def foo: Int Mappy (Boolean && String) = ???
def foo: Int Mappy (Boolean && String)
def foo: Int Mappy Boolean && String
scala> @scala.annotation.showAsInfix(false) class ||[T,U]
// defined class ||
scala> def foo: Int || Boolean = ???
def foo: ||[Int, Boolean]
scala> def foo: Int && Boolean & String = ???
def foo: Int && Boolean & String
scala> def foo: (Int && Boolean) & String = ???
def foo: Int && Boolean & String
scala> def foo: Int && (Boolean & String) = ???
def foo: Int && (Boolean & String)
scala> def foo: Int && (Boolean with String) = ???
def foo: Int && (Boolean & String)
scala> def foo: (Int && Boolean) with String = ???
def foo: Int && Boolean & String
scala> def foo: Int && Boolean with String = ???
def foo: Int && (Boolean & String)
scala> def foo: Int && Boolean | String = ???
def foo: Int && Boolean | String
scala> def foo: Int && (Boolean | String) = ???
def foo: Int && (Boolean | String)
scala> def foo: (Int && Boolean) | String = ???
def foo: Int && Boolean | String
26 changes: 25 additions & 1 deletion compiler/test/dotty/tools/dotc/printing/PrinterTests.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package dotty.tools.dotc.printing

import dotty.tools.DottyTest
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.{Trees,tpd}
import dotty.tools.dotc.core.Names._
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.core.Decorators._
import org.junit.Assert.assertEquals
import org.junit.Test

class PrinterTests extends DottyTest {

private def newContext = {
initialCtx.setSetting(ctx.settings.color, "never")
}
ctx = newContext

import tpd._

@Test
Expand All @@ -24,4 +31,21 @@ class PrinterTests extends DottyTest {
assertEquals("package object foo", bar.symbol.owner.show)
}
}

@Test
def tpTreeInfixOps: Unit = {
val source = """
|class &&[T,U]
|object Foo {
| def bar1: Int && (Boolean | String) = ???
| def bar2: Int & (Boolean | String) = ???
|}
""".stripMargin

checkCompile("frontend", source) { (tree, context) =>
implicit val ctx = context
val bar @ Trees.DefDef(_, _, _, _, _) = tree.find(tree => tree.symbol.name == termName("bar2")).get
assertEquals("Int & (Boolean | String)", bar.tpt.show)
}
}
}
2 changes: 1 addition & 1 deletion tests/patmat/andtype-opentype-interaction.check
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
31: Pattern Match Exhaustivity: _: SealedTrait & OpenClass, _: Trait & OpenClass
35: Pattern Match Exhaustivity: _: SealedTrait & OpenTrait & OpenClass, _: Trait & OpenTrait & OpenClass
43: Pattern Match Exhaustivity: _: SealedTrait & OpenAbstractClass, _: Trait & OpenAbstractClass
47: Pattern Match Exhaustivity: _: SealedTrait & OpenClass & OpenTrait & OpenClassSubclass, _: Trait & OpenClass & OpenTrait & OpenClassSubclass
47: Pattern Match Exhaustivity: _: SealedTrait & OpenClass & (OpenTrait & OpenClassSubclass), _: Trait & OpenClass & (OpenTrait & OpenClassSubclass)
4 changes: 2 additions & 2 deletions tests/patmat/andtype-refinedtype-interaction.check
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
32: Pattern Match Exhaustivity: _: Trait & C1{x: Int}
48: Pattern Match Exhaustivity: _: Clazz & (C1 | C2 | T1){x: Int} & (C3 | C4 | T2){x: Int}, _: Trait & (C1 | C2 | T1){x: Int} & (C3 | C4 | T2){x: Int}
54: Pattern Match Exhaustivity: _: Trait & (C1 | C2 | T1){x: Int} & C3{x: Int}
48: Pattern Match Exhaustivity: _: Clazz & (C1 | (C2 | T1)){x: Int} & (C3 | (C4 | T2)){x: Int}, _: Trait & (C1 | (C2 | T1)){x: Int} & (C3 | (C4 | T2)){x: Int}
54: Pattern Match Exhaustivity: _: Trait & (C1 | (C2 | T1)){x: Int} & C3{x: Int}
65: Pattern Match Exhaustivity: _: Trait & (C1 | C2){x: Int} & (C3 | SubC1){x: Int}
72: Pattern Match Exhaustivity: _: Trait & (T1 & (C1 | SubC2)){x: Int} & (T2 & (C2 | C3 | SubC1)){x: Int} &
SubSubC1{x: Int}
Expand Down