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

Allow toplevel definitions #5754

Merged
merged 48 commits into from Feb 4, 2019
Merged

Allow toplevel definitions #5754

merged 48 commits into from Feb 4, 2019

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Jan 20, 2019

With this PR, we now allow arbitrary definitions at the toplevel. This means package objects are now redundant, and will be phased out.

@odersky odersky added stat:wip and removed stat:wip labels Jan 20, 2019
@Glavo
Copy link
Contributor

Glavo commented Jan 22, 2019

Current implicit classes must be defined inside of another, but it can be defined in the package object, will lift this restriction in this pull requests?

@biboudis biboudis requested review from smarter and sjrd January 23, 2019 15:17
@odersky odersky self-assigned this Jan 23, 2019
@biboudis biboudis assigned smarter and unassigned smarter Jan 23, 2019
@odersky
Copy link
Contributor Author

odersky commented Jan 23, 2019

Current implicit classes must be defined inside of another, but it can be defined in the package object, will lift this restriction in this pull requests?

Yes. Implicit classes and implicit objects can now be defined at the toplevel.

@therealcisse
Copy link
Contributor

With this, will we be getting zero parameter implicit classes instead of having to write a class and a implicit function?

@odersky
Copy link
Contributor Author

odersky commented Jan 24, 2019

With this, will we be getting zero parameter implicit classes instead of having to write a class and a implicit function?

The plan is to drop implicit classes altogether. See #5458.

Copy link
Member

@sjrd sjrd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked too much at the implementation, as I don't think I'm qualified. For tests, overall I'm pleased. Here are a few more suggestions for things that could wrong:

  • Top-level val X/def X in one file, and a class X in another file
  • Same with an object X in another file
  • Top-level type X in one file, and a class X, object X or val x in another file.

I'd like to know what would happen if we have a top-level def main(Array[String]): Unit. Can that work as a JVM main method? If yes, what's the incantation to invoke it? If not, at least we should emit a warning at the definition, because users are bound to try that. In either case there should be an appropriate test.

The documentation should clearly highlight that the enclosing file name becomes significant at the binary level. Changing a file's name is now a binary incompatible change if it contains any top-level definition. We should not lie by omission of saying that.

I also believe the documentation should at least talk about the trade-off with the alternative implementation scheme, which is that top-level definitions are put in a class file named after themselves, e.g., val x goes into x$toplevel.class or something like that (overloaded methods would go to the same class file).

tests/run/toplevel-overloads/defs_1.scala Show resolved Hide resolved
tests/run/toplevel-implicits/defs_1.scala Outdated Show resolved Hide resolved
@drdozer
Copy link

drdozer commented Jan 24, 2019

Will we also be able to put opaque types and their companion objects at top-level?

@sjrd
Copy link
Member

sjrd commented Jan 24, 2019

Will we also be able to put opaque types and their companion objects at top-level?

https://github.com/lampepfl/dotty/pull/5754/files#diff-6368aeb78c7b36b5919b14e40a804b34

@odersky
Copy link
Contributor Author

odersky commented Jan 24, 2019

I added the suggested tests.

Regarding main methods, it follows directly from the spec. I put a note in the docs stating this, together with a note on binary compatibility.

I am not really sure we need a warning. What would such a warning say, anyway? It feels like too much hand-holding to me.

@odersky
Copy link
Contributor Author

odersky commented Jan 24, 2019

Regarding the implementation scheme, it felt most straightforward to do it this way.

I considered the alternative of naming toplevel files after definitions. Some challenges with this scheme would be:

  • how to handle overloading? Mangle overloaded signatures (unappealing)? Or combine all overloaded variants in one class file? But then we'd have to put in place and check restrictions that overloaded variants must all come from the same file.

  • how to handle type/term name matches? Encode type/term-ness in the name of the file? If we do not do that we face again the problem that different sources might define parts of the same logical output file.

  • how to handle side effects in initializations? This one is the killer, IMO. In the current scheme, you know what objects things get wrapped in. The first time you access a toplevel member of a source file, all definitions in that file get initialized, which might have side effects. If there is an elaborate scheme to put each definition group into its separate file, we have to explain precisely how that works so that side effects are predictable. So the implementation scheme cannot be hidden, it becomes the spec. And that spec might be quite surprising for instance in

    var a: Int = 1
    val b = { a += 1; a }
    val c = a

it looks like c would have the value 2, but since the implementation scheme would make all
val's effectively lazy it could also be 1.

