Skip to content

Multi-source extension methods isn't in effect within the defined method #19830

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 29, 2024 · 8 comments
Closed

Comments

@soronpo
Copy link
Contributor

soronpo commented Feb 29, 2024

Compiler version

v3.4.0

Minimized code

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

class Foo[T]
object FooInt:
  extension (foo: Foo[Int])
    def bar: Unit = {}
object FooString:
  import FooInt.*
  extension (foo: Foo[String])
    def bar: Unit = 
      val f = Foo[Int]()
      f.bar

Output

value bar is not a member of Playground.Foo[Int].
An extension method was tried, but could not be fully constructed:

    Playground.FooString.bar(f)
    failed with:
        Found:    (f : Playground.Foo[Int])
        Required: Playground.Foo[String]

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 29, 2024
@bishabosha
Copy link
Member

bishabosha commented Mar 1, 2024

from the spec

If m is imported by several imports which are all on the nesting level, try each import as an extension method instead of failing with an ambiguity.

I would think the surrounding local definition counts as more nested than outside imports.

However it does still fail even if you bring the import inside the definition, so my guess is local def always trumps imports

object FooString:
  extension (foo: Foo[String])
    def bar: Unit = 
      import FooInt.*
      val f = Foo[Int]()
      f.bar // error

This page has probably the answer https://scala-lang.org/files/archive/spec/3.4/02-identifiers-names-and-scopes.html

@soronpo
Copy link
Contributor Author

soronpo commented Mar 1, 2024

If we had used implicit classes this compiles successfully:

class Foo[T]

object FooInt:
  implicit class FooIntExt(foo: Foo[Int]):
    def bar: Unit = {}

object FooString:
  import FooInt.*
  implicit class FooStringExt(foo: Foo[String]):
    def bar: Unit = 
      val f = Foo[Int]()
      f.bar

So for the original motivation of SIP 54, the extension-method code should compile, even if we need an additional language change.

@soronpo
Copy link
Contributor Author

soronpo commented Mar 1, 2024

In other words, the ambiguity is not the problem. The overloading mechanism should always choose the most specific method for extensions, no matter what the scope level is.

@odersky
Copy link
Contributor

odersky commented Mar 1, 2024

That's not how name resolution is defined. Inner scopes do take precedence. The behavior is as expected.

@soronpo
Copy link
Contributor Author

soronpo commented Mar 1, 2024

As expected by the spec, not the user intuition.

@odersky
Copy link
Contributor

odersky commented Mar 1, 2024

Yes, but it's deeply ingrained. Changing that would require a SIP, or, more likely, a new language.

@odersky odersky closed this as not planned Won't fix, can't repro, duplicate, stale Mar 1, 2024
@dwijnand
Copy link
Member

dwijnand commented Mar 4, 2024

I'm surprised that resolving a method within an extension method doesn't have the same scoping levels as the same thing in an enriching class - feels like it's a desugaring detail that shouldn't affect behaviour.

@som-snytt
Copy link
Contributor

I thought I'd consult my old ticket (old to me, early pandemic era) #8092 which requires a periodic refresher course.

That turned on what "nesting" means for imports and members, during implicit search.

But its behavior is inverted in 3.4. (!) This ticket seems the same on 3.3.3 or 3.4.0.

This "works" but not if the imports are reversed.

  extension (foo: Foo[String])
    def bar: Unit =
      import FooInt.*
      import FooString.*
      val f = Foo[Int]()
      f.bar

  final module class FooString() extends Object() { this: FooString.type =>
    extension (foo: Foo[String]) def bar: Unit =
      {
        import FooInt.*
        import FooString.*
        val f: Foo[Int] = new Foo[Int]()
        FooInt.bar(f)
      }
  }

If someone suggests that decoding these behaviors will lead to a profound insight, I will surely pursue it. Maybe on the weekend.

I understand that name binding (of bar) is different from implicit search (even if summoning and extensions both leverage overloading resolution), but it's not clear to me whether there are also different notions of "nesting".

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

5 participants