-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Conversation
Looks reasonable, what would you like to do better? |
I wanted to use |
e490aa3
to
2fc99ca
Compare
This comment has been minimized.
This comment has been minimized.
`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.
There was a problem hiding this 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, |
There was a problem hiding this comment.
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 = |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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>" } |
There was a problem hiding this comment.
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
😄
There was a problem hiding this comment.
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 😅
I verified the links use correct Dottydoc syntax.
950adde
to
f9990c7
Compare
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
andchangePrec
.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.