Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unexplained "ambiguous implicit arguments" error #14585

Closed
erikerlandson opened this issue Feb 28, 2022 · 12 comments
Closed

unexplained "ambiguous implicit arguments" error #14585

erikerlandson opened this issue Feb 28, 2022 · 12 comments

Comments

@erikerlandson
Copy link

Compiler version

3.1.1

Minimized code

The setup definitions

object repro:
    abstract class Add[VL, VR]:
        type VO
        def apply(vl: VL, vr: VR): VO

    object Add:
        // invoking this rule(same-type args) seems to work as expected
        transparent inline given [V](using num: scala.math.Numeric[V]): Add[V, V] =
            new Add[V, V]:
                type VO = V
                def apply(vl: V, vr: V): V = num.plus(vl, vr)

        // trying to invoke this rule (differing types) causes strange error
        transparent inline given [VL, VR](using
            nev: scala.util.NotGiven[VL =:= VR],
            numL: scala.math.Numeric[VL],
            numR: scala.math.Numeric[VR]): Add[VL, VR] =
            new Add[VL, VR]:
                type VO = Double
                def apply(vl: VL, vr: VR): Double = numL.toDouble(vl) + numR.toDouble(vr)

    transparent inline def plus[VL, VR](vl: VL, vr: VR)(using add: Add[VL, VR]): add.VO =
        add(vl, vr)

Output

scala> import repro.*
                                                                                                                                                               
scala> plus(1,1)  // invoking with identical types works
val res0: Int = 2
                                                                                                                                                               
scala> plus(1f,1f)
val res1: Float = 2.0
                                                                                                                                                               
scala> plus(1d,1d)
val res2: Double = 2.0
                                                                                                                                                               
scala> plus(1,1d)  // attempting to trigger the different-types rule errors out unxpectedly
-- Error: ----------------------------------------------------------------------
1 |plus(1,1d)
  |          ^
  |ambiguous implicit arguments of type coulomb.repro.Add[Int, Double] found for parameter add of method plus in object repro.
  |I found:
  |
  |    coulomb.repro.Add.given_Add_V_V[V](
  |      /* ambiguous: both object DoubleIsFractional in object Numeric and object LongIsIntegral in object Numeric match type Numeric[V] */
  |        summon[Numeric[V]]
  |    )
  |
  |But both object DoubleIsFractional in object Numeric and object LongIsIntegral in object Numeric match type Numeric[V].
1 error found

Expectation

I would expect these rules to resolve correctly with no ambiguities

@erikerlandson erikerlandson added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Feb 28, 2022
@erikerlandson
Copy link
Author

@armanbilge
Copy link
Contributor

@erikerlandson here's a potential workaround.

object repro:
    abstract class Add[VL, VR]:
        type VO
        def apply(vl: VL, vr: VR): VO

    object Add:
        // invoking this rule(same-type args) seems to work as expected
        transparent inline given sameSame[VL, VR](using num: scala.math.Numeric[VL], same: VR =:= VL): Add[VL, VR] =
            new Add[VL, VR]:
                type VO = VL
                def apply(vl: VL, vr: VR): VL = num.plus(vl, same(vr))

        // trying to invoke this rule (differing types) causes strange error
        transparent inline given different[VL, VR](using
            nev: scala.util.NotGiven[VL =:= VR],
            numL: scala.math.Numeric[VL],
            numR: scala.math.Numeric[VR]): Add[VL, VR] =
            new Add[VL, VR]:
                type VO = Double
                def apply(vl: VL, vr: VR): Double = numL.toDouble(vl) + numR.toDouble(vr)

    transparent inline def plus[VL, VR](vl: VL, vr: VR)(using add: Add[VL, VR]): add.VO =
        add(vl, vr)

@erikerlandson
Copy link
Author

@armanbilge that seems to work!
I had absolutely no idea that =:= has an apply method to convert that way 😮

@erikerlandson
Copy link
Author

I'm am going to leave this open because it still seems like a bug, feel free to close it otherwise

@bishabosha bishabosha added area:implicits related to implicits and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 1, 2022
@odersky
Copy link
Contributor

odersky commented Mar 3, 2022

Why does it look like a bug to you? Is there one of the two alternatives that is more specific than the other? if yes, why?

@erikerlandson
Copy link
Author

erikerlandson commented Mar 3, 2022

is there one of the two alternatives that is more specific than the other? if yes, why?

My interpretation is that the first rule is "clearly" more specific, it has only one type parameter. Also, in any situation where the second rule matches (V1 not equal V2), the first should unambiguously fail, since it requires identical V in both type positions.

I'm also puzzled by the particular nature of the error:

/* ambiguous: both object DoubleIsFractional in object Numeric and object LongIsIntegral in object Numeric match type Numeric[V] */

It should be looking for Numeric[Int] and Numeric[Double]: I do not understand why it is comparing DoubleIsFractional with LongIsIntegral

@odersky
Copy link
Contributor

odersky commented Mar 3, 2022

It does try the first rule, which is more specific. But it can't rule out the rule because of the presence of implicit conversions. There could be a type V to which both 1 and 1d are convertible. (In fact there is at least one: Double, but there could be others depending on user-defined implicit conversions). Hence, it tries
to find a solution for the using clause and fails with the error.

The whole thing should work if we get rid of implicit conversions. So that's a good demonstration what we could gain by such a step.

@odersky odersky closed this as completed Mar 3, 2022
@erikerlandson
Copy link
Author

I see, my mental model of how these searches behave doesn't include "account for possible implicit conversions"

In general, I like the scala 3 concept of "you only get the implicit conversions that you deliberately import". If you (reasonably) do not wish to allow them, then do not import them. My drafts of scala-3-coulomb support this principle.

Ignoring implicit conversions during context searching certainly seems like it would be cleaner. You get whatever matches without implicit conversion. I don't know if it is reasonable to decouple that from considering implicit conversions elsewhere, though.

@erikerlandson
Copy link
Author

maybe what I'm suggesting is equivalent to: "implicit conversions should not apply to using parameters"

@odersky
Copy link
Contributor

odersky commented Mar 4, 2022

@erikerlandson Unfortunately, there's a lot of code that would not work anymore with that.

@erikerlandson
Copy link
Author

thanks for looking at it!

Being able to use A =:= B as A => B is a pretty clean workaround, so it's not blocking me 🎉

@benhutchison
Copy link
Contributor

@armanbilge I sure am grateful we have you in the Scala community. That's a handy trick to know 🙏

For anyone else following along from home, this is the apply that Eric makes reference to above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants