Skip to content

Commit 71aa636

Browse files
committed
Use infix extension to indicate right associativity with natural order
1 parent e598bef commit 71aa636

File tree

22 files changed

+133
-130
lines changed

22 files changed

+133
-130
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -996,11 +996,18 @@ object desugar {
996996

997997
def badRightAssoc(problem: String) =
998998
report.error(em"right-associative extension method $problem", mdef.srcPos)
999-
() // extParamss ++ mdef.paramss
999+
extParamss ++ mdef.paramss
10001000

10011001
rightParam match
10021002
case ValDefs(vparam :: Nil) =>
1003-
if !vparam.mods.is(Given) then
1003+
if vparam.mods.is(Given) then
1004+
badRightAssoc("cannot start with using clause")
1005+
else if mdef.mods.is(Infix) then
1006+
// New encoding:
1007+
// we keep the extension method as is and rely on the swap of arguments at call site
1008+
extParamss ++ mdef.paramss
1009+
else
1010+
// Old encoding:
10041011
// we merge the extension parameters with the method parameters,
10051012
// swapping the operator arguments:
10061013
// e.g.
@@ -1010,16 +1017,13 @@ object desugar {
10101017
// def %:[A](using B)[E](f: F)(c: C)(using D)(g: G)(using H): Res = ???
10111018
//
10121019
// If you change the names of the clauses below, also change them in right-associative-extension-methods.md
1013-
// val (leftTyParamsAndLeadingUsing, leftParamAndTrailingUsing) = extParamss.span(isUsingOrTypeParamClause)
1014-
() // leftTyParamsAndLeadingUsing ::: rightTyParams ::: rightParam :: leftParamAndTrailingUsing ::: paramss1
1015-
else
1016-
badRightAssoc("cannot start with using clause")
1020+
val (leftTyParamsAndLeadingUsing, leftParamAndTrailingUsing) = extParamss.span(isUsingOrTypeParamClause)
1021+
leftTyParamsAndLeadingUsing ::: rightTyParams ::: rightParam :: leftParamAndTrailingUsing ::: paramss1
10171022
case _ =>
10181023
badRightAssoc("must start with a single parameter")
10191024
case _ =>
10201025
// no value parameters, so not an infix operator.
1021-
() // extParamss ++ mdef.paramss
1022-
extParamss ++ mdef.paramss
1026+
extParamss ++ mdef.paramss
10231027
else
10241028
extParamss ++ mdef.paramss
10251029
).withMods(mdef.mods | ExtensionMethod)

compiler/src/dotty/tools/dotc/ast/Positioned.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import util.{SourceFile, SourcePosition, SrcPos}
77
import core.Contexts.*
88
import core.Decorators.*
99
import core.NameOps.*
10-
import core.Flags.{JavaDefined, ExtensionMethod}
10+
import core.Flags.{JavaDefined, ExtensionMethod, Infix}
1111
import core.StdNames.nme
1212
import ast.Trees.mods
1313
import annotation.constructorOnly
@@ -215,8 +215,8 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src
215215
check(tree.trailingParamss)
216216
case tree: DefDef if tree.mods.is(ExtensionMethod) =>
217217
tree.paramss match
218-
// case vparams1 :: vparams2 :: rest if tree.name.isRightAssocOperatorName =>
219-
// // omit check for right-associatiove extension methods; their parameters were swapped
218+
case vparams1 :: vparams2 :: rest if tree.name.isRightAssocOperatorName && !tree.mods.is(Infix) =>
219+
// omit check for right-associatiove extension methods; their parameters were swapped
220220
case _ =>
221221
check(tree.paramss)
222222
check(tree.tpt)

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -933,35 +933,35 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
933933
val coreSig =
934934
if isExtension then
935935
val paramss =
936-
// if tree.name.isRightAssocOperatorName then
937-
// // If you change the names of the clauses below, also change them in right-associative-extension-methods.md
938-
// // we have the following encoding of tree.paramss:
939-
// // (leftTyParams ++ leadingUsing
940-
// // ++ rightTyParams ++ rightParam
941-
// // ++ leftParam ++ trailingUsing ++ rest)
942-
// // e.g.
943-
// // extension [A](using B)(c: C)(using D)
944-
// // def %:[E](f: F)(g: G)(using H): Res = ???
945-
// // will have the following values:
946-
// // - leftTyParams = List(`[A]`)
947-
// // - leadingUsing = List(`(using B)`)
948-
// // - rightTyParams = List(`[E]`)
949-
// // - rightParam = List(`(f: F)`)
950-
// // - leftParam = List(`(c: C)`)
951-
// // - trailingUsing = List(`(using D)`)
952-
// // - rest = List(`(g: G)`, `(using H)`)
953-
// // we need to swap (rightTyParams ++ rightParam) with (leftParam ++ trailingUsing)
954-
// val (leftTyParams, rest1) = tree.paramss.span(isTypeParamClause)
955-
// val (leadingUsing, rest2) = rest1.span(isUsingClause)
956-
// val (rightTyParams, rest3) = rest2.span(isTypeParamClause)
957-
// val (rightParam, rest4) = rest3.splitAt(1)
958-
// val (leftParam, rest5) = rest4.splitAt(1)
959-
// val (trailingUsing, rest6) = rest5.span(isUsingClause)
960-
// if leftParam.nonEmpty then
961-
// leftTyParams ::: leadingUsing ::: leftParam ::: trailingUsing ::: rightTyParams ::: rightParam ::: rest6
962-
// else
963-
// tree.paramss // it wasn't a binary operator, after all.
964-
// else
936+
if tree.name.isRightAssocOperatorName && !tree.mods.is(Infix) && !tree.symbol.is(Infix) then
937+
// If you change the names of the clauses below, also change them in right-associative-extension-methods.md
938+
// we have the following encoding of tree.paramss:
939+
// (leftTyParams ++ leadingUsing
940+
// ++ rightTyParams ++ rightParam
941+
// ++ leftParam ++ trailingUsing ++ rest)
942+
// e.g.
943+
// extension [A](using B)(c: C)(using D)
944+
// def %:[E](f: F)(g: G)(using H): Res = ???
945+
// will have the following values:
946+
// - leftTyParams = List(`[A]`)
947+
// - leadingUsing = List(`(using B)`)
948+
// - rightTyParams = List(`[E]`)
949+
// - rightParam = List(`(f: F)`)
950+
// - leftParam = List(`(c: C)`)
951+
// - trailingUsing = List(`(using D)`)
952+
// - rest = List(`(g: G)`, `(using H)`)
953+
// we need to swap (rightTyParams ++ rightParam) with (leftParam ++ trailingUsing)
954+
val (leftTyParams, rest1) = tree.paramss.span(isTypeParamClause)
955+
val (leadingUsing, rest2) = rest1.span(isUsingClause)
956+
val (rightTyParams, rest3) = rest2.span(isTypeParamClause)
957+
val (rightParam, rest4) = rest3.splitAt(1)
958+
val (leftParam, rest5) = rest4.splitAt(1)
959+
val (trailingUsing, rest6) = rest5.span(isUsingClause)
960+
if leftParam.nonEmpty then
961+
leftTyParams ::: leadingUsing ::: leftParam ::: trailingUsing ::: rightTyParams ::: rightParam ::: rest6
962+
else
963+
tree.paramss // it wasn't a binary operator, after all.
964+
else
965965
tree.paramss
966966
val trailingParamss = paramss
967967
.dropWhile(isUsingOrTypeParamClause)

library/src/scala/IArray.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,10 +319,14 @@ object IArray:
319319
def zipAll[T1 >: T, U](that: Iterable[U], thisElem: T1, thatElem: U): IArray[(T1, U)] = genericArrayOps(arr).zipAll(that, thisElem, thatElem)
320320
def zipWithIndex: IArray[(T, Int)] = genericArrayOps(arr).zipWithIndex
321321

322-
extension [T, U >: T: ClassTag](arr: IArray[U])
323-
def ++:(prefix: IterableOnce[T]): IArray[U] = genericArrayOps(arr).prependedAll(prefix)
324-
def ++:(prefix: IArray[T]): IArray[U] = genericArrayOps(arr).prependedAll(prefix)
325-
def +:(x: T): IArray[U] = genericArrayOps(arr).prepended(x)
322+
extension [T, U >: T: ClassTag](prefix: IterableOnce[T])
323+
def ++:(arr: IArray[U]): IArray[U] = genericArrayOps(arr).prependedAll(prefix)
324+
325+
extension [T, U >: T: ClassTag](prefix: IArray[T])
326+
def ++:(arr: IArray[U]): IArray[U] = genericArrayOps(arr).prependedAll(prefix)
327+
328+
extension [T, U >: T: ClassTag](x: T)
329+
def +:(arr: IArray[U]): IArray[U] = genericArrayOps(arr).prepended(x)
326330

327331
// For backwards compatibility with code compiled without -Yexplicit-nulls
328332
private inline def mapNull[A, B](a: A, inline f: B): B =

presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ class HoverTypeSuite extends BaseHoverSuite:
152152
|class C
153153
|
154154
|object Foo:
155-
| extension [R](using A)(res: R)(using B)
156-
| def %:[T](main: T)(using C): R = ???
155+
| extension [T](using A)(main: T)(using B)
156+
| def %:[R](res: R)(using C): R = ???
157157
| given A with {}
158158
| given B with {}
159159
| given C with {}
@@ -162,7 +162,7 @@ class HoverTypeSuite extends BaseHoverSuite:
162162
|end Foo
163163
|""".stripMargin,
164164
"""|Int
165-
|extension [R](using A)(using B)(res: R) def %:[T](main: T)(using C): R""".stripMargin.hover
165+
|extension [T](using A)(main: T) def %:[R](res: R)(using B)(using C): R""".stripMargin.hover
166166
)
167167