Overall, it seemed more straightforward to just pick the name of the source. It also means we can re-use almost all of the existing package object infrastructure, so the added implementation effort was quite low.

@smarter
Copy link
Member

smarter commented Jan 24, 2019

/cc @lihaoyi, would be interesting to know if this covers all the usecases for the top-level definitions support in https://ammonite.io/#ScalaScripts currently.

@lihaoyi
Copy link
Contributor

lihaoyi commented Jan 24, 2019

@smarter some, but not all.

The main thing that Ammonite scripts provide on top of this is configurable wrapping modes, e.g. :

  • The default REPL uses more-or-less this wrapping scheme, but with additional methods being injected into the wrapper for pretty-printing & stuff.

  • Class-based wrapping is necessary sometimes to make Futures, parallel collections, or Spark work top level IIRC and is used by the Ammonite-Jupyter integration Almond. Notably, class-based wrapping also breaks value classes (which cannot be nested) and so cannot be the default.

  • Mill uses a slightly different wrapping scheme to make sure the top-level object inherits from the relevant mill.Module trait so it can be treated as one. Mill also injects a few implicits into the wrapper object

Another thing that Ammonite scripts provide is automatic package inference: the JVM package path of the script's compilation unit is inferred based on the path of the script relative to the user's working directory or current script, more or less. This isn't fully specced out, and definitely has some holes as currently implemented, but it can be cleaned up with a bit of effort and is of great convenience. The equivalent here would be to have .scala files live in a source-root, rather than being passed to the compiler individually, so their path relative to source-root would be relevant. This isn't how Java or Scala traditionally worked, but other languages like Python, Go, etc. have used this scheme forever and it works quite well. In fact, IntelliJ already is aware of the concept of source-roots, and will highlight any file whose package declaration does not match the relative path from the relevant source root.

It's not clear to me that we want the default Scala compiler to have the flexibility to do all this stuff, but I would definitely be happy to drop Ammonite's custom logic in favor of upstream support in the Scala compiler where-ever possible

@@ -954,6 +954,36 @@ object desugar {
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
}

