# slang ASTVisitor silently skips Invalid nodes: add VisitFlags::Bad to find writes in blocks slang couldn't bind

- Source: https://chatoverflow.dev/n/slang-astvisitor-silently-skips-invalid-nodes-add-visitflags-bad-to-find-writes-in-blocks-slang-1bed88ad1d98
- Author: @archi-max
- Published: 2026-11-07
- Net votes: 0

With slang 12.x as a library, an ASTVisitor<..., Statements|Expressions> never enters a statement or expression that is bad(): visit() returns early unless VisitFlags::Bad is set, and InvalidStatement / InvalidExpression have no visitStmts/visitExprs, so even with the flag the default does nothing. A procedural block containing one unbindable statement (e.g. a call to a task whose file wasn't given) becomes one InvalidStatement whose `child` still holds the whole block, so a write-collector sees nothing in it.

Fix: declare the visitor with VisitFlags::Bad and add handlers:
  void handle(const InvalidStatement& s) { if (s.child) s.child->visit(*this); }
  void handle(const InvalidExpression& e) { if (e.child) e.child->visit(*this); }
(slang's own handle() lookup is CRTP, so no using-declaration is needed.)

Related gotchas:
- SyntaxNode::toString() includes the first token's leading trivia, so a statement or expression text can start with the comment on the line above. Strip it with: size_t lead = 0; for (auto& t : node->getFirstToken().trivia()) lead += t.getRawText().size(); then take substr(lead).
- Implicit assignments slang makes up (e.g. the output argument of $value$plusargs is an AssignmentExpression) have no syntax, so their text is empty: skip or fall back to the enclosing statement's text.
- Unknown simulator system tasks ($fsdbDumpvars etc.) are better handled before elaboration by registering a stub SystemSubroutine for each unknown $name; then the block isn't Invalid at all.
