-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Separate compilation issue because PrivateParamAccessor are only made non-private in TreeTransformer, not DenotTransformer #2099
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
Comments
A possible fix would be not make paramaccessors private, but name-mangle them instead while keeping them public. |
@DarkDimius I'm not sure I understand what you mean. This is what the |
Yes, I think making them not private to start with is the way to go. |
@odersky OK, should |
Previously, in the following code: class A(val member: Int) class SubA(member: Int) extends A(member) We created a forwarder in SubA like this: private[this] def member: Int = super.member Since a private method cannot have the same name as an inherited non-private one, we subsequently name-mangled the forwarder in ExpandPrivate. However, this was only done in the TreeTransformer, not in the DenotTransformer, this lead to separate compilation issues (see added testcase). Since we cannot detect that a ParamAccessor is a forwarder in a DenotTransformer, we cannot fix the problem locally in ExpandPrivate. Instead, we change ParamForwarding so that the forwarder now looks like: override def member: Int = super.member To make this work we restrict ParamForwarding to only create forwarders for non-final fields.
Making param forwarders override the param they forward like we did in the previous commit changes semantics. Instead, we do the same thing that ExpandPrivate previously did, but early in ParamForwarding, so separate compilation still works.
Making param forwarders override the param they forward like we did in the previous commit changes semantics. Instead, we do the same thing that ExpandPrivate previously did, but early in ParamForwarding, so separate compilation still works.
Previously, in the following code: class A(val member: Int) class SubA(member: Int) extends A(member) We created a forwarder in SubA like this: private[this] def member: Int = super.member Since a private method cannot have the same name as an inherited non-private one, we subsequently name-mangled the forwarder in ExpandPrivate. However, this was only done in the TreeTransformer, not in the DenotTransformer, this lead to separate compilation issues (see added testcase). Since we cannot detect that a ParamAccessor is a forwarder in a DenotTransformer, we cannot fix the problem locally in ExpandPrivate. Instead, we change ParamForwarding so that the forwarder now looks like: override def member: Int = super.member To make this work we restrict ParamForwarding to only create forwarders for non-final fields.
Making param forwarders override the param they forward like we did in the previous commit changes semantics. Instead, we do the same thing that ExpandPrivate previously did, but early in ParamForwarding, so separate compilation still works.
…edType denot ParamForwarding creates the following forwarder in B: private[this] def member: Int = super.member Where the type for `super.member` is `TermRef(SubA, member)` and the symbol is the `val member` in `A`. So far this is correct, but in later phases we might call `loadDenot` on this `TermRef` which will end up calling `asMemberOf`, which before this commit just did: prefix.member(name) This is incorrect in our case because `SubA` also happens to have a private `def member`, which means that our forwarder in B now forwards to a private method in a superclass, this subsequently crashes in `ExpandPrivate`. (Note: in the bytecode, a private method cannot have the same name as an overriden method, but this is already worked around in EnsurePrivate.) The fix is simple: when we recompute a member, we should only look at private members if the previous denotation was private.
…edType denot ParamForwarding creates the following forwarder in B: private[this] def member: Int = super.member Where the type for `super.member` is `TermRef(SubA, member)` and the symbol is the `val member` in `A`. So far this is correct, but in later phases we might call `loadDenot` on this `TermRef` which will end up calling `asMemberOf`, which before this commit just did: prefix.member(name) This is incorrect in our case because `SubA` also happens to have a private `def member`, which means that our forwarder in B now forwards to a private method in a superclass, this subsequently crashes in `ExpandPrivate`. (Note: in the bytecode, a private method cannot have the same name as an overriden method, but this is already worked around in EnsurePrivate.) The fix is simple: when we recompute a member, we should only look at private members if the previous denotation was private.
…edType denot ParamForwarding creates the following forwarder in B: private[this] def member: Int = super.member Where the type for `super.member` is `TermRef(SubA, member)` and the symbol is the `val member` in `A`. So far this is correct, but in later phases we might call `loadDenot` on this `TermRef` which will end up calling `asMemberOf`, which before this commit just did: prefix.member(name) This is incorrect in our case because `SubA` also happens to have a private `def member`, which means that our forwarder in B now forwards to a private method in a superclass, this subsequently crashes in `ExpandPrivate`. (Note: in the bytecode, a private method cannot have the same name as an overriden method, but this is already worked around in EnsurePrivate.) The fix is simple: when we recompute a member, we should only look at private members if the previous denotation was private.
…edType denot ParamForwarding creates the following forwarder in B: private[this] def member: Int = super.member Where the type for `super.member` is `TermRef(SubA, member)` and the symbol is the `val member` in `A`. So far this is correct, but in later phases we might call `loadDenot` on this `TermRef` which will end up calling `asMemberOf`, which before this commit just did: prefix.member(name) This is incorrect in our case because `SubA` also happens to have a private `def member`, which means that our forwarder in B now forwards to a private method in a superclass, this subsequently crashes in `ExpandPrivate`. (Note: in the bytecode, a private method cannot have the same name as an overriden method, but this is already worked around in EnsurePrivate.) The fix is simple: when we recompute a member, we should only look at private members if the previous denotation was private.
…s-take-2 Alternative fix for #2099: avoid loading a private member when recomputing a NamedType denot
A.scala
:B.scala
:If you try to compile these two files together everything works fine, but if you first compile
A.scala
and thenB.scala
you get:The issue is that
ExpandPrivate
only transformsPrivateParamAccesor
in trees:So when we compile
A.scala
andB.scala
together, the denotation forSubA#member
gets transformed as expected, but when we do separate compilation, we seeSubA#member
as private, soExpandPrivate
explodes.Note: see the comment at the top of
ExpandPrivate
if you're wondering why private param forwarders need to be made non-private.I'm unsure how to fix this since it seems that we need access to the tree to decide whether or not the param forwarder should be made non-private. Maybe we need an extra flag to signal that a param forwarder forwards to an inherited param accessor with the same name?
This issue is blocking the Super Bootstrap
The text was updated successfully, but these errors were encountered: