From e598bef1b8d8faa1b1c3bf7aafddee12abe5cf3b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 5 Dec 2023 15:18:24 +0100 Subject: [PATCH 1/2] Fix right associativity of extension methods This patch removes the special treatment of extension methods while desugaring. Instead of swapping the arguments of the extension method at definition site, we let the usual desugaring take care of it. ```scala extension (xs: List[Int]) def -:(x: Int) = xs.remove(x) 1 -: List(2, 3) // desugars into List(2, 3).-:(1) 1 :: List(2, 3) // desugars into List(2, 3).::(1) ``` This implies that the following makes sense now: ```scala List(2, 3).-:(1) // equivalent to the one bellow -:(List(2, 3))(1) ``` Note that the parameters are passed in order as in the signature, as they should. This used to break intuition in the previous implementation. Futhermore, this fixes parameter scoping and dependencies, as it trivially respects the original signature. An example of this is in the following code example, where `T` is bounded by `tuple.type`, which would have been out of scope in the previous implementation due to the swapping of parameters. ```scala extension (tuple: Tuple) def *:[T >: tuple.type <: Tuple, H](x: H): H *: T = ... ``` The downside is that this is a breaking change as we change the semantics of the extension method definitions. We need to find a way to migrate from one to the other without breaking code. One option might be to have a different syntax for the new semantics, and deprecate the old one. One possibility would be to use `infix def` extensions to mark to mark these methods. One issue encountered in the library is with the `IArray.{++:, +:}` operations. Luckily, it seems that by rewriting them into the new semantics we get a binary and TASTy compatible signature. Fixes #19197 --- .../src/dotty/tools/dotc/ast/Desugar.scala | 9 +- .../src/dotty/tools/dotc/ast/Positioned.scala | 4 +- .../tools/dotc/printing/RefinedPrinter.scala | 58 ++++++------- library/src/scala/IArray.scala | 12 +-- .../tools/pc/tests/hover/HoverTypeSuite.scala | 6 +- .../captures/lazylists-exceptions.scala | 4 +- tests/neg/i13075.scala | 4 +- tests/neg/i9562.scala | 3 +- .../captures/lazylists-exceptions.scala | 4 +- tests/pos-custom-args/captures/logger.scala | 4 +- .../captures/strictlists.scala | 7 +- tests/pos/IArrayToCons.scala | 13 +++ tests/pos/i19197.scala | 6 ++ tests/pos/i9562.scala | 4 +- tests/pos/reference/extension-methods.scala | 2 +- tests/run/errorhandling/Result.scala | 5 +- tests/run/export-in-extension.scala | 4 +- tests/run/i11583.scala | 8 +- tests/run/i9530.scala | 2 +- tests/run/instances.scala | 2 +- tests/run/interleaving.scala | 86 +++++++++---------- .../expect/RightAssociativeExtension.scala | 4 +- tests/semanticdb/metac.expect | 16 ++-- 23 files changed, 144 insertions(+), 123 deletions(-) create mode 100644 tests/pos/IArrayToCons.scala create mode 100644 tests/pos/i19197.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 3386dc7d7a6c..ff4f2bc12a4c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -996,7 +996,7 @@ object desugar { def badRightAssoc(problem: String) = report.error(em"right-associative extension method $problem", mdef.srcPos) - extParamss ++ mdef.paramss + () // extParamss ++ mdef.paramss rightParam match case ValDefs(vparam :: Nil) => @@ -1010,15 +1010,16 @@ object desugar { // def %:[A](using B)[E](f: F)(c: C)(using D)(g: G)(using H): Res = ??? // // If you change the names of the clauses below, also change them in right-associative-extension-methods.md - val (leftTyParamsAndLeadingUsing, leftParamAndTrailingUsing) = extParamss.span(isUsingOrTypeParamClause) - leftTyParamsAndLeadingUsing ::: rightTyParams ::: rightParam :: leftParamAndTrailingUsing ::: paramss1 + // val (leftTyParamsAndLeadingUsing, leftParamAndTrailingUsing) = extParamss.span(isUsingOrTypeParamClause) + () // leftTyParamsAndLeadingUsing ::: rightTyParams ::: rightParam :: leftParamAndTrailingUsing ::: paramss1 else badRightAssoc("cannot start with using clause") case _ => badRightAssoc("must start with a single parameter") case _ => // no value parameters, so not an infix operator. - extParamss ++ mdef.paramss + () // extParamss ++ mdef.paramss + extParamss ++ mdef.paramss else extParamss ++ mdef.paramss ).withMods(mdef.mods | ExtensionMethod) diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index d8017783f47f..fa27b2f9df0e 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -215,8 +215,8 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src check(tree.trailingParamss) case tree: DefDef if tree.mods.is(ExtensionMethod) => tree.paramss match - case vparams1 :: vparams2 :: rest if tree.name.isRightAssocOperatorName => - // omit check for right-associatiove extension methods; their parameters were swapped + // case vparams1 :: vparams2 :: rest if tree.name.isRightAssocOperatorName => + // // omit check for right-associatiove extension methods; their parameters were swapped case _ => check(tree.paramss) check(tree.tpt) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 8ad1188a3e7e..e2af31069632 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -933,35 +933,35 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val coreSig = if isExtension then val paramss = - 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.span(isTypeParamClause) - 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. - else + // 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.span(isTypeParamClause) + // 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. + // else tree.paramss val trailingParamss = paramss .dropWhile(isUsingOrTypeParamClause) diff --git a/library/src/scala/IArray.scala b/library/src/scala/IArray.scala index 887ca517ef2b..dadb9de6c74b 100644 --- a/library/src/scala/IArray.scala +++ b/library/src/scala/IArray.scala @@ -319,14 +319,10 @@ object IArray: def zipAll[T1 >: T, U](that: Iterable[U], thisElem: T1, thatElem: U): IArray[(T1, U)] = genericArrayOps(arr).zipAll(that, thisElem, thatElem) def zipWithIndex: IArray[(T, Int)] = genericArrayOps(arr).zipWithIndex - extension [T, U >: T: ClassTag](prefix: IterableOnce[T]) - def ++:(arr: IArray[U]): IArray[U] = genericArrayOps(arr).prependedAll(prefix) - - extension [T, U >: T: ClassTag](prefix: IArray[T]) - def ++:(arr: IArray[U]): IArray[U] = genericArrayOps(arr).prependedAll(prefix) - - extension [T, U >: T: ClassTag](x: T) - def +:(arr: IArray[U]): IArray[U] = genericArrayOps(arr).prepended(x) + extension [T, U >: T: ClassTag](arr: IArray[U]) + def ++:(prefix: IterableOnce[T]): IArray[U] = genericArrayOps(arr).prependedAll(prefix) + def ++:(prefix: IArray[T]): IArray[U] = genericArrayOps(arr).prependedAll(prefix) + def +:(x: T): IArray[U] = genericArrayOps(arr).prepended(x) // For backwards compatibility with code compiled without -Yexplicit-nulls private inline def mapNull[A, B](a: A, inline f: B): B = diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala index 2157aa891bae..0f73aae7970f 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala @@ -152,8 +152,8 @@ class HoverTypeSuite extends BaseHoverSuite: |class C | |object Foo: - | extension [T](using A)(main: T)(using B) - | def %:[R](res: R)(using C): R = ??? + | extension [R](using A)(res: R)(using B) + | def %:[T](main: T)(using C): R = ??? | given A with {} | given B with {} | given C with {} @@ -162,7 +162,7 @@ class HoverTypeSuite extends BaseHoverSuite: |end Foo |""".stripMargin, """|Int - |extension [T](using A)(main: T) def %:[R](res: R)(using B)(using C): R""".stripMargin.hover + |extension [R](using A)(using B)(res: R) def %:[T](main: T)(using C): R""".stripMargin.hover ) @Test def `using` = diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.scala b/tests/neg-custom-args/captures/lazylists-exceptions.scala index 295147f7f3c5..ab57af13d1ba 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.scala +++ b/tests/neg-custom-args/captures/lazylists-exceptions.scala @@ -20,8 +20,8 @@ final class LazyCons[+T](val x: T, val xs: () => LazyList[T]^) extends LazyList[ def tail: LazyList[T]^{this} = xs() end LazyCons -extension [A](x: A) - def #:(xs1: => LazyList[A]^): LazyList[A]^{xs1} = +extension [A](xs1: => LazyList[A]^) + def #:(x: A): LazyList[A]^{xs1} = LazyCons(x, () => xs1) def tabulate[A](n: Int)(gen: Int => A): LazyList[A]^{gen} = diff --git a/tests/neg/i13075.scala b/tests/neg/i13075.scala index 2a5ff70c0481..7bf0ec3203cb 100644 --- a/tests/neg/i13075.scala +++ b/tests/neg/i13075.scala @@ -8,8 +8,8 @@ object Implementing_Tuples: type *:[H, T <: Tup] = ConsTup[H, T] // for type matching type EmptyTup = EmptyTup.type // for type matching - extension [H](head: H) - def *:[T <: Tup](tail: T) = ConsTup(head, tail) + extension [T <: Tup](tail: T) + def *:[H](head: H) = ConsTup(head, tail) type Fold[T <: Tup, Seed, F[_,_]] = T match case EmptyTup => Seed diff --git a/tests/neg/i9562.scala b/tests/neg/i9562.scala index 23d1bebdbba9..6c917cfabe84 100644 --- a/tests/neg/i9562.scala +++ b/tests/neg/i9562.scala @@ -9,4 +9,5 @@ object Unrelated: def h1: Int = foo // error def h2: Int = h1 + 1 // OK def h3: Int = g // error - def ++: (x: Int): Int = h1 + x // OK \ No newline at end of file + extension (x: Int) + def ++:(f: Foo): Int = f.h1 + x // OK diff --git a/tests/pos-custom-args/captures/lazylists-exceptions.scala b/tests/pos-custom-args/captures/lazylists-exceptions.scala index afc6616108bc..58183d4b5b31 100644 --- a/tests/pos-custom-args/captures/lazylists-exceptions.scala +++ b/tests/pos-custom-args/captures/lazylists-exceptions.scala @@ -42,8 +42,8 @@ extension [A](xs: LzyList[A]^) if n == 0 then xs else xs.tail.drop(n - 1) end extension -extension [A](x: A) - def #:(xs1: => LzyList[A]^): LzyList[A]^{xs1} = +extension [A](xs1: => LzyList[A]^) + def #:(x: A): LzyList[A]^{xs1} = LzyCons(x, () => xs1) def lazyCons[A](x: A, xs1: => LzyList[A]^): LzyList[A]^{xs1} = diff --git a/tests/pos-custom-args/captures/logger.scala b/tests/pos-custom-args/captures/logger.scala index d95eeaae74cf..97b0ef293b2a 100644 --- a/tests/pos-custom-args/captures/logger.scala +++ b/tests/pos-custom-args/captures/logger.scala @@ -32,8 +32,8 @@ final class LazyCons[+T](val x: T, val xs: () => LazyList[T]^) extends LazyList[ def tail: LazyList[T]^{this} = xs() end LazyCons -extension [A](x: A) - def #::(xs1: => LazyList[A]^): LazyList[A]^{xs1} = +extension [A](xs1: => LazyList[A]^) + def #::(x: A): LazyList[A]^{xs1} = LazyCons(x, () => xs1) extension [A](xs: LazyList[A]^) diff --git a/tests/pos-custom-args/captures/strictlists.scala b/tests/pos-custom-args/captures/strictlists.scala index cb58a09a161f..09c360d4f192 100644 --- a/tests/pos-custom-args/captures/strictlists.scala +++ b/tests/pos-custom-args/captures/strictlists.scala @@ -28,11 +28,10 @@ extension [A](xs: StrictList[A]) def concat(ys: StrictList[A]): StrictList[A] = if xs.isEmpty then ys else xs.head #: xs.tail.concat(ys) -end extension -extension [A](x: A) - def #:(xs1: StrictList[A]): StrictList[A] = - StrictCons(x, xs1) + def #:(x: A): StrictList[A] = + StrictCons(x, xs) +end extension def tabulate[A](n: Int)(gen: Int => A) = def recur(i: Int): StrictList[A] = diff --git a/tests/pos/IArrayToCons.scala b/tests/pos/IArrayToCons.scala new file mode 100644 index 000000000000..6a0acce88391 --- /dev/null +++ b/tests/pos/IArrayToCons.scala @@ -0,0 +1,13 @@ +import IArray.{+:, ++:} +def test(arr: IArray[Int]): Unit = + 1 +: arr; + arr.+:(1); + +:(arr)(1); + + arr ++: arr; + arr.++:(arr); + ++:(arr)(arr); + + Nil ++: arr; + arr.++:(Nil); + ++:(arr)(Nil); diff --git a/tests/pos/i19197.scala b/tests/pos/i19197.scala new file mode 100644 index 000000000000..009cc2907a3e --- /dev/null +++ b/tests/pos/i19197.scala @@ -0,0 +1,6 @@ +extension (tuple: Tuple) + def **:[T >: tuple.type <: Tuple, H](x: H): H *: T = ??? + +def test1: (Int, String, Char) = 1 **: ("a", 'b') +def test2: (Int, String, Char) = ("a", 'b').**:(1) +def test3: (Int, String, Char) = **:("a", 'b')(1) diff --git a/tests/pos/i9562.scala b/tests/pos/i9562.scala index 9f58ff309d3c..54f3713acb06 100644 --- a/tests/pos/i9562.scala +++ b/tests/pos/i9562.scala @@ -8,4 +8,6 @@ object Unrelated: extension (f: Foo) def h1: Int = 0 def h2: Int = h1 + 1 // OK - def ++: (x: Int): Int = h2 + x // OK \ No newline at end of file + + extension (x: Int) + def ++: (f: Foo): Int = f.h2 + x // OK diff --git a/tests/pos/reference/extension-methods.scala b/tests/pos/reference/extension-methods.scala index 64fd23322c1b..57bd15f7d0ba 100644 --- a/tests/pos/reference/extension-methods.scala +++ b/tests/pos/reference/extension-methods.scala @@ -10,7 +10,7 @@ object ExtMethods: assert(circle.circumference == circumference(circle)) extension (x: String) def < (y: String) = x.compareTo(y) < 0 - extension [Elem](x: Elem) def #: (xs: Seq[Elem]) = x +: xs + extension [Elem](xs: Seq[Elem]) def #: (x: Elem) = x +: xs extension (x: Number) infix def min (y: Number) = x assert("a" < "bb") diff --git a/tests/run/errorhandling/Result.scala b/tests/run/errorhandling/Result.scala index 07d7a9f90c8a..29e91b11b232 100644 --- a/tests/run/errorhandling/Result.scala +++ b/tests/run/errorhandling/Result.scala @@ -35,12 +35,15 @@ object Result: case (Err(e), Ok(_)) => Err(e :: Nil) case (Err(e1), Err(e2)) => Err(e1 :: e2 :: Nil) + end extension + + extension [U <: Tuple, E](other: Result[U, List[E]]) /** Validate both `r` and `other`; return a tuple of successes or a list of failures. * Unlike with `zip`, the right hand side `other` must be a `Result` returning a `Tuple`, * and the left hand side is added to it. See `Result.empty` for a convenient * right unit of chains of `*:`s. */ - def *: [U <: Tuple](other: Result[U, List[E]]): Result[T *: U, List[E]] = (r, other) match + def *: [T](r: Result[T, E]): Result[T *: U, List[E]] = (r, other) match case (Ok(x), Ok(ys)) => Ok(x *: ys) case (Ok(_), es: Err[?]) => es case (Err(e), Ok(_)) => Err(e :: Nil) diff --git a/tests/run/export-in-extension.scala b/tests/run/export-in-extension.scala index 26becc280ff3..e71a2adfdcb7 100644 --- a/tests/run/export-in-extension.scala +++ b/tests/run/export-in-extension.scala @@ -11,7 +11,7 @@ object O: export cm.* def succ: Int = x + 1 def succ2: Int = succ + 1 - def ::: (y: Int) = x - y + def ::: (y: Int) = y - x object O2: import O.C @@ -20,7 +20,7 @@ object O2: export cm.{bar, baz, bam, ::} def succ: Int = x + 1 def succ2: Int = succ + 1 - def ::: (y: Int) = x - y + def ::: (y: Int) = y - x @main def Test = import O.* diff --git a/tests/run/i11583.scala b/tests/run/i11583.scala index 87701ac95310..c9901a38ebf5 100644 --- a/tests/run/i11583.scala +++ b/tests/run/i11583.scala @@ -12,11 +12,11 @@ class Env: // */ // def &&:[T <: ctx.Term](trm: T)(ext: env.Extra): (ctx.Type, T, env.Extra) = (tpe, trm, ext) -extension [Ctx <: Context](using ctx: Ctx)(tpe: String)(using env: Env) - def :#:[T <: Boolean](trm: T)(ext: env.Extra): (String, T, env.Extra) = (tpe, trm, ext) +extension [Ctx <: Context, T <: Boolean](using ctx: Ctx)(trm: T)(using env: Env) + def :#:(tpe: String)(ext: env.Extra): (String, T, env.Extra) = (tpe, trm, ext) -extension [A](a: A) - def :*:[T <: Tuple](t: T): A *: T = a *: t +extension [T <: Tuple](t: T) + def :*:[A](a: A): A *: T = a *: t @main def Test = diff --git a/tests/run/i9530.scala b/tests/run/i9530.scala index e0262764039f..c644ec74bd95 100644 --- a/tests/run/i9530.scala +++ b/tests/run/i9530.scala @@ -8,7 +8,7 @@ trait Scope: extension (using s: Scope)(expr: s.Expr) def show = expr.toString def eval = s.value(expr) - def *: (other: s.Expr) = s.combine(expr, other) + def *: (other: s.Expr) = s.combine(other, expr) def f(using s: Scope)(x: s.Expr): (String, s.Value) = (x.show, x.eval) diff --git a/tests/run/instances.scala b/tests/run/instances.scala index 128ea0700e02..812d3ad23477 100644 --- a/tests/run/instances.scala +++ b/tests/run/instances.scala @@ -31,7 +31,7 @@ object Test extends App { extension [T](xs: List[List[T]]) def flattened = xs.foldLeft[List[T]](Nil)(_ ++ _) - extension [T](x: T) def :: (xs: Seq[T]) = x +: xs + extension [T](xs: Seq[T]) def :: (x: T) = x +: xs val ss: Seq[Int] = List(1, 2, 3) val ss1 = 0 :: ss diff --git a/tests/run/interleaving.scala b/tests/run/interleaving.scala index 557741032e8a..2be444992b2e 100644 --- a/tests/run/interleaving.scala +++ b/tests/run/interleaving.scala @@ -56,47 +56,47 @@ object Test extends App { extension (db: DB) def ?:(k: Key)[V >: k.Value](default: V): V = db.getOrElse(k)(default) - assert((db1 ?: (key1))[Option[Int]](default1) == Some(4)) - assert((db1 ?: (key2))[Option[Int]](default1) == default1) - assert((db1 ?: (key3))[Option[String]](default1) == default1) - assert((db1 ?: (key1))(default1) == Some(4)) - assert((db1 ?: (key2))(default1) == default1) - assert((db1 ?: (key3))(default1) == default1) - - assert((db1 ?: (key1))[Any](default2) == Some(4)) - assert((db1 ?: (key2))[Any](default2) == default2) - assert((db1 ?: (key3))[Any](default2) == default2) - assert((db1 ?: (key1))(default2) == Some(4)) - assert((db1 ?: (key2))(default2) == default2) - assert((db1 ?: (key3))(default2) == default2) - - - assert(key1.?:(db1)[Option[Int]](default1) == Some(4)) - assert(key2.?:(db1)[Option[Int]](default1) == default1) - assert(key3.?:(db1)[Option[String]](default1) == default1) - assert(key1.?:(db1)(default1) == Some(4)) - assert(key2.?:(db1)(default1) == default1) - assert(key3.?:(db1)(default1) == default1) - - assert(key1.?:(db1)[Any](default2) == Some(4)) - assert(key2.?:(db1)[Any](default2) == default2) - assert(key3.?:(db1)[Any](default2) == default2) - assert(key1.?:(db1)(default2) == Some(4)) - assert(key2.?:(db1)(default2) == default2) - assert(key3.?:(db1)(default2) == default2) - - - assert(?:(key1)(db1)[Option[Int]](default1) == Some(4)) - assert(?:(key2)(db1)[Option[Int]](default1) == default1) - assert(?:(key3)(db1)[Option[String]](default1) == default1) - assert(?:(key1)(db1)(default1) == Some(4)) - assert(?:(key2)(db1)(default1) == default1) - assert(?:(key3)(db1)(default1) == default1) - - assert(?:(key1)(db1)[Any](default2) == Some(4)) - assert(?:(key2)(db1)[Any](default2) == default2) - assert(?:(key3)(db1)[Any](default2) == default2) - assert(?:(key1)(db1)(default2) == Some(4)) - assert(?:(key2)(db1)(default2) == default2) - assert(?:(key3)(db1)(default2) == default2) + assert((db1.?:(key1))[Option[Int]](default1) == Some(4)) + assert((db1.?:(key2))[Option[Int]](default1) == default1) + assert((db1.?:(key3))[Option[String]](default1) == default1) + assert((db1.?:(key1))(default1) == Some(4)) + assert((db1.?:(key2))(default1) == default1) + assert((db1.?:(key3))(default1) == default1) + + assert((db1.?:(key1))[Any](default2) == Some(4)) + assert((db1.?:(key2))[Any](default2) == default2) + assert((db1.?:(key3))[Any](default2) == default2) + assert((db1.?:(key1))(default2) == Some(4)) + assert((db1.?:(key2))(default2) == default2) + assert((db1.?:(key3))(default2) == default2) + + + assert(db1.?:(key1)[Option[Int]](default1) == Some(4)) + assert(db1.?:(key2)[Option[Int]](default1) == default1) + assert(db1.?:(key3)[Option[String]](default1) == default1) + assert(db1.?:(key1)(default1) == Some(4)) + assert(db1.?:(key2)(default1) == default1) + assert(db1.?:(key3)(default1) == default1) + + assert(db1.?:(key1)[Any](default2) == Some(4)) + assert(db1.?:(key2)[Any](default2) == default2) + assert(db1.?:(key3)[Any](default2) == default2) + assert(db1.?:(key1)(default2) == Some(4)) + assert(db1.?:(key2)(default2) == default2) + assert(db1.?:(key3)(default2) == default2) + + + assert(?:(db1)(key1)[Option[Int]](default1) == Some(4)) + assert(?:(db1)(key2)[Option[Int]](default1) == default1) + assert(?:(db1)(key3)[Option[String]](default1) == default1) + assert(?:(db1)(key1)(default1) == Some(4)) + assert(?:(db1)(key2)(default1) == default1) + assert(?:(db1)(key3)(default1) == default1) + + assert(?:(db1)(key1)[Any](default2) == Some(4)) + assert(?:(db1)(key2)[Any](default2) == default2) + assert(?:(db1)(key3)[Any](default2) == default2) + assert(?:(db1)(key1)(default2) == Some(4)) + assert(?:(db1)(key2)(default2) == default2) + assert(?:(db1)(key3)(default2) == default2) } diff --git a/tests/semanticdb/expect/RightAssociativeExtension.scala b/tests/semanticdb/expect/RightAssociativeExtension.scala index 680dba16fc3c..00a47c19b7ec 100644 --- a/tests/semanticdb/expect/RightAssociativeExtension.scala +++ b/tests/semanticdb/expect/RightAssociativeExtension.scala @@ -1,6 +1,6 @@ package ext -extension (s: String) - def :*: (i: Int): (String, Int) = (s, i) +extension (i: Int) + def :*: (s: String): (String, Int) = (s, i) val b = "foo" :*: 23 \ No newline at end of file diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 2fd8eca47a7b..7fab92e44dbe 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -3383,15 +3383,15 @@ ext/RightAssociativeExtension$package.b. => val method b Tuple2[String, Int] Occurrences: [0:8..0:11): ext <- ext/ -[2:11..2:12): s <- ext/RightAssociativeExtension$package.`:*:`().(s) -[2:14..2:20): String -> scala/Predef.String# +[2:11..2:12): i <- ext/RightAssociativeExtension$package.`:*:`().(i) +[2:14..2:17): Int -> scala/Int# [3:6..3:9): :*: <- ext/RightAssociativeExtension$package.`:*:`(). -[3:11..3:12): i <- ext/RightAssociativeExtension$package.`:*:`().(i) -[3:14..3:17): Int -> scala/Int# -[3:21..3:27): String -> scala/Predef.String# -[3:29..3:32): Int -> scala/Int# -[3:37..3:38): s -> ext/RightAssociativeExtension$package.`:*:`().(s) -[3:40..3:41): i -> ext/RightAssociativeExtension$package.`:*:`().(i) +[3:11..3:12): s <- ext/RightAssociativeExtension$package.`:*:`().(s) +[3:14..3:20): String -> scala/Predef.String# +[3:24..3:30): String -> scala/Predef.String# +[3:32..3:35): Int -> scala/Int# +[3:40..3:41): s -> ext/RightAssociativeExtension$package.`:*:`().(s) +[3:43..3:44): i -> ext/RightAssociativeExtension$package.`:*:`().(i) [5:4..5:5): b <- ext/RightAssociativeExtension$package.b. [5:14..5:17): :*: -> ext/RightAssociativeExtension$package.`:*:`(). From 3b7c9ff8ed310fa4bffed710c3ca3923a1460320 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Dec 2023 09:29:12 +0100 Subject: [PATCH 2/2] Use infix extension to indicate right associativity with natural order --- .../src/dotty/tools/dotc/ast/Desugar.scala | 20 +++-- .../src/dotty/tools/dotc/ast/Positioned.scala | 6 +- .../tools/dotc/printing/RefinedPrinter.scala | 58 ++++++------- .../right-associative-extension-methods.md | 42 ++++++++- library/src/scala/IArray.scala | 12 ++- .../tools/pc/tests/hover/HoverTypeSuite.scala | 6 +- .../captures/lazylists-exceptions.scala | 4 +- tests/neg/i13075.scala | 4 +- tests/neg/i9562.scala | 3 +- .../captures/lazylists-exceptions.scala | 4 +- tests/pos-custom-args/captures/logger.scala | 4 +- .../captures/strictlists.scala | 7 +- tests/pos/i19197.scala | 2 +- tests/pos/i9562.scala | 4 +- tests/pos/reference/extension-methods.scala | 2 +- tests/run/errorhandling/Result.scala | 5 +- tests/run/export-in-extension.scala | 4 +- tests/run/i11583.scala | 8 +- tests/run/i9530.scala | 2 +- tests/run/instances.scala | 2 +- tests/run/interleaving.scala | 86 +++++++++---------- .../expect/RightAssociativeExtension.scala | 4 +- tests/semanticdb/metac.expect | 16 ++-- 23 files changed, 172 insertions(+), 133 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index ff4f2bc12a4c..22ba1fb698b6 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -996,11 +996,18 @@ object desugar { def badRightAssoc(problem: String) = report.error(em"right-associative extension method $problem", mdef.srcPos) - () // extParamss ++ mdef.paramss + extParamss ++ mdef.paramss rightParam match case ValDefs(vparam :: Nil) => - if !vparam.mods.is(Given) then + if vparam.mods.is(Given) then + badRightAssoc("cannot start with using clause") + else if mdef.mods.is(Infix) then + // New encoding: + // we keep the extension method as is and rely on the swap of arguments at call site + extParamss ++ mdef.paramss + else + // Old encoding: // we merge the extension parameters with the method parameters, // swapping the operator arguments: // e.g. @@ -1010,16 +1017,13 @@ object desugar { // def %:[A](using B)[E](f: F)(c: C)(using D)(g: G)(using H): Res = ??? // // If you change the names of the clauses below, also change them in right-associative-extension-methods.md - // val (leftTyParamsAndLeadingUsing, leftParamAndTrailingUsing) = extParamss.span(isUsingOrTypeParamClause) - () // leftTyParamsAndLeadingUsing ::: rightTyParams ::: rightParam :: leftParamAndTrailingUsing ::: paramss1 - else - badRightAssoc("cannot start with using clause") + val (leftTyParamsAndLeadingUsing, leftParamAndTrailingUsing) = extParamss.span(isUsingOrTypeParamClause) + leftTyParamsAndLeadingUsing ::: rightTyParams ::: rightParam :: leftParamAndTrailingUsing ::: paramss1 case _ => badRightAssoc("must start with a single parameter") case _ => // no value parameters, so not an infix operator. - () // extParamss ++ mdef.paramss - extParamss ++ mdef.paramss + extParamss ++ mdef.paramss else extParamss ++ mdef.paramss ).withMods(mdef.mods | ExtensionMethod) diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index fa27b2f9df0e..4dd171393d92 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -7,7 +7,7 @@ import util.{SourceFile, SourcePosition, SrcPos} import core.Contexts.* import core.Decorators.* import core.NameOps.* -import core.Flags.{JavaDefined, ExtensionMethod} +import core.Flags.{JavaDefined, ExtensionMethod, Infix} import core.StdNames.nme import ast.Trees.mods import annotation.constructorOnly @@ -215,8 +215,8 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src check(tree.trailingParamss) case tree: DefDef if tree.mods.is(ExtensionMethod) => tree.paramss match - // case vparams1 :: vparams2 :: rest if tree.name.isRightAssocOperatorName => - // // omit check for right-associatiove extension methods; their parameters were swapped + case vparams1 :: vparams2 :: rest if tree.name.isRightAssocOperatorName && !tree.mods.is(Infix) => + // omit check for right-associatiove extension methods; their parameters were swapped case _ => check(tree.paramss) check(tree.tpt) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index e2af31069632..bc7060df72d5 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -933,35 +933,35 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val coreSig = if isExtension then val paramss = - // 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.span(isTypeParamClause) - // 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. - // else + if tree.name.isRightAssocOperatorName && !tree.mods.is(Infix) && !tree.symbol.is(Infix) 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.span(isTypeParamClause) + 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. + else tree.paramss val trailingParamss = paramss .dropWhile(isUsingOrTypeParamClause) diff --git a/docs/_docs/reference/contextual/right-associative-extension-methods.md b/docs/_docs/reference/contextual/right-associative-extension-methods.md index 61f0beece6ed..5227ee44533a 100644 --- a/docs/_docs/reference/contextual/right-associative-extension-methods.md +++ b/docs/_docs/reference/contextual/right-associative-extension-methods.md @@ -36,7 +36,22 @@ single explicit term parameter (in other words, `rightParam` is present). In the The Scala compiler pre-processes a right-associative infix operation such as `x +: xs` to `xs.+:(x)` if `x` is a pure expression or a call-by-name parameter and to `val y = x; xs.+:(y)` otherwise. This is necessary since a regular right-associative infix method -is defined in the class of its right operand. To make up for this swap, +is defined in the class of its right operand. + +### Natural Order Right-Associative Extension Methods +If the right-associative extension methods is defined as infix, then the extension is used in its natural order. The `leftParam` is the receiver and +the `rightParam` is the argument. The order of the parameters is kept consistent with the order of the arguments at call site after desugaring. +For instance: + +```scala + extension [T](xs: List[T]) + infix def +:: (x: T): List[T] = ... + + y +:: ys // ys.::y // +::(ys)(y) +``` + +### Inverted Right-Associative Extension Methods +To make up for the swap in the order at call site, the expansion of right-associative extension methods performs the inverse parameter swap. More precisely, if `rightParam` is present, the total parameter sequence of the extension method's expansion is: @@ -59,6 +74,27 @@ For instance, the `+::` method above would become ``` This expansion has to be kept in mind when writing right-associative extension -methods with inter-parameter dependencies. +methods with inter-parameter dependencies. To avoid this limitation use _natural order right-associative extension methods_. + +This expansion also introduces some inconsistencies when calling the extension methods in non infix form. The user needs to invert the order of the arguments at call site manually. For instance: + +```scala + extension [T](x: T) + def *:(xs: List[T]): List[T] = ... + + y.*:(ys) // error when following the parameter definition order + ys.*:(y) + + *:(y)(ys) // error when following the parameter definition order + *:(ys)(y) +``` -An overall simpler design could be obtained if right-associative operators could _only_ be defined as extension methods, and would be disallowed as normal methods. In that case neither arguments nor parameters would have to be swapped. Future versions of Scala should strive to achieve this simplification. +Another limitation of this representation is that it is impossible to pass the +type parameters of the `def` explicitly. For instance: + +```scala + extension (x: Int) + def *:[T](xs: List[T]): List[T] = ??? + + xs.*:[Int](1) // error when trying to set T explicitly +``` diff --git a/library/src/scala/IArray.scala b/library/src/scala/IArray.scala index dadb9de6c74b..887ca517ef2b 100644 --- a/library/src/scala/IArray.scala +++ b/library/src/scala/IArray.scala @@ -319,10 +319,14 @@ object IArray: def zipAll[T1 >: T, U](that: Iterable[U], thisElem: T1, thatElem: U): IArray[(T1, U)] = genericArrayOps(arr).zipAll(that, thisElem, thatElem) def zipWithIndex: IArray[(T, Int)] = genericArrayOps(arr).zipWithIndex - extension [T, U >: T: ClassTag](arr: IArray[U]) - def ++:(prefix: IterableOnce[T]): IArray[U] = genericArrayOps(arr).prependedAll(prefix) - def ++:(prefix: IArray[T]): IArray[U] = genericArrayOps(arr).prependedAll(prefix) - def +:(x: T): IArray[U] = genericArrayOps(arr).prepended(x) + extension [T, U >: T: ClassTag](prefix: IterableOnce[T]) + def ++:(arr: IArray[U]): IArray[U] = genericArrayOps(arr).prependedAll(prefix) + + extension [T, U >: T: ClassTag](prefix: IArray[T]) + def ++:(arr: IArray[U]): IArray[U] = genericArrayOps(arr).prependedAll(prefix) + + extension [T, U >: T: ClassTag](x: T) + def +:(arr: IArray[U]): IArray[U] = genericArrayOps(arr).prepended(x) // For backwards compatibility with code compiled without -Yexplicit-nulls private inline def mapNull[A, B](a: A, inline f: B): B = diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala index 0f73aae7970f..2157aa891bae 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala @@ -152,8 +152,8 @@ class HoverTypeSuite extends BaseHoverSuite: |class C | |object Foo: - | extension [R](using A)(res: R)(using B) - | def %:[T](main: T)(using C): R = ??? + | extension [T](using A)(main: T)(using B) + | def %:[R](res: R)(using C): R = ??? | given A with {} | given B with {} | given C with {} @@ -162,7 +162,7 @@ class HoverTypeSuite extends BaseHoverSuite: |end Foo |""".stripMargin, """|Int - |extension [R](using A)(using B)(res: R) def %:[T](main: T)(using C): R""".stripMargin.hover + |extension [T](using A)(main: T) def %:[R](res: R)(using B)(using C): R""".stripMargin.hover ) @Test def `using` = diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.scala b/tests/neg-custom-args/captures/lazylists-exceptions.scala index ab57af13d1ba..295147f7f3c5 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.scala +++ b/tests/neg-custom-args/captures/lazylists-exceptions.scala @@ -20,8 +20,8 @@ final class LazyCons[+T](val x: T, val xs: () => LazyList[T]^) extends LazyList[ def tail: LazyList[T]^{this} = xs() end LazyCons -extension [A](xs1: => LazyList[A]^) - def #:(x: A): LazyList[A]^{xs1} = +extension [A](x: A) + def #:(xs1: => LazyList[A]^): LazyList[A]^{xs1} = LazyCons(x, () => xs1) def tabulate[A](n: Int)(gen: Int => A): LazyList[A]^{gen} = diff --git a/tests/neg/i13075.scala b/tests/neg/i13075.scala index 7bf0ec3203cb..2a5ff70c0481 100644 --- a/tests/neg/i13075.scala +++ b/tests/neg/i13075.scala @@ -8,8 +8,8 @@ object Implementing_Tuples: type *:[H, T <: Tup] = ConsTup[H, T] // for type matching type EmptyTup = EmptyTup.type // for type matching - extension [T <: Tup](tail: T) - def *:[H](head: H) = ConsTup(head, tail) + extension [H](head: H) + def *:[T <: Tup](tail: T) = ConsTup(head, tail) type Fold[T <: Tup, Seed, F[_,_]] = T match case EmptyTup => Seed diff --git a/tests/neg/i9562.scala b/tests/neg/i9562.scala index 6c917cfabe84..23d1bebdbba9 100644 --- a/tests/neg/i9562.scala +++ b/tests/neg/i9562.scala @@ -9,5 +9,4 @@ object Unrelated: def h1: Int = foo // error def h2: Int = h1 + 1 // OK def h3: Int = g // error - extension (x: Int) - def ++:(f: Foo): Int = f.h1 + x // OK + def ++: (x: Int): Int = h1 + x // OK \ No newline at end of file diff --git a/tests/pos-custom-args/captures/lazylists-exceptions.scala b/tests/pos-custom-args/captures/lazylists-exceptions.scala index 58183d4b5b31..afc6616108bc 100644 --- a/tests/pos-custom-args/captures/lazylists-exceptions.scala +++ b/tests/pos-custom-args/captures/lazylists-exceptions.scala @@ -42,8 +42,8 @@ extension [A](xs: LzyList[A]^) if n == 0 then xs else xs.tail.drop(n - 1) end extension -extension [A](xs1: => LzyList[A]^) - def #:(x: A): LzyList[A]^{xs1} = +extension [A](x: A) + def #:(xs1: => LzyList[A]^): LzyList[A]^{xs1} = LzyCons(x, () => xs1) def lazyCons[A](x: A, xs1: => LzyList[A]^): LzyList[A]^{xs1} = diff --git a/tests/pos-custom-args/captures/logger.scala b/tests/pos-custom-args/captures/logger.scala index 97b0ef293b2a..d95eeaae74cf 100644 --- a/tests/pos-custom-args/captures/logger.scala +++ b/tests/pos-custom-args/captures/logger.scala @@ -32,8 +32,8 @@ final class LazyCons[+T](val x: T, val xs: () => LazyList[T]^) extends LazyList[ def tail: LazyList[T]^{this} = xs() end LazyCons -extension [A](xs1: => LazyList[A]^) - def #::(x: A): LazyList[A]^{xs1} = +extension [A](x: A) + def #::(xs1: => LazyList[A]^): LazyList[A]^{xs1} = LazyCons(x, () => xs1) extension [A](xs: LazyList[A]^) diff --git a/tests/pos-custom-args/captures/strictlists.scala b/tests/pos-custom-args/captures/strictlists.scala index 09c360d4f192..cb58a09a161f 100644 --- a/tests/pos-custom-args/captures/strictlists.scala +++ b/tests/pos-custom-args/captures/strictlists.scala @@ -28,11 +28,12 @@ extension [A](xs: StrictList[A]) def concat(ys: StrictList[A]): StrictList[A] = if xs.isEmpty then ys else xs.head #: xs.tail.concat(ys) - - def #:(x: A): StrictList[A] = - StrictCons(x, xs) end extension +extension [A](x: A) + def #:(xs1: StrictList[A]): StrictList[A] = + StrictCons(x, xs1) + def tabulate[A](n: Int)(gen: Int => A) = def recur(i: Int): StrictList[A] = if i == n then StrictNil diff --git a/tests/pos/i19197.scala b/tests/pos/i19197.scala index 009cc2907a3e..826c80269f74 100644 --- a/tests/pos/i19197.scala +++ b/tests/pos/i19197.scala @@ -1,5 +1,5 @@ extension (tuple: Tuple) - def **:[T >: tuple.type <: Tuple, H](x: H): H *: T = ??? + infix def **:[T >: tuple.type <: Tuple, H](x: H): H *: T = ??? def test1: (Int, String, Char) = 1 **: ("a", 'b') def test2: (Int, String, Char) = ("a", 'b').**:(1) diff --git a/tests/pos/i9562.scala b/tests/pos/i9562.scala index 54f3713acb06..9f58ff309d3c 100644 --- a/tests/pos/i9562.scala +++ b/tests/pos/i9562.scala @@ -8,6 +8,4 @@ object Unrelated: extension (f: Foo) def h1: Int = 0 def h2: Int = h1 + 1 // OK - - extension (x: Int) - def ++: (f: Foo): Int = f.h2 + x // OK + def ++: (x: Int): Int = h2 + x // OK \ No newline at end of file diff --git a/tests/pos/reference/extension-methods.scala b/tests/pos/reference/extension-methods.scala index 57bd15f7d0ba..64fd23322c1b 100644 --- a/tests/pos/reference/extension-methods.scala +++ b/tests/pos/reference/extension-methods.scala @@ -10,7 +10,7 @@ object ExtMethods: assert(circle.circumference == circumference(circle)) extension (x: String) def < (y: String) = x.compareTo(y) < 0 - extension [Elem](xs: Seq[Elem]) def #: (x: Elem) = x +: xs + extension [Elem](x: Elem) def #: (xs: Seq[Elem]) = x +: xs extension (x: Number) infix def min (y: Number) = x assert("a" < "bb") diff --git a/tests/run/errorhandling/Result.scala b/tests/run/errorhandling/Result.scala index 29e91b11b232..07d7a9f90c8a 100644 --- a/tests/run/errorhandling/Result.scala +++ b/tests/run/errorhandling/Result.scala @@ -35,15 +35,12 @@ object Result: case (Err(e), Ok(_)) => Err(e :: Nil) case (Err(e1), Err(e2)) => Err(e1 :: e2 :: Nil) - end extension - - extension [U <: Tuple, E](other: Result[U, List[E]]) /** Validate both `r` and `other`; return a tuple of successes or a list of failures. * Unlike with `zip`, the right hand side `other` must be a `Result` returning a `Tuple`, * and the left hand side is added to it. See `Result.empty` for a convenient * right unit of chains of `*:`s. */ - def *: [T](r: Result[T, E]): Result[T *: U, List[E]] = (r, other) match + def *: [U <: Tuple](other: Result[U, List[E]]): Result[T *: U, List[E]] = (r, other) match case (Ok(x), Ok(ys)) => Ok(x *: ys) case (Ok(_), es: Err[?]) => es case (Err(e), Ok(_)) => Err(e :: Nil) diff --git a/tests/run/export-in-extension.scala b/tests/run/export-in-extension.scala index e71a2adfdcb7..26becc280ff3 100644 --- a/tests/run/export-in-extension.scala +++ b/tests/run/export-in-extension.scala @@ -11,7 +11,7 @@ object O: export cm.* def succ: Int = x + 1 def succ2: Int = succ + 1 - def ::: (y: Int) = y - x + def ::: (y: Int) = x - y object O2: import O.C @@ -20,7 +20,7 @@ object O2: export cm.{bar, baz, bam, ::} def succ: Int = x + 1 def succ2: Int = succ + 1 - def ::: (y: Int) = y - x + def ::: (y: Int) = x - y @main def Test = import O.* diff --git a/tests/run/i11583.scala b/tests/run/i11583.scala index c9901a38ebf5..87701ac95310 100644 --- a/tests/run/i11583.scala +++ b/tests/run/i11583.scala @@ -12,11 +12,11 @@ class Env: // */ // def &&:[T <: ctx.Term](trm: T)(ext: env.Extra): (ctx.Type, T, env.Extra) = (tpe, trm, ext) -extension [Ctx <: Context, T <: Boolean](using ctx: Ctx)(trm: T)(using env: Env) - def :#:(tpe: String)(ext: env.Extra): (String, T, env.Extra) = (tpe, trm, ext) +extension [Ctx <: Context](using ctx: Ctx)(tpe: String)(using env: Env) + def :#:[T <: Boolean](trm: T)(ext: env.Extra): (String, T, env.Extra) = (tpe, trm, ext) -extension [T <: Tuple](t: T) - def :*:[A](a: A): A *: T = a *: t +extension [A](a: A) + def :*:[T <: Tuple](t: T): A *: T = a *: t @main def Test = diff --git a/tests/run/i9530.scala b/tests/run/i9530.scala index c644ec74bd95..e0262764039f 100644 --- a/tests/run/i9530.scala +++ b/tests/run/i9530.scala @@ -8,7 +8,7 @@ trait Scope: extension (using s: Scope)(expr: s.Expr) def show = expr.toString def eval = s.value(expr) - def *: (other: s.Expr) = s.combine(other, expr) + def *: (other: s.Expr) = s.combine(expr, other) def f(using s: Scope)(x: s.Expr): (String, s.Value) = (x.show, x.eval) diff --git a/tests/run/instances.scala b/tests/run/instances.scala index 812d3ad23477..128ea0700e02 100644 --- a/tests/run/instances.scala +++ b/tests/run/instances.scala @@ -31,7 +31,7 @@ object Test extends App { extension [T](xs: List[List[T]]) def flattened = xs.foldLeft[List[T]](Nil)(_ ++ _) - extension [T](xs: Seq[T]) def :: (x: T) = x +: xs + extension [T](x: T) def :: (xs: Seq[T]) = x +: xs val ss: Seq[Int] = List(1, 2, 3) val ss1 = 0 :: ss diff --git a/tests/run/interleaving.scala b/tests/run/interleaving.scala index 2be444992b2e..557741032e8a 100644 --- a/tests/run/interleaving.scala +++ b/tests/run/interleaving.scala @@ -56,47 +56,47 @@ object Test extends App { extension (db: DB) def ?:(k: Key)[V >: k.Value](default: V): V = db.getOrElse(k)(default) - assert((db1.?:(key1))[Option[Int]](default1) == Some(4)) - assert((db1.?:(key2))[Option[Int]](default1) == default1) - assert((db1.?:(key3))[Option[String]](default1) == default1) - assert((db1.?:(key1))(default1) == Some(4)) - assert((db1.?:(key2))(default1) == default1) - assert((db1.?:(key3))(default1) == default1) - - assert((db1.?:(key1))[Any](default2) == Some(4)) - assert((db1.?:(key2))[Any](default2) == default2) - assert((db1.?:(key3))[Any](default2) == default2) - assert((db1.?:(key1))(default2) == Some(4)) - assert((db1.?:(key2))(default2) == default2) - assert((db1.?:(key3))(default2) == default2) - - - assert(db1.?:(key1)[Option[Int]](default1) == Some(4)) - assert(db1.?:(key2)[Option[Int]](default1) == default1) - assert(db1.?:(key3)[Option[String]](default1) == default1) - assert(db1.?:(key1)(default1) == Some(4)) - assert(db1.?:(key2)(default1) == default1) - assert(db1.?:(key3)(default1) == default1) - - assert(db1.?:(key1)[Any](default2) == Some(4)) - assert(db1.?:(key2)[Any](default2) == default2) - assert(db1.?:(key3)[Any](default2) == default2) - assert(db1.?:(key1)(default2) == Some(4)) - assert(db1.?:(key2)(default2) == default2) - assert(db1.?:(key3)(default2) == default2) - - - assert(?:(db1)(key1)[Option[Int]](default1) == Some(4)) - assert(?:(db1)(key2)[Option[Int]](default1) == default1) - assert(?:(db1)(key3)[Option[String]](default1) == default1) - assert(?:(db1)(key1)(default1) == Some(4)) - assert(?:(db1)(key2)(default1) == default1) - assert(?:(db1)(key3)(default1) == default1) - - assert(?:(db1)(key1)[Any](default2) == Some(4)) - assert(?:(db1)(key2)[Any](default2) == default2) - assert(?:(db1)(key3)[Any](default2) == default2) - assert(?:(db1)(key1)(default2) == Some(4)) - assert(?:(db1)(key2)(default2) == default2) - assert(?:(db1)(key3)(default2) == default2) + assert((db1 ?: (key1))[Option[Int]](default1) == Some(4)) + assert((db1 ?: (key2))[Option[Int]](default1) == default1) + assert((db1 ?: (key3))[Option[String]](default1) == default1) + assert((db1 ?: (key1))(default1) == Some(4)) + assert((db1 ?: (key2))(default1) == default1) + assert((db1 ?: (key3))(default1) == default1) + + assert((db1 ?: (key1))[Any](default2) == Some(4)) + assert((db1 ?: (key2))[Any](default2) == default2) + assert((db1 ?: (key3))[Any](default2) == default2) + assert((db1 ?: (key1))(default2) == Some(4)) + assert((db1 ?: (key2))(default2) == default2) + assert((db1 ?: (key3))(default2) == default2) + + + assert(key1.?:(db1)[Option[Int]](default1) == Some(4)) + assert(key2.?:(db1)[Option[Int]](default1) == default1) + assert(key3.?:(db1)[Option[String]](default1) == default1) + assert(key1.?:(db1)(default1) == Some(4)) + assert(key2.?:(db1)(default1) == default1) + assert(key3.?:(db1)(default1) == default1) + + assert(key1.?:(db1)[Any](default2) == Some(4)) + assert(key2.?:(db1)[Any](default2) == default2) + assert(key3.?:(db1)[Any](default2) == default2) + assert(key1.?:(db1)(default2) == Some(4)) + assert(key2.?:(db1)(default2) == default2) + assert(key3.?:(db1)(default2) == default2) + + + assert(?:(key1)(db1)[Option[Int]](default1) == Some(4)) + assert(?:(key2)(db1)[Option[Int]](default1) == default1) + assert(?:(key3)(db1)[Option[String]](default1) == default1) + assert(?:(key1)(db1)(default1) == Some(4)) + assert(?:(key2)(db1)(default1) == default1) + assert(?:(key3)(db1)(default1) == default1) + + assert(?:(key1)(db1)[Any](default2) == Some(4)) + assert(?:(key2)(db1)[Any](default2) == default2) + assert(?:(key3)(db1)[Any](default2) == default2) + assert(?:(key1)(db1)(default2) == Some(4)) + assert(?:(key2)(db1)(default2) == default2) + assert(?:(key3)(db1)(default2) == default2) } diff --git a/tests/semanticdb/expect/RightAssociativeExtension.scala b/tests/semanticdb/expect/RightAssociativeExtension.scala index 00a47c19b7ec..680dba16fc3c 100644 --- a/tests/semanticdb/expect/RightAssociativeExtension.scala +++ b/tests/semanticdb/expect/RightAssociativeExtension.scala @@ -1,6 +1,6 @@ package ext -extension (i: Int) - def :*: (s: String): (String, Int) = (s, i) +extension (s: String) + def :*: (i: Int): (String, Int) = (s, i) val b = "foo" :*: 23 \ No newline at end of file diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 7fab92e44dbe..2fd8eca47a7b 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -3383,15 +3383,15 @@ ext/RightAssociativeExtension$package.b. => val method b Tuple2[String, Int] Occurrences: [0:8..0:11): ext <- ext/ -[2:11..2:12): i <- ext/RightAssociativeExtension$package.`:*:`().(i) -[2:14..2:17): Int -> scala/Int# +[2:11..2:12): s <- ext/RightAssociativeExtension$package.`:*:`().(s) +[2:14..2:20): String -> scala/Predef.String# [3:6..3:9): :*: <- ext/RightAssociativeExtension$package.`:*:`(). -[3:11..3:12): s <- ext/RightAssociativeExtension$package.`:*:`().(s) -[3:14..3:20): String -> scala/Predef.String# -[3:24..3:30): String -> scala/Predef.String# -[3:32..3:35): Int -> scala/Int# -[3:40..3:41): s -> ext/RightAssociativeExtension$package.`:*:`().(s) -[3:43..3:44): i -> ext/RightAssociativeExtension$package.`:*:`().(i) +[3:11..3:12): i <- ext/RightAssociativeExtension$package.`:*:`().(i) +[3:14..3:17): Int -> scala/Int# +[3:21..3:27): String -> scala/Predef.String# +[3:29..3:32): Int -> scala/Int# +[3:37..3:38): s -> ext/RightAssociativeExtension$package.`:*:`().(s) +[3:40..3:41): i -> ext/RightAssociativeExtension$package.`:*:`().(i) [5:4..5:5): b <- ext/RightAssociativeExtension$package.b. [5:14..5:17): :*: -> ext/RightAssociativeExtension$package.`:*:`().