Skip to content

Ecompile diagnostics for vscode - #991

Open
turleypol wants to merge 21 commits into
masterfrom
ecompile-diagnostics-140b0dc4
Open

turleypol wants to merge 21 commits into
masterfrom
ecompile-diagnostics-140b0dc4

Conversation

@turleypol

@turleypol turleypol commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Kevin is to blame ;)

Brings the compiler work behind the vscode-escript language
server (diagnostics, semantic highlighting, completion,
signature help, formatting) to master. This is the
long-running ecompile-diagnostics branch, rebased onto
current master. Code used only for analysis runs only under
Compiler::analyze(), so ecompile and the server compile
scripts as they do on master.

The branch combines these PRs, each reviewed against the
diagnostics branch:

Five later commits have no PR of their own: regular
expression literals, FunctionCall children gathering, uninit
class functions, enum class / scoped constants, and
short-circuit evaluation warnings.

Companion: polserver/vscode-escript#132

KevinEady and others added 20 commits October 20, 2026 21:10
* Allow console or diagnostics reporting

* Make SourceLocation track ranges

* Introduce ScopeTree

* Implement ScopeTree finding for variables

* Implement DiagnosticReporter

* Introduce Compiler::analyze

* Use strings in Report instead of chars

* Remove newline endings from reporter calls

* Introduce SourceFileLoader

* Introduce SemanticTokensBuilder

* Add -fPIC to antlr

* Introduce Report[er]::clear

* Allow workspace module building
* SemanticTokensBuilder updates
- A single lexer token can be multiple semantic tokens (eg. multi-
line string)

* Add ScopeTree to grab local variables

* Change FunctionCall::parameters to return a ptr
This internal error was previously never triggered, but now since we no
longer "fast fail", it can occur.

* Null CompilerWorkspace checks
A CompilerWorkspace cannot be created if the source cannot be found.

* Make module load failure an error, not a fatal
This change is needed to continue diagnostics after failing to locate a
dependent module.

* Use parse tree visitor for semantic tokens

* Fix nullptr issues with failed parsing

* Fix memory issue with SourceFileIdentifier
* Make include load failure an error, not a fatal

* Updates to ScopeTree

* Update find_constant

* Address review comments
- Remove newline
* Begin work to support completion

* Address review comments
* Add continue_on_error for analysis

* Fix compiler warning on assignment in conditional

