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

SIP-68: Reference-able Package Objects #100

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

lihaoyi
Copy link
Contributor

@lihaoyi lihaoyi commented Dec 14, 2024

No description provided.

@kyouko-taiga kyouko-taiga changed the title SIP-XX: Reference-able Package Objects SIP-68: Reference-able Package Objects Dec 17, 2024
Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

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

Overall I am in favor. It clearly improves on the status quo that we can refer to a package object in the natural way, without using the .package encoding.

My only concern is that accepting this proposal would effectively bury the idea of dropping package objects. But on reflection I don't think we will be able to drop package objects anyway for the reasons that were stated here.


## Limitations

* `a.b` only expands to `a.b.package` when used "standalone", i.e. not when part of a
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this limitation is necessary and it would be hard to implement. I think we can simply say that in selection path a.b.c, a.b means the full package, not just the package object. But it's OK to have operators. By the time we check valid package object use, we have already expanded out all these operators to selections.

Comment on lines +54 to +56
Although package objects have been discussed [being dropped](https://docs.scala-lang.org/scala3/reference/dropped-features/package-objects.html)
in Scala 3, no concrete plans have been made as to how to do so, and we argue that they
are sufficiently useful that keeping them around is preferably to dropping them.
Copy link
Member

@smarter smarter Jan 24, 2025

Choose a reason for hiding this comment

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

This means accepting this SIP is conditional on not dropping package object and therefore reworking how we teach top-level definitions, in particular https://docs.scala-lang.org/scala3/book/taste-toplevel-definitions.html# and https://docs.scala-lang.org/tour/package-objects.html need to be adapted. I guess we need to compare and contrast three kind of definitions:

  1. Regular class/object definitions in a package
  2. Top-level definition (including the special case of a companion object to an opaque type which is not treated like case 1)
  3. Package object

These three concepts behave differently with respect to:

  • Regular scope
  • Given scope
  • Accessibility modifiers

For these three cases we need to consider:

  • What happens in the scope where the definition is written
  • What happens in a different scope in the same package

So there's a lot of hidden complexity here, which is currently under-documented, eventually deprecating package objects would have provided some relief here, but if we don't deprecate them I believe we'll need to do a much better job of explaining all these subtleties and providing guidelines on when to use what feature.

Copy link
Contributor

@odersky odersky Jan 24, 2025

Choose a reason for hiding this comment

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

I think we can avoid some of the complexity by changing the approach. The compiler treats package objects and toplevel definitions mostly the same. The only thing to explain is that toplevel definitions are partitioned: Classes and objects are treated as if they live directly in the package and all other definitions are treated as if they live in a synthesized package object. That needs to be explained anyway. (Maybe it is already explained? If not, a PR would be great.) After that, resolution proceeds the same for members of explicit and synthesized package objects.

@sjrd
Copy link
Member

sjrd commented Jan 24, 2025

I have no strong feeling on this proposal. It's a bit sad that we could not actually get rid of package objects, but oh well.

One comment: the proposal argues that it improves regularity: objects and package objects become more the same. While that is true, it also degrades another regularity aspect. Now, a package that has a package object does not behave the same as a package that doesn't have one. If there is a package object, one can use a.b as a value; but if there isn't, we cannot.

I don't think this is a deal breaker, but it should be clearly acknowledged in the proposal, IMO.

@lrytz
Copy link
Member

lrytz commented Jan 24, 2025

Another inconsistency that this proposal introduces: a.b.c (where a.b is a package) can now sometimes be refactored to val x = a.b; x.c. Not if c is defined in the package (a top-level class / object / term definition).

IIUC, the motivation for this proposal is to improve the ergonomics when package objects are deliberately used for building the API of a library. Like package object requests extends requests.BaseSession, which is useful for import requests._ but also a useful first-class value. That seems fine with me, like others said it cements the status of package objects with inheritance in the language. (sbt uses it too).

The Mill use case I don't understand so well as there's additional magic going on in the build tool. I looked a bit at

Again, the SIP seems fine and helpful if we keep package objects. (I wonder why in the examples they are declared as package x; object `package` instead of package object x, and what the magic import $packages._ is.)

@lihaoyi
Copy link
Contributor Author

lihaoyi commented Jan 25, 2025

I have removed the unnecessary limitations, added text mentioning the irregularity brought up by @sjrd and @lrytz, and added open questions related to our discussion about package objects participating in .apply and .type syntax. I think this proposal is good to go now

lihaoyi added a commit to com-lihaoyi/mill that referenced this pull request Feb 4, 2025
Fixes #4459
Fixes #3894

With this PR, all `.mill` files in a folder are in the same flat
`package` namespace, similar to how normal Scala files work. The
previous `import $file` syntax was inherited from Ammonite, and
unfortunately never took off across the Scala community. In effect this
makes `.mill` files behave almost exactly like `.scala` files, which is
part of the longer term goal of removing special handling for `.mill`
files.

The code generation for the helper files is not changed at all in this
PR, rather we just make use of Scala 3's `export` clauses to make them
available under the main `build.mill`/`package.mill` file as well

There is the caveat that the `package object`s in `.mill` files can be
referenced via `build.foo.bar` without the trailing `.package`.
Implementing this caveat is the cause of a lot of the hackiness in the
codegen, which will hopefully go away once the upstream language feature
lands scala/improvement-proposals#100

We leave the codegenned `import _root_.{build_ => $file}` in place so
`import $file` will continue to work to ease in the migration, but are
no longer the recommended way of referencing helper files
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants