From 1c30071ad4bdee0a30789966723521e62248e637 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 11:30:27 +0200 Subject: [PATCH 01/16] Use consistent naming and drop remaining braces in tuple type ops --- library/src/scala/Tuple.scala | 67 ++++++++++++++----------------- tests/neg/print-tuple-union.check | 2 +- tests/neg/wildcard-match.check | 7 ++-- 3 files changed, 34 insertions(+), 42 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 30f0e44ecf45..b643f606baae 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -22,13 +22,13 @@ sealed trait Tuple extends Product: runtime.Tuples.toIArray(this) /** Return a copy of `this` tuple with an element appended */ - inline def :* [This >: this.type <: Tuple, L] (x: L): Append[This, L] = + inline def :* [This >: this.type <: Tuple, L](x: L): Append[This, L] = runtime.Tuples.append(x, this).asInstanceOf[Append[This, L]] /** Return a new tuple by prepending the element to `this` tuple. * This operation is O(this.size) */ - inline def *: [H, This >: this.type <: Tuple] (x: H): H *: This = + inline def *: [H, This >: this.type <: Tuple](x: H): H *: This = runtime.Tuples.cons(x, this).asInstanceOf[H *: This] /** Return a new tuple by concatenating `this` tuple with `that` tuple. @@ -104,14 +104,13 @@ object Tuple: /** The size of a tuple, represented as a literal constant subtype of Int */ type Size[X <: Tuple] <: Int = X match case EmptyTuple => 0 - case x *: xs => S[Size[xs]] + case _ *: xs => S[Size[xs]] /** The type of the element at position N in the tuple X */ type Elem[X <: Tuple, N <: Int] = X match - case x *: xs => - N match - case 0 => x - case S[n1] => Elem[xs, n1] + case x *: xs => N match + case 0 => x + case S[n1] => Elem[xs, n1] /** The type of the first element of a tuple */ // Only bounded by `<: Tuple` not `<: NonEmptyTuple` @@ -134,8 +133,7 @@ object Tuple: /** The type of the initial part of a tuple without its last element */ type Init[X <: Tuple] <: Tuple = X match case _ *: EmptyTuple => EmptyTuple - case x *: xs => - x *: Init[xs] + case x *: xs => x *: Init[xs] /** The type of the tuple consisting of the first `N` elements of `X`, * or all elements if `N` exceeds `Size[X]`. @@ -149,27 +147,24 @@ object Tuple: /** The type of the tuple consisting of all elements of `X` except the first `N` ones, * or no elements if `N` exceeds `Size[X]`. */ - type Drop[X <: Tuple, N <: Int] <: Tuple = N match { + type Drop[X <: Tuple, N <: Int] <: Tuple = N match case 0 => X - case S[n1] => X match { + case S[n1] => X match case EmptyTuple => EmptyTuple - case x *: xs => Drop[xs, n1] - } - } + case _ *: xs => Drop[xs, n1] /** The pair type `(Take(X, N), Drop[X, N]). */ type Split[X <: Tuple, N <: Int] = (Take[X, N], Drop[X, N]) /** Type of a tuple with an element appended */ - type Append[X <: Tuple, Y] <: NonEmptyTuple = X match { + type Append[X <: Tuple, Y] <: NonEmptyTuple = X match case EmptyTuple => Y *: EmptyTuple case x *: xs => x *: Append[xs, Y] - } /** Type of the concatenation of two tuples `X` and `Y` */ type Concat[X <: Tuple, +Y <: Tuple] <: Tuple = X match case EmptyTuple => Y - case x1 *: xs1 => x1 *: Concat[xs1, Y] + case x *: xs => x *: Concat[xs, Y] /** An infix shorthand for `Concat[X, Y]` */ infix type ++[X <: Tuple, +Y <: Tuple] = Concat[X, Y] @@ -179,27 +174,27 @@ object Tuple: */ type IndexOf[X <: Tuple, Y] <: Int = X match case Y *: _ => 0 - case x *: xs => S[IndexOf[xs, Y]] + case _ *: xs => S[IndexOf[xs, Y]] case EmptyTuple => 0 /** Fold a tuple `(T1, ..., Tn)` into `F[T1, F[... F[Tn, Z]...]]]` */ - type Fold[Tup <: Tuple, Z, F[_, _]] = Tup match + type Fold[X <: Tuple, Z, F[_, _]] = X match case EmptyTuple => Z - case h *: t => F[h, Fold[t, Z, F]] + case x *: xs => F[x, Fold[xs, Z, F]] /** The type of tuple `X` mapped with the type-level function `F`. * If `X = (T1, ..., Ti)` then `Map[X, F] = `(F[T1], ..., F[Ti])`. */ - type Map[Tup <: Tuple, F[_ <: Union[Tup]]] <: Tuple = Tup match + type Map[X <: Tuple, F[_ <: Union[X]]] <: Tuple = X match case EmptyTuple => EmptyTuple - case h *: t => F[h] *: Map[t, F] + case x *: xs => F[x] *: Map[xs, F] /** The type of tuple `X` flat-mapped with the type-level function `F`. * If `X = (T1, ..., Ti)` then `FlatMap[X, F] = `F[T1] ++ ... ++ F[Ti]` */ - type FlatMap[Tup <: Tuple, F[_ <: Union[Tup]] <: Tuple] <: Tuple = Tup match + type FlatMap[X <: Tuple, F[_ <: Union[X]] <: Tuple] <: Tuple = X match case EmptyTuple => EmptyTuple - case h *: t => Concat[F[h], FlatMap[t, F]] + case x *: xs => Concat[F[x], FlatMap[xs, F]] // TODO: implement term level analogue /** The type of the tuple consisting of all elements of tuple `X` that have types @@ -217,9 +212,9 @@ object Tuple: */ type Filter[X <: Tuple, P[_] <: Boolean] <: Tuple = X match case EmptyTuple => EmptyTuple - case h *: t => P[h] match - case true => h *: Filter[t, P] - case false => Filter[t, P] + case x *: xs => P[x] match + case true => x *: Filter[xs, P] + case false => Filter[xs, P] /** A tuple consisting of those indices `N` of tuple `X` where the predicate `P` * is true for `Elem[X, N]`. Indices are type level values <: Int. @@ -242,17 +237,16 @@ object Tuple: * ``` * @syntax markdown */ - type Zip[T1 <: Tuple, T2 <: Tuple] <: Tuple = (T1, T2) match - case (h1 *: t1, h2 *: t2) => (h1, h2) *: Zip[t1, t2] + type Zip[X <: Tuple, Y <: Tuple] <: Tuple = (X, Y) match + case (x *: xs, y *: ys) => (x, y) *: Zip[xs, ys] case (EmptyTuple, _) => EmptyTuple case (_, EmptyTuple) => EmptyTuple case _ => Tuple /** Converts a tuple `(F[T1], ..., F[Tn])` to `(T1, ... Tn)` */ - type InverseMap[X <: Tuple, F[_]] <: Tuple = X match { - case F[x] *: t => x *: InverseMap[t, F] + type InverseMap[X <: Tuple, F[_]] <: Tuple = X match + case F[x] *: xs => x *: InverseMap[xs, F] case EmptyTuple => EmptyTuple - } /** Implicit evidence. IsMappedBy[F][X] is present in the implicit scope iff * X is a tuple for which each element's type is constructed via `F`. E.g. @@ -280,7 +274,7 @@ object Tuple: */ type Contains[X <: Tuple, Y] <: Boolean = X match case Y *: _ => true - case x *: xs => Contains[xs, Y] + case _ *: xs => Contains[xs, Y] case EmptyTuple => false /** A type level Boolean indicating whether the type `Y` contains @@ -288,10 +282,9 @@ object Tuple: * @pre The elements of `X` and `Y` are assumed to be singleton types */ type Disjoint[X <: Tuple, Y <: Tuple] <: Boolean = X match - case x *: xs => - Contains[Y, x] match - case true => false - case false => Disjoint[xs, Y] + case x *: xs => Contains[Y, x] match + case true => false + case false => Disjoint[xs, Y] case EmptyTuple => true /** Empty tuple */ diff --git a/tests/neg/print-tuple-union.check b/tests/neg/print-tuple-union.check index f3754aa5b17e..7d2c019de5a6 100644 --- a/tests/neg/print-tuple-union.check +++ b/tests/neg/print-tuple-union.check @@ -13,6 +13,6 @@ | and cannot be shown to be disjoint from it either. | Therefore, reduction cannot advance to the remaining case | - | case h *: t => h | Tuple.Fold[t, Nothing, [x, y] =>> x | y] + | case x *: xs => x | Tuple.Fold[xs, Nothing, [x, y] =>> x | y] | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/wildcard-match.check b/tests/neg/wildcard-match.check index d405326c3d2b..fd20443c0a9f 100644 --- a/tests/neg/wildcard-match.check +++ b/tests/neg/wildcard-match.check @@ -87,8 +87,7 @@ | trying to reduce shapeless.tuples.length[T2] | trying to reduce Tuple.Size[shapeless.tuples.to[T2]] | failed since selector shapeless.tuples.to[T2] - | does not uniquely determine parameters x, xs in - | case x *: xs => scala.compiletime.ops.int.S[Tuple.Size[xs]] - | The computed bounds for the parameters are: - | x <: Int + | does not uniquely determine parameter xs in + | case _ *: xs => scala.compiletime.ops.int.S[Tuple.Size[xs]] + | The computed bounds for the parameter are: | xs <: (Int, Int) From 92311551918a2d8676ca48e4d9e8589216f69566 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 11:32:05 +0200 Subject: [PATCH 02/16] Add an infix shorthand for `Append[X, Y]` as is the case for `Concat` --- library/src/scala/Tuple.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index b643f606baae..34274c4b0413 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -22,7 +22,7 @@ sealed trait Tuple extends Product: runtime.Tuples.toIArray(this) /** Return a copy of `this` tuple with an element appended */ - inline def :* [This >: this.type <: Tuple, L](x: L): Append[This, L] = + inline def :* [This >: this.type <: Tuple, L](x: L): This :* L = runtime.Tuples.append(x, this).asInstanceOf[Append[This, L]] /** Return a new tuple by prepending the element to `this` tuple. @@ -34,7 +34,7 @@ sealed trait Tuple extends Product: /** Return a new tuple by concatenating `this` tuple with `that` tuple. * This operation is O(this.size + that.size) */ - inline def ++ [This >: this.type <: Tuple](that: Tuple): Concat[This, that.type] = + inline def ++ [This >: this.type <: Tuple](that: Tuple): This ++ that.type = runtime.Tuples.concat(this, that).asInstanceOf[Concat[This, that.type]] /** Return the size (or arity) of the tuple */ @@ -161,6 +161,9 @@ object Tuple: case EmptyTuple => Y *: EmptyTuple case x *: xs => x *: Append[xs, Y] + /** An infix shorthand for `Append[X, Y]` */ + infix type :*[X <: Tuple, Y] = Append[X, Y] + /** Type of the concatenation of two tuples `X` and `Y` */ type Concat[X <: Tuple, +Y <: Tuple] <: Tuple = X match case EmptyTuple => Y From 86e5ca21ee5d5e57389039db13b4da131ab52a04 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 11:53:11 +0200 Subject: [PATCH 03/16] Drop unreachable case from `type Zip` --- library/src/scala/Tuple.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 34274c4b0413..7afa96a067b0 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -244,7 +244,6 @@ object Tuple: case (x *: xs, y *: ys) => (x, y) *: Zip[xs, ys] case (EmptyTuple, _) => EmptyTuple case (_, EmptyTuple) => EmptyTuple - case _ => Tuple /** Converts a tuple `(F[T1], ..., F[Tn])` to `(T1, ... Tn)` */ type InverseMap[X <: Tuple, F[_]] <: Tuple = X match From 0146eb3ed4bd6cd47686b042346dde902e84ca57 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 11:53:50 +0200 Subject: [PATCH 04/16] Document `Concat` covariance in 2nd parameter --- library/src/scala/Tuple.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 7afa96a067b0..e46d5c5348d8 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -34,6 +34,8 @@ sealed trait Tuple extends Product: /** Return a new tuple by concatenating `this` tuple with `that` tuple. * This operation is O(this.size + that.size) */ + // Contrarily to `this`, `that` does not need a type parameter + // since `++` is covariant in its second argument. inline def ++ [This >: this.type <: Tuple](that: Tuple): This ++ that.type = runtime.Tuples.concat(this, that).asInstanceOf[Concat[This, that.type]] @@ -165,6 +167,7 @@ object Tuple: infix type :*[X <: Tuple, Y] = Append[X, Y] /** Type of the concatenation of two tuples `X` and `Y` */ + // Can be covariant in `Y` since it never appears as a match type scrutinee. type Concat[X <: Tuple, +Y <: Tuple] <: Tuple = X match case EmptyTuple => Y case x *: xs => x *: Concat[xs, Y] From 1d05ee62009fc4ad65d8332fdd86a5603c0e4fe2 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 12:07:54 +0200 Subject: [PATCH 05/16] Refine bounds of `type Filter` predicate to only require being defined on the element types. Similar to what we have for `type FlatMap` --- library/src/scala/Tuple.scala | 8 ++++---- tests/pos/tuple-filter.scala | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index e46d5c5348d8..fa598f3bd105 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -94,7 +94,7 @@ sealed trait Tuple extends Product: * for which the given type level predicate `P` reduces to the literal * constant `true`. */ - inline def filter[This >: this.type <: Tuple, P[_] <: Boolean]: Filter[This, P] = + inline def filter[This >: this.type <: Tuple, P[_ <: Union[This]] <: Boolean]: Filter[This, P] = val toInclude = constValueTuple[IndicesWhere[This, P]].toArray val arr = new Array[Object](toInclude.length) for i <- 0 until toInclude.length do @@ -216,7 +216,7 @@ object Tuple: * ``` * @syntax markdown */ - type Filter[X <: Tuple, P[_] <: Boolean] <: Tuple = X match + type Filter[X <: Tuple, P[_ <: Union[X]] <: Boolean] <: Tuple = X match case EmptyTuple => EmptyTuple case x *: xs => P[x] match case true => x *: Filter[xs, P] @@ -225,7 +225,7 @@ object Tuple: /** A tuple consisting of those indices `N` of tuple `X` where the predicate `P` * is true for `Elem[X, N]`. Indices are type level values <: Int. */ - type IndicesWhere[X <: Tuple, P[_] <: Boolean] = + type IndicesWhere[X <: Tuple, P[_ <: Union[X]] <: Boolean] = helpers.IndicesWhereHelper[X, P, 0] /** The type of the tuple consisting of all element values of @@ -355,7 +355,7 @@ object Tuple: private object helpers: /** Used to implement IndicesWhere */ - type IndicesWhereHelper[X <: Tuple, P[_] <: Boolean, N <: Int] <: Tuple = X match + type IndicesWhereHelper[X <: Tuple, P[_ <: Union[X]] <: Boolean, N <: Int] <: Tuple = X match case EmptyTuple => EmptyTuple case h *: t => P[h] match case true => N *: IndicesWhereHelper[t, P, S[N]] diff --git a/tests/pos/tuple-filter.scala b/tests/pos/tuple-filter.scala index 2c9638b2e47b..0964d2e982d9 100644 --- a/tests/pos/tuple-filter.scala +++ b/tests/pos/tuple-filter.scala @@ -8,3 +8,6 @@ def Test = summon[Tuple.Filter[(1, 2, 3, 4), P] =:= (1, 2, 4)] summon[Tuple.Filter[(1, 2, 3, 4), RejectAll] =:= EmptyTuple] summon[Tuple.Filter[EmptyTuple, P] =:= EmptyTuple] + + import compiletime.ops.int.< + summon[Tuple.Filter[(1, 4, 7, 2, 10, 3, 4), [X <: Int] =>> X < 5] =:= (1, 4, 2, 3, 4)] From a825ef4d8bc1776f6e9c814c4537f245fd9daa94 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 13:44:15 +0200 Subject: [PATCH 06/16] Do `contains` runtime operation based on term equality --- library/src/scala/Tuple.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index fa598f3bd105..21aa9dbf598f 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -338,9 +338,15 @@ object Tuple: */ transparent inline def indexOf(y: Any): Int = constValue[IndexOf[X, y.type]] - /** A boolean indicating whether there is an element `y.type` in the type `X` of `x` - */ - transparent inline def contains(y: Any): Boolean = constValue[Contains[X, y.type]] + /** A boolean indicating whether there is an element `y.type` in the type `X` of `x` */ + // Note this isn't equivalent to `constValue[Contains[X, y.type]]` + // since it also accepts cases unknown at compiletime. + // Also note it would be unsound to use a type parameter for `y` in the + // type level `Contains`, since it is rightfully not covariant in `Y`. + inline def contains(y: Any): Contains[X, y.type] = + x.productIterator.contains(y).asInstanceOf[Contains[X, y.type]] + + // TODO containsType ? end extension From 308569cb3397310a23704e6e3066ef8a1803fcbe Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 13:58:58 +0200 Subject: [PATCH 07/16] Do `indexOf` runtime operation based on term equality and refine `type IndexOf` doc --- library/src/scala/Tuple.scala | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 21aa9dbf598f..7f6923c976dd 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -176,7 +176,7 @@ object Tuple: infix type ++[X <: Tuple, +Y <: Tuple] = Concat[X, Y] /** The index of `Y` in tuple `X` as a literal constant Int, - * or `Size[X]` if `Y` does not occur in `X` + * or `Size[X]` if `Y` is disjoint from all element types in `X`. */ type IndexOf[X <: Tuple, Y] <: Int = X match case Y *: _ => 0 @@ -332,21 +332,22 @@ object Tuple: runtime.Tuples.fromProduct(product) extension [X <: Tuple](inline x: X) + // Note the two methods are not equivalent to using `constValue`, + // since they also allow cases unknown at compiletime. + // Also note it would be unsound to use a type parameter for `y` in the type level + // operations, since they are rightfully not covariant in their second parameter. /** The index (starting at 0) of the first occurrence of y.type in the type `X` of `x` * or Size[X] if no such element exists. */ - transparent inline def indexOf(y: Any): Int = constValue[IndexOf[X, y.type]] + inline def indexOf(y: Any): IndexOf[X, y.type] = + x.productIterator.indexOf(y).asInstanceOf[IndexOf[X, y.type]] /** A boolean indicating whether there is an element `y.type` in the type `X` of `x` */ - // Note this isn't equivalent to `constValue[Contains[X, y.type]]` - // since it also accepts cases unknown at compiletime. - // Also note it would be unsound to use a type parameter for `y` in the - // type level `Contains`, since it is rightfully not covariant in `Y`. inline def contains(y: Any): Contains[X, y.type] = x.productIterator.contains(y).asInstanceOf[Contains[X, y.type]] - // TODO containsType ? + // TODO indexOfType & containsType ? end extension From 01b5de029ce1e9f4e626c3d1038e8f5696af79cb Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 14:04:45 +0200 Subject: [PATCH 08/16] Do `filter` runtime operation based on a term level predicate --- library/src/scala/Tuple.scala | 13 ++++--------- tests/pos/named-tuples-strawman-2.scala | 5 ++++- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 7f6923c976dd..208f6b464286 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -90,15 +90,10 @@ sealed trait Tuple extends Product: inline def reverseOnto[This >: this.type <: Tuple, Acc <: Tuple](acc: Acc): ReverseOnto[This, Acc] = (this.reverse ++ acc).asInstanceOf[ReverseOnto[This, Acc]] - /** A tuple consisting of all elements of this tuple that have types - * for which the given type level predicate `P` reduces to the literal - * constant `true`. - */ - inline def filter[This >: this.type <: Tuple, P[_ <: Union[This]] <: Boolean]: Filter[This, P] = - val toInclude = constValueTuple[IndicesWhere[This, P]].toArray - val arr = new Array[Object](toInclude.length) - for i <- 0 until toInclude.length do - arr(i) = this.productElement(toInclude(i).asInstanceOf[Int]).asInstanceOf[Object] + /** A tuple consisting of all elements of this tuple that satisfy the predicate `p`. */ + inline def filter[This >: this.type <: Tuple, P[_ <: Union[This]] <: Boolean] + (p: (x: Union[This]) => P[x.type]): Filter[This, P] = + val arr = this.toArray.filter(x => p(x.asInstanceOf[Union[This]])) Tuple.fromArray(arr).asInstanceOf[Filter[This, P]] object Tuple: diff --git a/tests/pos/named-tuples-strawman-2.scala b/tests/pos/named-tuples-strawman-2.scala index 4b32dd83f2eb..7cd763bb7b00 100644 --- a/tests/pos/named-tuples-strawman-2.scala +++ b/tests/pos/named-tuples-strawman-2.scala @@ -60,7 +60,10 @@ object TupleOps: case EmptyTuple => X inline def concatDistinct[X <: Tuple, Y <: Tuple](xs: X, ys: Y): ConcatDistinct[X, Y] = - (xs ++ ys.filter[Y, [Elem] =>> ![Contains[X, Elem]]]).asInstanceOf[ConcatDistinct[X, Y]] + // Note the type parameter is needed due to the invariance of compiletime.ops.boolean.! + extension [B <: Boolean](self: B) def negated: ![B] = (!self).asInstanceOf + val ysDistinct = ys.filter[Y, [y] =>> ![Contains[X, y]]](xs.contains(_).negated) + (xs ++ ysDistinct).asInstanceOf[ConcatDistinct[X, Y]] object NamedTupleDecomposition: import NamedTupleOps.* From 5da69b95437068d2482ea33f54a6f15b21a51bfb Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 14:17:48 +0200 Subject: [PATCH 09/16] Mark `type Append` 2nd argument as covariant --- library/src/scala/Tuple.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 208f6b464286..e32a1bd8f124 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -154,12 +154,12 @@ object Tuple: type Split[X <: Tuple, N <: Int] = (Take[X, N], Drop[X, N]) /** Type of a tuple with an element appended */ - type Append[X <: Tuple, Y] <: NonEmptyTuple = X match + type Append[X <: Tuple, +Y] <: NonEmptyTuple = X match case EmptyTuple => Y *: EmptyTuple case x *: xs => x *: Append[xs, Y] /** An infix shorthand for `Append[X, Y]` */ - infix type :*[X <: Tuple, Y] = Append[X, Y] + infix type :*[X <: Tuple, +Y] = Append[X, Y] /** Type of the concatenation of two tuples `X` and `Y` */ // Can be covariant in `Y` since it never appears as a match type scrutinee. From b3455cff8e863028ef1e46fbb570009844cef215 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 14:52:42 +0200 Subject: [PATCH 10/16] Move `NonEmptyTuple` methods into `Tuple` This is for the same reason as we changed `type Head[X <: NonEmptyTuple] = ...` to `type Head[X <: Tuple] = ...` Also, this is no more unsafe than the other operations already defined for all tuples. `drop(1)` for example was always defined, even though `tail` wasn't. --- library/src/scala/Tuple.scala | 52 +++++++++++++------------- library/src/scala/runtime/Tuples.scala | 8 ++-- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index e32a1bd8f124..1c009e4d65e6 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -31,6 +31,30 @@ sealed trait Tuple extends Product: inline def *: [H, This >: this.type <: Tuple](x: H): H *: This = runtime.Tuples.cons(x, this).asInstanceOf[H *: This] + /** Get the i-th element of this tuple. + * Equivalent to productElement but with a precise return type. + */ + inline def apply[This >: this.type <: Tuple](n: Int): Elem[This, n.type] = + runtime.Tuples.apply(this, n).asInstanceOf[Elem[This, n.type]] + + /** Get the head of this tuple */ + inline def head[This >: this.type <: Tuple]: Head[This] = + runtime.Tuples.apply(this, 0).asInstanceOf[Head[This]] + + /** Get the initial part of the tuple without its last element */ + inline def init[This >: this.type <: Tuple]: Init[This] = + runtime.Tuples.init(this).asInstanceOf[Init[This]] + + /** Get the last of this tuple */ + inline def last[This >: this.type <: Tuple]: Last[This] = + runtime.Tuples.last(this).asInstanceOf[Last[This]] + + /** Get the tail of this tuple. + * This operation is O(this.size) + */ + inline def tail[This >: this.type <: Tuple]: Tail[This] = + runtime.Tuples.tail(this).asInstanceOf[Tail[This]] + /** Return a new tuple by concatenating `this` tuple with `that` tuple. * This operation is O(this.size + that.size) */ @@ -375,33 +399,7 @@ case object EmptyTuple extends Tuple { } /** Tuple of arbitrary non-zero arity */ -sealed trait NonEmptyTuple extends Tuple { - import Tuple.* - - /** Get the i-th element of this tuple. - * Equivalent to productElement but with a precise return type. - */ - inline def apply[This >: this.type <: NonEmptyTuple](n: Int): Elem[This, n.type] = - runtime.Tuples.apply(this, n).asInstanceOf[Elem[This, n.type]] - - /** Get the head of this tuple */ - inline def head[This >: this.type <: NonEmptyTuple]: Head[This] = - runtime.Tuples.apply(this, 0).asInstanceOf[Head[This]] - - /** Get the initial part of the tuple without its last element */ - inline def init[This >: this.type <: NonEmptyTuple]: Init[This] = - runtime.Tuples.init(this).asInstanceOf[Init[This]] - - /** Get the last of this tuple */ - inline def last[This >: this.type <: NonEmptyTuple]: Last[This] = - runtime.Tuples.last(this).asInstanceOf[Last[This]] - - /** Get the tail of this tuple. - * This operation is O(this.size) - */ - inline def tail[This >: this.type <: NonEmptyTuple]: Tail[This] = - runtime.Tuples.tail(this).asInstanceOf[Tail[This]] -} +sealed trait NonEmptyTuple extends Tuple @showAsInfix sealed abstract class *:[+H, +T <: Tuple] extends NonEmptyTuple diff --git a/library/src/scala/runtime/Tuples.scala b/library/src/scala/runtime/Tuples.scala index be6904b9d1d0..8da21c777943 100644 --- a/library/src/scala/runtime/Tuples.scala +++ b/library/src/scala/runtime/Tuples.scala @@ -357,7 +357,7 @@ object Tuples { } } - def tail(self: NonEmptyTuple): Tuple = (self: Any) match { + def tail(self: Tuple): Tuple = (self: Any) match { case xxl: TupleXXL => xxlTail(xxl) case _ => specialCaseTail(self) } @@ -565,16 +565,16 @@ object Tuples { } } - def init(self: NonEmptyTuple): Tuple = (self: Any) match { + def init(self: Tuple): Tuple = (self: Any) match { case xxl: TupleXXL => xxlInit(xxl) case _ => specialCaseInit(self) } - def last(self: NonEmptyTuple): Any = (self: Any) match { + def last(self: Tuple): Any = (self: Any) match { case self: Product => self.productElement(self.productArity - 1) } - def apply(self: NonEmptyTuple, n: Int): Any = + def apply(self: Tuple, n: Int): Any = self.productElement(n) // Benchmarks showed that this is faster than doing (it1 zip it2).copyToArray(...) From 13da9bc897e5284377ccf0dbf1cde7fe86b83524 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 15:23:36 +0200 Subject: [PATCH 11/16] Reorder operations to be same between term and type level --- library/src/scala/Tuple.scala | 202 +++++++++++++++++----------------- 1 file changed, 101 insertions(+), 101 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 1c009e4d65e6..7f9e220b2bf2 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -21,18 +21,22 @@ sealed trait Tuple extends Product: inline def toIArray: IArray[Object] = runtime.Tuples.toIArray(this) - /** Return a copy of `this` tuple with an element appended */ - inline def :* [This >: this.type <: Tuple, L](x: L): This :* L = - runtime.Tuples.append(x, this).asInstanceOf[Append[This, L]] - /** Return a new tuple by prepending the element to `this` tuple. * This operation is O(this.size) */ inline def *: [H, This >: this.type <: Tuple](x: H): H *: This = runtime.Tuples.cons(x, this).asInstanceOf[H *: This] + /** Return a copy of `this` tuple with an element appended */ + inline def :* [This >: this.type <: Tuple, L](x: L): This :* L = + runtime.Tuples.append(x, this).asInstanceOf[Append[This, L]] + + /** Return the size (or arity) of the tuple */ + inline def size[This >: this.type <: Tuple]: Size[This] = + runtime.Tuples.size(this).asInstanceOf[Size[This]] + /** Get the i-th element of this tuple. - * Equivalent to productElement but with a precise return type. + * Equivalent to productElement but with a precise return type. */ inline def apply[This >: this.type <: Tuple](n: Int): Elem[This, n.type] = runtime.Tuples.apply(this, n).asInstanceOf[Elem[This, n.type]] @@ -41,19 +45,38 @@ sealed trait Tuple extends Product: inline def head[This >: this.type <: Tuple]: Head[This] = runtime.Tuples.apply(this, 0).asInstanceOf[Head[This]] - /** Get the initial part of the tuple without its last element */ - inline def init[This >: this.type <: Tuple]: Init[This] = - runtime.Tuples.init(this).asInstanceOf[Init[This]] + /** Get the tail of this tuple. + * This operation is O(this.size) + */ + inline def tail[This >: this.type <: Tuple]: Tail[This] = + runtime.Tuples.tail(this).asInstanceOf[Tail[This]] /** Get the last of this tuple */ inline def last[This >: this.type <: Tuple]: Last[This] = runtime.Tuples.last(this).asInstanceOf[Last[This]] - /** Get the tail of this tuple. - * This operation is O(this.size) + /** Get the initial part of the tuple without its last element */ + inline def init[This >: this.type <: Tuple]: Init[This] = + runtime.Tuples.init(this).asInstanceOf[Init[This]] + + /** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting + * of its first n elements. */ - inline def tail[This >: this.type <: Tuple]: Tail[This] = - runtime.Tuples.tail(this).asInstanceOf[Tail[This]] + inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] = + runtime.Tuples.take(this, n).asInstanceOf[Take[This, n.type]] + + /** Given a tuple `(a1, ..., am)`, returns the tuple `(an+1, ..., am)` consisting + * all its elements except the first n ones. + */ + inline def drop[This >: this.type <: Tuple](n: Int): Drop[This, n.type] = + runtime.Tuples.drop(this, n).asInstanceOf[Drop[This, n.type]] + + /** Given a tuple `(a1, ..., am)`, returns a pair of the tuple `(a1, ..., an)` + * consisting of the first n elements, and the tuple `(an+1, ..., am)` consisting + * of the remaining elements. + */ + inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] = + runtime.Tuples.splitAt(this, n).asInstanceOf[Split[This, n.type]] /** Return a new tuple by concatenating `this` tuple with `that` tuple. * This operation is O(this.size + that.size) @@ -63,10 +86,6 @@ sealed trait Tuple extends Product: inline def ++ [This >: this.type <: Tuple](that: Tuple): This ++ that.type = runtime.Tuples.concat(this, that).asInstanceOf[Concat[This, that.type]] - /** Return the size (or arity) of the tuple */ - inline def size[This >: this.type <: Tuple]: Size[This] = - runtime.Tuples.size(this).asInstanceOf[Size[This]] - /** Given two tuples, `(a1, ..., an)` and `(a1, ..., an)`, returns a tuple * `((a1, b1), ..., (an, bn))`. If the two tuples have different sizes, * the extra elements of the larger tuple will be disregarded. @@ -85,24 +104,11 @@ sealed trait Tuple extends Product: inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] = runtime.Tuples.map(this, f).asInstanceOf[Map[this.type, F]] - /** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting - * of its first n elements. - */ - inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] = - runtime.Tuples.take(this, n).asInstanceOf[Take[This, n.type]] - - /** Given a tuple `(a1, ..., am)`, returns the tuple `(an+1, ..., am)` consisting - * all its elements except the first n ones. - */ - inline def drop[This >: this.type <: Tuple](n: Int): Drop[This, n.type] = - runtime.Tuples.drop(this, n).asInstanceOf[Drop[This, n.type]] - - /** Given a tuple `(a1, ..., am)`, returns a pair of the tuple `(a1, ..., an)` - * consisting of the first n elements, and the tuple `(an+1, ..., am)` consisting - * of the remaining elements. - */ - inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] = - runtime.Tuples.splitAt(this, n).asInstanceOf[Split[This, n.type]] + /** A tuple consisting of all elements of this tuple that satisfy the predicate `p`. */ + inline def filter[This >: this.type <: Tuple, P[_ <: Union[This]] <: Boolean] + (p: (x: Union[This]) => P[x.type]): Filter[This, P] = + val arr = this.toArray.filter(x => p(x.asInstanceOf[Union[This]])) + Tuple.fromArray(arr).asInstanceOf[Filter[This, P]] /** Given a tuple `(a1, ..., am)`, returns the reversed tuple `(am, ..., a1)` * consisting all its elements. @@ -114,14 +120,16 @@ sealed trait Tuple extends Product: inline def reverseOnto[This >: this.type <: Tuple, Acc <: Tuple](acc: Acc): ReverseOnto[This, Acc] = (this.reverse ++ acc).asInstanceOf[ReverseOnto[This, Acc]] - /** A tuple consisting of all elements of this tuple that satisfy the predicate `p`. */ - inline def filter[This >: this.type <: Tuple, P[_ <: Union[This]] <: Boolean] - (p: (x: Union[This]) => P[x.type]): Filter[This, P] = - val arr = this.toArray.filter(x => p(x.asInstanceOf[Union[This]])) - Tuple.fromArray(arr).asInstanceOf[Filter[This, P]] - object Tuple: + /** Type of a tuple with an element appended */ + type Append[X <: Tuple, +Y] <: NonEmptyTuple = X match + case EmptyTuple => Y *: EmptyTuple + case x *: xs => x *: Append[xs, Y] + + /** An infix shorthand for `Append[X, Y]` */ + infix type :*[X <: Tuple, +Y] = Append[X, Y] + /** The size of a tuple, represented as a literal constant subtype of Int */ type Size[X <: Tuple] <: Int = X match case EmptyTuple => 0 @@ -142,15 +150,15 @@ object Tuple: type Head[X <: Tuple] = X match case x *: _ => x + /** The type of a tuple consisting of all elements of tuple X except the first one */ + type Tail[X <: Tuple] <: Tuple = X match + case _ *: xs => xs + /** The type of the last element of a tuple */ type Last[X <: Tuple] = X match case x *: EmptyTuple => x case _ *: xs => Last[xs] - /** The type of a tuple consisting of all elements of tuple X except the first one */ - type Tail[X <: Tuple] <: Tuple = X match - case _ *: xs => xs - /** The type of the initial part of a tuple without its last element */ type Init[X <: Tuple] <: Tuple = X match case _ *: EmptyTuple => EmptyTuple @@ -177,14 +185,6 @@ object Tuple: /** The pair type `(Take(X, N), Drop[X, N]). */ type Split[X <: Tuple, N <: Int] = (Take[X, N], Drop[X, N]) - /** Type of a tuple with an element appended */ - type Append[X <: Tuple, +Y] <: NonEmptyTuple = X match - case EmptyTuple => Y *: EmptyTuple - case x *: xs => x *: Append[xs, Y] - - /** An infix shorthand for `Append[X, Y]` */ - infix type :*[X <: Tuple, +Y] = Append[X, Y] - /** Type of the concatenation of two tuples `X` and `Y` */ // Can be covariant in `Y` since it never appears as a match type scrutinee. type Concat[X <: Tuple, +Y <: Tuple] <: Tuple = X match @@ -194,18 +194,25 @@ object Tuple: /** An infix shorthand for `Concat[X, Y]` */ infix type ++[X <: Tuple, +Y <: Tuple] = Concat[X, Y] - /** The index of `Y` in tuple `X` as a literal constant Int, - * or `Size[X]` if `Y` is disjoint from all element types in `X`. + /** The type of the tuple consisting of all element values of + * tuple `X` zipped with corresponding elements of tuple `Y`. + * If the two tuples have different sizes, + * the extra elements of the larger tuple will be disregarded. + * For example, if + * ``` + * X = (S1, ..., Si) + * Y = (T1, ..., Tj) where j >= i + * ``` + * then + * ``` + * Zip[X, Y] = ((S1, T1), ..., (Si, Ti)) + * ``` + * @syntax markdown */ - type IndexOf[X <: Tuple, Y] <: Int = X match - case Y *: _ => 0 - case _ *: xs => S[IndexOf[xs, Y]] - case EmptyTuple => 0 - - /** Fold a tuple `(T1, ..., Tn)` into `F[T1, F[... F[Tn, Z]...]]]` */ - type Fold[X <: Tuple, Z, F[_, _]] = X match - case EmptyTuple => Z - case x *: xs => F[x, Fold[xs, Z, F]] + type Zip[X <: Tuple, Y <: Tuple] <: Tuple = (X, Y) match + case (x *: xs, y *: ys) => (x, y) *: Zip[xs, ys] + case (EmptyTuple, _) => EmptyTuple + case (_, EmptyTuple) => EmptyTuple /** The type of tuple `X` mapped with the type-level function `F`. * If `X = (T1, ..., Ti)` then `Map[X, F] = `(F[T1], ..., F[Ti])`. @@ -214,6 +221,18 @@ object Tuple: case EmptyTuple => EmptyTuple case x *: xs => F[x] *: Map[xs, F] + /** Converts a tuple `(F[T1], ..., F[Tn])` to `(T1, ... Tn)` */ + type InverseMap[X <: Tuple, F[_]] <: Tuple = X match + case F[x] *: xs => x *: InverseMap[xs, F] + case EmptyTuple => EmptyTuple + + /** Implicit evidence. IsMappedBy[F][X] is present in the implicit scope iff + * X is a tuple for which each element's type is constructed via `F`. E.g. + * (F[A1], ..., F[An]), but not `(F[A1], B2, ..., F[An])` where B2 does not + * have the shape of `F[A]`. + */ + type IsMappedBy[F[_]] = [X <: Tuple] =>> X =:= Map[InverseMap[X, F], F] + /** The type of tuple `X` flat-mapped with the type-level function `F`. * If `X = (T1, ..., Ti)` then `FlatMap[X, F] = `F[T1] ++ ... ++ F[Ti]` */ @@ -241,44 +260,6 @@ object Tuple: case true => x *: Filter[xs, P] case false => Filter[xs, P] - /** A tuple consisting of those indices `N` of tuple `X` where the predicate `P` - * is true for `Elem[X, N]`. Indices are type level values <: Int. - */ - type IndicesWhere[X <: Tuple, P[_ <: Union[X]] <: Boolean] = - helpers.IndicesWhereHelper[X, P, 0] - - /** The type of the tuple consisting of all element values of - * tuple `X` zipped with corresponding elements of tuple `Y`. - * If the two tuples have different sizes, - * the extra elements of the larger tuple will be disregarded. - * For example, if - * ``` - * X = (S1, ..., Si) - * Y = (T1, ..., Tj) where j >= i - * ``` - * then - * ``` - * Zip[X, Y] = ((S1, T1), ..., (Si, Ti)) - * ``` - * @syntax markdown - */ - type Zip[X <: Tuple, Y <: Tuple] <: Tuple = (X, Y) match - case (x *: xs, y *: ys) => (x, y) *: Zip[xs, ys] - case (EmptyTuple, _) => EmptyTuple - case (_, EmptyTuple) => EmptyTuple - - /** Converts a tuple `(F[T1], ..., F[Tn])` to `(T1, ... Tn)` */ - type InverseMap[X <: Tuple, F[_]] <: Tuple = X match - case F[x] *: xs => x *: InverseMap[xs, F] - case EmptyTuple => EmptyTuple - - /** Implicit evidence. IsMappedBy[F][X] is present in the implicit scope iff - * X is a tuple for which each element's type is constructed via `F`. E.g. - * (F[A1], ..., F[An]), but not `(F[A1], B2, ..., F[An])` where B2 does not - * have the shape of `F[A]`. - */ - type IsMappedBy[F[_]] = [X <: Tuple] =>> X =:= Map[InverseMap[X, F], F] - /** A tuple with the elements of tuple `X` in reversed order */ type Reverse[X <: Tuple] = ReverseOnto[X, EmptyTuple] @@ -287,11 +268,30 @@ object Tuple: case x *: xs => ReverseOnto[xs, x *: Acc] case EmptyTuple => Acc + /** Fold a tuple `(T1, ..., Tn)` into `F[T1, F[... F[Tn, Z]...]]]` */ + type Fold[X <: Tuple, Z, F[_, _]] = X match + case EmptyTuple => Z + case x *: xs => F[x, Fold[xs, Z, F]] + /** Given a tuple `(T1, ..., Tn)`, returns a union of its * member types: `T1 | ... | Tn`. Returns `Nothing` if the tuple is empty. */ type Union[T <: Tuple] = Fold[T, Nothing, [x, y] =>> x | y] + /** The index of `Y` in tuple `X` as a literal constant Int, + * or `Size[X]` if `Y` is disjoint from all element types in `X`. + */ + type IndexOf[X <: Tuple, Y] <: Int = X match + case Y *: _ => 0 + case _ *: xs => S[IndexOf[xs, Y]] + case EmptyTuple => 0 + + /** A tuple consisting of those indices `N` of tuple `X` where the predicate `P` + * is true for `Elem[X, N]`. Indices are type level values <: Int. + */ + type IndicesWhere[X <: Tuple, P[_ <: Union[X]] <: Boolean] = + helpers.IndicesWhereHelper[X, P, 0] + /** A type level Boolean indicating whether the tuple `X` has an element * that matches `Y`. * @pre The elements of `X` are assumed to be singleton types From 93071555d0bba8a96ea1c6902b44e7ba812ae709 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 29 Apr 2024 15:34:25 +0200 Subject: [PATCH 12/16] Drop braces remaining at term level --- library/src/scala/Tuple.scala | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 7f9e220b2bf2..a9401ea1be7e 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -246,10 +246,9 @@ object Tuple: * constant `true`. A predicate `P[X]` is a type that can be either `true` * or `false`. For example: * ```scala - * type IsString[x] <: Boolean = x match { + * type IsString[x] <: Boolean = x match * case String => true * case _ => false - * } * summon[Tuple.Filter[(1, "foo", 2, "bar"), IsString] =:= ("foo", "bar")] * ``` * @syntax markdown @@ -325,26 +324,21 @@ object Tuple: fromArray(xs, xs.length) /** Convert the first `n` elements of an array into a tuple of unknown arity and types */ - def fromArray[T](xs: Array[T], n: Int): Tuple = { - val xs2 = xs match { + def fromArray[T](xs: Array[T], n: Int): Tuple = + val xs2 = xs match case xs: Array[Object] => xs case xs => xs.map(_.asInstanceOf[Object]) - } runtime.Tuples.fromArray(xs2, n) - } /** Convert an immutable array into a tuple of unknown arity and types */ def fromIArray[T](xs: IArray[T]): Tuple = fromIArray(xs, xs.length) /** Convert the first `n` elements of an immutable array into a tuple of unknown arity and types */ - def fromIArray[T](xs: IArray[T], n: Int): Tuple = { - val xs2: IArray[Object] = xs match { + def fromIArray[T](xs: IArray[T], n: Int): Tuple = + val xs2: IArray[Object] = xs match case xs: IArray[Object] @unchecked => xs - case _ => - xs.map(_.asInstanceOf[Object]) - } + case _ => xs.map(_.asInstanceOf[Object]) runtime.Tuples.fromIArray(xs2, n) - } /** Convert a Product into a tuple of unknown arity and types */ def fromProduct(product: Product): Tuple = @@ -394,9 +388,8 @@ end Tuple type EmptyTuple = EmptyTuple.type /** A tuple of 0 elements. */ -case object EmptyTuple extends Tuple { +case object EmptyTuple extends Tuple: override def toString(): String = "()" -} /** Tuple of arbitrary non-zero arity */ sealed trait NonEmptyTuple extends Tuple @@ -404,6 +397,5 @@ sealed trait NonEmptyTuple extends Tuple @showAsInfix sealed abstract class *:[+H, +T <: Tuple] extends NonEmptyTuple -object *: { +object `*:`: def unapply[H, T <: Tuple](x: H *: T): (H, T) = (x.head, x.tail) -} From 5fcebd82a9bdcbd75b5819b27b78c870f7d03edf Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 1 May 2024 14:09:50 +0200 Subject: [PATCH 13/16] Fix `def indexOf` to return the size instead of -1 --- library/src/scala/Tuple.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index a9401ea1be7e..92460c078b54 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -350,13 +350,14 @@ object Tuple: // Also note it would be unsound to use a type parameter for `y` in the type level // operations, since they are rightfully not covariant in their second parameter. - /** The index (starting at 0) of the first occurrence of y.type in the type `X` of `x` - * or Size[X] if no such element exists. + /** The index (starting at 0) of the first occurrence of `y` in `x` + * or its size if no such element exists. */ inline def indexOf(y: Any): IndexOf[X, y.type] = - x.productIterator.indexOf(y).asInstanceOf[IndexOf[X, y.type]] + val i = x.productIterator.indexOf(y) + (if i >= 0 then i else x.size).asInstanceOf[IndexOf[X, y.type]] - /** A boolean indicating whether there is an element `y.type` in the type `X` of `x` */ + /** A boolean indicating whether `x` contains the element `y` */ inline def contains(y: Any): Contains[X, y.type] = x.productIterator.contains(y).asInstanceOf[Contains[X, y.type]] From 2e9bb8d70679473a78c43975669f66e4c6aef86e Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 1 May 2024 18:23:16 +0200 Subject: [PATCH 14/16] Revert doing tuple runtime operations based on a term level predicates --- library/src/scala/Tuple.scala | 32 +++++++++++-------------- tests/pos/named-tuples-strawman-2.scala | 5 +--- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 92460c078b54..364124481db7 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -104,10 +104,15 @@ sealed trait Tuple extends Product: inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] = runtime.Tuples.map(this, f).asInstanceOf[Map[this.type, F]] - /** A tuple consisting of all elements of this tuple that satisfy the predicate `p`. */ - inline def filter[This >: this.type <: Tuple, P[_ <: Union[This]] <: Boolean] - (p: (x: Union[This]) => P[x.type]): Filter[This, P] = - val arr = this.toArray.filter(x => p(x.asInstanceOf[Union[This]])) + /** A tuple consisting of all elements of this tuple that have types + * for which the given type level predicate `P` reduces to the literal + * constant `true`. + */ + inline def filter[This >: this.type <: Tuple, P[_ <: Union[This]] <: Boolean]: Filter[This, P] = + val toInclude = constValueTuple[IndicesWhere[This, P]].toArray + val arr = new Array[Object](toInclude.length) + for i <- toInclude.indices do + arr(i) = this.productElement(toInclude(i).asInstanceOf[Int]).asInstanceOf[Object] Tuple.fromArray(arr).asInstanceOf[Filter[This, P]] /** Given a tuple `(a1, ..., am)`, returns the reversed tuple `(am, ..., a1)` @@ -345,23 +350,14 @@ object Tuple: runtime.Tuples.fromProduct(product) extension [X <: Tuple](inline x: X) - // Note the two methods are not equivalent to using `constValue`, - // since they also allow cases unknown at compiletime. - // Also note it would be unsound to use a type parameter for `y` in the type level - // operations, since they are rightfully not covariant in their second parameter. - /** The index (starting at 0) of the first occurrence of `y` in `x` - * or its size if no such element exists. + /** The index (starting at 0) of the first occurrence of `y.type` in the type `X` of `x` + * or `Size[X]` if no such element exists. */ - inline def indexOf(y: Any): IndexOf[X, y.type] = - val i = x.productIterator.indexOf(y) - (if i >= 0 then i else x.size).asInstanceOf[IndexOf[X, y.type]] - - /** A boolean indicating whether `x` contains the element `y` */ - inline def contains(y: Any): Contains[X, y.type] = - x.productIterator.contains(y).asInstanceOf[Contains[X, y.type]] + inline def indexOf(y: Any): IndexOf[X, y.type] = constValue[IndexOf[X, y.type]] - // TODO indexOfType & containsType ? + /** A boolean indicating whether there is an element `y.type` in the type `X` of `x` */ + inline def contains(y: Any): Contains[X, y.type] = constValue[Contains[X, y.type]] end extension diff --git a/tests/pos/named-tuples-strawman-2.scala b/tests/pos/named-tuples-strawman-2.scala index 7cd763bb7b00..4b32dd83f2eb 100644 --- a/tests/pos/named-tuples-strawman-2.scala +++ b/tests/pos/named-tuples-strawman-2.scala @@ -60,10 +60,7 @@ object TupleOps: case EmptyTuple => X inline def concatDistinct[X <: Tuple, Y <: Tuple](xs: X, ys: Y): ConcatDistinct[X, Y] = - // Note the type parameter is needed due to the invariance of compiletime.ops.boolean.! - extension [B <: Boolean](self: B) def negated: ![B] = (!self).asInstanceOf - val ysDistinct = ys.filter[Y, [y] =>> ![Contains[X, y]]](xs.contains(_).negated) - (xs ++ ysDistinct).asInstanceOf[ConcatDistinct[X, Y]] + (xs ++ ys.filter[Y, [Elem] =>> ![Contains[X, Elem]]]).asInstanceOf[ConcatDistinct[X, Y]] object NamedTupleDecomposition: import NamedTupleOps.* From 1aebb7243464eb41a3784def6fbf5efc1c1be891 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 1 May 2024 18:51:40 +0200 Subject: [PATCH 15/16] Make named-tuples-strawman-2.scala a run test The tuple term level definitions were not being tested before --- compiler/test/dotc/pos-test-pickling.blacklist | 1 - compiler/test/dotc/run-test-pickling.blacklist | 1 + tests/{pos => run}/named-tuples-strawman-2.scala | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename tests/{pos => run}/named-tuples-strawman-2.scala (100%) diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 94e510e04396..a856a5b84d92 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -67,7 +67,6 @@ mt-redux-norm.perspective.scala i18211.scala 10867.scala named-tuples1.scala -named-tuples-strawman-2.scala # Opaque type i5720.scala diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index 954a64db1b66..dacbc63bb520 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -45,4 +45,5 @@ t6138-2 i12656.scala trait-static-forwarder i17255 +named-tuples-strawman-2.scala diff --git a/tests/pos/named-tuples-strawman-2.scala b/tests/run/named-tuples-strawman-2.scala similarity index 100% rename from tests/pos/named-tuples-strawman-2.scala rename to tests/run/named-tuples-strawman-2.scala From 9b7fa6adf65acfb7dd1cf3897859e5fcd810b35e Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Thu, 2 May 2024 16:59:05 +0200 Subject: [PATCH 16/16] import language.experimental.namedTuples in pos/fieldsOf.scala --- tests/pos/fieldsOf.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/pos/fieldsOf.scala b/tests/pos/fieldsOf.scala index 08f20a1f7e8e..2594dae2cbf7 100644 --- a/tests/pos/fieldsOf.scala +++ b/tests/pos/fieldsOf.scala @@ -1,3 +1,5 @@ +import language.experimental.namedTuples + case class Person(name: String, age: Int) type PF = NamedTuple.From[Person]