vitessio/vitess · error
[BUG] tried to replace 'VisitThis' on 'Visitable'
Error message
[BUG] tried to replace 'VisitThis' on 'Visitable'
What it means
The generated rewriter for the Visitable wrapper node rewrites node.VisitThis() but its replacement callback panics because the Visitable wrapper itself cannot be swapped. Replacements must happen at the inner node level, not on the wrapper.
Source
Thrown at go/vt/sqlparser/ast_rewrite.go:16432
func (a *application) rewriteVisitable(parent SQLNode, node Visitable, replacer replacerFunc) bool {
if a.pre != nil {
a.cur.replacer = replacer
a.cur.parent = parent
a.cur.node = node
kontinue := !a.pre(&a.cur)
if a.cur.revisit {
a.cur.revisit = false
return a.rewriteSQLNode(parent, a.cur.node, replacer)
}
if kontinue {
return true
}
}
if a.collectPaths {
a.cur.current.AddStep(uint16(VisitableInner))
}
if !a.rewriteSQLNode(node, node.VisitThis(), func(newNode, parent SQLNode) {
panic("[BUG] tried to replace 'VisitThis' on 'Visitable'")
}) {
return false
}
if a.collectPaths {
a.cur.current.Pop()
}
if a.post != nil {
a.cur.replacer = replacer
a.cur.parent = parent
a.cur.node = node
if !a.post(&a.cur) {
return false
}
}
return true
}
View on GitHub (pinned to 01a25a7d17)
Solutions
- Match and replace the inner node returned by VisitThis() in your callback, not the Visitable wrapper itself
- Skip wrapper nodes: return (node, false) when node is a Visitable wrapper
- Adjust your rewriter to operate on the concrete AST node types you care about
- Regenerate with `make codegen` if your generated rewriter is out of sync with AST definitions
Example fix
// before
func repl(node, parent sqlparser.SQLNode) (sqlparser.SQLNode, bool) {
if _, ok := parent.(*sqlparser.Visitable); ok {
return newInner, true // panics: cannot replace on Visitable
}
return node, false
}
// after
func repl(node, parent sqlparser.SQLNode) (sqlparser.SQLNode, bool) {
if inner, ok := node.(matchingInnerType); ok {
return newInner, true // replace the concrete inner node
}
return node, false
} Defensive patterns
Strategy: validation
Validate before calling
func repl(node, parent sqlparser.SQLNode) (sqlparser.SQLNode, bool) {
if v, ok := parent.(*sqlparser.Visitable); ok {
_ = v // never replace through the wrapper; target inner nodes
return node, false
}
return replaceIfMatched(node)
} Try / catch
err := func() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("rewrite panicked on Visitable wrapper: %v", r)
}
}()
return sqlparser.SafeRewrite(stmt, opts, repl)
}() Prevention
- Match concrete inner node types, never Visitable wrappers
- Return (node, false) for wrapper nodes in callbacks
- Test rewriters against ASTs containing extension Visitable nodes
When it happens
Trigger: A rewrite callback that returns a replacement when parent is a *Visitable wrapper (used for extension points in the AST), replacing the wrapper's inner node through the wrapper's own rewrite slot.
Common situations: Custom SQLNode extensions embedded via Visitable; rewriters that intercept wrapper nodes instead of the concrete inner nodes; forked ASTs after parser changes.
Related errors
- [BUG] tried to replace 'SQLNode' on 'RootNode'
- [BUG] tried to replace 'Name' on 'TableName'
- [BUG] tried to replace 'Qualifier' on 'TableName'
- [BUG] tried to replace 'Key' on 'VindexParam'
- [BUG] tried to replace 'ASTType' on 'ValueContainer'
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/2995347f3b46da76.
Report an issue: GitHub.