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

  1. 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}.
  2. Switch the verb to %v if the argument is genuinely not an AST-node slice.
  3. 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

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


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