Better bounds for inline matches #17870
Replies: 0 comments 5 replies
-
Your reasoning about what the compiler could conclude makes sense. The reason why it doesn't conclude that is that we reason about For a normal If someone was feeling adventurous, as far as compiler features go, this one isn't that difficult to implement. The function for constraining pattern types is here. What needs to be done is that we need a different function (say, |
Beta Was this translation helpful? Give feedback.
-
I wonder if we need a different mechanism at all, instead of complexifying the existing mechanism. I’m still experimenting with the API, so I might be doing something absurd, but here is how I would handle different paths of derivation for case classes with optional fields (ie, of type val tcHead: TC[head] =
inline erasedValue[head] match
case _: Option[inner] =>
val tc: TC[Option[inner]] = ??? // some code specific to `TC`
tc
case _: head =>
val tc: TC[head] = ??? // some code specific to `TC`
tc Of course, I get the following error:
Here, the reasoning I made in the issue description does not apply. Maybe the type of the field (here, the type So, I would say that we need a mechanism to match exactly on a type. E.g., here I want to check if the type |
Beta Was this translation helpful? Give feedback.
-
Here is a workaround (add an invariant "box" type): import compiletime.{erasedValue, summonInline}
trait TC[T]
final case class INV[T] private ()
inline def loop[Elems]: TC[Elems] =
inline erasedValue[INV[Elems]] match
case _: INV[EmptyTuple] =>
val tcEmptyTuple: TC[EmptyTuple] = ??? // create an instance of `TC` for the empty tuple
tcEmptyTuple
case _: INV[(head *: tail)] =>
val tcHead: TC[head] = summonInline[TC[head]]
val tcTail: TC[tail] = loop[tail]
val tcHeadTail: TC[head *: tail] = ??? // combine instances for `head` and `tail` according to `TC` abilities
tcHeadTail |
Beta Was this translation helpful? Give feedback.
-
This seems to be an instance of the following bug #8739 |
Beta Was this translation helpful? Give feedback.
-
It might be simpler to implement this with a macro (see https://github.com/lampepfl/dotty/blob/master/tests/run-macros/quoted-ToExpr-derivation-macro/Derivation_1.scala#L50-L53). |
Beta Was this translation helpful? Give feedback.
-
The typical code for iterating over a
Tuple
at the type level looks like the following:Note that we have to use
asInstanceOf
in the branches of theinline match
.Let me explain my understanding of why these
asInstanceOf
calls are necessary.Here is the error message we get if we remove the
asInstanceOf
:So, on the right-hand side of
case _: EmptyTuple.type =>
, the compiler concludes thatElems >: EmptyTuple.type
, so returning aTC[EmptyTuple.type]
gives a type mismatch error becauseTC[EmptyTuple.type]
is not a subtype ofTC[Elems]
, sinceTC
is invariant in this example.By the way, I must say that there is one thing I don’t understand: why do we have
Elems >: EmptyTuple.type
? I would expect the opposite relation:Elems <: EmptyTuple.type
.But, even with this change, the situation would be the same (ie, we would get a type mismatch error). But, what we the compiler could do would be to conclude that, since
EmptyTuple.type
is a singleton type, the relationship should really beElems =:= EmptyTuple.type
. In such a case, returning aTC[EmptyTuple.type]
would type check without the need for anasInstanceOf
call.Now, let’s look at the second case.
Here, the situation is a bit more subtle. It is similar in the fact that
Elems >: (head *: tail)
. Again, it is not clear to me why the relation is not in the opposite side (Elems <: (head *: tail)
).In such a case, since the type
*:
is sealed and it has no known subtypes, so the compiler could conclude thatElems =:= (head *: tail)
, and theasInstanceOf
call would not be necessary.I am not sure what feature I am requesting, to be honest. I just found that implementing type class derivation forces us to rely on unsound code, which is unfortunate, and seems to be preventable.
Beta Was this translation helpful? Give feedback.
All reactions