scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.83k stars 1.05k forks source link

separate compilation of top level overloaded methods can't be selected #18673

Open ntnj opened 12 months ago

ntnj commented 12 months ago

Compiler version

3.4.0-RC1-bin-20231006-b1ccc6a-NIGHTLY

Minimized code

a.scala

import language.experimental.relaxedExtensionImports
class A
extension (a: A)
  def f = "a"

b.scala

import language.experimental.relaxedExtensionImports
class B
extension (b: B)
  def f = "b"

main.scala

import language.experimental.relaxedExtensionImports
@main def main() =
  val a = A()
  println(a.f)

Replit Link: https://replit.com/@replitit1/TubbyHighlevelCoordinate

Output

Compile error (edit: when compiled together)

[error] -- [E161] Naming Error: /home/runner/TubbyHighlevelCoordinate/b.scala:6:6 ------
[error] 6 |  def f = "b"
[error]   |  ^^^^^^^^^^^
[error]   |f is already defined as method f in /home/runner/TubbyHighlevelCoordinate/a.scala
[error]   |
[error]   |Note that overloaded methods must all be defined in the same group of toplevel definitions
[error] one error found

Edit: when compiled separately (a, then b, then main) - another error:

-- [E008] Not Found Error: main.scala:6:12 -------------------------------------
6 |  println(a.f)
  |          ^^^
  |        value f is not a member of example.A.
  |        An extension method was tried, but could not be fully constructed:
  |
  |            example.f(a)
  |
  |            failed with:
  |
  |                Found:    (a : example.A)
  |                Required: example.B
1 error found

Expectation

a

https://github.com/lampepfl/dotty/issues/16920 fixed this issue when extension methods with same names are imported from different objects, but it doesn't seem to work when extensions are defined at top level across multiple files.

Ichoran commented 11 months ago

The very annoying but possible workaround is to create a third file that has all the extensions that collide in it.

Unlike the multiple-namespace situation, which has no workaround save for relaxedExtensionImports, this at least is possible.

bishabosha commented 11 months ago

I can't really call this a bug, the error is very clear: "overloaded methods must all be defined in the same group of toplevel definitions" this is more a feature request.

bishabosha commented 11 months ago

The more surprising thing is that with separate compilation, you are allowed to compile a.scala, then b.scala, then main.scala, leading to this error:

-- [E008] Not Found Error: main.scala:6:12 -------------------------------------
6 |  println(a.f)
  |          ^^^
  |        value f is not a member of example.A.
  |        An extension method was tried, but could not be fully constructed:
  |
  |            example.f(a)
  |
  |            failed with:
  |
  |                Found:    (a : example.A)
  |                Required: example.B
1 error found

which certainly seems a bug, probably a side effect of assuming separate compilation will not occur

bishabosha commented 11 months ago

we can minimise further by not using extension methods at all (compiled separately):

// a.scala
class A
def f(a: A) = "a"
// b.scala
class B
def f(b: B) = "b"
// main.scala
@main def main() =
  val a = A()
  println(f(a))

producing

-- [E007] Type Mismatch Error: main.scala:3:12 ---------------------------------
3 |  println(f(a))
  |            ^
  |            Found:    (a : A)
  |            Required: B
  |
  | longer explanation available when compiling with `-explain`
1 error found
som-snytt commented 3 months ago

This cannot be a bug in general. I may wish to insert my monkeypatched class at any time.

It's a bit weird that, compiling to the same output dir, it compiles fine if I switch the order.

Does the classpath implementation ingest class files in file creation order? It would at least be consistent if it were stably sorted (by name instead of time stamp). Currently, "last compiled wins" from different files.

Related issue about companionship: https://github.com/scala/scala3/issues/20510

The compiler can offer assistance to avoid obvious pitfalls (just at the class path level, aside from language-level correctness).

Edit: classpath delivers sorted files, so something else is going on.