168168
@Test def `using` =

tests/neg-custom-args/captures/lazylists-exceptions.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ final class LazyCons[+T](val x: T, val xs: () => LazyList[T]^) extends LazyList[
2020
def tail: LazyList[T]^{this} = xs()
2121
end LazyCons
2222

23-
extension [A](xs1: => LazyList[A]^)
24-
def #:(x: A): LazyList[A]^{xs1} =
23+
extension [A](x: A)
24+
def #:(xs1: => LazyList[A]^): LazyList[A]^{xs1} =
2525
LazyCons(x, () => xs1)
2626

2727
def tabulate[A](n: Int)(gen: Int => A): LazyList[A]^{gen} =

tests/neg/i13075.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ object Implementing_Tuples:
88
type *:[H, T <: Tup] = ConsTup[H, T] // for type matching
99
type EmptyTup = EmptyTup.type // for type matching
1010

11-
extension [T <: Tup](tail: T)
12-
def *:[H](head: H) = ConsTup(head, tail)
11+
extension [H](head: H)
12+
def *:[T <: Tup](tail: T) = ConsTup(head, tail)
1313

1414
type Fold[T <: Tup, Seed, F[_,_]] = T match
1515
case EmptyTup => Seed

tests/neg/i9562.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,4 @@ object Unrelated:
99
def h1: Int = foo // error
1010
def h2: Int = h1 + 1 // OK
1111
def h3: Int = g // error
12-
extension (x: Int)
13-
def ++:(f: Foo): Int = f.h1 + x // OK
12+
def ++: (x: Int): Int = h1 + x // OK

tests/pos-custom-args/captures/lazylists-exceptions.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ extension [A](xs: LzyList[A]^)
4242
if n == 0 then xs else xs.tail.drop(n - 1)
4343
end extension
4444

45-
extension [A](xs1: => LzyList[A]^)
46-
def #:(x: A): LzyList[A]^{xs1} =
45+
extension [A](x: A)
46+
def #:(xs1: => LzyList[A]^): LzyList[A]^{xs1} =
4747
LzyCons(x, () => xs1)
4848

4949
def lazyCons[A](x: A, xs1: => LzyList[A]^): LzyList[A]^{xs1} =

tests/pos-custom-args/captures/logger.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ final class LazyCons[+T](val x: T, val xs: () => LazyList[T]^) extends LazyList[
3232
def tail: LazyList[T]^{this} = xs()
3333
end LazyCons
3434

35-
extension [A](xs1: => LazyList[A]^)
36-
def #::(x: A): LazyList[A]^{xs1} =
35+
extension [A](x: A)
36+
def #::(xs1: => LazyList[A]^): LazyList[A]^{xs1} =
3737
LazyCons(x, () => xs1)
3838

3939
extension [A](xs: LazyList[A]^)

0 commit comments

Comments
 (0)