Ambiguous given with lower priority

This fails with

Ambiguous given instances: both given instance derived$JsonRW in object MyData and given instance autoderiveNamedTuple in trait LowPriorityJsonRWInstances match type Playground.JsonRW\[T\] of parameter rw of method parseJson in object Playground“
import NamedTuple.AnyNamedTuple

trait JsonRW[T]:
  def parse: T
object JsonRW extends LowPriorityJsonRWInstances:
  inline def derived[T]: JsonRW[T] =
    new JsonRW { def parse: T = ??? }

trait LowPriorityJsonRWInstances:
 // inline given autoderiveUnion[T]: JsonRW[T] =
    //new JsonRW { def parse: T = ??? }
  inline given autoderiveNamedTuple[T <: AnyNamedTuple]: JsonRW[T] =
    new JsonRW { def parse: T = ??? }

extension (strValue: String)
  def parseJson[T](using rw: JsonRW[T]): T = rw.parse

case class MyData() derives JsonRW

def parseMyData: MyData = "".parseJson


It works if I add explicit type like parseJson[MyData].

Is this expected behavior?

Is this edit box a bit funky after a version update? Anyway, trying again.

IDK but perhaps because overloading chooses the first f:

object X:
  def f[A <: Int](using Int): A = 42.asInstanceOf[A]
  def f[A](using String): A = "hello, world".asInstanceOf[A]

and if the givens are:

  given compete[T]: JsonRW[T] = new JsonRW { def parse: T = ??? }
  given autoderiveNamedTuple[T <: Int]: JsonRW[T] = new JsonRW { def parse: T = ??? }

then it will choose the second and infer T as Nothing. It doesn’t matter if expressed parseJson(“”) and doesn’t matter if importing into lexical scope versus implicit scope. It doesn’t matter if the bound is Int or AnyNamedTuple, but I didn’t want to guess if opaque was somehow dealiased or if that made a difference. Also inline doesn’t matter.

So if in the OP autoderive gets a point for “more specific” and the derived in MyData gets a point for being derived, then they are ambiguous.

I haven’t checked whether derived is worth a point the way “defined in a subclass” is. (Edit: no, that is not considered; only a subclassing relationship between classes or the companions of modules.)

My takeaway is that “feature intersection” is also about whether I can reason about a behavior if more than one feature is in play, even if there is no bug at the intersection. (Or, feature intersection is the crossroads at which Oedipus found himself, no wonder he was confused.)

Edit: I just remembered that implicit search prefers the least-constrained alternative in Scala 3, so my reasoning from “more specific” must be wrong.

Edit: My reasoning was wrong because what’s actually compared is JsonRW[MyData] and JsonRW[T], and neither is preferable because JsonRW is invariant. (Covariant will prefer the expected one.)

So my other takeaway follows on the first, that I can be distracted from the obvious by the moving parts.

The issue is that both of these actually have the same bounds, since type AnyNamedTuple <: Any and generic type T is basically type T <: Any
You can distinguish these 2 by materialising additional evidence given autoderiveNamedTuple[T <: AnyNamedTuple](using T <:< AnyNamedTuple): JsonRW[T]

At that point there is no need for priority tweaks. By compiling with -Vprint:typer we can confirm correct derivation is used

    type Other = (foo : Int, bar : String)
    def parseMyData: MyData = parseJson("")[MyData](MyData.derived$JsonRW)
    def parseOther: Other =
      parseJson("")[Nothing](JsonRW.autoderiveNamedTuple[Nothing](<:<.refl[NamedTuple.AnyNamedTuple]))
2 Likes

Thank you very much!
This works as expected now. :slight_smile:

If someone is interested, I made it all working now.

Named tuples + Union types, veeery neat and useful! :smiley:

1 Like