Skip to content

Extension methods ambiguity when type argument is added #14451

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 · Fixed by #16010
Closed

Extension methods ambiguity when type argument is added #14451

soronpo opened this issue Feb 11, 2022 · 7 comments · Fixed by #16010

Comments

@soronpo
Copy link
Contributor

soronpo commented Feb 11, 2022

Just adding a type argument causes the compiler to report ambiguity where there is none.

Compiler version

v3.1.2-RC1

Minimized code

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

class Foo

extension (dfVal: Foo)
  def prevOK(step: Int): Foo = ???
  def prevOK: Foo = ???
val ok = (new Foo).prevOK

extension (dfVal: Foo)
  def prevErr[S <: Int](step: S): Foo = ???
  def prevErr: Foo = ???
val err = (new Foo).prevErr

Output

value prevErr is not a member of Playground.Foo.
Extension methods were tried, but could not be fully constructed:

    Playground.prevErr(new Playground.Foo())

    prevErr(new Playground.Foo())    failed with

        Ambiguous overload. The overloaded alternatives of method prevErr in object Playground with types
         (dfVal: Playground.Foo): Playground.Foo
         (dfVal: Playground.Foo): [S <: Int](step: S): Playground.Foo
        both match arguments (Playground.Foo)

Expectation

No error.

@soronpo soronpo added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label area:extension-methods and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Feb 11, 2022
@soronpo
Copy link
Contributor Author

soronpo commented Feb 11, 2022

May be related to #14450

@prolativ
Copy link
Contributor

prolativ commented Feb 11, 2022

If you paste your examples you should see the reason why this doesn't work.
First one has keep in mind that extension methods eventually get desugared to normal methods.
So for

extension (dfVal: Foo)
  def prevOK(step: Int): Foo = ???
  def prevOK: Foo = ???

you get

def prevOK(dfVal: Foo)(step: Int): Foo
def prevOK(dfVal: Foo): Foo

This is an overloaded method with a different number of parameter lists but the compiler is smart enough to distinguish between them at compile time in most cases.

But in case of

extension (dfVal: Foo)
  def prevErr[S <: Int](step: S): Foo = ???
  def prevErr: Foo = ???

you get

def prevErr(dfVal: Foo): [S <: Int](step: S): Foo
def prevErr(dfVal: Foo): Foo

Now both of the overloaded variants take just a single parameter but they have different return types and it's not clear for the compiler which one you would like when writing

val err = (new Foo).prevErr

The reason for a different desugaring is that currently you cannot have a method like def prevErr(dfVal: Foo)[S <: Int](step: S): Foo with interweaved value and type parameters. I hope this restriction will get releaxed soon but I'm not sure of its status #14019

You can make your example compile by specifying the result type explicitly, e.g.

val err: Foo = (new Foo).prevErr

@soronpo
Copy link
Contributor Author

soronpo commented Feb 11, 2022

Ok. Thanks. I guess that in the meanwhile it's another one for my growing list of examples where extension methods cannot replace implicit classes.

@dwijnand dwijnand changed the title Extension methods amibguity when type argument is added Extension methods ambiguity when type argument is added Feb 11, 2022
@Sporarum
Copy link
Contributor

Sporarum commented Feb 17, 2022

But in case of

extension (dfVal: Foo)
  def prevErr[S <: Int](step: S): Foo = ???
  def prevErr: Foo = ???

you get

def prevErr(dfVal: Foo): [S <: Int](step: S): Foo
def prevErr(dfVal: Foo): Foo

This is somewhat misleading, the actual translation would be pretty printed as:

def prevErr(dfVal: Foo)[S <: Int](step: S): Foo
def prevErr(dfVal: Foo): Foo

The actual representation is a MethodType returning a PolyType returning a MethodType returning a Foo
This is similar to the actual representation for prevOk: a MethodType returning a MethodType returning a Foo
This is because MethodType contains only one (term) clause

Clause Interweaving doesn't change the representation inside the compiler, it uses the one introduced by #10950
(which I described above)

I believe the check that allows ok assumes there can only be MethodType as return, and doesn't handle PolyType.
(Thus the fix should be fairly easy)

Even if that is not the case, merging Clause Interweaving would not solve the problem, as it doesn't change the representation used
(but solving the problem is important for Clause Interweaving)

@Sporarum
Copy link
Contributor

Sporarum commented Feb 17, 2022

The reason for a different desugaring is that currently you cannot have a method like def prevErr(dfVal: Foo)[S <: Int](step: S): Foo with interweaved value and type parameters.

My previous message could be simplified to:

This method is completely allowed inside the compiler
But users can only create it through extension methods
Clause Interweaving only adds the ability for users to create those methods directly

@soronpo
Copy link
Contributor Author

soronpo commented Apr 9, 2022

It's good to document the workaround. We can move the type argument to the extension in many cases:

extension [S <: Int](dfVal: Foo)
  def prevErr(step: S): Foo = ???
  def prevErr: Foo = ???
val err = (new Foo).prevErr

@Sporarum
Copy link
Contributor

It turns out this is relatively easy to fix:
#14451

It does not require clause interleaving, but it would have made overloading with it very limited

bishabosha added a commit that referenced this issue Sep 15, 2022
Fixes #14451

Implicitly assumed type clauses could only be at the beginning, which is
wrong since:
```scala
extension (x: Int) def foo[T](y: T) = ???
```
de-sugars to something like:
```scala
def foo(x: Int)[T](y: T) = ???
```

To fix it, I implement `stripInferrable`, a variant of `stripImplicit`
which also drops type clauses, and use it in `resultIsMethod`
I suspect the other uses of `stripImplicit` could be simplified, or even
fixed (assuming they make the same mistake as `resultIsMethod`), by
using `stripInferrable`
@Kordyjan Kordyjan added this to the 3.2.2 milestone Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants