From f1f819ba22a61463fc5dd8cd3a0103a3cc6a78b5 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 5 Oct 2023 18:14:29 +0100 Subject: [PATCH 1/7] Check MatchType tree case bodies --- .../src/dotty/tools/dotc/core/TypeEval.scala | 1 + .../tools/dotc/transform/PostTyper.scala | 10 ++-- library/src/scala/Tuple.scala | 4 +- tests/neg/i15272.scala | 4 +- tests/pos/10867.scala | 3 +- tests/pos/13633.scala | 4 +- tests/pos/9239.scala | 6 +-- tests/pos/9890.scala | 2 +- tests/pos/Tuple.FlatMap.scala | 3 ++ tests/pos/Tuple.Map.scala | 53 +++++++++++++++++++ tests/pos/i15926.extract.scala | 8 ++- tests/pos/i15926.min.scala | 8 ++- tests/pos/i15926.scala | 14 +++-- tests/pos/i16596.orig.scala | 4 +- tests/pos/i16596.scala | 2 +- tests/pos/i16706.scala | 2 +- tests/pos/singleton-ops-dispatch.scala | 8 +-- tests/run/i13332shapeless.scala | 4 +- 18 files changed, 107 insertions(+), 33 deletions(-) create mode 100644 tests/pos/Tuple.FlatMap.scala create mode 100644 tests/pos/Tuple.Map.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeEval.scala b/compiler/src/dotty/tools/dotc/core/TypeEval.scala index b5684b07f181..b317749eed66 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeEval.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeEval.scala @@ -21,6 +21,7 @@ object TypeEval: case tp: TypeProxy => val tp1 = tp.superType if tp1.isStable then tp1.fixForEvaluation else tp + case AndType(tp1: ConstantType, tp2) if tp1.frozen_<:<(tp2) => tp1 case tp => tp def constValue(tp: Type): Option[Any] = tp.fixForEvaluation match diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 4ce41ca21bfe..8504f7cc74fb 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -488,12 +488,16 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => // case x: (_: Tree[?]) case m @ MatchTypeTree(bounds, selector, cases) => // Analog to the case above for match types - def transformIgnoringBoundsCheck(x: CaseDef): CaseDef = - withMode(Mode.Pattern)(super.transform(x)).asInstanceOf[CaseDef] + def transformCase(x: CaseDef): CaseDef = + cpy.CaseDef(tree)( + withMode(Mode.Pattern)(transform(x.pat)), + transform(x.guard), + transform(x.body), + ) cpy.MatchTypeTree(tree)( super.transform(bounds), super.transform(selector), - cases.mapConserve(transformIgnoringBoundsCheck) + cases.mapConserve(transformCase) ) case Block(_, Closure(_, _, tpt)) if ExpandSAMs.needsWrapperClass(tpt.tpe) => superAcc.withInvalidCurrentClass(super.transform(tree)) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 3738bd05a19b..7ce58b51f7dc 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -147,13 +147,13 @@ object Tuple { /** Converts a tuple `(T1, ..., Tn)` to `(F[T1], ..., F[Tn])` */ type Map[Tup <: Tuple, F[_ <: Union[Tup]]] <: Tuple = Tup match { case EmptyTuple => EmptyTuple - case h *: t => F[h] *: Map[t, F] + case h *: t => F[h & Union[Tup]] *: Map[t, [x <: Union[t]] =>> F[x & Union[Tup]]] } /** Converts a tuple `(T1, ..., Tn)` to a flattened `(..F[T1], ..., ..F[Tn])` */ type FlatMap[Tup <: Tuple, F[_ <: Union[Tup]] <: Tuple] <: Tuple = Tup match { case EmptyTuple => EmptyTuple - case h *: t => Concat[F[h], FlatMap[t, F]] + case h *: t => Concat[F[h & Union[Tup]], FlatMap[t, [x <: Union[t]] =>> F[x & Union[Tup]]]] } /** Filters out those members of the tuple for which the predicate `P` returns `false`. diff --git a/tests/neg/i15272.scala b/tests/neg/i15272.scala index 1d380a3b5333..4e74d3c3108f 100644 --- a/tests/neg/i15272.scala +++ b/tests/neg/i15272.scala @@ -3,9 +3,9 @@ case class Head[+NT, +From <: NT, +To <: NT] (from: From, to: To ) extends EdgeN[NT] case class Cons[+NT, +From <: NT, +ToE <: EdgeN[NT]](from: From, to: ToE) extends EdgeN[NT] final type InNodesTupleOf[NT, E <: EdgeN[NT]] <: Tuple = E match - case Cons[nt,from,toE] => from *: InNodesTupleOf[nt,toE] + case Cons[nt,from,toE] => from *: InNodesTupleOf[nt,toE] // error case Head[nt,from ,to] => from *: EmptyTuple def inNodesTuple[NT,E <: EdgeN[NT]](edge: E): InNodesTupleOf[NT,E] = edge match case e: Cons[nt,from,toE] => e.from *: inNodesTuple[nt,toE](e.to) // error case e: Head[nt,from,to] => e.from *: EmptyTuple - end EdgeN \ No newline at end of file + end EdgeN diff --git a/tests/pos/10867.scala b/tests/pos/10867.scala index e08b0c60a491..c6ba65d52543 100644 --- a/tests/pos/10867.scala +++ b/tests/pos/10867.scala @@ -1,8 +1,9 @@ object Test { + // e.g inserts[z, (a, b)] =:= ((z, a, b), (a, z, b), (a, b, z)) type inserts[a, as <: Tuple] <: Tuple = as match case EmptyTuple => (a *: EmptyTuple) *: EmptyTuple - case y *: ys => (a *: y *: ys) *: Tuple.Map[inserts[a, ys], [t <: Tuple] =>> y *: t] + case y *: ys => (a *: y *: ys) *: Tuple.Map[inserts[a, ys], [t] =>> y *: (t & Tuple)] type inserts2[a] = [as <: Tuple] =>> inserts[a, as] diff --git a/tests/pos/13633.scala b/tests/pos/13633.scala index ca0f7e68e81e..6c257c3f609a 100644 --- a/tests/pos/13633.scala +++ b/tests/pos/13633.scala @@ -38,7 +38,7 @@ object Sums extends App: case false => A case true => Inc[A] - type PlusLoop[A <: Tuple, B <: Tuple, O] <: Tuple = (A, B) match + type PlusLoop[A <: Tuple, B <: Tuple, O <: Boolean] <: Tuple = (A, B) match case (EmptyTuple, EmptyTuple) => O match case true => (true *: EmptyTuple) @@ -47,4 +47,4 @@ object Sums extends App: case (A, EmptyTuple) => IncT[A, O] case (a *: as, b *: bs) => PlusTri[a, b, O] match - case (x, y) => y *: PlusLoop[as, bs, x] + case (x, y) => y *: PlusLoop[as, bs, x & Boolean] diff --git a/tests/pos/9239.scala b/tests/pos/9239.scala index c9c0ec268218..911aceb7f052 100644 --- a/tests/pos/9239.scala +++ b/tests/pos/9239.scala @@ -10,17 +10,17 @@ object ABug: type Zero = B0 :: Nil type One = B1 :: Nil - type --[B <: Bin] = + type --[B <: Bin] <: Bin = B match case B1 :: d => B0 :: d case B0 :: B1 :: Nil => B1 :: Nil case B0 :: d => B1 :: --[d] - type ×[N <: Bin, M <: Bin] = + type ×[N <: Bin, M <: Bin] <: Bin = (N, M) match case (Zero, ?) => Zero - type ![N <: Bin] = + type ![N <: Bin] <: Bin = N match case Zero => One case One => One diff --git a/tests/pos/9890.scala b/tests/pos/9890.scala index 96d6f1db33b7..4c38a867320f 100644 --- a/tests/pos/9890.scala +++ b/tests/pos/9890.scala @@ -10,7 +10,7 @@ object Test { type TupleMap[Tup <: Tuple, Bound, F[_ <: Bound]] <: Tuple = Tup match { case EmptyTuple => EmptyTuple - case h *: t => F[h] *: TupleMap[t, Bound, F] + case h *: t => F[h & Bound] *: TupleMap[t, Bound, F] } type TupleDedup[Tup <: Tuple, Mask] <: Tuple = Tup match { case EmptyTuple => EmptyTuple diff --git a/tests/pos/Tuple.FlatMap.scala b/tests/pos/Tuple.FlatMap.scala new file mode 100644 index 000000000000..b833bc9bbf24 --- /dev/null +++ b/tests/pos/Tuple.FlatMap.scala @@ -0,0 +1,3 @@ +type FlatMap[Tup <: Tuple, F[_] <: Tuple] <: Tuple = Tup match + case EmptyTuple => EmptyTuple + case h *: t => Tuple.Concat[F[h], FlatMap[t, F]] diff --git a/tests/pos/Tuple.Map.scala b/tests/pos/Tuple.Map.scala new file mode 100644 index 000000000000..2dc5e0c0fa77 --- /dev/null +++ b/tests/pos/Tuple.Map.scala @@ -0,0 +1,53 @@ +type Fold0[Tup <: Tuple, Z, F[_, _]] = Tup match + case EmptyTuple => Z + case h *: t => F[h, Fold0[t, Z, F]] + + +type Union0[T <: Tuple] = Fold0[T, Nothing, [x, y] =>> x | y] + +type Union1[Tup <: Tuple] = Tup match + case EmptyTuple => Nothing + case h *: t => h | Union1[t] + + +import Tuple.Map as Map0 + +type Map1[Tup <: Tuple, F[_ <: Union0[Tup]]] <: Tuple = Tup match + case EmptyTuple => EmptyTuple + case h *: t => F[h & Union0[Tup]] *: Map1[t, [x <: Union0[t]] =>> F[x & Union0[Tup]]] + +type Map2[Tup <: Tuple, F[_]] <: Tuple = Tup match + case EmptyTuple => EmptyTuple + case h *: t => F[h] *: Map2[t, F] + +//type Map3[Tup <: Tuple, F[_ <: Union1[Tup]]] <: Tuple = Tup match +// case EmptyTuple => EmptyTuple +// case h *: t => F[h] *: Map3[t, F] + +type Map4 [Tup <: Tuple, F[_ <: Union1[Tup]]] = Map4UB[Tup, F, Union1[Tup]] +type Map4UB[Tup <: Tuple, F[_ <: UB], UB] <: Tuple = Tup match + case EmptyTuple => EmptyTuple + case h *: t => F[h & UB] *: Map4UB[t, F, UB] + +type Map5 [Tup <: Tuple, F[_ <: Union1[Tup]]] = Map5UB[Tup, Union1[Tup], F, Tup] +type Map5UB[Tup <: Tuple, UB, F[_ <: UB], Tup1 <: Tuple] <: Tuple = Tup1 match + case EmptyTuple => EmptyTuple + case h *: t => F[h & UB] *: Map5UB[Tup, UB, F, t] + +trait Dt[T] +case class IBox[A <: Int](v: A) + +class Test[H, T <: Tuple]: +//def t0 = { val x: Dt[H] *: Map0[T, Dt] = ???; val y: Map0[H *: T, Dt] = x } + def t1 = { val x: Dt[H] *: Map1[T, Dt] = ???; val y: Map1[H *: T, Dt] = x } + def t2 = { val x: Dt[H] *: Map2[T, Dt] = ???; val y: Map2[H *: T, Dt] = x } +//def t3 = { val x: Dt[H] *: Map3[T, Dt] = ???; val y: Map3[H *: T, Dt] = x } +//def t4 = { val x: Dt[H] *: Map4[T, Dt] = ???; val y: Map4[H *: T, Dt] = x } +//def t5 = { val x: Dt[H] *: Map5[T, Dt] = ???; val y: Map5[H *: T, Dt] = x } + + def i0 = { val x: Map0[(1, 2), IBox] = (IBox(1), IBox(2)) } + def i1 = { val x: Map1[(1, 2), IBox] = (IBox(1), IBox(2)) } +//def i2 = { val x: Map2[(1, 2), IBox] = (IBox(1), IBox(2)) } +//def i3 = { val x: Map3[(1, 2), IBox] = (IBox(1), IBox(2)) } + def i4 = { val x: Map4[(1, 2), IBox] = (IBox(1), IBox(2)) } + def i5 = { val x: Map5[(1, 2), IBox] = (IBox(1), IBox(2)) } diff --git a/tests/pos/i15926.extract.scala b/tests/pos/i15926.extract.scala index 45177bd3c946..db13d71ed39f 100644 --- a/tests/pos/i15926.extract.scala +++ b/tests/pos/i15926.extract.scala @@ -7,7 +7,7 @@ final case class Succ[+N <: Nat]() extends Nat final case class Neg[+N <: Succ[Nat]]() -type Sum[X, Y] = Y match +type Sum[X <: Nat, Y] = Y match case Zero => X case Succ[y] => Sum[Succ[X], y] @@ -15,7 +15,11 @@ type IntSum[A, B] = B match case Neg[b] => IntSumNeg[A, b] type IntSumNeg[A, B] = A match - case Neg[a] => Neg[Sum[a, B]] + case Neg[a] => Negate[Sum[a, B]] + +type Negate[A] = A match + case Zero => Zero + case Succ[x] => Neg[A & Succ[x]] type One = Succ[Zero] type Two = Succ[One] diff --git a/tests/pos/i15926.min.scala b/tests/pos/i15926.min.scala index 531467fd4a0f..8eb72e3a0dfd 100644 --- a/tests/pos/i15926.min.scala +++ b/tests/pos/i15926.min.scala @@ -7,13 +7,17 @@ final case class Succ[+N <: Nat]() extends Nat final case class Neg[+N <: Succ[Nat]]() -type Sum[X, Y] = Y match +type Sum[X <: Nat, Y] <: Nat = Y match case Zero => X case Succ[y] => Sum[Succ[X], y] type IntSum[A, B] = B match case Neg[b] => A match - case Neg[a] => Neg[Sum[a, b]] + case Neg[a] => Negate[Sum[a, b]] + +type Negate[A] = A match + case Zero => Zero + case Succ[x] => Neg[A & Succ[x]] type One = Succ[Zero] type Two = Succ[One] diff --git a/tests/pos/i15926.scala b/tests/pos/i15926.scala index df4fc2271cd2..82ad9bd983bb 100644 --- a/tests/pos/i15926.scala +++ b/tests/pos/i15926.scala @@ -15,14 +15,18 @@ type NatSum[X <: NatT, Y <: NatT] <: NatT = Y match type NatDif[X <: NatT, Y <: NatT] <: IntT = Y match case Zero => X case Succ[y] => X match - case Zero => Minus[Y] + case Zero => Minus[Y & Succ[y]] case Succ[x] => NatDif[x, y] type Sum[X <: IntT, Y <: IntT] <: IntT = Y match case Zero => X case Minus[y] => X match - case Minus[x] => Minus[NatSum[x, y]] - case _ => NatDif[X, y] + case Minus[x] => Negate[NatSum[x, y]] + case _ => NatDif[X & NatT, y] case _ => X match - case Minus[x] => NatDif[Y, x] - case _ => NatSum[X, Y] \ No newline at end of file + case Minus[x] => NatDif[Y & NatT, x] + case _ => NatSum[X & NatT, Y & NatT] + +type Negate[A] <: IntT = A match + case Zero => Zero + case Succ[x] => Minus[A & Succ[x]] diff --git a/tests/pos/i16596.orig.scala b/tests/pos/i16596.orig.scala index 0a562c6936c0..dd0b8da90074 100644 --- a/tests/pos/i16596.orig.scala +++ b/tests/pos/i16596.orig.scala @@ -1,6 +1,6 @@ import scala.compiletime.ops.int -type Count0[N,T] <: Tuple = (N,T) match +type Count0[N <: Int,T] <: Tuple = (N,T) match case (0,_) => EmptyTuple case (N,String) => String *: Count0[int.-[N, 1], String] case (N,Int) => Int *: Count0[int.-[N, 1], Int] @@ -8,7 +8,7 @@ type Count0[N,T] <: Tuple = (N,T) match case (N,Double) => Double *: Count0[int.-[N, 1], Double] -type Count1[N,T] <: Tuple = (N,T) match +type Count1[N <: Int,T] <: Tuple = (N,T) match case (0,T) => EmptyTuple case (N,String) => String *: Count1[int.-[N, 1], String] case (N,Int) => Int *: Count1[int.-[N, 1], Int] diff --git a/tests/pos/i16596.scala b/tests/pos/i16596.scala index 679417548df0..42112545ac29 100644 --- a/tests/pos/i16596.scala +++ b/tests/pos/i16596.scala @@ -1,6 +1,6 @@ import scala.compiletime.ops.int, int.- -type Count[N, T] <: Tuple = (N, T) match +type Count[N <: Int, T] <: Tuple = (N, T) match case (0, T) => EmptyTuple case (N, T) => T *: Count[N - 1, T] diff --git a/tests/pos/i16706.scala b/tests/pos/i16706.scala index e94ce37c0c86..cc84436bf60f 100644 --- a/tests/pos/i16706.scala +++ b/tests/pos/i16706.scala @@ -4,7 +4,7 @@ import scala.deriving.Mirror import scala.reflect.ClassTag type TupleUnionLub[T <: Tuple, Lub, Acc <: Lub] <: Lub = T match { - case (h & Lub) *: t => TupleUnionLub[t, Lub, Acc | h] + case (h & Lub) *: t => TupleUnionLub[t, Lub, (Acc | h) & Lub] case EmptyTuple => Acc } diff --git a/tests/pos/singleton-ops-dispatch.scala b/tests/pos/singleton-ops-dispatch.scala index 4a1c56515577..c0586c967975 100644 --- a/tests/pos/singleton-ops-dispatch.scala +++ b/tests/pos/singleton-ops-dispatch.scala @@ -2,10 +2,10 @@ import scala.compiletime.ops.* object Test { infix type +[X <: Int | String, Y <: Int | String] = (X, Y) match { - case (Int, Int) => int.+[X, Y] - case (String, String) => string.+[X, Y] - case (String, Int) => string.+[X, int.ToString[Y]] - case (Int, String) => string.+[int.ToString[X], Y] + case (Int, Int) => int.+[X & Int, Y & Int] + case (String, String) => string.+[X & String, Y & String] + case (String, Int) => string.+[X & String, int.ToString[Y & Int]] + case (Int, String) => string.+[int.ToString[X & Int], Y & String] } val t0: "a" + 1 = "a1" diff --git a/tests/run/i13332shapeless.scala b/tests/run/i13332shapeless.scala index 204980d8fe62..ff5263e19ead 100644 --- a/tests/run/i13332shapeless.scala +++ b/tests/run/i13332shapeless.scala @@ -333,7 +333,7 @@ package shapeless { def Inl[E](elem: E): E :+: Nothing = :+:(elem) def Inr[Elems <: CList](elems: Elems): Nothing :+: Elems = :+:(elems.e) - type CListRefl[T <: Tuple] = T match { + type CListRefl[T <: Tuple] <: CList = T match { case EmptyTuple => CNil case h *: tl => h :+: CListRefl[tl] } @@ -358,7 +358,7 @@ package shapeless { object Generic: - type Repr[T, M, Elems] = M match + type Repr[T, M, Elems <: Tuple] = M match case Mirror.Sum { type MirroredType = T } => CListRefl[Elems] case Mirror.Product { type MirroredType = T } => Elems From cb4cab43a195dc7fab0a6071fc534fa4e86c64e5 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 5 Oct 2023 18:14:40 +0100 Subject: [PATCH 2/7] rm --- tests/pos/i17257.min.scala | 16 ---------- tests/run-macros/i17257/a.scala | 53 --------------------------------- tests/run-macros/i17257/b.scala | 23 -------------- 3 files changed, 92 deletions(-) delete mode 100644 tests/pos/i17257.min.scala delete mode 100644 tests/run-macros/i17257/a.scala delete mode 100644 tests/run-macros/i17257/b.scala diff --git a/tests/pos/i17257.min.scala b/tests/pos/i17257.min.scala deleted file mode 100644 index f4e101cdbe21..000000000000 --- a/tests/pos/i17257.min.scala +++ /dev/null @@ -1,16 +0,0 @@ -//> using options -Yno-deep-subtypes:false -// Minimisation of tests/run-macros/i17257 -// to understand how changes to match type reduction -// impacted this use of Tuple.IsMappedBy. -// -// During match type reduction -// if we do NOT simplify the case lambda parameter instances -// then this large tuple make TypeComparer breach LogPendingSubTypesThreshold -// which, under -Yno-deep-subtypes, crashes the compilation. -class C[+A] -def foo[T <: Tuple : Tuple.IsMappedBy[C]] = ??? -def bar[X] = foo[( - C[X], C[X], C[X], C[X], C[X], C[X], C[X], C[X], C[X], C[X], - C[X], C[X], C[X], C[X], C[X], C[X], C[X], C[X], C[X], C[X], - C[X], C[X], C[X], -)] diff --git a/tests/run-macros/i17257/a.scala b/tests/run-macros/i17257/a.scala deleted file mode 100644 index 4a5682327604..000000000000 --- a/tests/run-macros/i17257/a.scala +++ /dev/null @@ -1,53 +0,0 @@ -package derivation -import scala.quoted.* - -import scala.annotation.tailrec - -object Helpers: - - // file a.scala - inline def summonAllOptimized[T <: Tuple]: T = - ${ summonAllOptimizedImpl[T] } - - inline def summon23[E]: (E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E) = - ${ summon23Impl[E] } - - private def summonAllOptimizedImpl[T <: Tuple: Type](using q: Quotes): Expr[T] = { - import q.reflect.* - - Expr - .ofTupleFromSeq(typesOfTuple(TypeRepr.of[T], Nil).map { tpe => - tpe.asType match { - case '[t] => - Expr.summon[t].getOrElse(report.errorAndAbort(s"Unable to to find implicit instance for ${tpe.show}")) - } - }) - .asExprOf[T] - } - - private def summon23Impl[E: Type](using q: Quotes): Expr[(E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E)] = { - import q.reflect.* - - val e = Expr.summon[E].getOrElse(report.errorAndAbort(s"Unable to to find implicit instance for ${TypeRepr.of[E].show}")) - - val tuple = (e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e, e) - - assert(tuple.size == 23) - - Expr.ofTuple(tuple) - } - - @tailrec - private[derivation] def typesOfTuple( - using q: Quotes - )(tpe: q.reflect.TypeRepr, acc: List[q.reflect.TypeRepr]): List[q.reflect.TypeRepr] = - import q.reflect.* - val cons = Symbol.classSymbol("scala.*:") - tpe.widenTermRefByName.dealias match - case AppliedType(fn, tpes) if defn.isTupleClass(fn.typeSymbol) => - tpes.reverse_:::(acc) - case AppliedType(tp, List(headType, tailType)) if tp.derivesFrom(cons) => - typesOfTuple(tailType, headType :: acc) - case tpe => - if tpe.derivesFrom(Symbol.classSymbol("scala.EmptyTuple")) then acc.reverse - else report.errorAndAbort(s"Unknown type encountered in tuple ${tpe.show}") diff --git a/tests/run-macros/i17257/b.scala b/tests/run-macros/i17257/b.scala deleted file mode 100644 index 65d66fb20bff..000000000000 --- a/tests/run-macros/i17257/b.scala +++ /dev/null @@ -1,23 +0,0 @@ -package derivation { - //file b.scala - val test = Helpers.summonAllOptimized[( - ValueOf["a"], ValueOf["a"], ValueOf["a"], ValueOf["a"], ValueOf["a"], - ValueOf["a"], ValueOf["a"], ValueOf["a"], ValueOf["a"], ValueOf["a"], - ValueOf["a"], ValueOf["a"], ValueOf["a"], ValueOf["a"], ValueOf["a"], - ValueOf["a"], ValueOf["a"], ValueOf["a"], ValueOf["a"], ValueOf["a"], - ValueOf["a"], ValueOf["a"], ValueOf["a"] //Commenting out the last one here fixes the compile error - )] - val test2 = Helpers.summon23[ValueOf["a"]] -} -@main def Test = - def assertions(list: List[ValueOf["a"]]): Unit = - assert(list.size == 23) - assert(list.map(_.value) == List( - "a", "a", "a", "a", "a", - "a", "a", "a", "a", "a", - "a", "a", "a", "a", "a", - "a", "a", "a", "a", "a", - "a", "a", "a" - )) - assertions(derivation.test.toList) - assertions(derivation.test2.toList) From fa89ac407b38c5d4a455532f3732ef1542d07d8d Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 6 Oct 2023 09:39:24 +0100 Subject: [PATCH 3/7] Fixup onnx-scala --- community-build/community-projects/onnx-scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/onnx-scala b/community-build/community-projects/onnx-scala index 3a5a45016d1a..e4631027a16e 160000 --- a/community-build/community-projects/onnx-scala +++ b/community-build/community-projects/onnx-scala @@ -1 +1 @@ -Subproject commit 3a5a45016d1a48d2a84dc3159d3e08c1ad5ac587 +Subproject commit e4631027a16eac4837d0b27b18fd359c6946efdf From 1c7544736103238d93481afd143b201bcf2e6aee Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 13 Oct 2023 16:52:41 +0100 Subject: [PATCH 4/7] Teach TypeComparer that compiletime ops returns singletons --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index b23bfe9fe14b..07f8ee34147a 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1487,10 +1487,15 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * Delegates to compareS if `tycon` is scala.compiletime.S. Otherwise, constant folds if possible. */ def compareCompiletimeAppliedType(tp: AppliedType, other: Type, fromBelow: Boolean): Boolean = { - if (defn.isCompiletime_S(tp.tycon.typeSymbol)) compareS(tp, other, fromBelow) - else { + defn.isCompiletime_S(tp.tycon.typeSymbol) && compareS(tp, other, fromBelow) + || { val folded = tp.tryCompiletimeConstantFold if (fromBelow) recur(other, folded) else recur(folded, other) + } || other.match { + case other: TypeRef if !fromBelow && other.symbol == defn.SingletonClass => + tp.args.forall(arg => isSubType(arg, defn.SingletonType)) + // Compile-time operations with singleton arguments are singletons + case _ => false } } From f304a078764d09cf7996f7776aa2e15f540a305e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 13 Oct 2023 17:11:15 +0100 Subject: [PATCH 5/7] Fix back onnx-scala --- community-build/community-projects/onnx-scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/onnx-scala b/community-build/community-projects/onnx-scala index e4631027a16e..3a5a45016d1a 160000 --- a/community-build/community-projects/onnx-scala +++ b/community-build/community-projects/onnx-scala @@ -1 +1 @@ -Subproject commit e4631027a16eac4837d0b27b18fd359c6946efdf +Subproject commit 3a5a45016d1a48d2a84dc3159d3e08c1ad5ac587 From ecb4207b362f130c8f2c80cf1d2c9d55ddab8072 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 13 Oct 2023 17:10:35 +0100 Subject: [PATCH 6/7] Add some very basic GADT constraints from type cases --- .../tools/dotc/transform/PostTyper.scala | 7 +- .../src/dotty/tools/dotc/typer/Namer.scala | 5 +- .../src/dotty/tools/dotc/typer/Typer.scala | 8 +- tests/pos/mini-onnx/Indices.scala | 29 ++++ tests/pos/mini-onnx/Shape.scala | 68 +++++++++ .../pos/mini-onnx/TensorShapeDenotation.scala | 24 ++++ tests/pos/mini-onnx/Tensors.scala | 130 ++++++++++++++++++ tests/pos/nano-onnx.scala | 20 +++ 8 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 tests/pos/mini-onnx/Indices.scala create mode 100644 tests/pos/mini-onnx/Shape.scala create mode 100644 tests/pos/mini-onnx/TensorShapeDenotation.scala create mode 100644 tests/pos/mini-onnx/Tensors.scala create mode 100644 tests/pos/nano-onnx.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 8504f7cc74fb..e08abe738d93 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -489,11 +489,14 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case m @ MatchTypeTree(bounds, selector, cases) => // Analog to the case above for match types def transformCase(x: CaseDef): CaseDef = - cpy.CaseDef(tree)( + val gadtCtx = x.pat.removeAttachment(typer.Typer.InferredGadtConstraints) match + case Some(gadt) => ctx.fresh.setGadtState(GadtState(gadt)) + case None => ctx + inContext(gadtCtx)(cpy.CaseDef(tree)( withMode(Mode.Pattern)(transform(x.pat)), transform(x.guard), transform(x.body), - ) + )) cpy.MatchTypeTree(tree)( super.transform(bounds), super.transform(selector), diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a48026063415..02ecb21281ee 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1003,7 +1003,10 @@ class Namer { typer: Typer => override final def typeSig(sym: Symbol): Type = val tparamSyms = completerTypeParams(sym)(using ictx) - given ctx: Context = nestedCtx.nn + given ctx: Context = if tparamSyms.isEmpty then nestedCtx.nn else + given ctx: Context = nestedCtx.nn.fresh.setFreshGADTBounds + ctx.gadtState.addToConstraint(tparamSyms) + ctx def abstracted(tp: TypeBounds): TypeBounds = HKTypeLambda.boundsFromParams(tparamSyms, tp) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 165484091260..95a0f996b82c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2005,13 +2005,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } if !ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef then withMode(Mode.GadtConstraintInference) { - TypeComparer.constrainPatternType(pat1.tpe, selType) + selType match + case scr: TypeRef if ctx.gadt.contains(scr.symbol) => pat1.tpe match + case pat: TypeRef => scr <:< pat + case _ => TypeComparer.constrainPatternType(pat1.tpe, selType) + case _ => TypeComparer.constrainPatternType(pat1.tpe, selType) } val pat2 = indexPattern(cdef).transform(pat1) var body1 = typedType(cdef.body, pt) if !body1.isType then assert(ctx.reporter.errorsReported) body1 = TypeTree(errorType(em"", cdef.srcPos)) + else if ctx.gadt.isNarrowing then + pat2.putAttachment(InferredGadtConstraints, ctx.gadt) assignType(cpy.CaseDef(cdef)(pat2, EmptyTree, body1), pat2, body1) } caseRest(using ctx.fresh.setFreshGADTBounds.setNewScope) diff --git a/tests/pos/mini-onnx/Indices.scala b/tests/pos/mini-onnx/Indices.scala new file mode 100644 index 000000000000..5e87ca3a384d --- /dev/null +++ b/tests/pos/mini-onnx/Indices.scala @@ -0,0 +1,29 @@ +import scala.compiletime.ops.string.+ +import scala.compiletime.ops.any + +type Index = Int & Singleton + +sealed trait Indices + +final case class :::[+H <: Index, +T <: Indices](head: H, tail: T) extends Indices: + override def toString = s"$head ::: $tail" + +sealed trait INil extends Indices +case object INil extends INil + +object Indices: + type ToString[X <: Indices] <: String = X match + case INil => "INil" + case head ::: tail => any.ToString[head] + " ::: " + ToString[tail] + + type Contains[Haystack <: Indices, Needle <: Index] <: Boolean = Haystack match + case head ::: tail => head match + case Needle => true + case _ => Contains[tail, Needle] + case INil => false + + type RemoveValue[RemoveFrom <: Indices, Value <: Index] <: Indices = RemoveFrom match + case INil => INil + case head ::: tail => head match + case Value => RemoveValue[tail, Value] + case _ => head ::: RemoveValue[tail, Value] diff --git a/tests/pos/mini-onnx/Shape.scala b/tests/pos/mini-onnx/Shape.scala new file mode 100644 index 000000000000..83e0aef6f6b1 --- /dev/null +++ b/tests/pos/mini-onnx/Shape.scala @@ -0,0 +1,68 @@ +import scala.compiletime.ops.int.{S, +, <, <=, *} +import scala.compiletime.ops.boolean.&& + +type Dimension = Int & Singleton + +sealed trait Shape extends Product with Serializable + +final case class #:[+H <: Dimension, +T <: Shape](head: H, tail: T) extends Shape: + override def toString = (head: Any) match + case _ #: _ => s"($head) #: $tail" + case _ => s"$head #: $tail" + +sealed trait SNil extends Shape +case object SNil extends SNil + +object Shape: + def scalar: SNil = SNil + + type Concat[X <: Shape, Y <: Shape] <: Shape = X match + case SNil => Y + case head #: tail => head #: Concat[tail, Y] + + type Reverse[X <: Shape] <: Shape = X match + case SNil => SNil + case head #: tail => Concat[Reverse[tail], head #: SNil] + + type NumElements[X <: Shape] <: Int = X match + case SNil => 1 + case head #: tail => head * NumElements[tail] + + type Rank[X <: Shape] <: Int = X match + case SNil => 0 + case head #: tail => Rank[tail] + 1 + + type IsEmpty[X <: Shape] <: Boolean = X match + case SNil => true + case _ #: _ => false + + type Head[X <: Shape] <: Dimension = X match { case head #: _ => head } + type Tail[X <: Shape] <: Shape = X match { case _ #: tail => tail } + + type Reduce[S <: Shape, Axes <: None.type | Indices] <: Shape = Axes match + case None.type => SNil + case Indices => ReduceLoop[S, Axes, 0] + + protected type ReduceLoop[RemoveFrom <: Shape, ToRemove <: Indices, I <: Index] <: Shape = RemoveFrom match + case head #: tail => Indices.Contains[ToRemove, I] match + case true => ReduceLoop[tail, Indices.RemoveValue[ToRemove, I], S[I]] + case false => head #: ReduceLoop[tail, ToRemove, S[I]] + case SNil => ToRemove match { case INil => SNil } + + type WithinBounds[I <: Index, S <: Shape] = (0 <= I && I < Rank[S]) + + type RemoveIndex[RemoveFrom <: Shape, I <: Index] <: Shape = WithinBounds[I, RemoveFrom] match + case true => RemoveIndexLoop[RemoveFrom, I, 0] + + protected type RemoveIndexLoop[RemoveFrom <: Shape, I <: Index, Current <: Index] <: Shape = RemoveFrom match + case head #: tail => Current match + case I => tail + case _ => head #: RemoveIndexLoop[tail, I, S[Current]] + + type Map[X <: Shape, F[_ <: Dimension] <: Dimension] <: Shape = X match + case SNil => SNil + case head #: tail => F[head] #: Map[tail, F] + + type FoldLeft[B, X <: Shape, Z <: B, F[_ <: B, _ <: Int] <: B] <: B = X match + case SNil => Z + case head #: tail => FoldLeft[B, tail, F[Z, head], F] diff --git a/tests/pos/mini-onnx/TensorShapeDenotation.scala b/tests/pos/mini-onnx/TensorShapeDenotation.scala new file mode 100644 index 000000000000..6d9978c34f98 --- /dev/null +++ b/tests/pos/mini-onnx/TensorShapeDenotation.scala @@ -0,0 +1,24 @@ +import scala.compiletime.ops.int.S + +type DimensionDenotation = String & Singleton + +sealed trait TensorShapeDenotation extends Product with Serializable + +final case class ##:[+H <: DimensionDenotation, +T <: TensorShapeDenotation](head: H, tail: T) extends TensorShapeDenotation: + override def toString = (head: Any) match + case _ ##: _ => s"($head) ##: $tail" + case _ => s"$head ##: $tail" + +sealed trait TSNil extends TensorShapeDenotation +case object TSNil extends TSNil + +object TensorShapeDenotation: + type Reduce[S <: TensorShapeDenotation, Axes <: None.type | Indices] <: TensorShapeDenotation = Axes match + case None.type => TSNil + case Indices => ReduceLoop[S, Axes, 0] + + protected type ReduceLoop[RemoveFrom <: TensorShapeDenotation, ToRemove <: Indices, I <: Index] <: TensorShapeDenotation = RemoveFrom match + case head ##: tail => Indices.Contains[ToRemove, I] match + case true => ReduceLoop[tail, Indices.RemoveValue[ToRemove, I], S[I]] + case false => head ##: ReduceLoop[tail, ToRemove, S[I]] + case TSNil => ToRemove match { case INil => TSNil } diff --git a/tests/pos/mini-onnx/Tensors.scala b/tests/pos/mini-onnx/Tensors.scala new file mode 100644 index 000000000000..b4b80271827d --- /dev/null +++ b/tests/pos/mini-onnx/Tensors.scala @@ -0,0 +1,130 @@ +import scala.compiletime.ops.int.* + +object Tensors: + import Shape.Reverse + + type Supported = Int | Long | Float | Double | Byte | Short | Boolean | String + + type TensorTypeDenotation = String & Singleton + + type Axes = Tuple3[TensorTypeDenotation, TensorShapeDenotation, Shape] + + opaque type Tensor[T <: Supported, +Ax <: Axes] = Tuple2[Array[T], Ax] + + type SparseTensor[T <: Supported, A <: Axes] = Tensor[T, A] + + type KeepOrReduceDims[S <: Shape, AxisIndices <: None.type | Indices, KeepDims <: (Boolean & Singleton)] <: Shape = KeepDims match + case true => ReduceKeepDims[S, AxisIndices] + case false => Shape.Reduce[S, AxisIndices] + + type KeepOrReduceDimDenotations[Td <: TensorShapeDenotation, AxisIndices <: None.type | Indices, KeepDims <: (Boolean & Singleton)] <: TensorShapeDenotation = KeepDims match + case true => Td + case false => TensorShapeDenotation.Reduce[Td, AxisIndices] + + type ReduceKeepDims[S <: Shape, AxisIndices <: None.type | Indices] <: Shape = AxisIndices match + case None.type => SNil + case Indices => ReduceKeepDimsLoop[S, AxisIndices, 0] + + protected type ReduceKeepDimsLoop[ReplaceFrom <: Shape, ToReplace <: Indices, I <: Index] <: Shape = ReplaceFrom match + case head #: tail => Indices.Contains[ToReplace, I] match + case true => 1 #: ReduceKeepDimsLoop[tail, Indices.RemoveValue[ToReplace, I], S[I]] + case false => head #: ReduceKeepDimsLoop[tail, ToReplace, S[I]] + case SNil => ToReplace match { case INil => SNil } + + type AddGivenAxisSize[S <: Shape, S1 <: Shape, AxisIndices <: None.type | Indices] <: Shape = AxisIndices match + case None.type => SNil + case Indices => AddGivenAxisSizeLoop[S, S1, AxisIndices, 0] + + protected type AddGivenAxisSizeLoop[First <: Shape, Second <: Shape, AxisIndex <: Indices, I <: Index] <: Shape = First match + case head #: tail => Indices.Contains[AxisIndex, I] match + case true => Second match + case secondHead #: secondTail => (head + secondHead) #: AddGivenAxisSizeLoop[tail, secondTail, Indices.RemoveValue[AxisIndex, I], S[I]] + case SNil => AxisIndex match { case INil => SNil } + case false => Second match + case secondHead #: secondTail => (head) #: AddGivenAxisSizeLoop[tail, secondTail, AxisIndex, S[I]] + case SNil => AxisIndex match { case INil => SNil } + + type UnsqueezeShape[S <: Shape, AxisIndex <: None.type | Indices] <: Shape = AxisIndex match + case None.type => SNil + case Indices => UnsqueezeShapeLoop[S, AxisIndex, 0] + + protected type UnsqueezeShapeLoop[ToUnsqueeze <: Shape, AxisIndex <: Indices, I <: Index] <: Shape = ToUnsqueeze match + case head #: tail => Indices.Contains[AxisIndex, I] match + case true => 1 #: head #: UnsqueezeShapeLoop[tail, Indices.RemoveValue[AxisIndex, I], S[I]] + case false => head #: UnsqueezeShapeLoop[tail, AxisIndex, S[I]] + case SNil => AxisIndex match { case INil => SNil } + + type GatheredShape[S <: Shape, AxisIndex <: None.type | Indices, AxisIndices <: Indices] <: Shape = AxisIndex match + case None.type => SNil + case Indices => GatheredShapeLoop[S, AxisIndex, 0, AxisIndices] + + protected type GatheredShapeLoop[ToGather <: Shape, AxisIndex <: Indices, I <: Index, AxisIndices <: Indices] <: Shape = ToGather match + case head #: tail => Indices.Contains[AxisIndex, I] match + case true => IndicesSize[AxisIndices] #: GatheredShapeLoop[tail, Indices.RemoveValue[AxisIndex, I], S[I], AxisIndices] + case false => head #: GatheredShapeLoop[tail, AxisIndex, S[I], AxisIndices] + case SNil => AxisIndex match { case INil => SNil } + + type IndicesSize[AxisIndices <: Indices] = IndicesSizeLoop[AxisIndices, 0] + + type IndicesSizeLoop[AxisIndices <: Indices, Acc <: Dimension] <: Dimension = AxisIndices match + case head ::: tail => IndicesSizeLoop[tail, S[Acc]] + case INil => Acc + + type FlattenedShape[S <: Shape, AxisIndex <: None.type | Indices] <: Shape = AxisIndex match + case None.type => SNil + case Indices => FlattenedShapeLoop[S, AxisIndex, 0, 1] + + protected type FlattenedShapeLoop[ToFlatten <: Shape, AxisIndex <: Indices, I <: Index, Acc <: Index] <: Shape = ToFlatten match + case head #: tail => Indices.Contains[AxisIndex, I] match + case true => Acc #: FlattenedShapeLoop[tail, Indices.RemoveValue[AxisIndex, I], S[I], head] + case false => FlattenedShapeLoop[tail, AxisIndex, S[I], head * Acc] + case SNil => AxisIndex match { case INil => Acc #: SNil } + + type SlicedShape[AxisIndicesStarts <: None.type | Indices, AxisIndicesEnds <: None.type | Indices] <: Shape = AxisIndicesStarts match + case None.type => SNil + case Indices => AxisIndicesEnds match + case None.type => SNil + case Indices => SlicedShapeLoop[AxisIndicesStarts, AxisIndicesEnds] + + protected type SlicedShapeLoop[Starts <: Indices, Ends <: Indices] <: Shape = Starts match + case head ::: tail => Ends match + case endsHead ::: endsTail => (endsHead - head) #: SlicedShapeLoop[tail, endsTail] + case INil => SNil + case INil => Ends match { case INil => SNil } + + type PaddedShape[PadFrom <: Shape, AxisBefore <: None.type | Shape, AxisAfter <: None.type | Shape] <: Shape = AxisBefore match + case None.type => PadFrom + case Shape => AxisAfter match + case None.type => PadFrom + case Shape => Reverse[PaddedShapeLoop[Reverse[PadFrom], Reverse[AxisBefore], Reverse[AxisAfter]]] + + protected type PaddedShapeLoop[PadFrom <: Shape, Before <: Shape, After <: Shape] <: Shape = Before match + case head #: tail => After match + case afterHead #: afterTail => PadFrom match + case padFromHead #: padFromTail => (head + padFromHead + afterHead) #: PaddedShapeLoop[padFromTail, tail, afterTail] + case SNil => SNil + case SNil => SNil + case SNil => After match + case SNil => PadFrom match + case padFromHead #: padFromTail => padFromHead #: PaddedShapeLoop[padFromTail, SNil, SNil] + case SNil => SNil + + type TiledShape[TileFrom <: Shape, AxisRepeats <: None.type | Indices] <: Shape = AxisRepeats match + case None.type => SNil + case Indices => TiledShapeLoop[TileFrom, AxisRepeats] + + protected type TiledShapeLoop[TileFrom <: Shape, Repeats <: Indices] <: Shape = Repeats match + case head ::: tail => TileFrom match + case tileFromHead #: tileFromTail => (head * tileFromHead) #: TiledShapeLoop[tileFromTail, tail] + case SNil => SNil + case INil => SNil + + type PoolShape[From <: Shape, KernelShape <: None.type | Shape] <: Shape = KernelShape match + case None.type => SNil + case Shape => Reverse[PoolShapeLoop[Reverse[From], Reverse[KernelShape]]] + + protected type PoolShapeLoop[From <: Shape, KernelShape <: Shape] <: Shape = KernelShape match + case head #: tail => From match + case fromHead #: fromTail => (fromHead - head + 1) #: PoolShapeLoop[fromTail, tail] + case SNil => SNil + case SNil => From diff --git a/tests/pos/nano-onnx.scala b/tests/pos/nano-onnx.scala new file mode 100644 index 000000000000..6ef2d43cd033 --- /dev/null +++ b/tests/pos/nano-onnx.scala @@ -0,0 +1,20 @@ +import scala.compiletime.ops.int.* + +type Index = Int & Singleton +type Dimension = Int & Singleton + +sealed trait Indices extends Product with Serializable +sealed trait Shape extends Product with Serializable +final case class :::[+H <: Index, +T <: Indices](head: H, tail: T) extends Indices +final case class #:[+H <: Dimension, +T <: Shape ](head: H, tail: T) extends Shape +sealed trait INil extends Indices; case object INil extends INil +sealed trait SNil extends Shape; case object SNil extends SNil + +object Ts: + type ReduceKeepDims[S <: Shape, AxisIndices <: None.type | Indices] <: Shape = AxisIndices match + case None.type => SNil + case Indices => ReduceKeepDimsLoop[S, AxisIndices, 0] + + protected type ReduceKeepDimsLoop[ReplaceFrom <: Shape, ToReplace <: Indices, I <: Index] <: Shape = ReplaceFrom match + case head #: tail => ReduceKeepDimsLoop[tail, ToReplace, S[I]] + case SNil => ToReplace match { case INil => SNil } From 76c8268d4055ca48c1a2ee2e89f97995cb8428ec Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 16 Oct 2023 09:44:54 +0100 Subject: [PATCH 7/7] Fix legitimate onnx-scala bounds check --- community-build/community-projects/onnx-scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/onnx-scala b/community-build/community-projects/onnx-scala index 3a5a45016d1a..c8aab5f7f99b 160000 --- a/community-build/community-projects/onnx-scala +++ b/community-build/community-projects/onnx-scala @@ -1 +1 @@ -Subproject commit 3a5a45016d1a48d2a84dc3159d3e08c1ad5ac587 +Subproject commit c8aab5f7f99bf1bb217d16f33068475ad28ae887