Skip to content

Extension methods and implicit class defs do not always interact well #14450

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

Closed
soronpo opened this issue Feb 11, 2022 · 7 comments
Closed

Extension methods and implicit class defs do not always interact well #14450

soronpo opened this issue Feb 11, 2022 · 7 comments

Comments

@soronpo
Copy link
Contributor

soronpo commented Feb 11, 2022

Extension methods and implicit class defs do not always interact well and we can get unrelated ambiguity.
I minimized this to the example below. There are various crucial elements for this error recipe:

  • Two implicit class defs that differ in number of argument blocks (but no ambiguity under normal circumstances).
  • One extension def applied on the same type of the implicit classes.
  • The extended type must be an alias from another namespace (just change BlaB.Foo to a class and this example will compile).
  • Implicit context argument for the definitions.

Compiler version

v3.1.2-RC1

Minimized code

https://scastie.scala-lang.org/IwKSNqrxSZSnd6LVmBXJHg

object BlaA:
  class Foo

object BlaB:
  trait Ctx
  type Foo = BlaA.Foo
  implicit class __FooExt(dfVal: Foo):
    def prev(step: Int)(implicit dfc: Ctx): Foo = ???
    def prev(implicit dfc: Ctx): Foo = ???
  extension (lhs: Foo) def baz: Foo = ???

  given Ctx = ???
  val f: Foo = new Foo
  val x = f.prev.baz
end BlaB

Output

None of the overloaded alternatives of method prev in class __FooExt with types
 (implicit dfc: Playground.BlaB.Ctx): Playground.BlaB.Foo
 (step: Int)(implicit dfc: Playground.BlaB.Ctx): Playground.BlaB.Foo
match expected type ?{ baz: ? }

Expectation

No error.

@som-snytt
Copy link
Contributor

som-snytt commented Feb 11, 2022

Moving the extension to the companion of BlaA.Foo works.

Guessing that you get a pass with lexically scoped givens, then the second pass with implicitly scoped givens plus other implicit conversions. For the resolution of the second selection, it doesn't hop back to first pass resolution.

Maybe that is because of some subtlety of how f.prev is typechecked during overload resolution.

It doesn't work to just change the alias to an import. It doesn't work to make the implicit class extend AnyVal.

It doesn't work to make it an opaque alias and create an anchor object Foo with the extension, though that might be a bug, as I would expect it to work the same as the "regular" companion object Foo that does work. Oh wait, it's because it is not opaque to us. Moving it into a "closet" module works. Edit: actually, making it opaque also makes the original extension method work.

  object stuff:
    opaque type Foo = BlaA.Foo
    object Foo:
      extension (lhs: Foo) def baz: Foo = ???
    def foo: Foo = new Foo
  end stuff
  import stuff.*

This ticket reads like a subplot of Agatha Christie's Death on the Nile.

@odersky
Copy link
Contributor

odersky commented Feb 21, 2022

I don't think it's worth to out much more effort into this, since implicit classes will be get deprecated in the future. I'll close but if someone volunteers to take this one, please re-open.

@odersky odersky closed this as completed Feb 21, 2022
@soronpo
Copy link
Contributor Author

soronpo commented Feb 21, 2022

@odersky I don't understand the policy on closing this. The bug exists. Whether it's important or not is relevant for a tag, IMO. The reason is simple. If someone looks for an existing open issue they look under OPEN issues.

@soronpo
Copy link
Contributor Author

soronpo commented Feb 21, 2022

And additionally, when interacting with Scala 2 libraries you can get this issue even if implicit classes do not exist at all in your codebase.

@odersky
Copy link
Contributor

odersky commented Feb 21, 2022

We currently have 766 open issues, and have to prioritize between issues that we think are worth fixing and issues where it is less likely. Issues that are currently not on the horizon for a possible fix get closed with stat:revisit. If someone wants to have a go at solving them they can be re-opened at any moment. But otherwise, if we leave an issue open, we should also assign someone to spend potentially a lot of time solving it. And I don't see anyone we could assign this to right now.

@soronpo
Copy link
Contributor Author

soronpo commented Feb 22, 2022

Keeping the issue open with a flag costs us nothing. Closing the issue while it is unresolved can potentially cost another person valuable time in minimizing and debugging the same issue yet again.

@odersky
Copy link
Contributor

odersky commented Feb 22, 2022

@soronpo That's a good point. We should discuss what to do with stat:revisit issues, and whether another category is more appropriate.

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

3 participants