Skip to content

Conversation

@adriaanm
Copy link
Contributor

@adriaanm adriaanm commented Jan 17, 2019

UPDATE: the deprecation ended up being put behind -Xsource:2.14 (see #7857)

Fix scala/scala-dev#441

Workaround: use a regular object and import it. I consider the explicit import an advantage: it makes it more clear where implicits are coming from. In general, I would not recommend defining implicits in package objects.

Longer term, package objects will be replaced by top-level definitions.

To enable this migration to top-level definitions as a replacement
for package objects, we must first remove the main aspect of
package objects that complicates their implementation and
that would not longer be supported by top-level definitions.

Once that's done, the migration would mean rewriting
package object foo { ... } to package foo { ... },
assuming the ... are exclusively definitions, and not statements.

Losing inheritance for package objects means that some code duplication
will be required (you'll have to add val/type aliases for members,
instead of inheriting the members directly). You also can't use inheritance
to prioritize implicits, but, implicits defined in packages are also
being phased out (i.e., package prefixe don't contribute to the implicit scope).

The main complication with supporting full-on package objects is
that name resolution needs to know all definitions in a package
in order to resolve the parents of said package, and when there's
a cycle there (a package object inherits a class defined in the package),
the implementation becomes challenging due to the open nature of packages.

While we can easily rule out the cycle in the following snippet that
uses plain objects, with package objects it's not so easy:

object Dogs extends Dogs.Animal { // cycle!
  class Animal
}

The spec currently has similar awkwardness in place to deal with this:

The package object should not define a member with the same name as
one of the top-level objects or classes defined in package $p$. If
there is a name conflict, the behavior of the program is currently
undefined. It is expected that this restriction will be lifted in a
future version of Scala.

Finally, package objects are objects in name only -- they already are
not first-class values, and now they also can't extend anything. This
leaves them as simply syntactic baggage. Top-level definitions are
intuitive, require no syntax, and can be implemented in the same way
as package objects behind the scenes, albeit without the issue of cycles.

package object foo

class D {
  println(foo) // error: package foo is not a value
}

@scala-jenkins scala-jenkins added this to the 2.13.0-RC1 milestone Jan 17, 2019
@adriaanm adriaanm requested a review from som-snytt January 17, 2019 13:44
@smarter
Copy link
Member

smarter commented Jan 17, 2019

Based on the discussion in scala/scala-dev#441 this seems to be a pretty controversial change, are you intending to go ahead with this anyway ?

@adriaanm
Copy link
Contributor Author

Yep

@adriaanm
Copy link
Contributor Author

Seriously, though, I read that discussion as having valid pros and cons for deprecation, though with the pros outnumbering the cons.

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.

I read that discussion as having valid pros and cons for deprecation, though with the pros outnumbering the cons.

Please list these pros/cons in the commit message :).

@adriaanm
Copy link
Contributor Author

will do -- in the mean time, here's a beauty: 357905c

@adriaanm
Copy link
Contributor Author

@dwijnand
Copy link
Member

How does

object `package` extends C

behave, instead?

@adriaanm adriaanm changed the title Deprecate package object with extends clause Deprecate package object with extends clause [ci: last-only] Jan 17, 2019
@SethTisue SethTisue added the release-notes worth highlighting in next release notes label Jan 17, 2019
@adriaanm adriaanm force-pushed the depr-pkg-extends branch 2 times, most recently from 5e9acc1 to 9ee9469 Compare January 18, 2019 10:43
@adriaanm adriaanm changed the title Deprecate package object with extends clause [ci: last-only] Deprecate package object with extends clause Jan 18, 2019
@som-snytt
Copy link
Contributor

The best part is that it blows the future wide open to re-introduce package objects as a module system.

Copy link
Contributor

@som-snytt som-snytt left a comment

Choose a reason for hiding this comment

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

OK. People want to take away our cycles, but where would we be without cycle sheds?

val tstart = in.offset
atPos(start, if (name == nme.ERROR) start else nameOffset) {
val template = templateOpt(mods, name, NoMods, Nil, tstart)
val template = templateOpt(mods, if (isPackageObject) nme.PACKAGEkw else name, NoMods, Nil, tstart)
Copy link
Contributor

Choose a reason for hiding this comment

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

scalafmt where are you? SIP-12 where are you? There's always either too many spaces or not enough spaces around a paren. if ( isPO ) ofc. Also ofc we need API for name.orElsePackageIf(b). But I'd be satisfied with deleting one space, if my new computer glasses worked.

Copy link
Contributor Author

@adriaanm adriaanm Jan 25, 2019

Choose a reason for hiding this comment

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

but! who doesn't like more space! (hint: markdown)


// we can't easily check this later, because `gen.mkParents` adds the default AnyRef parent, and we need to warn based on what the user wrote
if (name == nme.PACKAGEkw && parents.nonEmpty) deprecationWarning(tstart, s"package object inheritance is deprecated", "2.13.0")

Copy link
Contributor

Choose a reason for hiding this comment

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

It's a pity it's quite pithy. I wonder if it should say another word, like, "And we're taking package objects away from you entirely in the next release." First they took away my inheritance, then my objects, next there will be no packages at all under the tree. No fun living at the orphanage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I blame the boomers. Anyway, I added some links and verbiage.

To enable migration to top-level definitions as a replacement
for package objects, we must first remove the main aspect of
package objects that complicates their implementation and
that would not longer be supported by top-level definitions.

Once that's done, the migration would mean rewriting
`package object foo { ... }` to `package foo { ... }`,
assuming the `...` are exclusively definitions, and not statements.

Losing inheritance for package objects means that some code duplication
will be required (you'll have to add val/type aliases for members,
instead of inheriting the members directly). You also can't use inheritance
to prioritize implicits, but, implicits defined in packages are also
being phased out (i.e., package prefixe don't contribute to the implicit scope).

The main complication with supporting full-on package objects is
that name resolution needs to know all definitions in a package
in order to resolve the parents of said package, and when there's
a cycle there (a package object inherits a class defined in the package),
the implementation becomes challenging due to the open nature of packages.

While we can easily rule out the cycle in the following snippet that
uses plain objects, with package objects it's not so easy:

```
object Dogs extends Dogs.Animal { // cycle!
  class Animal
}
```

The spec currently has similar awkwardness in place to deal with this:

> The package object should not define a member with the same name as
> one of the top-level objects or classes defined in package $p$. If
> there is a name conflict, the behavior of the program is currently
> undefined. It is expected that this restriction will be lifted in a
> future version of Scala.

Finally, package objects are objects in name only -- they already are
not first-class values, and now they also can't extend anything. This
leaves them as simply syntactic baggage. Top-level definitions are
intuitive, require no syntax, and can be implemented in the same way
as package objects behind the scenes, albeit without the issue of cycles.

```
package object foo

class D {
  println(foo) // error: package foo is not a value
}
```
@neko-kai
Copy link
Contributor

It's a big change that will break a lot of libraries. Shouldn't this go through a SIP?

@adriaanm
Copy link
Contributor Author

The actual change will. Deprecation won't.

@neko-kai
Copy link
Contributor

It's nearly the same thing, though – I doubt it would be 'undeprecated' if the SIP doesn't go through. Seems like a very one-sided decision to me.

@soronpo
Copy link

soronpo commented Jan 25, 2019

It's nearly the same thing, though – I doubt it would be 'undeprecated' if the SIP doesn't go through. Seems like a very one-sided decision to me.

I tend to agree. A mitigation can be if there was a warning suppression mechanism for the library to use so it can use fatal warnings. Deprecating is a way to say "please don't do this/this is not recommended". That is fine, but we need away to reply to the compiler "I agree, but shut up please".

@adriaanm
Copy link
Contributor Author

Sure, I started working on that here
https://github.com/adriaanm/scala/tree/deprconf. Help finishing the job very welcome

@sjrd sjrd mentioned this pull request Jan 30, 2019
51 tasks
@adriaanm adriaanm merged commit 9deac55 into scala:2.13.x Feb 4, 2019
@soronpo
Copy link

soronpo commented Feb 4, 2019

@adriaanm Can you please schedule a 2.13 community-build for this PR?

@adriaanm
Copy link
Contributor Author

adriaanm commented Feb 4, 2019

Since it's merged, the community build will pick it up. I assume you're worried about its effect on warnings, but those won't show up in the community build because we already disable -Xfatal-warnings there in most projects.

@godenji
Copy link

godenji commented Feb 17, 2019

@adriaanm from your reply on the corresponding issue

I think it's actually desirable to have a least an explicit entry point (an import in this case) to understanding where implicits come from

But it's not just implicits, you may have non-implicit methods, vals, objects, etc. that you want in scope at the package level. Having to convert package object extends ... to plain object extends ... and then import that in every source file is honestly a massive pain compared to the wonderous status quo that obviates all this (IMO) needless extra work.

This is going to wreak serious havok, every code base built with -Xfatal-warnings is going to blow up. What the heck, this is just merged into 2.13, no SIP??

@dwijnand
Copy link
Member

They can drop fatal warnings or resolve the deprecation warnings. The compiler development can't be bound by the constraints of fatal warnings.

@som-snytt
Copy link
Contributor

som-snytt commented Feb 17, 2019

Besides using -Yimports to add an "auto-imported predef", I'm following up Adriaan's "filter ALL the things" idea at #7728, so users can keep fatal warnings by either moving a package object to root import position (which might work well for top packages of a library or app) or just whitelisting the warnings.

Note that -Xfatal-warnings will become -Werror, but the old name will still work without itself emitting a deprecation.

(Edit: now I wish it were called, -Xmortal-admonitions.)

@godenji
Copy link

godenji commented Feb 17, 2019

They can drop fatal warnings or resolve the deprecation warnings. The compiler development can't be bound by the constraints of fatal warnings.

Well, the issue is that there's no viable replacement. Top-level definitions aren't yet supported; custom predef is globally scoped (IDE support is probably amazing); plain objects require an import in every source file.

I can appreciate that this change will make life easier for the compiler team, but it's strictly worse for users that have benefited for years from package scoped auto-imports.

@dwijnand
Copy link
Member

Another alternative is writing the forwarders in the package object. But I agree with you those are the tradeoffs for users making use of this feature which is being deprecated.

@SethTisue
Copy link
Member

@godenji thanks for speaking up with your concerns about this, I think the questions you are asking and the points you're bringing up will be of concern to others as well.

no SIP??

well, I think everyone on the SIP committee is aware of these changes, and IIRC this has come up in several meetings...

...but on the other hand, I was a bit surprised not to find top level definitions on the list at scala/scala3#5221, and I've now said so at scala/scala3#5221 (comment)

every code base built with -Xfatal-warnings is going to blow up.

Adriaan and I both agree with @dwijnand that compiler development and the progress of the language can't be bound by the constraints of fatal warnings.

https://github.com/ghik/silencer provides a escape hatch. another possible escape hatch is to enable fatal warnings only on the main Scala version you are targeting (e.g., 2.12)

trying to cross-compile to multiple Scala versions without any warnings at all in any of the versions you are targeting may sometimes be possible for some projects, but making it possible in general isn't really a goal for us, it's far too constraining.

Well, the issue is that there's no viable replacement. Top-level definitions aren't yet supported

good point. @adriaanm is there a reason this deprecation couldn't wait for 2.14? are we deprecating in 2.13 because we want to remove it in 2.14 because there's something in the 2.14 roadmap that conflicts?I'd like to understand this as well.

@SethTisue
Copy link
Member

SethTisue commented Feb 20, 2019

I think everyone on the SIP committee is aware of these changes, and IIRC this has come up in several meetings...

ah, just found scala/scala-dev#441 (comment) where I wrote:

this was discussed by the SIP committee at the SIP meeting this week

unfortunately it seems that the minutes from that meeting were never published; at least, they're missing from https://github.com/scala/docs.scala-lang/tree/master/_sips/minutes (probably a casualty of the changeover in the SIP process lead position, I suppose), but there is a video at https://www.youtube.com/watch?v=dFEkS71rbW8 and the agenda includes "Remove package objects", with a link to scala/scala-dev#441

so essentially scala/scala-dev#441 is the SIP, so it was up for public discussion, and the committee was made aware as well

so process-wise, I think once this is added to the list at
scala/scala3#5221 we can say the process was followed. especially since this is, for now, only a deprecation

@adriaanm
Copy link
Contributor Author

adriaanm commented Feb 20, 2019 via email

@smarter
Copy link
Member

smarter commented Feb 20, 2019

Dotty right now has both package objects with inheritance and top-level objects, so from what I can see there's no urgency to deprecate this.

@Ichoran
Copy link
Contributor

Ichoran commented Feb 20, 2019

There doesn't seem to be any logical reason why package objects' inheritance has to be deprecated in order for top-level definitions to go in in 2.14--or now, for that matter. Even if you use the package object mechanism for TLDs, you just need to mangle names slightly differently and the two can coexist. Yes, there's weird stuff about how the two can look into each other, but you just make some ad-hoc rules to simplify the logic during the period where both coexist. (E.g. allow the visibility to go only one way.)

That the deprecation is going in before the replacement is ready is rather disappointing. That the envisioned replacement isn't actually a full replacement because it doesn't cleanly solve the implicit priority thing (as far as I can tell) is also disappointing.

I understand the inherent problems that package objects have, and look forward to them going away. I haven't liked to have to use that mechanism, and I haven't enjoyed the bugs. But they're here now and work and aid usability. So without a good alternative for inheritance use cases, the deprecation doesn't feel ready to me.

@neko-kai
Copy link
Contributor

I, for one, am NOT looking forward to package objects going away. The fact that top-level is first-class and composable by mixins like any other object is one of the beauties of Scala to me.

@dwijnand
Copy link
Member

Even if you use the package object mechanism, you just need to mangle names slightly differently and the two can coexist. Yes, there's weird stuff about how the two can look into each other, but you just make some ad-hoc rules to simplify the logic during the period where both coexist. (E.g. allow the visibility to go only one way.)

There's lots of wonderfully written detail about that "weird stuff" in gkossakowski/kentuckymule#6 (comment).

@SethTisue
Copy link
Member

SethTisue commented Feb 20, 2019

I, for one, am NOT looking forward to package objects going away

please use scala/scala-dev#441 for any general discussion, so we can keep the discussion here on the pull request focused on 1) the implementation, and 2) the timing of the deprecation

@adriaanm
Copy link
Contributor Author

adriaanm commented Feb 20, 2019 via email

@soronpo
Copy link

soronpo commented Feb 20, 2019

Since it's merged, the community build will pick it up. I assume you're worried about its effect on warnings, but those won't show up in the community build because we already disable -Xfatal-warnings there in most projects.

@SethTisue is it possible to run a community build that does not disable -Xfatal-warnings to get a feel of how exposed we are to this deprecation?

@SethTisue
Copy link
Member

SethTisue commented Feb 20, 2019

is it possible to run a community build that does not disable -Xfatal-warnings to get a feel of how exposed we are to this deprecation?

I think that would give less information than the current setup gives, not more. if we enable -Xfatal-warnings, then almost certainly some low-level project will fail and knock out the rest of the build

I think what you want is just to analyze the log of the existing community build and see how often a specific warning we are interested in (such as this one) crops up. I guess we'd want to make sure -deprecation is enabled build-wide so that we'd always see the specific form of the warning and not just a "there were 37 deprecation warnings" type message. dbuild supports that.

@Ichoran
Copy link
Contributor

Ichoran commented Feb 21, 2019

@dwijnand - Despite the problems, people who have used inheritance with package objects have already made as much peace as there is to be made with those issues. So it still seems plausible that the deprecation could wait until there was something with fewer rough edges to migrate to.

@SethTisue - Getting that deprecation log would be great. I know I use the package object extends pattern a number of times in my code (entirely for implicits, not API exporting), but I can't tell whether I'm an anomaly in that regard.

@smarter
Copy link
Member

smarter commented Mar 13, 2019

New development: #7857

@SethTisue SethTisue removed the release-notes worth highlighting in next release notes label Mar 18, 2019
@SethTisue SethTisue changed the title Deprecate package object with extends clause [REVERTED] Deprecate package object with extends clause Apr 4, 2019
@som-snytt som-snytt changed the title [REVERTED] Deprecate package object with extends clause Deprecate package object with extends clause Oct 5, 2023
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.

Drop package objects with inheritance

10 participants