Yes, and I got bit by a shark when I went into those waters
Do we need it? I am not sure. It may have been just my module caching implementation. Remember in that email when I said it should be relatively straight forward? HA! The problem is there are gaps in my knowledge of a lot of this stuff. Here's what I tried...
The parsing service in MonoDevelop runs in a different thread than the completion extension does. You need a compiler instance to parse the code and you need access to the fully-bound AST from within the completion extension if you want to do proper completion. I guess technically you don't "need" them but I didn't feel like writing a new parser. We might need to anyways but more on that Saturday.
What I did was create a CobraModuleCache class with multiple shared dictionaries that mapped fileNames to modules. There was one dictionary for unbound modules and another for bound modules.
The parser would add an entry to the unbound module map after it parsed a file. When the cache saw a new unbound module arrive, it would determine if there were enough modules to try and run the binding phases and if so, add those bound modules (do you only need one?) to the bound map.
Meanwhile, the completion extension says "Hey, cache, give me the module for filename Foo.cobra cause the user just typed the letter 'C'" The cache would check if a bound module was available, if not, it would check to see if there were enough unbound modules to try and run the binding phases, if not it would return the unbound module, and if there was no unbound module, it would ask the parser to create one. If the parser couldn't generate one, then it tells the completion extension, "Sorry, instead of an ast you get nil".
I was locking on the dictionaries correctly, but the problem, as you pointed out, was that there are some shared vars that clobbered the different phases as they were running concurrently. Also, any time a new compiler instance is created Node.reset gets called which screws up any phases that were being run by a different compiler instance. I tried to workaround this by having both threads share the same compiler instance and "reset it manually" when necessary but this seemed to open some kind of vortex of SourceExceptions from which there was no escape.
I ended up scrapping that code and am just providing some Cobra keywords and using (or will be by Friday) unbound ASTs, aka parse trees, for local member and method completion because without type information, that's about all that can be done.
Did any of that make sense?