Summary
The newly-added 43rd analyzer writebytestring (pkg/linters/writebytestring/writebytestring.go) rewrites w.Write([]byte(s)) → io.WriteString(w, s). Its isStringType check accepts named string types (defined types whose underlying type is string), but io.WriteString's signature requires an exact string. For a named-string argument the emitted SuggestedFix (and the diagnostic's suggestion text) does not compile. The linter's own testdata encodes this broken suggestion, and it slips through because the test uses analysistest.Run (diagnostic-only) instead of analysistest.RunWithSuggestedFixes — the exact verification-parity gap previously filed/closed as #43313.
Not currently CI-enforced, so this is latent today (no -fix runs in cgo.yml), but it makes the autofix unsafe to apply and blocks safe enforcement of this linter.
The defect
isStringType (writebytestring.go:152-159) matches via t.Underlying():
func isStringType(pass *analysis.Pass, expr ast.Expr) bool {
t := pass.TypesInfo.TypeOf(expr)
if t == nil { return false }
basic, ok := t.Underlying().(*types.Basic)
return ok && basic.Kind() == types.String // <-- also true for `type MyStr string`
}
The fix builder (writebytestring.go:184-194) then inserts io.WriteString(<w>, <s-text>) verbatim. But io.WriteString(w Writer, s string) takes a predeclared string. Go assignability: two distinct named types with identical underlying types are not assignable, and string is itself a named (predeclared) type. So passing a MyStr value yields cannot use s (variable of type MyStr) as string value in argument to io.WriteString.
Evidence — the testdata proves it
pkg/linters/writebytestring/testdata/src/writebytestring/writebytestring.go:21-27:
type myString string
func badNamedString() {
var buf bytes.Buffer
s := myString("hello")
buf.Write([]byte(s)) // want `... io\.WriteString\(&buf, s\) ...`
}
The // want regex asserts the linter emits io.WriteString(&buf, s). Applied as a fix, that line is io.WriteString(&buf, s) with s of type myString → compile error.
writebytestring_test.go:15 runs only analysistest.Run(...), which verifies diagnostic messages but never compiles the fixed output — so the broken fix passes tests.
Why the copied logic is wrong here (contrast with appendbytestring)
writebytestring mirrors appendbytestring (same isStringType via Underlying()). That is correct for appendbytestring because append(b, s...)'s string special-case accepts any operand with core type string (named strings included). It is wrong for writebytestring because io.WriteString's parameter is a concrete string, requiring an explicit conversion for named types.
All three existing SuggestedFix siblings verify their fixes:
appendbytestring_test.go:15 → analysistest.RunWithSuggestedFixes
stringsindexcontains_test.go:15 → analysistest.RunWithSuggestedFixes
sprintfint_test.go:15 → analysistest.RunWithSuggestedFixes
writebytestring regresses this convention — the same gap closed in #43313.
Impact
- Applying the autofix (gopls /
-fix) to any w.Write([]byte(namedStr)) site produces code that does not compile.
- The linter cannot be safely added to
cgo.yml enforcement (with -fix) until corrected.
- 18 production
Write([]byte(...)) sites exist (e.g. pkg/logger/logger.go:102, pkg/workflow/strings.go:171-172, pkg/parser/schedule_fuzzy_scatter.go:175); plain-string ones fix correctly, but any named-string arg would break.
Recommended fix
- Correct the fix for named strings — wrap when the arg is not the predeclared
string:
// exact predeclared string vs. defined/named string
func isExactString(t types.Type) bool {
b, ok := t.(*types.Basic) // *types.Named for `type MyStr string`
return ok && b.Kind() == types.String
}
// ...
sExpr := sText
if st := pass.TypesInfo.TypeOf(strArg); st != nil && !isExactString(st) {
sExpr = "string(" + sText + ")" // io.WriteString(w, string(s))
}
Use sExpr in both the message suggestion and buildFix. (Alternatively, skip named-string args entirely — but wrapping preserves coverage.)
- Add fix verification — switch the test to
analysistest.RunWithSuggestedFixes and add a .golden file (matching the 3 siblings). The golden file is compiled by the harness, so it would have caught this. Update the badNamedString expectation to io.WriteString(&buf, string(s)).
Validation checklist
Effort: Small (single file + testdata/golden).
Sergo R60 — new 43rd-linter (writebytestring) audit. Registry delta 42→43. Reconciled: all prior sergo issues (incl. #43934 / #43683) closed; zero open pre-run.
Generated by 🤖 Sergo - Serena Go Expert · 270.9 AIC · ⌖ 14.1 AIC · ⊞ 5.8K · ◷
Summary
The newly-added 43rd analyzer
writebytestring(pkg/linters/writebytestring/writebytestring.go) rewritesw.Write([]byte(s))→io.WriteString(w, s). ItsisStringTypecheck accepts named string types (defined types whose underlying type isstring), butio.WriteString's signature requires an exactstring. For a named-string argument the emitted SuggestedFix (and the diagnostic's suggestion text) does not compile. The linter's own testdata encodes this broken suggestion, and it slips through because the test usesanalysistest.Run(diagnostic-only) instead ofanalysistest.RunWithSuggestedFixes— the exact verification-parity gap previously filed/closed as #43313.Not currently CI-enforced, so this is latent today (no
-fixruns incgo.yml), but it makes the autofix unsafe to apply and blocks safe enforcement of this linter.The defect
isStringType(writebytestring.go:152-159) matches viat.Underlying():The fix builder (writebytestring.go:184-194) then inserts
io.WriteString(<w>, <s-text>)verbatim. Butio.WriteString(w Writer, s string)takes a predeclaredstring. Go assignability: two distinct named types with identical underlying types are not assignable, andstringis itself a named (predeclared) type. So passing aMyStrvalue yieldscannot use s (variable of type MyStr) as string value in argument to io.WriteString.Evidence — the testdata proves it
pkg/linters/writebytestring/testdata/src/writebytestring/writebytestring.go:21-27:The
// wantregex asserts the linter emitsio.WriteString(&buf, s). Applied as a fix, that line isio.WriteString(&buf, s)withsof typemyString→ compile error.writebytestring_test.go:15runs onlyanalysistest.Run(...), which verifies diagnostic messages but never compiles the fixed output — so the broken fix passes tests.Why the copied logic is wrong here (contrast with appendbytestring)
writebytestringmirrorsappendbytestring(sameisStringTypeviaUnderlying()). That is correct for appendbytestring becauseappend(b, s...)'s string special-case accepts any operand with core typestring(named strings included). It is wrong for writebytestring becauseio.WriteString's parameter is a concretestring, requiring an explicit conversion for named types.All three existing SuggestedFix siblings verify their fixes:
appendbytestring_test.go:15→analysistest.RunWithSuggestedFixesstringsindexcontains_test.go:15→analysistest.RunWithSuggestedFixessprintfint_test.go:15→analysistest.RunWithSuggestedFixeswritebytestringregresses this convention — the same gap closed in #43313.Impact
-fix) to anyw.Write([]byte(namedStr))site produces code that does not compile.cgo.ymlenforcement (with-fix) until corrected.Write([]byte(...))sites exist (e.g.pkg/logger/logger.go:102,pkg/workflow/strings.go:171-172,pkg/parser/schedule_fuzzy_scatter.go:175); plain-stringones fix correctly, but any named-string arg would break.Recommended fix
string:Use
sExprin both the message suggestion andbuildFix. (Alternatively, skip named-string args entirely — but wrapping preserves coverage.)analysistest.RunWithSuggestedFixesand add a.goldenfile (matching the 3 siblings). The golden file is compiled by the harness, so it would have caught this. Update thebadNamedStringexpectation toio.WriteString(&buf, string(s)).Validation checklist
type MyStr stringarg produces a fix that compiles (io.WriteString(w, string(s))).stringargs unchanged (io.WriteString(w, s)); verifylogger.go:102/workflow/strings.go:171-172fixes still compile..golden+RunWithSuggestedFixes;go test ./pkg/linters/writebytestring/...passes and the golden compiles.&bufforbytes.Buffer) still correct alongside thestring(...)wrap.Effort: Small (single file + testdata/golden).
Sergo R60 — new 43rd-linter (
writebytestring) audit. Registry delta 42→43. Reconciled: all prior sergo issues (incl. #43934 / #43683) closed; zero open pre-run.