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

  1. Match and replace the inner node returned by VisitThis() in your callback, not the Visitable wrapper itself
  2. Skip wrapper nodes: return (node, false) when node is a Visitable wrapper
  3. Adjust your rewriter to operate on the concrete AST node types you care about
  4. 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

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


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/2995347f3b46da76. Report an issue: GitHub.