Skip to content

Bullet-proofing companion objects #69

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

Merged
merged 2 commits into from
Mar 16, 2014

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Mar 13, 2014

Companion class/module computations now also work for local classes and modules. For this to work,
either one of two conditions must be met: (1) some enclosing context refers to a scope that
contains the companions. (2) the context's compilation unit has a typed tree that contains the
companions. (1) is usually true when type-checking, (2) when transforming trees.

Local companions are searched as follows: If (2) holds, we locate the statement sequence containing
the companions by searching down from the root stored in the compilation unit. Otherwise, we search
outwards in the enclosing contexts for a scope containing the
companions.

Companion class/module computations now also work for local classes and modules. For this to work,
either one of two conditions must be met: (1) some enclosing context refers to a scope that
contains the companions. (2) the context's compilation unit has a typed tree that contains the
companions. (1) is usually true when type-checking, (2) when transforming trees.

Local companions are searched as follows: If (2) holds, we locate the statement sequence containing
the companions by searching down from the root stored in the compilation unit. Otherwise, we search
outwards in the enclosing contexts for a scope containing the
companions.
@odersky
Copy link
Contributor Author

odersky commented Mar 13, 2014

Review by @DarkDimius @retronym

Dotty currently cannot parse @unchecked annotations in pattern types. That's why the previous commit failed. We need to come back to this and fix it. For the moment, to make on the current branch, the annotation is removed.
case tree: Tree => // Dotty problem: cannot write Tree @ unchecked, this currently gives a syntax error
if (definedSym(tree) == sym) tree :: Nil
else if (tree.envelope.contains(sym.pos)) {
val p = search(tree.productIterator)
Copy link
Member

Choose a reason for hiding this comment

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

This looks a bit dicey to me. Could this search be expressed with a regular TreeAccumulator, customized to prune based on positions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's an interesting idea. But it would affect more code than just this one. I.e. compare with Positioned#contains, which is used in Inferencing to determine whether a type variable may be instantiated. So I prefer delegating a change like this to a separate commit.

Copy link
Member

Choose a reason for hiding this comment

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

The duplication with Positioned#contains makes it all the more worthwhile to either factor out this productIterator based approach or replace it with a TreeAccumulator :)

Copy link
Member

Choose a reason for hiding this comment

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

Here's a stab at the refactoring: #76

@retronym
Copy link
Member

we locate the statement sequence containing the companions by searching down from the root stored in the compilation unit.

What about a Transformer that, say, duplicates method bodies (like specialization). The new symbols for the class/module would not be locatable in unit.root.

scalac blows up before it gets to that point:

class C[@specialized(Int) X] {
  def foo(x: X) = {
    class C {
       private def y: Any = C.x
    }
    object C {
      private def x: Any = new C().y
    }
    (y: Int) => x
  }
}
% scalac-hash v2.11.0-RC1 -no-specialization sandbox/test.scala
% scalac-hash v2.11.0-RC1 sandbox/test.scala
sandbox/test.scala:6: error: C is already defined as class C
    object C {
           ^
one error found

@DarkDimius
Copy link
Contributor

LGTM

odersky added a commit that referenced this pull request Mar 16, 2014
@odersky odersky merged commit 027abb4 into scala:master Mar 16, 2014
@DarkDimius
Copy link
Contributor

What about a Transformer that, say, duplicates method bodies (like specialization). The new symbols for the class/module would not be locatable in unit.root.

@retronym why would they, AFAIK specialization creates new symbols with custom suffix, and they enter scope of their owner, amirite?

@retronym
Copy link
Member

Compiling with -optimise eliminates the closure, we rely on that in scalac.

* if no such path exists.
* Pre: `sym` must have a position.
*/
def defPath(sym: Symbol, root: Tree)(implicit ctx: Context): List[Tree] = ctx.debugTraceIndented(s"defpath($sym with position ${sym.pos}, ${root.show})") {
Copy link
Member

Choose a reason for hiding this comment

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

It seems that this code is never executed by current tests. I guess that's expected as we don't have transformer phases calling companionModule right now, but it would be good to mention that sort of thing explicitly in the pull request.

@retronym
Copy link
Member

@DarkDimius The part of specialization I was referring to is Duplicators.{retyped, BodyDuplicator}. That's actuallly expressed as a Typer, rather than a simple transformer, so it wouldn't go through the defPath route in dotty.

I can't actually think of any places in scalac that would need to call companionModule on local classes in a transformer phase. extmethods only works on top-level classes. Maybe if you deferred the full translation of names/defaults a bit, you would hit it, as constructor defaults end up in the companion object.

@DarkDimius
Copy link
Contributor

FYI: I'm implementing lazyvals transformer in dotty and it uses companionModule to store field offsets to be able to invoke Unsafe.compareAndSwapLong.
So, at least this implementation, will indeed call companionModule on local classes.

@retronym
Copy link
Member

Okay. So long as the symbols for the class/module exist in the tree that was fed into the phase, this approach ought to work. But if you ended up with new symbols for some reason, I think it could be brittle.

@retronym
Copy link
Member

Oh, I guess you might have to will the companion module into existence during that phase, if one doesn't already exist. So you might get into interesting territory.

@retronym
Copy link
Member

Come to think of it, you might be better adding the offsets as static members of the class, rather than to the companion. See the reflection cache added for structural types as an example.

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.

3 participants