vitessio/vitess · error
'%n' directive requires a slice
Error message
'%n' directive requires a slice
What it means
In astfmtgen, the custom %n directive is reserved for slices of AST nodes; it rewrites the call into typed writes at generation time and therefore must know the static type. If the argument supplied for a %n directive is not a slice (per go/types), the tool panics since it cannot generate the node-iteration code.
Source
Thrown at go/tools/astfmtgen/main.go:255
X: rightExpr,
Sel: &ast.Ident{Name: "FormatFast"},
},
Args: []ast.Expr{callexpr.X},
}
}
cursor.InsertBefore(&ast.ExprStmt{X: call})
case 'd':
call := &ast.CallExpr{
Fun: &ast.Ident{Name: "fmt.Sprintf"},
Args: []ast.Expr{&ast.BasicLit{Value: `"%d"`, Kind: gotoken.STRING}, expr.Args[2+fieldnum]},
}
cursor.InsertBefore(r.rewriteLiteral(callexpr.X, "WriteString", call))
case 'n': // directive for slices of AST nodes checked at code generation time
inputExpr := expr.Args[2+fieldnum]
inputType := r.pkg.TypesInfo.Types[inputExpr].Type
sliceType, ok := inputType.(*types.Slice)
if !ok {
panic("'%n' directive requires a slice")
}
if types.Implements(sliceType.Elem(), r.astExpr) {
// Fast path: input is []Expr
call := &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: callexpr.X,
Sel: &ast.Ident{Name: "formatExprs"},
},
Args: []ast.Expr{inputExpr},
}
cursor.InsertBefore(&ast.ExprStmt{X: call})
break
}
panic("slow path for `n` directive for slice of type other than Expr")
default:
panic(fmt.Sprintf("unsupported escape %q", token))
}
fieldnum++View on GitHub (pinned to 01a25a7d17)
Solutions
- Pass a slice for every %n directive, e.g. ast_sprintf(n, "%n", []ast.Expr{a, b}) or wrap the single node: []ast.Expr{node}.
- Switch the verb to %v if the argument is genuinely not an AST-node slice.
- Verify TypesInfo can resolve the argument type (wrong package/imports can also yield an unexpected type).
Example fix
// before
ast_sprintf(sel, "%n", sel.Sel) // panics: not a slice
// after
ast_sprintf(sel, "%n", []ast.Expr{sel.Sel}) Defensive patterns
Strategy: validation
Validate before calling
// check the %n argument is a slice before generation
tv := pkg.TypesInfo.Types[arg].Type
if _, ok := tv.(*types.Slice); !ok {
return fmt.Errorf("%n argument must be a slice, got %s", tv)
} Prevention
- Pair every %n verb with a slice of ast.Expr-implementing nodes; wrap single nodes in a slice.
- Use %v for non-slice arguments.
- Run the generator after any printf-call refactor to catch mismatches immediately.
When it happens
Trigger: A rewritten printf call has a %n directive whose corresponding argument has a non-slice type, e.g. ast_sprintf(n, "%n", node) where node is a single ast.Expr, or a scalar/pointer type.
Common situations: Mixing %v and %n incorrectly; passing a single node where a slice is expected; a type changed from []ast.Expr to ast.Expr during refactoring; argument-position drift after adding format verbs without updating args.
Related errors
- bad literal argument
- slow path for `n` directive for slice of type other than Exp
- package '%s' does not contain 'ast_format.go'
- generator failed for type %s: %w
- unsupported pointer type %T
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/1f8a6fdfb9566df7.
Report an issue: GitHub.