The saga continues. Last week I thought I’d shaved the yak, but this week I found it still very hairy. Last week, I got closures in closures to get past the inference stage, but silly me, I didn’t actually try to compile or run them. Haha. Turns out they still don’t work.
This Sunday I took another crack at creating view closures in Shatner. That’s where I discovered this little mess. Shatner is a great test bed for Mirah because it does crazy things with closures and macros. It’s also kind of irritating to debug because it does crazy things with closures and macros.
My test app looked like this:
I was trying to build closures for the view so it could share variable scope with the responding block. This particular example is pretty dumb–it doesn’t even have any variables to share. But it didn’t work and gave me the following stacktrace.
The thing that caused the error on lib/mirah/ast/scope.rb#L164 shouldn’t have been nil, that we needed a name from, was a binding.
Bindings are how Mirah shares state between closures. It’s pretty neat how it works actually. The compiler determines what local variables are shared between the outer scope and the closure and creates a binding class to hold them. Then both the outer and inner scope use the binding object instead of local variables. That’s how it’s supposed to work anyway.
What Mirah was trying to do, was to ask the closure’s scope’s defining class for a binding type–a class definition for the shared binding, but the scope didn’t have a defining class. The scope of the closure was the static scope for SomeAppWithAnUnmacroedView, which didn’t have a defining_class because it’s the wrong sort of scope to have one. I think that a static scope for a class body doesn’t have a defining_class because it doesn’t belong to an instance of a class, but I’m not exactly sure.
SomeAppWithAnUnmacroedView’s scope was the wrong scope because the block had been moved to the class’s initializer by Shatner. The get macro takes the passed block and appends it into the initializer. Moving it caused a problem because Mirah currently caches the scope of a scoped node to avoid having to do look up every time. This is fine usually, but because the macro had moved the block its scope should have changed. I unmemoized the scope by changing ||= to = to see what would happen (lib/mirah/ast/scope.rb#L20) .
We’re making progress. Now the error happens in a different place. ScopedBody nodes don’t have a defining_class method. Maybe they should. I tried adding one, following the pattern I’d seen elsewhere.
And got a new stacktrace. This is great–it made it past inference and tried generating bytecode. And failed. But we’re still moving forward. Where the exception is raised, it looks like we’re trying to get a binding out of a hash, but the hash is nil (lib/mirah/jvm/compiler/jvm_bytecode.rb#L529). Going up a level, we’re asking the parent compiler for the binding with the passed name and we’re not finding it (lib/mirah/jvm/compiler/jvm_bytecode.rb#L827
) . Why? The parent is another ClosureCompiler and they don’t have bindings on them.
So I added bindings to the closure compiler by calling super. The Base compiler class creates the binding hash used by most things, so I thought I just needed closures to do that too.
Hooray it compiled! And we got a new and interesting error!
The new problem is that the code being generated is invalid. Ugh. Regenerating with -j shows us that something’s wrong with how things are organized. Argh!
And that’s as far as I got this week. I learned a quite a lot more about Mirah’s closure support and lack there of and got pretty far just following the stacktraces. I think I’ll do some more digging next week.