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 ¶
Functions ¶
This section is empty.
Types ¶
This section is empty.