Nested type matches don't resolve

A little depentent match type function question. I managed to solve the problem actually via a delegate (a custom If construct), but why that is necessary, and if it could be overcome, would be of much interest for me going forward!
Full context is here with a little more explanation of what I’m trying to do: Scastie - An interactive playground for Scala.

Problem: this function does not work, to my dismay:

  type IsAnOutputT3[N, Edges] <: Boolean = Edges match
    case h *: t => IsOutputT[N,h] match
      case true => true
      case false => IsAnOutputT3[N,t]
    case EmptyTuple => false

  def isAnOutput3[N, Edges <: Tuple](node: N, edges: Edges): IsAnOutputT3[N,Edges] = 
    edges match
      case tup: *:[h, t] => isOutput[N,h](node, tup.head) match
        case _: true => true
        case _: false => isAnOutput3(node, tup.tail)
      case _: EmptyTuple => false

while this does with the help of the If/ifthenelse construct:

  type IsAnOutputT2[N, Edges] <: Boolean = Edges match
    case h *: t => If[
      h, 
      [X] =>> IsOutputT[N, X],
      true,
      IsAnOutputT2[N, t]
    ]
    case EmptyTuple => false

  inline def isAnOutput2[N, Edges <: Tuple](node: N, edges: Edges): IsAnOutputT2[N,Edges] = 
    inline edges match
      case tup: *:[h, t] => {
        ifThenElse2(
          (tup.head: h), 
          [Edge] => (c: Edge) => isOutput[N,Edge](node,c), 
          true, 
          isAnOutput2(node, tup.tail)
        )
      }
      case _: EmptyTuple => false

  type If[C, P[_] <: Boolean, A, B] <: A | B = P[C] match
    case true => A
    case false => B

  def ifThenElse2[C, P[_] <: Boolean, A, B](cond: C, predicate: [CC <: C] => (CC) => P[CC], thenVal: => A, elseVal: => B): If[C, P, A, B] =
    predicate(cond) match
      case _: true => thenVal
      case _: false => elseVal

but the latter is way clunky I think. I would hate to proceed with that style.

The Scastie link doesn’t work for me…

Weird… Scastie - An interactive playground for Scala.

object Test3:
  
  // ---------- demonstration
  // - consider a graph that's made of edges, represented by a tuple of tuples.
  //   - the last tuple element is the destination node
  //   - the leading tuple elements are input nodes (so its a hyperedge)
  // - the `IsAnOutputT`/`isAnOutput` methods should test whether a node is the receiver of an edge

  @main def Test3Main(): Unit =
    sealed trait Nodes; object Nodes:
      case object P         extends Nodes
      case object Q         extends Nodes
      case object PxQ       extends Nodes
      case object N         extends Nodes
    import Nodes.*
    
    // tuple of edges; each edge: from1 *: from2 *: ... *: to *: EmptyTuple
    val NGraph = 
      (P *: Q *: PxQ *: EmptyTuple) *: 
      (PxQ *: N *: EmptyTuple) *: 
      EmptyTuple

    summon[IsAnOutputT2[P.type  , NGraph.type] =:= false]
    summon[IsAnOutputT2[Q.type  , NGraph.type] =:= false]
    summon[IsAnOutputT2[PxQ.type, NGraph.type] =:= true]
    summon[IsAnOutputT2[N.type  , NGraph.type] =:= true]
    
    summon[IsAnOutputT3[P.type  , NGraph.type] =:= false]
    summon[IsAnOutputT3[Q.type  , NGraph.type] =:= false]
    summon[IsAnOutputT3[PxQ.type, NGraph.type] =:= true]
    summon[IsAnOutputT3[N.type  , NGraph.type] =:= true]

  // -------- helper functions

  type GetOutputT[E <: Tuple] = Tuple.Last[E]
  def getOutput[E <: NonEmptyTuple](edge: E): GetOutputT[E] = edge.last

  type IsOutputT[N, E] <: Boolean = GetOutputT[E] match
    case N => true
    case _ => false
  def isOutput[N, E](node: N, edge: E): IsOutputT[N,E] = {
    edge match
      case tup: *:[h,t] => getOutput(tup) == node
      case _ => false
  }.asInstanceOf[IsOutputT[N,E]]

  
  // --------- this doesn't work, but would be far more elegant

  type IsAnOutputT3[N, Edges] <: Boolean = Edges match
    case h *: t => IsOutputT[N,h] match
      case true => true
      case false => IsAnOutputT3[N,t]
    case EmptyTuple => false

  def isAnOutput3[N, Edges <: Tuple](node: N, edges: Edges): IsAnOutputT3[N,Edges] = 
    edges match
      case tup: *:[h, t] => isOutput[N,h](node, tup.head) match
        case _: true => true
        case _: false => isAnOutput3(node, tup.tail)
      case _: EmptyTuple => false



  // --------- this works, with a helper "If" type and method

  type IsAnOutputT2[N, Edges] <: Boolean = Edges match
    case h *: t => If[
      h, 
      [X] =>> IsOutputT[N, X],
      true,
      IsAnOutputT2[N, t]
    ]
    case EmptyTuple => false

  inline def isAnOutput2[N, Edges <: Tuple](node: N, edges: Edges): IsAnOutputT2[N,Edges] = 
    inline edges match
      case tup: *:[h, t] => {
        ifThenElse2(
          (tup.head: h), 
          [Edge] => (c: Edge) => isOutput[N,Edge](node,c), 
          true, 
          isAnOutput2(node, tup.tail)
        )
      }
      case _: EmptyTuple => false

  type If[C, P[_] <: Boolean, A, B] <: A | B = P[C] match
    case true => A
    case false => B

  def ifThenElse2[C, P[_] <: Boolean, A, B](cond: C, predicate: [CC <: C] => (CC) => P[CC], thenVal: => A, elseVal: => B): If[C, P, A, B] =
    predicate(cond) match
      case _: true => thenVal
      case _: false => elseVal

I note this technique with the If type breaks down also, when you nest it further…

I only had a look at IsOutputT and isOutputT. The cast .asInstanceOf[IsOutputT[N,E]] seems incorrect to me, as IsOutputT[N,E] can return true even though the function call isOutputT(node: N, edge: E) returns false. Is it intentional?