Summary
nilctxpassed (the newest un-audited linter; registry is now 45 analyzers) reports a false positive for a variadic spread call such as takesVariadicCtx(nil...). In that call nil is the whole []context.Context slice (a nil slice = zero contexts, which is perfectly valid), not a context.Context element. The linter never inspects call.Ellipsis, so it treats the spread arg as an individual variadic element and emits nil passed as context.Context; use context.Background() or context.TODO() instead — a wrong, non-actionable diagnostic.
nilctxpassed is the only custom linter that does variadic element-wise indexing via sig.Variadic(), and the only one that fails to check call.Ellipsis. The analogous variadic linter appendbytestring gets this right (pkg/linters/appendbytestring/appendbytestring.go:57 gates on call.Ellipsis.IsValid()).
Location
pkg/linters/nilctxpassed/nilctxpassed.go:57-70 (the per-arg variadic branch) together with the nil check at :76.
Root-cause walkthrough
For takesVariadicCtx(nil...) where func takesVariadicCtx(args ...context.Context):
call.Args has length 1: the single nil expression, and call.Ellipsis is valid (the ... spread).
- Loop
for i, arg := range call.Args at i=0: sig.Variadic() && params.Len() > 0 && i >= params.Len()-1 is true && true && 0 >= 0 → enters the variadic element branch.
paramType is set to the slice element type context.Context (lines 61-65).
isContextContext(paramType) → true; isBuiltinNil(pass, arg) → true (the predeclared nil, Uses[nil] is *types.Nil).
pass.Report(...) fires.
But the code never distinguishes a spread call from a plain variadic call. In a spread call the final argument corresponds to the slice type []context.Context, not its element — and passing nil as that slice is a valid empty variadic, not a nil context.
Evidence it is unique:
$ grep -rln 'Variadic()' pkg/linters --include=*.go | grep -v _test
pkg/linters/nilctxpassed/nilctxpassed.go # only element-indexing variadic linter
$ grep -rln '\.Ellipsis' pkg/linters --include=*.go | grep -v _test
pkg/linters/appendbytestring/appendbytestring.go # the only linter that guards on Ellipsis
Testdata (testdata/src/nilctxpassed/nilctxpassed.go:33-35) only exercises the non-spread variadic case takesVariadicCtx(nil) (a legitimately-flagged nil context). There is no spread-call fixture, so the FP is unguarded and untested.
Impact
- False positive on valid code
f(nil...) that spreads a nil []context.Context — the message is factually wrong (no context value is passed).
- Latent enforcement blocker:
nilctxpassed is otherwise clean and well-behaved (type-resolved context.Context identity via obj.Pkg().Path() == "context", wires internal/nolint, skips test + generated files via filecheck.ShouldSkipFilename). It is a natural CI-enforcement candidate, but this FP would fail CI on any legitimate nil... spread site.
- Severity is low (spread-of-nil to a variadic context param is uncommon), but it is a clear correctness defect in a brand-new linter with zero test coverage for the case.
Recommendation
In the variadic branch, treat a spread call's final argument as the whole slice rather than an element. A nil slice is a valid empty variadic, so skip the element-wise nil-context check:
Intent (before → after):
- Before: for
takesVariadicCtx(nil...) the linter enters the element branch and reports a nil context.Context.
- After: when
call.Ellipsis.IsValid() and the arg is the variadic slice position, the linter recognizes nil as a nil []context.Context slice and does not report.
Concretely, guard the variadic branch on !call.Ellipsis.IsValid() (mirroring appendbytestring), or continue for the final arg when the call is a spread. The non-spread variadic path (takesVariadicCtx(nil)) must stay flagged.
Validation checklist
Effort
XS — one call.Ellipsis.IsValid() guard in run plus 1-2 testdata fixtures.
References: §29472028410
Generated by 🤖 Sergo - Serena Go Expert · 322.5 AIC · ⌖ 15 AIC · ⊞ 5.8K · ◷
Summary
nilctxpassed(the newest un-audited linter; registry is now 45 analyzers) reports a false positive for a variadic spread call such astakesVariadicCtx(nil...). In that callnilis the whole[]context.Contextslice (a nil slice = zero contexts, which is perfectly valid), not acontext.Contextelement. The linter never inspectscall.Ellipsis, so it treats the spread arg as an individual variadic element and emitsnil passed as context.Context; use context.Background() or context.TODO() instead— a wrong, non-actionable diagnostic.nilctxpassedis the only custom linter that does variadic element-wise indexing viasig.Variadic(), and the only one that fails to checkcall.Ellipsis. The analogous variadic linterappendbytestringgets this right (pkg/linters/appendbytestring/appendbytestring.go:57gates oncall.Ellipsis.IsValid()).Location
pkg/linters/nilctxpassed/nilctxpassed.go:57-70(the per-arg variadic branch) together with the nil check at:76.Root-cause walkthrough
For
takesVariadicCtx(nil...)wherefunc takesVariadicCtx(args ...context.Context):call.Argshas length 1: the singlenilexpression, andcall.Ellipsisis valid (the...spread).for i, arg := range call.Argsat i=0:sig.Variadic() && params.Len() > 0 && i >= params.Len()-1istrue && true && 0 >= 0→ enters the variadic element branch.paramTypeis set to the slice element typecontext.Context(lines 61-65).isContextContext(paramType)→ true;isBuiltinNil(pass, arg)→ true (the predeclarednil,Uses[nil]is*types.Nil).pass.Report(...)fires.But the code never distinguishes a spread call from a plain variadic call. In a spread call the final argument corresponds to the slice type
[]context.Context, not its element — and passingnilas that slice is a valid empty variadic, not a nil context.Evidence it is unique:
Testdata (
testdata/src/nilctxpassed/nilctxpassed.go:33-35) only exercises the non-spread variadic casetakesVariadicCtx(nil)(a legitimately-flagged nil context). There is no spread-call fixture, so the FP is unguarded and untested.Impact
f(nil...)that spreads a nil[]context.Context— the message is factually wrong (no context value is passed).nilctxpassedis otherwise clean and well-behaved (type-resolvedcontext.Contextidentity viaobj.Pkg().Path() == "context", wiresinternal/nolint, skips test + generated files viafilecheck.ShouldSkipFilename). It is a natural CI-enforcement candidate, but this FP would fail CI on any legitimatenil...spread site.Recommendation
In the variadic branch, treat a spread call's final argument as the whole slice rather than an element. A nil slice is a valid empty variadic, so skip the element-wise nil-context check:
Intent (before → after):
takesVariadicCtx(nil...)the linter enters the element branch and reports a nilcontext.Context.call.Ellipsis.IsValid()and the arg is the variadic slice position, the linter recognizesnilas a nil[]context.Contextslice and does not report.Concretely, guard the variadic branch on
!call.Ellipsis.IsValid()(mirroringappendbytestring), orcontinuefor the final arg when the call is a spread. The non-spread variadic path (takesVariadicCtx(nil)) must stay flagged.Validation checklist
takesVariadicCtx(nil...)— asserted not flagged.takesVariadicCtx(existingSlice...)— asserted not flagged (regression guard for the general spread case).takesVariadicCtx(nil)(non-spread) — still flagged.go test ./pkg/linters/nilctxpassed/...passes.Effort
XS — one
call.Ellipsis.IsValid()guard inrunplus 1-2 testdata fixtures.References: §29472028410