Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "JDT Core Programmer Guide/Completion"

(Recovery)
(fallBackToSpringForward())
(One intermediate revision by the same user not shown)
Line 4: Line 4:
 
Various parties influence the source range that is analysed (scanned & parsed):
 
Various parties influence the source range that is analysed (scanned & parsed):
 
* Parser.parseBlockStatements() starts with <code>scanner.resetTo(md.bodyStart, bodyEnd(md))</code>
 
* Parser.parseBlockStatements() starts with <code>scanner.resetTo(md.bodyStart, bodyEnd(md))</code>
** CompletionParser implements <code>bodyEnd</code> by answering the <code>cursorLocation</code>
+
** CompletionParser implements <code>bodyEnd()</code> as answering the <code>cursorLocation</code>
* Since stopping at the cursorLocation is unacceptable in some situations (notably involving lambdas), <code>AssistParser.fallBackToSpringForward()</code> may set eofPosition to one of (a) actual source end or (b) real body end of the method.
+
* Since stopping at the cursorLocation is unacceptable in some situations (notably involving lambdas), <code>AssistParser.[[#fallBackToSpringForward()]]</code> may set eofPosition to one of (a) actual source end or (b) real body end of the method (see below).
  
  
Line 12: Line 12:
 
* does it improve performance? ''(I doubt it has significant impact. --SH)''
 
* does it improve performance? ''(I doubt it has significant impact. --SH)''
 
* in {{FixedBug|473654}} experiments to avoid setting eof to cursorLocation caused regressions, so some code must meanwhile depend on this strategy.
 
* in {{FixedBug|473654}} experiments to avoid setting eof to cursorLocation caused regressions, so some code must meanwhile depend on this strategy.
 +
* it definitely helps to avoid parsing incomplete code in a way that overruns method boundaries (as was observed in {{FixedBug|473654#c26}}).
  
 
==Recovery==
 
==Recovery==
Line 19: Line 20:
 
* <code>buildInitialRecoveryState()</code> essentially converts existing ASTNodes in <code>Parser.astStack</code> into <code>RecoveredElement</code>s.
 
* <code>buildInitialRecoveryState()</code> essentially converts existing ASTNodes in <code>Parser.astStack</code> into <code>RecoveredElement</code>s.
 
** This step maps the nested structure indicated by astStack into a proper tree of recovered elements.
 
** This step maps the nested structure indicated by astStack into a proper tree of recovered elements.
 +
*** More elements will be added later using <code>RecoveredElement.add(*)</code>
 +
*** <code>currentElement</code> will always point to a current leaf in the tree, the rest being reachable via the <code>parent</code> chain.
 
** Expressions are explicitly omitted.
 
** Expressions are explicitly omitted.
 
*** An exception is implemented for block lambdas, so that an empty block is inserted into the tree of recovered elements.
 
*** An exception is implemented for block lambdas, so that an empty block is inserted into the tree of recovered elements.
Line 32: Line 35:
  
 
''to be continued''.
 
''to be continued''.
 +
 +
==Lambda Specifics==
 +
Completion involving lambdas has essentially been implemented via {{FixedBug|422468}} and {{FixedBug|423987}}.
 +
 +
===fallBackToSpringForward()===
 +
This method will essentially try two strategies:
 +
# Can we close a pending lambda, either by the next real token, or by one of a fixed set of <code>RECOVERY_TOKENS</code>?
 +
# If not, can we just jump to the next 'thing'?
 +
 +
For strategy (1) we first check if the <code>eofPosition</code> needs to be pushed past the cursor location, allowing to scan at least up-to the end of the current method body.
 +
 +
For strategy (2) we keep a stack (since {{FixedBug|535743}}) of snapshots, represented by instances of CompletionParser, which mainly hold copies of the various stacks, plus various assist-specific fields.
 +
* The snapshots are created / updated by calling <code>commit()</code> when various elements are consumed which could contain a lambda expression (see <code>triggerRecoveryUponLambdaClosure()</code>).
 +
* In one arm, <code>fallBackToSpringForward()</code> will copy the state of the latest / matching snapshot into the current parser. After such copyState() the parser will be told, to <code>RESUME</code> (rather than <code>RESTART</code>) in order to keep the parsing state reached thus far.
 +
* It's crucial that each snapShot will be removed from the stack at the right point in time (from <code>triggerRecoveryUponLambdaClosure</code>, <code>consumeBlock</code>, or <code>consumeMethodDeclration</code>).
 +
 +
===actFromTokenOrSynthetic()===
 +
Via {{FixedBug|473654}} another strategy for closing a pending lambda has been added:
 +
 +
Method <code>actFromTokenOrSynthetic()</code> may synthesize an arbitrary sequence of <code>RECOVERY_TOKENS</code> as long as they can be accepted by the parser. Here we are feeding the tokens right into the parser as if they were coming from the scanner.
  
 
[[Category:JDT]]
 
[[Category:JDT]]

Revision as of 16:58, 4 October 2018

Note.png
In this page we collect our understanding of how code completion is implemented, in particular the beast that is CompletionParser


Source code range

Various parties influence the source range that is analysed (scanned & parsed):

  • Parser.parseBlockStatements() starts with scanner.resetTo(md.bodyStart, bodyEnd(md))
    • CompletionParser implements bodyEnd() as answering the cursorLocation
  • Since stopping at the cursorLocation is unacceptable in some situations (notably involving lambdas), AssistParser.#fallBackToSpringForward() may set eofPosition to one of (a) actual source end or (b) real body end of the method (see below).


It is unclear, how much is gained by stopping to parse in the middle of code:

  • does it help to cope with broken code beyond the cursor location?
  • does it improve performance? (I doubt it has significant impact. --SH)
  • in bug 473654 experiments to avoid setting eof to cursorLocation caused regressions, so some code must meanwhile depend on this strategy.
  • it definitely helps to avoid parsing incomplete code in a way that overruns method boundaries (as was observed in bug 473654#c26).

Recovery

This is the story of Parser.currentElement.

Recovery starts when a syntax error is encountered, the chain resumeOnSyntaxError() -> buildInitialRecoveryState() sets things in motion:

  • buildInitialRecoveryState() essentially converts existing ASTNodes in Parser.astStack into RecoveredElements.
    • This step maps the nested structure indicated by astStack into a proper tree of recovered elements.
      • More elements will be added later using RecoveredElement.add(*)
      • currentElement will always point to a current leaf in the tree, the rest being reachable via the parent chain.
    • Expressions are explicitly omitted.
      • An exception is implemented for block lambdas, so that an empty block is inserted into the tree of recovered elements.
      • If a lambda appears, e.g., in the RHS of a local declaration, the block cannot be made a child of the local, so the hierarchy has to be tweaked:
        • The local declaration is considered as complete
        • The enclosing block is resumed
        • The new block (body of the lambda) is inserted as a sibling to the local declaration.

The last expression may, however, be processed by updateRecoveryState() (right after resumeOnSyntaxError()):

  • relevant things may happen below attachOrphanCompletionNode() -> buildMoreCompletionContext().
    • buildMoreCompletionEnclosingContext() has special treatment for code nested in an if statement and specifically for instanceof.


to be continued.

Lambda Specifics

Completion involving lambdas has essentially been implemented via bug 422468 and bug 423987.

fallBackToSpringForward()

This method will essentially try two strategies:

  1. Can we close a pending lambda, either by the next real token, or by one of a fixed set of RECOVERY_TOKENS?
  2. If not, can we just jump to the next 'thing'?

For strategy (1) we first check if the eofPosition needs to be pushed past the cursor location, allowing to scan at least up-to the end of the current method body.

For strategy (2) we keep a stack (since bug 535743) of snapshots, represented by instances of CompletionParser, which mainly hold copies of the various stacks, plus various assist-specific fields.

  • The snapshots are created / updated by calling commit() when various elements are consumed which could contain a lambda expression (see triggerRecoveryUponLambdaClosure()).
  • In one arm, fallBackToSpringForward() will copy the state of the latest / matching snapshot into the current parser. After such copyState() the parser will be told, to RESUME (rather than RESTART) in order to keep the parsing state reached thus far.
  • It's crucial that each snapShot will be removed from the stack at the right point in time (from triggerRecoveryUponLambdaClosure, consumeBlock, or consumeMethodDeclration).

actFromTokenOrSynthetic()

Via bug 473654 another strategy for closing a pending lambda has been added:

Method actFromTokenOrSynthetic() may synthesize an arbitrary sequence of RECOVERY_TOKENS as long as they can be accepted by the parser. Here we are feeding the tokens right into the parser as if they were coming from the scanner.

Back to the top