nullarray

package
v0.96.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package nullarray catches a nil slice reaching a JSON field the wire schema requires to be an array, where it encodes as null.

The bugs (core/mcp): tools/call answered `"content": null` when a handler returned a nil []Content or a ToolResult flagged IsError with no blocks (bdb716482), and prompts/get answered `"messages": null` for a prompt handler's nil messages (9e207f909). Both fields are required arrays in the MCP schema, and validating clients (the TypeScript SDK) reject null, so an in-band failure became a client-side protocol error. The fixes route the value through contentArray, or guard it with `if messages == nil { messages = []PromptMessage{} }`.

Marker-scoped: a slice-typed struct field whose json tag has no omitempty and that carries //gofastr:required-array (in its doc comment or trailing comment) is a required array. The marker is exported as a fact, so a field declared in core/a2a and filled in another package is checked there too. A marked field with omitempty is reported: omitempty drops an empty slice, which is no more an array than null.

It fires where a marked field is set (a keyed or positional composite literal element, or `x.F = v`), and where a composite literal of the struct OMITS a required field (toolsCallResult{IsError: true} → "content": null, bdb716482's own spelling) — the omission is quiet only when the zero value rides beside a non-nil error return (return T{}, err) or the same object's field is filled later in the body. Package-level literals and package-level function literals (var x = T{F: v}, var h = func() {...}) have no FuncDecl and are checked all the same. Proven non-nil, each silent:

  • a composite literal (`[]Content{}`, `[]Content{v}`), make(...);
  • append with at least one element argument;
  • a slice expression or conversion of a proven value;
  • a local every assignment of which is proven (declared with one, never `var x []T` bare), or one guarded before the sink by `if x == nil { x = <proven> }` or `if x == nil { return … }` (also spelled len(x) == 0) — the rebind anywhere in the guard body, a log line after it changes nothing — where the guard dominates the sink (it sits directly in a block that encloses the sink, not under some other condition) and no later assignment to the variable sits between the guard and the sink (msgs = maybeNil() after the rebind leaves the value nil again), or one read inside `if len(x) > 0` / `if x != nil`;
  • X.F = append(X.F, …) when that field has a dominating non-nil initialization, with no later possibly-nil write;
  • a call to a function whose every return is proven (contentArray: its `return c` sits behind `if c == nil { return []Content{} }`), exported as a fact so a helper in another package counts; or one whose every return is proven or a slice of one parameter, called with a proven argument there (core/mcp pageList returns items[:0] or items[a:b]). A multi-result call's first result is the one the facts describe (page, next := pageList(...)).

Anything else fires: a callback's or parameter's result, a field read off a parameter, a type-switch binding, a bare `var out []T` filled by a loop that may never run, a literal nil.

Index

Constants

This section is empty.

Variables

View Source
var Analyzer = &analysis.Analyzer{
	Name:      "nullarray",
	Doc:       "forbids a possibly-nil slice in a //gofastr:required-array JSON field, which encodes as null where the schema requires an array",
	Run:       run,
	FactTypes: []analysis.Fact{new(requiredArray), new(nonNilResult), new(passThrough)},
}

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL