Skip to content

Add -ast, outputting annotated AST information for Anvil files - #83

Open
wxwern wants to merge 50 commits into
AnvilHDL:masterfrom
wxwern:add-annotated-ast-output
Open

wxwern wants to merge 50 commits into
AnvilHDL:masterfrom
wxwern:add-annotated-ast-output

Conversation

@wxwern

@wxwern wxwern commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

A preliminary implementation of outputting annotated AST information from the AnvilHDL compiler.

This allows for language servers to query AST information from Anvil, and supply hover hints, jump to definition, etc., to code editors, for an improved user experience.

Together with anvil-lsp, this resolves #73.


Current limitations:

  • Compiler warnings are lone print statements and have no mechanism to be collected and outputted at this time. This should be added in the future.

  • Not all symbols are annotated with their exact ranges without much more significant refactors. This makes it impossible to support symbol renames under LSP.

  • Definitions and types with unknown parameters are not analyzed by the compiler. This means most LSP features currently don't work correctly if a definition contains type parameters.

Important side effects:

  • Many nodes are now wrapped with ast_node or have codespan information added.
  • Added ErrorCollector to support collecting multiple errors, by injecting a modified raise function into the global scope when opened.

this includes unimplemented:
- definition span annotations
- event graph annotations

this does not include a public API to obtain the JSON output yet
this invokes and outputs the internally implemented ast-to-JSON conversion
This commit adds experimental support for collecting a set of errors
instead of bailing out on any error.

These errors are reported within the AST as an example.
Comment thread lib/lang.ml Outdated
Comment thread lib/lang.ml Outdated
Comment thread lib/astJsonSerializer.ml Outdated
Comment thread lib/astJsonSerializer.ml Outdated
Comment thread lib/errorCollector.ml
let collect_errors : bool ref = ref false

(* Method injection *)
let raise (e: exn) =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is problematic right ?

Sometimes we may have structural errors, after which we should not continue

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why there's an alternate pathway where errors can be raised through raise_fatal instead.

The idea is this intercepts all raise methods in the file if opened, and therefore makes the default behaviour fallthrough and we can use the fatal variant if we can't recover from it.

Generally, if there are strict structural errors that we can't handle, the OCaml type checker will prevent compilation anyways, since raise will fall-through with a return type unit, in which case we can decide ourselves if there's a fallback we can use or if we should change it to be a fatal exception.

Comment thread lib/graphBuilder.ml

@arj4web arj4web left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This almost seems fine, except for a few redundant additions and what appear to be some genuine errors.

You can try fixing the errors first.

This PR is very detail-oriented, so hopefully I didn't miss anything major. I'll still ask Copilot to look for any remaining details.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

lib/graphBuilder.ml:177

  • The argument-count check for function calls only guards the raise line; List.iter2 and the recursive call execute unconditionally. When ErrorCollector.collect_errors is enabled, raise will not abort, so List.iter2 can throw due to list-length mismatch and crash instead of producing a collected compiler error.
      if List.length td_args <> List.length func.args then
        raise (event_graph_error_default "Arguments missing in function call" e.span);
        List.iter2 (fun td arg ->

Comment thread lib/jsonOutput.ml
Comment on lines +117 to +119
let cunit_json = compilation_unit_with_event_graph_to_yojson cunit gcol_list in
let _ = assert (cunit.cunit_file_name = Some fname) in
cunit_json

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to fix this? Is it possible that there's no file name when -ast is supplied?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I suppose this is an implicit invariant specific to the implementation. assertion looks reasonable

@wxwern
wxwern force-pushed the add-annotated-ast-output branch 2 times, most recently from 7ccf8d4 to f9ff1b4 Compare July 26, 2026 11:31
@wxwern
wxwern force-pushed the add-annotated-ast-output branch from f9ff1b4 to 03660e1 Compare July 26, 2026 11:32
@jasonyu1996

Copy link
Copy Markdown
Collaborator

@wxwern Sorry let me review in the next few days. Always forgot

Comment thread lib/jsonOutput.ml
(** Create successful JSON output *)
let success_output output_str =
{ success = true; errors = []; output = Some output_str }
let transpiled_output output_str =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps unimportant: why is it called "transpile"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cuz it's transpiled to system verilog technically and it makes it clear that this is source code output

Comment thread lib/compileDriver.mli Outdated
Comment thread lib/errorCollector.ml
let has_collected_errors () : bool =
!_global_errors <> []

let dedup_collected_errors () =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to do dedup?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for non-blocking errors many will be re-reported if a validation pathway reencounters the same issue after a loop or any repeated evaluation, and it was easier to just deduplicate it than to trace every instance of duplication

Comment thread lib/lifetimeCheck.ml
add_msg msg ev ({d = {ty = Recv msg; until = ev}; def_span = ac_span.def_span; action_event = ac_span.action_event; span = ac_span.span})
| ImmediateSend (msg, td) ->
add_msg msg ev ({d = {ty = Send (msg, td); until = ev}; span = ac_span.span})
add_msg msg ev ({d = {ty = Send (msg, td); until = ev}; def_span = ac_span.def_span; action_event = ac_span.action_event; span = ac_span.span})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern shows up quite a lot it seems. Maybe can have a polymorphic function, something like map : (U -> V) -> (U ast_node) -> (V ast_node).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would result in a much larger refactor than the already large PR, but i'm fine if y'all are fine with that

Comment thread lib/typedefMap.ml Outdated

@jasonyu1996 jasonyu1996 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look good overall. Just some minor questions/nits

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.

LSP support

4 participants