* Fix formatting
… condition (#606)

* add uninit, false, true keywords for sem tokens

* add missing continue_on_error check
… SourceFile tokens (#617)

* Add token_index to Range

* Use SourceFile token_stream to bypass lexer reset
…e strings (#621)

* Replace CRLF with LF

* No need to replace CRLF since its handled in the loop

* Remove duplicate code
…l path for module path names (#624)

* Keep track of replaced Node from Optimizer

* Use `Clib::FullPath` on module path names
… within extension (#627)

* Add support for formatting ranges

* Formatting

* Fix NO_PCH compilation

* Really fix NO_PCH compilation...

* Fix colmn offsets
* Add nullptr checks in AST generation

* Fix adding new line at EOF with ranged formatting

* Changes needed for hover and completion support

* Refactor for proper scope tree query

* Additional nullptr checks

* More scope tree query enhancements

* Add support for class member and method scope queries

* Address Discord discussions
- Make `find_*` an exact match
Fixes an issue with ShortCircuitWarning visiting a FunctionCall whose
children are nullptrs due to the FunctionCall having taken its arguments
via `fc.take_arguments()`.
…alysis mode (#988)

* Keep a missing module or include fatal outside analysis

The diagnostics work turned the report.fatal() for an
unreadable
module (use_module) and include (locate_include_file) into
report.error() plus return, in every mode. ecompile then
kept going
and printed follow-on errors about the missing functions and
constants.

BuilderWorkspace now carries analysis_mode. Only
Compiler::analyze()
sets it; compile_file_steps and ExpressionEvaluator pass
false. The
two failures are fatal again unless analysis_mode is set.
vscode-escript keeps the error-and-continue behaviour it
needs to
analyse the rest of the file.

* Keep the first declaration of a duplicated class method

class_declaration() replaced the class's method FunctionLink
each
time a method name repeated. master skips a name that is
already in
the methods map (#808), but the rebase of the diagnostics
work
dropped that check. ecompile is unaffected, because the
duplicate
definition is an error that stops compilation before the
analyzer
runs. Under Compiler::analyze() the link pointed at the last
declaration instead of the first.

* Print the source line under a short-circuit warning again

The diagnostics work dropped the source line that ecompile
prints
below "Short circuit change: ... will potentially not been
executed".
vscode-escript does not want the line, because the editor
already
marks the location.

ShortCircuitReporter now takes analysis_mode. Outside
analysis it
appends the line as master does. Under Compiler::analyze()
the
message stays a single line.

* Keep the final newline when a format-off block runs to EOF

The newline that FormatterInsertNewlineAtEOF adds moved into
PrettifyLineBuilder::mergeEOFNonTokens(). It is skipped when
_rangeSkippedNewlineAtEOF is set, and mergeRawContent() set
that
flag for any skipped block that reached the last line. So
without a
format range, a file ending in an unclosed "// format-off"
lost its
final newline under ecompile -F and -Fi.

setSkipLines() now also receives whether a format range is
active,
and only then does reaching EOF set the flag.
vscode-escript's
format-range requests behave as before.

* Record a class method's location even without a parameter
  list

class_declaration() added the method to
all_function_locations only
when functionParameters() existed. master records it for
every
method. ecompile never sees the difference, because a
missing
parameter list is a parse error and AST building stops after
it.
Under analysis with continue_on_error, a method whose
parameter list
fails to parse was left out, so the analyzer's "See also"
hints
could not point at it.

* Build debugger expressions without parse-error recovery

ExpressionEvaluator, which evaluates expressions for the DAP
debugger, built its workspaces with continue_on_error set.
master's
evaluator does no such recovery. A parse error already
throws
"Invalid expression" before the AST is built. So the flag
only
changed an expression that parses but hits an unhandled AST
case:
it evaluated to an uninitialized value instead of failing
with an
error. Both workspaces now pass false.

* Name the module in build_module()'s load errors

build_module() passed its "Refusing to load" and "Unable to
load"
messages to report.error() as separate pieces ("Unable to
load '",
pathname, "'."). report.error() takes a format string, and
fmt drops
the unused arguments, so the message was cut off before the
path.
Both now use "{}".

* Skip a case group without labels in the Disambiguator

visit_case_dispatch_group called selectors.children.back().
The grammar
(switchLabel+) and the error checks between compile stages
keep that
list non-empty in ecompile. Compiler::analyze() runs
disambiguate()
after parse errors, and recovery on a half-typed `case (
elem ) 5: x`
builds a group with no label, so the language server
crashed.

* Build ScopeTree and unoptimized nodes only in analysis mode

ecompile filled CompilerWorkspace::scope_tree on every
compile and the
optimizer kept every folded Identifier as
Node::unoptimized_node. Only
vscode-escript reads either, through Compiler::analyze().

scope_tree is now a unique_ptr created only in analysis
mode.
LocalVariableScope and SemanticAnalyzer::analyze() skip it
when null.
The optimizer keeps unoptimized_node only when analysis_mode
is set.
Outside analysis mode, globals.remove_all_but( 0 ) is no
longer called,
as on master.

* Mark the declarations only vscode-escript uses

Each declaration that only Compiler::analyze() or the
formatter's
range mode reaches carries "Analysis only: used by
vscode-escript, not
by ecompile or pol.", so a single grep lists them all. The
comment on
SourceFileLoader::get_contents says why it is virtual.

* Give the analysis helpers internal linkage

Move starts_with, with_base_classes and define() into
anonymous
namespaces. Convert to unsigned char before std::tolower,
which is
undefined for negative char values. Remove the unused
SemanticTokenType2 and reformat six branch files.

* Modernize the compiler's analysis helpers

Use range-for with structured bindings, starts_with,
contains and
std::ranges algorithms. Replace std::list and
unique_ptr-wrapped
returns with std::vector and std::optional. Have
SemanticToken::append_from_lexer_token append to the token
vector
instead of returning a heap-allocated list. Take strings by
const
reference and copy no parameter vector in
visit_function_call.
@coveralls

coveralls commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Coverage Status

coverage: 81.209% (-0.8%) from 81.995% — ecompile-diagnostics-140b0dc4 into master

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