From a9032b0aa77d194e078b91ce7cfd2d431259f96c Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 8 Jun 2017 21:29:40 +0200 Subject: [PATCH] SI-4700 Types with symbolic names print in infix by default This a partial port of https://github.com/scala/scala/pull/5589 to dotty: when pretty-printing an applied type, if its type constructor has a symbolic name, then always print it in infix form. The PR in scalac also adds an `@showAsInfix` annotation to control this behavior, but we cannot do the same in dotty since we still rely on the standard library from Scala 2.11 and the annotation only exists in 2.12 and up. --- .../tools/dotc/printing/RefinedPrinter.scala | 23 +++++++++++++++++++ tests/repl/infix-types.check | 19 +++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/repl/infix-types.check diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 10c6b369c136..5b2cbc5718cb 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -124,6 +124,28 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toTextTuple(args.init) ("implicit " provided isImplicit) ~ argStr ~ " => " ~ argText(args.last) } + + def isInfixType(tp: Type): Boolean = tp match { + case AppliedType(tycon, args) => + args.length == 2 && + !Character.isUnicodeIdentifierStart(tycon.typeSymbol.name.toString.head) + // TODO: Once we use the 2.12 stdlib, also check the @showAsInfix annotation + 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)) "(" ~ toText(l) ~ ")" else toText(l) + val rightArg = if (!isRightAssoc && isInfixType(r)) "(" ~ toText(r) ~ ")" else toText(r) + + leftArg ~ " " ~ toTextLocal(op) ~ " " ~ rightArg + } + homogenize(tp) match { case x: ConstantType if homogenizedView => return toText(x.widen) @@ -132,6 +154,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (tycon.isRepeatedParam) return toTextLocal(args.head) ~ "*" if (defn.isFunctionClass(cls)) return toTextFunction(args, cls.name.isImplicitFunction) if (defn.isTupleClass(cls)) return toTextTuple(args) + if (isInfixType(tp)) return toTextInfixType(tycon, args) return (toTextLocal(tycon) ~ "[" ~ Text(args map argText, ", ") ~ "]").close case tp: TypeRef => val hideType = !ctx.settings.debugAlias.value && (tp.symbol.isAliasPreferred) diff --git a/tests/repl/infix-types.check b/tests/repl/infix-types.check new file mode 100644 index 000000000000..1f2a9275c23a --- /dev/null +++ b/tests/repl/infix-types.check @@ -0,0 +1,19 @@ +scala> 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> class &:[L, R] +defined class &: +scala> def foo: Int &: String = ??? +def foo: Int &: String +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> :quit