/** Group all definitions that can't be at the toplebel in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toplebel -> toplevel

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** Group all definitions that can't be at the toplebel in
/** Group all definitions that can't be at the toplevel in

@soronpo
Copy link
Contributor

soronpo commented Jan 25, 2019

I think top-level final values should be tested as well.

@odersky
Copy link
Contributor Author

odersky commented Jan 25, 2019

The work on private definitions includes a simplification step: private for members of a package p is now translated to private[p]. There should be no way to distinguish the two, so it's better to have only one representation. The previous attempt to treat package private more like Java's default visibility #3735 has been reverted. 731c55a explains why.

@odersky
Copy link
Contributor Author

odersky commented Jan 25, 2019

That's all from my side.

@odersky
Copy link
Contributor Author

odersky commented Jan 25, 2019

test performance please

@odersky
Copy link
Contributor Author

odersky commented Feb 1, 2019

Rebased on top of #5825.

@odersky
Copy link
Contributor Author

odersky commented Feb 1, 2019

@smarter Do you still plan to review this? I want to merge this over the weekend.

@@ -954,6 +954,36 @@ object desugar {
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
}

/** Group all definitions that can't be at the toplebel in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** Group all definitions that can't be at the toplebel in
/** Group all definitions that can't be at the toplevel in

compiler/src/dotty/tools/dotc/ast/Desugar.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/ast/Desugar.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/ast/Desugar.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/typer/Typer.scala Outdated Show resolved Hide resolved
Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The revert of #3735 reintroduces the issue described in #3392 (comment) where code that works in join compilation fails with an error in separate compilation, I think the only way to avoid this and keep the behavior of this PR would be to emit all classes as public like scalac does.

@odersky
Copy link
Contributor Author

odersky commented Feb 2, 2019

The revert of #3735 reintroduces the issue described in #3392 (comment) where code that works in join compilation fails with an error in separate compilation, I think the only way to avoid this and keep the behavior of this PR would be to emit all classes as public like scalac does.

private for package members is widened to private[this], which is erased to public.

@smarter
Copy link
Member

smarter commented Feb 2, 2019

private for package members is widened to private[this], which is erased to public.

Ah indeed, but I can still reproduce the separate compilation issue described in #3392 (comment), when compiling separately B.scala fails with not found: type A

Change scheme how sourcenames are computed from filenames to account for filenames
that have additional `.`s in them. Fix comments.
@odersky
Copy link
Contributor Author

odersky commented Feb 2, 2019

Ah indeed, but I can still reproduce the separate compilation issue described in #3392 (comment), when compiling separately B.scala fails with not found: type A

I could not reproduce that. I added a test (i3339/*.scala) to run.

@smarter
Copy link
Member

smarter commented Feb 2, 2019

Ah it does work indeed, good to have the test.

Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, though this PR depends on #5825 which hasn't been reviewed yet.

@odersky odersky merged commit cd3b1ed into scala:master Feb 4, 2019
@allanrenucci allanrenucci deleted the add-toplevel branch February 4, 2019 22:13
@agilesteel
Copy link

It is currently possible to mix a trait into a package object. How would one accomplish something similar when package objects are dropped? Will it no longer be possible?

@smarter
Copy link
Member

smarter commented Mar 5, 2019

@agilesteel It won't be possible indeed, but this is unrelated to this PR, for discussing dropping package objects see scala/scala-dev#441 and scala/scala#7662

tanishiking added a commit to tanishiking/dotty-error-index that referenced this pull request Apr 14, 2022
Adding `// LEGACY` to 061.
It seems like TopLevelCantBeImplicit is no longer the case thanks to
scala/scala3#5754

This is actually confirmed in https://github.com/lampepfl/dotty/blob/93fc41fcb624df73cc12d52b79d518a30a778a7c/tests/run/toplevel-implicits/a.b.scala#L19-L21
tanishiking added a commit to tanishiking/dotty-error-index that referenced this pull request Apr 14, 2022
Adding `// LEGACY` to 061.
It seems like TopLevelCantBeImplicit is no longer the case thanks to
scala/scala3#5754

This is actually confirmed in https://github.com/lampepfl/dotty/blob/93fc41fcb624df73cc12d52b79d518a30a778a7c/tests/run/toplevel-implicits/a.b.scala#L19-L21
tanishiking added a commit to tanishiking/dotty-error-index that referenced this pull request Apr 14, 2022
Adding `// LEGACY` to 061.
It seems like TopLevelCantBeImplicit is no longer the case thanks to
scala/scala3#5754

This is actually confirmed in https://github.com/lampepfl/dotty/blob/93fc41fcb624df73cc12d52b79d518a30a778a7c/tests/run/toplevel-implicits/a.b.scala#L19-L21
tanishiking added a commit to tanishiking/dotty that referenced this pull request Apr 14, 2022
It seems like TopLevelCantBeImplicit is no longer the case as of scala#5754
And it is actually confirmed in https://github.com/lampepfl/dotty/blob/93fc41fcb624df73cc12d52b79d518a30a778a7c/tests/run/toplevel-implicits/a.b.scala#L19-L21

This commit removes the unnecessary check in from Checking.scala
and deleted the `ErrorMessage` definition for `TopLevelCantBeImplicit`.

I'm leaving the `TopLevelCantBeImplicitID` in `ErrorMessageID.scala` so
we don't screw up the error number.
tanishiking added a commit to tanishiking/dotty that referenced this pull request Apr 22, 2022
It seems like TopLevelCantBeImplicit is no longer the case as of scala#5754
And it is actually confirmed in https://github.com/lampepfl/dotty/blob/93fc41fcb624df73cc12d52b79d518a30a778a7c/tests/run/toplevel-implicits/a.b.scala#L19-L21

This commit replace the unnecessary check in from Checking.scala to
assertion and deleted the `ErrorMessage` definition for `TopLevelCantBeImplicit`.

I'm leaving the `TopLevelCantBeImplicitID` in `ErrorMessageID.scala` so
we don't screw up the error number.
michelou pushed a commit to michelou/dotty that referenced this pull request Apr 25, 2022
It seems like TopLevelCantBeImplicit is no longer the case as of scala#5754
And it is actually confirmed in https://github.com/lampepfl/dotty/blob/93fc41fcb624df73cc12d52b79d518a30a778a7c/tests/run/toplevel-implicits/a.b.scala#L19-L21

This commit replace the unnecessary check in from Checking.scala to
assertion and deleted the `ErrorMessage` definition for `TopLevelCantBeImplicit`.

I'm leaving the `TopLevelCantBeImplicitID` in `ErrorMessageID.scala` so
we don't screw up the error number.
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

Successfully merging this pull request may close these issues.

None yet