Skip to content

Pretty-printing: handle type-operator precedence correctly #4072

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 13 commits into from
Aug 21, 2018

Conversation

Blaisorblade
Copy link
Contributor

@Blaisorblade Blaisorblade commented Mar 5, 2018

Fix #4068 (pretty-printing precedence for function arrows).

And pretty-print type operators by handling precedence and associativity more correctly: that's the biggest part of the PR.

This also encapsulate the precedence of type operators, and don't assume it's the same as the one for term operators. It will be if SIP-33's implementation is merged, but since this flexibility is cheap, I'd rather keep it till we're sure it can be simplified.

The PR has been rebased and edited so that reviewing by commit should be feasible, but you'll want to start from the new documentation for atPrec and changePrec.

I didn't document it just for fun, but because they must be used in an extremely precise way, and the existing code showed the correct approach wasn't clear to past maintainers. If one doesn't use precedence correctly, one must handle pretty-printing for all combinations of operators.

https://github.com/lampepfl/dotty/pull/4072/files#diff-cc3cc3d0c223249636132a7b11e4690d

I'm not as confident about my handling of associativity mismatches, but the code still needs at worst to handle each type constructor, not each combination, so it should be OK.

@Blaisorblade Blaisorblade self-assigned this Mar 5, 2018
@OlivierBlanvillain
Copy link
Contributor

Looks reasonable, what would you like to do better?

@Blaisorblade
Copy link
Contributor Author

I wanted to use atPrec(...) but failed. Feel free to merge if you think it improves things, I can still fix it better later.

@Blaisorblade Blaisorblade force-pushed the fix-#4068 branch 3 times, most recently from e490aa3 to 2fc99ca Compare July 30, 2018 22:07
@Blaisorblade

This comment has been minimized.

@Blaisorblade Blaisorblade changed the title Fix precedence in pretty-printing Fix precedence for infix type-operators (pretty-printing & parsing) Aug 5, 2018
`argText` uses `toTextGlobal` to change priority; instead, call `toText(arg)`
and leave the priority up to the caller.
- call-sites where `GlobalPrec` makes sense use now `argsText`.
- to ensure this is a refactoring, other call-sites now use `atPrec(GlobalPrec)`
  explicitly; all those calls will be corrected in subsequent commits.
Using `atPrec(GlobalPrec)` ensures that `argText(args.head)` never adds
parentheses around its output, but that's inappropriate for a function type
`T1 => T2`. The nested calls to `atPrec` make no sense, but `argText` hid that
before it was refactored.
Encapsulate the precedence rules in `parsing.precedence` while allowing for
different precedence between term- and type-level operators, as mandated by the
spec.

This is just a refactoring, the actual priority changes are separate.

* Add an `isType` flag to `parsing.precedence` and add
  constants for `AndTypePrec` and `OrTypePrec` (distinct from `OrPrec` and
  `AndPrec`, which is now unused and dropped).
* In the parser, consider `isType` to decide priority.
* In the pretty-printer, use `AndTypePrec` and `OrTypePrec` where appropriate.
While the operators should be associative, they aren't actually guaranteed to
be, so correct pretty-printing will help users.

* PrinterTests: Add first test for TypeTree printing.
* PrinterTests: disable colors, following ErrorMessagesTest.scala.
This way, printing an infix type need not inspect its arguments; that ensures we
don't need to handle *all* the possible forms of types that need to be wrapped
in parentheses.

I did this purely as a refactoring, based on how pretty-printers are supposed to
be written. But the new test case, combining infix type operators and builting
connectives, didn't work with the old code. It also doesn't work in Scala 2:

```scala
Welcome to Scala 2.12.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_172).
Type in expressions for evaluation. Or try :help.

scala> class &&[T,U]
defined class $amp$amp

scala> def foo: Int && Boolean = ???
foo: Int && Boolean

scala> def foo: Int && Boolean with String = ???
foo: Int && Boolean with String

scala> def foo: Int && (Boolean with String) = ???
foo: Int && Boolean with String

scala> def foo: (Int && Boolean) with String = ???
foo: Int && Boolean with String
```
* RefinedPrinter: Handle associativity mismatches for Type operators.
* Add Documentation/tutorial on how to use atPrec and changePrec correctly,
  since so much code got it wrong and it takes a while to get it.
* Extend tests for Type printing.
Refines 8b941d6: classSymbol is unnecessarily
aggressive here.
@Blaisorblade Blaisorblade changed the title Fix precedence for infix type-operators (pretty-printing & parsing) Pretty-printing: handle type-operator precedence correctly Aug 9, 2018
Copy link
Contributor

@OlivierBlanvillain OlivierBlanvillain left a comment

Choose a reason for hiding this comment

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

Looks good!

@@ -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/

@@ -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?

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

@@ -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> def toInt: Any => Int = new { def apply(a: Any) = 1; override def toString() = "<func1>" }
Copy link
Contributor

@allanrenucci allanrenucci Aug 20, 2018

Choose a reason for hiding this comment

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

RHS can be ???. it would make the tests more readable. That was the point of changing to def 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

aaah sorry i wasn't thinking fixed 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Pretty-printing precedence issue
3 participants