-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
With the way right associative extension methods are desugared (https://docs.scala-lang.org/scala3/reference/contextual/right-associative-extension-methods.html), some different methods end up with the same desugared form.
Both of those methods:
extension (x: X) def +:: [T](y: Y)
extension [T](x: X) def +:: (y: Y)
are desugared to:
<extension> def +:: [T](y: Y)(x: X)
It's important that the desugaring is reversible for some tools in the ecosystem, to display correct signature.
Scaladoc hacked solved this by sorting the parameters by their source positions, but the problem remains in ShortenedTypePrinter
and RefinedPrinter
:
scala3/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala
Lines 425 to 436 in c61897d
if gsym.name.isRightAssocOperatorName then | |
val (leadingTyParamss, rest1) = paramss match | |
case fst :: tail if isTypeParamClause(fst) => (List(fst), tail) | |
case other => (List(), other) | |
val (leadingUsing, rest2) = rest1.span(isUsingClause) | |
val (rightTyParamss, rest3) = rest2.span(isTypeParamClause) | |
val (rightParamss, rest4) = rest3.splitAt(1) | |
val (leftParamss, rest5) = rest4.splitAt(1) | |
val (trailingUsing, rest6) = rest5.span(isUsingClause) | |
if leftParamss.nonEmpty then | |
leadingTyParamss ::: leadingUsing ::: leftParamss ::: rightTyParamss ::: rightParamss ::: trailingUsing ::: rest6 | |
else paramss // it wasn't a binary operator, after all. |
scala3/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Lines 987 to 1016 in c61897d
if tree.name.isRightAssocOperatorName then | |
// If you change the names of the clauses below, also change them in right-associative-extension-methods.md | |
// we have the following encoding of tree.paramss: | |
// (leftTyParams ++ leadingUsing | |
// ++ rightTyParams ++ rightParam | |
// ++ leftParam ++ trailingUsing ++ rest) | |
// e.g. | |
// extension [A](using B)(c: C)(using D) | |
// def %:[E](f: F)(g: G)(using H): Res = ??? | |
// will have the following values: | |
// - leftTyParams = List(`[A]`) | |
// - leadingUsing = List(`(using B)`) | |
// - rightTyParams = List(`[E]`) | |
// - rightParam = List(`(f: F)`) | |
// - leftParam = List(`(c: C)`) | |
// - trailingUsing = List(`(using D)`) | |
// - rest = List(`(g: G)`, `(using H)`) | |
// we need to swap (rightTyParams ++ rightParam) with (leftParam ++ trailingUsing) | |
val (leftTyParams, rest1) = tree.paramss match | |
case fst :: tail if isTypeParamClause(fst) => (List(fst), tail) | |
case other => (List(), other) | |
val (leadingUsing, rest2) = rest1.span(isUsingClause) | |
val (rightTyParams, rest3) = rest2.span(isTypeParamClause) | |
val (rightParam, rest4) = rest3.splitAt(1) | |
val (leftParam, rest5) = rest4.splitAt(1) | |
val (trailingUsing, rest6) = rest5.span(isUsingClause) | |
if leftParam.nonEmpty then | |
leftTyParams ::: leadingUsing ::: leftParam ::: trailingUsing ::: rightTyParams ::: rightParam ::: rest6 | |
else | |
tree.paramss // it wasn't a binary operator, after all. |
This bug in ShortenedTypePrinter
can be seen in Metals:
My question is, can we add some additional information in desugar so that the desugared form can be reversed easily to original signature?
This could be a flag added to type parameters that are next to extension
keyword, maybe the third flag here (?):
scala3/compiler/src/dotty/tools/dotc/core/Flags.scala
Lines 320 to 321 in c61897d
/** An extension method, or a collective extension instance */ | |
val (Extension @ _, ExtensionMethod @ _, _) = newFlags(28, "<extension>") |
Or this could be an annotation attached to such type params:
extension [T](x: X) def +:: (y: Y)
-> <extension> def +:: [@extensionTypeParam T](y: Y)(x: X)
extension (x: X) def +:: [T](y: Y)
-> <extension> def +:: [T](y: Y)(x: X)