Skip to content
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

Toplevel definitions in forgotten class files break compilation #7650

Closed
odersky opened this issue Dec 1, 2019 · 1 comment
Closed

Toplevel definitions in forgotten class files break compilation #7650

odersky opened this issue Dec 1, 2019 · 1 comment

Comments

@odersky
Copy link
Contributor

odersky commented Dec 1, 2019

Toplevel definitions have the downside that they can accumulate on the output path and influence subsequent compilations in unexpected ways. Here's an example:

First, compile file A.scala

@main def Test() = println("test")

Next, compile file B.scala

object Test

If the output directory is on the class path, this gives an error indicating that Test needs to be applied to (). What happens is that the compiler will load and merge the denotations of Test in both A.scala and B.scala and then will get confused since it thinks Test is a method.

The problem is more general: Whenever there are several files with toplevel definitions, these definitions go into separate wrapper objects that are named after their file names. This means that compiling one file with toplevel definitions does not invalidate the definitions in another file. This is normally intended, but it does mean that "junk" in long forgotten class files can come back in unexpected ways. For instance, if one has first toplevel definitions in a file A.scala, and then renames the file to B.scala, possibly with with some changes, the definitions of A and B will compete as overloaded variants with each other, as long as A$package.class is on the classpath.

The normal way to mitigate the problem is to clean compile whenever a file containing toplevel definitions is renamed. But there's one situation where this is impractical: Toplevel definitions in the empty package. These are created by most simple tests, all the time. It is impractical to require a clean compile before compiling an additional test.

I believe it's better to hide toplevel definitions in the empty package from being visible in other compilation units, unless both units are compiled together. Otherwise we are simply too vulnerable of getting junk in the system that can cause mysterious compilation errors.

Another, more complicated fix would be to drop shadowed toplevel definitions looking at file creation dates. That is, if we have two toplevel definitions of method foo with matching signatures in two different wrappers, go back to the wrapper's associated files and get the file creation date of each. Only keep the method in the newer file. This protects against conflicting versions of foo, but not against overloading. We'd still have the problem if we have toplevel definitions like this:

def foo(x: Long): Unit = ...
val bar = foo(1)

and then compile them with a junk class file that contains a definition of foo over Int. One could make the filtering even more aggressive by dropping all same-named methods independent of their signatures, but that would mean that we cannot define overloaded variants of existing methods
in new toplevel definitions anymore. So, no easy solution in sight...

odersky added a commit to dotty-staging/dotty that referenced this issue Dec 1, 2019
Don't load toplevel definitions in the empty package from separate files.
Only take them into consideration if they are compiled in the same run.
odersky added a commit to dotty-staging/dotty that referenced this issue Dec 1, 2019
@odersky
Copy link
Contributor Author

odersky commented Dec 2, 2019

I settled on the solution to invalidate toplevel definitions with the same name as a later one, which does outlaw overloaded toplevel definitions in several files. I think that one has the least downside of all considered alternatives.

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

No branches or pull requests

1 participant