REPL code review -- Eval
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Fri Sep 18 00:16:33 UTC 2015
On 17/09/15 21:17, Robert Field wrote:
>
> On 09/11/15 08:25, Maurizio Cimadamore wrote:
>> * Eval
>>
>> - just curious - you seem to do a lot of String-based analysis
>> (see example in computeQualifiedParameterTypes()) - why is it not
>> possible to rely on the more robust Element API to do that?
>
> Not sure what you mean by "String-based analysis". The final return
> is a String which is stored in the Snippet for equality comparison
> with incoming Snippets. But all the processing using compiler API
> constructs: a compiler analysis pass is run "AnalyzeTask",
> TreeDissector walks the tree to extract the the MethodType. Then a
> subclass of the compiler's SignatureGenerator converts it to a String.
>
>> I.e. couldn't a snippet corresponding to a declaration have an Element?
>
> Well, it does have an Element intermediate. It is not stored in the
> Snippet because there is no on-going need to analyze the Snippet once
> it is place. I use Type directly because Symbol hiding behind Element
> does not have the functionality I need.
>
>> Or, even more generally, why not just create real AST nodes for wraps?
>
> A high-level design decision was that wraps would be source-to-source
> this is for specification and design-docs. Also, in some cases trees
> do not break the source down sufficiently.
Not sure I understand what you mean here - but I understand this was a
deliberate choice; I just note that, at the end of the day, I'm not sure
if it buys you simplicity.
>
>>
>> - UnresolvedExtractor - it would probably help to use
>> DiagnosticFlag.RECOVERABLE instead of hard-coded strings to filter
>> out resolution error (I also noted this somewhere below). However, I
>> also note how the logic for extracting the unresolved symbol is
>> inherently fragile and dependent on what is the shape of the javac
>> diagnostics.
>
> Using a flag seems better indeed. I see DiagnosticFlag.RESOLVE_ERROR
> which seems to be what matches resolution errors (there is no doc on
> these enum values). Yes? What is DiagnosticFlag.RECOVERABLE?
Sorry - I messed them up - RESOLVE_ERROR is the flag associated with
recoverable errors during annotation processing - bottom line,
annotation processing has a very similar definition of recoverable as
you do.
>
>> I wonder if looking at the analyzed tree might not provide a more
>> robust solution here.
>
> I would not want to replicate javac's error detection logic, but after
> the error is detected I'd have to use trees to find the unresolved if
> I can't depend on the error format.
>
>>
>> - the Range logic is clever - but, again, it seems like a big
>> workaround for the fact that wrapping is defined in terms of strings
>> - not trees; any reason you went down this path?
>
> See above.
>
>>
>> - corralled - I found the Snippet hierarchy underused when it
>> comes to corralling; the task of corralling a snippet is basically
>> performed by Eval through some helper routines - couldn't we have
>> each snippet define how corralling should be done? You go half-way
>> there by having a corral() method in the Snippet base class, but it
>> doesn't do much, other then retrieving whatever value has been set
>> from outside.
>
> Corralling logic is in two parts. In Eval.processMethod() the
> corralled wrap is generated and used as one of the values to create
> the MethodSnippet (currently only methods can be corralled). In
> Eval.declare(), on failing compilation, if a corralled wrap is
> available, it attempts to compile the corralled wrap.
>
> The *Snippet classes present as immutable values, though some
> internal values are mutable. This is an internal immutable value. So,
> it could be computed after creation. But the tree dissection, range,
> and wrapping code has everything in common with Eval, and nothing in
> common with *Snippet.
>
> The corralled compilation logic is independent of Snippet kind.
>
>>
>> - the corralling logic doesn't seem to work on supertypes using FQN:
>>
>> -> class A extends B.Inner { static class Inner { } }
>> | Error:
>> | package B does not exist
>> | class A extends B.Inner { static class Inner { } }
>> | ^-----^
>
>> I would have expected the snippet to wait for B.
>
>
> Hmmm,..
>
> It is a good thing you suggested changing those confusing names. I use
> the old term "corralling" still in the code, which refers to
> RECOVERABLE_DEFINED, which this isn't, but seems it should be
> RECOVERABLE_NOT_DEFINED.
>
> The problem is that the error is not a "compiler.err.cant.resolve"
> error but a "package does not exist" error. It seems, except for
> import that this error should be handled like resolution errors. The
> change to using a flag and more general extractor (as above) should
> address this.
If the flag is set for such errors (finger crossed) - anyway if it's not
I guess we can make it so (but we'd need to consult the annotation
processing gods).
>
>>
>> - the updates of corralled snippets seems to happen at the wrong
>> time - this could lead to chain of symbols that are not usable:
>>
>> -> class D extends E { }
>> | Added class D, however, it cannot be referenced until class E is
>> declared
>>
>> -> class E { D d; }
>> | Added class E, however, it cannot be referenced until class D is
>> declared
>>
>> I guess what happens here is that, since the decision on whether
>> to corral or not is based on the fact that we can compile cleanly ,
>> here we run into troubles. The first snippet obviously cannot be
>> compiled cleanly, so it's corralled; the second one cannot compile
>> cleanly because the first one is not 'defined' - so you get another
>> resolution error and another corralled snippet. Unfortunately, the
>> two snippets taken together form a legal source. Btw, I think the
>> current behavior is preventing stuff like this:
>>
>> -> class A extends B { }
>> | Added class A, however, it cannot be referenced until class B is
>> declared
>>
>> -> class B extends A { }
>> | Added class B, however, it cannot be referenced until class A is
>> declared
>>
>> from looping forever in Eval.declare; perhaps the code could be made
>> more robust by looking explicitly for update loops (i.e. this can be
>> done by keeping a stack of all the updated snippets and see if we
>> encounter the same snippet twice).
>>
>> - on update loops - I guess it is indeed possible to have loops,
>> even with current corralling limitations (by exploiting redefinition):
>>
>> -> class Outer { class Inner extends Foo { } }
>> | Added class Outer, however, it cannot be referenced until class
>> Foo is declared
>>
>> -> class Foo { }
>> | Added class Foo
>>
>> -> class Foo extends Outer { }
>>
>> Update loop check logic is in order I think. Also, interestingly -
>> I'm not able to stop the REPL once I get into such a loop. Actually,
>> later I found out it doesn't loop forever - I guess eventually it
>> runs out of stack (after several minutes) and exits saying this:
>>
>> | Replaced class Foo
>> | Update overwrote class Foo
>>
>>
>> Which seems bogus anyway - given there's cyclic inheritance (no error
>> is reported!).
>>
>
> Oy!
>
> Wrapped snippets are compiled one at a time -- simple and clean, but
> that fails in this case. One problem is that the set of updates is
> not known before compilation. If a new snippet is or causes an update
> to an existing snippet its dependencies need only be updated in the
> case that redefinition fails. Redefinition usually succeeds, and
> certainly does for the false positive updates. I think the initial
> snippet and set of updates will need to be recompiled as full sets in
> each iteration, at least in the case of failure.
Yeah - that seems a promising approach - bundle them up and recompile
them together.
Maurizio
>
> Thanks,
> Robert
>
More information about the kulla-dev
mailing list