Skip to content

Top-level methods with the same name in the same package overwrite each other #22721

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
mrdziuban opened this issue Mar 5, 2025 · 12 comments · Fixed by #22759
Closed

Top-level methods with the same name in the same package overwrite each other #22721

mrdziuban opened this issue Mar 5, 2025 · 12 comments · Fixed by #22759

Comments

@mrdziuban
Copy link

Compiler version

3.3.5, 3.4.3, 3.5.2, 3.6.3

Minimized code

// test1.scala
package example

private def foobar: String = "foo"
object test1 { val x = foobar }

// test2.scala
package example

private def foobar: Int = 1
object test2 { val x = foobar }

Output

In the console:

scala> example.test1.x
val res0: String = foo

scala> example.test2.x
val res1: String = foo

Expectation

Ideally the calls to foobar would be scoped properly in each file but I'm guessing that's hard, so at least a compiler warning or error would be nice.

@mrdziuban mrdziuban added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 5, 2025
@som-snytt
Copy link
Contributor

For a moment, I believed.

scala> example.test1.x
val res0: String = foo

scala> example.test2.x
val res1: Int = 1

scala>

That is separate compilation and on the REPL classpath of 3.7.

@tgodzik
Copy link
Contributor

tgodzik commented Mar 5, 2025

I do see the same issue with latest nightly and with 3.6.4-RC2.

@som-snytt
Copy link
Contributor

som-snytt commented Mar 5, 2025

@tgodzik How to reproduce? I get the same result with scala-cli repl --server=false -S 3.6.3 -Vprint:typer -cp ~/sandbox.

@tgodzik
Copy link
Contributor

tgodzik commented Mar 5, 2025

Image

And the code is exactly the same as in the issue.

Both methods seem to be present, but the example.foobar reference seems ambiguous.

@som-snytt
Copy link
Contributor

som-snytt commented Mar 5, 2025

@tgodzik Thanks! I don't normally use scala-cli that way. I get the problem now.

(Same result with regular joint compilation. The private within is not a repl artifact.)

TIL it's intended that top-level defs from different files can be overloaded.

This is not an overload: <SingleDenotation of type ExprType(AndType(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class scala)),object Predef),type String),TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)))>

I remember previous discussion on a ticket about the semantic of top-level private, but I incorrectly assumed I correctly intuited what was intended. Now I think explicit private[p] should be required for current behavior.

@Gedochao Gedochao added area:overloading and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 6, 2025
@som-snytt
Copy link
Contributor

FWIW, lookup in typer has an exclusion for opaque types (where opaque scope really is the package object). I would expect lookup to prefer my local package object, but in the presence of overloads, what I really mean is that if there is a competing symbol, prefer my local package object as a tie-breaker. (That does not seem stranger as a special rule than relaxed imports that make extension methods more useful or tractable.)

@jchyb
Copy link
Contributor

jchyb commented Mar 7, 2025

When compiling test1.scala and test2.scala separately the issue does not appear, so it seems like it's only a problem with choosing the correct reference or creating the denotation (and not with tasty or anything more serious).

@jchyb
Copy link
Contributor

jchyb commented Mar 7, 2025

Oh, but even if we choose to use the more-local reference, the problem will still happen for other files which do not have their own local ref:

package example
private def foobar: String = "foo"
object test1 { val x = foobar }
package example
private def foobar: Int = 0
object test2 { val x = foobar }
package example:
  object test3:
    def x = foobar // which one do we choose?

@main def main() =
  println(example.test1.x)
  println(example.test2.x)
  println(example.test3.x)

We could scope the private for file (instead of package), but that's a little arbitrary, especially that private objects in packages are scoped across different files (and having multiple of those in the same package throws an error). Also, vals have the same problems as defs here. What I'm trying to say here is that I think we should error this out.

@jchyb
Copy link
Contributor

jchyb commented Mar 7, 2025

Ok, via reference (https://docs.scala-lang.org/scala3/reference/dropped-features/package-objects.html):
A private top-level definition is always visible from everywhere in the enclosing package. If several top-level definitions are overloaded variants with the same name, they must all come from the same source file.

So it should definitely error out, like it already does when two of the same names with private[package] are used.

@som-snytt
Copy link
Contributor

Oh good. I didn't check the spec, I only misread a code comment in Typer.

@jchyb
Copy link
Contributor

jchyb commented Mar 7, 2025

This remains true if the 2 first files are compiled separately, regardless of the access modifier used, unfortunately

@som-snytt
Copy link
Contributor

Because we are now "aware" of the package object due to opaque types, I might have preferred a spec change, with private meaning private to the package object, and qualified private for current behavior. (Note to self when I read this later.)

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