{"record":{"id":"1f8a6fdfb9566df7","repo":"vitessio/vitess","slug":"n-directive-requires-a-slice","errorCode":null,"errorMessage":"'%n' directive requires a slice","messagePattern":"'%n' directive requires a slice","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/tools/astfmtgen/main.go","lineNumber":255,"sourceCode":"\t\t\t\t\t\tX:   rightExpr,\n\t\t\t\t\t\tSel: &ast.Ident{Name: \"FormatFast\"},\n\t\t\t\t\t},\n\t\t\t\t\tArgs: []ast.Expr{callexpr.X},\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursor.InsertBefore(&ast.ExprStmt{X: call})\n\t\tcase 'd':\n\t\t\tcall := &ast.CallExpr{\n\t\t\t\tFun:  &ast.Ident{Name: \"fmt.Sprintf\"},\n\t\t\t\tArgs: []ast.Expr{&ast.BasicLit{Value: `\"%d\"`, Kind: gotoken.STRING}, expr.Args[2+fieldnum]},\n\t\t\t}\n\t\t\tcursor.InsertBefore(r.rewriteLiteral(callexpr.X, \"WriteString\", call))\n\t\tcase 'n': // directive for slices of AST nodes checked at code generation time\n\t\t\tinputExpr := expr.Args[2+fieldnum]\n\t\t\tinputType := r.pkg.TypesInfo.Types[inputExpr].Type\n\t\t\tsliceType, ok := inputType.(*types.Slice)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"'%n' directive requires a slice\")\n\t\t\t}\n\t\t\tif types.Implements(sliceType.Elem(), r.astExpr) {\n\t\t\t\t// Fast path: input is []Expr\n\t\t\t\tcall := &ast.CallExpr{\n\t\t\t\t\tFun: &ast.SelectorExpr{\n\t\t\t\t\t\tX:   callexpr.X,\n\t\t\t\t\t\tSel: &ast.Ident{Name: \"formatExprs\"},\n\t\t\t\t\t},\n\t\t\t\t\tArgs: []ast.Expr{inputExpr},\n\t\t\t\t}\n\t\t\t\tcursor.InsertBefore(&ast.ExprStmt{X: call})\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tpanic(\"slow path for `n` directive for slice of type other than Expr\")\n\t\tdefault:\n\t\t\tpanic(fmt.Sprintf(\"unsupported escape %q\", token))\n\t\t}\n\t\tfieldnum++","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/tools/astfmtgen/main.go#L237-L273","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nast_sprintf(sel, \"%n\", sel.Sel) // panics: not a slice\n// after\nast_sprintf(sel, \"%n\", []ast.Expr{sel.Sel})","handlingStrategy":"validation","validationCode":"// check the %n argument is a slice before generation\n tv := pkg.TypesInfo.Types[arg].Type\n if _, ok := tv.(*types.Slice); !ok {\n    return fmt.Errorf(\"%n argument must be a slice, got %s\", tv)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["go","codegen","tooling","panic","astfmtgen","format-directive"],"backgroundTag":"format-directive-type-mismatch","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}