-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Disallow overriding val parameters #16096
Conversation
We disallow overriding of val parameters, which fixes the soundness problem discovered in scala#16092. There is one exception: If a val parameter is overridden by another val parameter that can be shown to always have the same value (in the sense established by Paramforwarding.inheritedAccessor). This exception is needed to make a not-so-uncommon pattern of case class inheritance go through. Example: abstract class A(val x: Int) case class B(override val x: Int) extends A(x) case class C(override val x: Int) extends A(x) case object D extends A(0) Here, the `override val`s are necessary since case class parameters are always vals, so they do override the val in class A. It should be noted that the override val generates a second field, so this not a very efficient representation. A better design would be to use an abstract field in `A`: abstract class A { val x: Int } case class B(val x: Int) extends A case class C(val x: Int) extends A case object D extends A { val a = 0 } But that causes slightly more work for cases as in D. Which seems to be why the first pattern is sometimes used. It might be desirable to disallow the second pattern, but that would cause quite a bit of migration hassle since it requires synchronized changes at several places of a class hierarchy.
Currently, the following CB projects have illegal overrides of val parameters - spire - scalaz - specs2 - akka I checked the spire issue and its seems to require a non-trivial refactoring to avoid the problem. More than I could achieve, given that I know nothing of spire. In light of this I think we can enforce the restriction only under -source future and make it a deprecation warning for now.
Currently, the following CB projects have illegal overrides of val parameters
I checked the spire issue and its seems to require a non-trivial refactoring to avoid the problem. In light of this I think we can enforce the restriction only under -source future and make it a |
@sjrd: jsinterop/NonNativeJSTypeTest.scala also has a by-now illegal override, which needs to be fixed. |
In fact, there is a systematic refactoring available, which should work for spire and all other projects. Given a super-class class A(val x: T):
... where class A(_x: T):
val x = _x That works universally. It does not have the same initialization behavior as the superclass, since |
|
The serious issue I see here is for the subclasses. The maintainer of the superclass can address the problem, but it's the maintainer of the subclass who gets the error. When the subclass upgrades to the newer compiler, it is faced with a compile error that it cannot address in any way (let alone a way that preserves its binary compatibility guarantees). Instead of making this an error under future, have you considered making it a warning right away? A warning that says very clearly that it will become an error in the future? The warning could be silenced locally while the issue gets addressed in the superclass. That's the approach we've taken in Scala.js about unsoundness issues. If there was any remote way that it could be valid in some use cases, we made it a warning in some 0.6.x version, and turned it into actual errors in 1.0.0. |
It is already a deprecation warning in this PR. |
Temporarily, always report an error instead of a deprecation warning to verify that projects compile. This will be reverted in the next commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For proper language design, making class parameters effectively final is the right direction to go --- for soundness, but also for semantic clarity 👍
But given that the open PR #16066 is precisely about warning about this dubious behavior we should be safe.
To clarify, the PR #16066 tries to address the same problem as this PR does: overcome problems caused by overriding of val parameters (Issue #15764).
In the issue #15764 , @olhotak and I initially tried to make val parameters final
, but it turns out to cause too many migration problems from empirical study: #15764 (comment).
To make it easier for migration, #16066 allows overriding of val parameters but ensures that:
- A class parameter may only be overridden by a class parameter.
- If a class parameter overrides another class parameter, they should have the same value at the end of the constructor.
In particular, it makes an effort to support secondary constructors & trait parameters. However, as it's just a migration facility, I think the solution in this PR is good enough therefore we do not need #16066.
@@ -264,6 +264,8 @@ object RefChecks { | |||
* 1.10. If O is inline (and deferred, otherwise O would be final), M must be inline | |||
* 1.11. If O is a Scala-2 macro, M must be a Scala-2 macro. | |||
* 1.12. If O is non-experimental, M must be non-experimental. | |||
* 1.13 Under -source future, if O is a val parameter, M must be a val parameter | |||
* that passes its on to O. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some word is missing after its
.
We may want to add a final
flag in the desugaring if sourceVersion.isAtLeast(future)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or we add ParamAccessor
to the set of EffectivelyFinalFlags
. But for the moment we want to treat them separately.
We disallow overriding of val parameters, which fixes the soundness problem discovered in #16092.
There is one exception: If a val parameter is overridden by another val parameter that can be shown to always have the same value (in the sense established by Paramforwarding.inheritedAccessor). This exception is needed to make a not-so-uncommon pattern of case class inheritance go through.
Example:
Here, the
override val
s are necessary since case class parameters are always vals, so they do override the val in class A. It should be noted that the override val generates a second field, so this not a very efficient representation. A better design would be to use an abstract field inA
:But that causes slightly more work for cases as in D. Which seems to be why the first pattern is sometimes used. It might be desirable to disallow the first pattern, but that would cause quite a bit of migration hassle since it requires synchronized changes at several places of a class hierarchy.
Fixes #16092