From d73fc19834957bcac1e344a65dea257e1fba37a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 04:22:20 +0000 Subject: [PATCH 1/3] Initial plan From eabac01b502d663b6f733a0dbf681c3ee230d7a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 04:29:27 +0000 Subject: [PATCH 2/3] Give stringbytesroundtrip.isExactString its own exactness logic Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../stringbytesroundtrip.go | 13 ++++++++++++- .../stringbytesroundtrip.go | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/pkg/linters/stringbytesroundtrip/stringbytesroundtrip.go b/pkg/linters/stringbytesroundtrip/stringbytesroundtrip.go index 47d411eeb16..3f0a880a4b1 100644 --- a/pkg/linters/stringbytesroundtrip/stringbytesroundtrip.go +++ b/pkg/linters/stringbytesroundtrip/stringbytesroundtrip.go @@ -135,13 +135,24 @@ func reportWastefulCloneRoundTrip(pass *analysis.Pass, outer, inner *ast.CallExp ) } +// isStringType reports whether t is a string basic type. Callers pass an +// already-.Underlying()-resolved type, so this also matches named string types. func isStringType(t types.Type) bool { basic, ok := t.(*types.Basic) return ok && basic.Kind() == types.String } +// isExactString reports whether t is the predeclared string type, not a named +// type whose underlying type is string. Unlike isStringType, which expects an +// already-.Underlying()-resolved type, isExactString must be given the raw type +// so it can tell string from `type MyString string`. That distinction matters +// because only a predeclared string can have both conversions removed; a named +// string type still needs an outer conversion. func isExactString(t types.Type) bool { - return isStringType(t) + if _, named := t.(*types.Named); named { + return false + } + return isStringType(t.Underlying()) } func isByteSliceType(t types.Type) bool { diff --git a/pkg/linters/stringbytesroundtrip/testdata/src/stringbytesroundtrip/stringbytesroundtrip.go b/pkg/linters/stringbytesroundtrip/testdata/src/stringbytesroundtrip/stringbytesroundtrip.go index 5ec67cd4c58..a30076bd7d9 100644 --- a/pkg/linters/stringbytesroundtrip/testdata/src/stringbytesroundtrip/stringbytesroundtrip.go +++ b/pkg/linters/stringbytesroundtrip/testdata/src/stringbytesroundtrip/stringbytesroundtrip.go @@ -37,6 +37,23 @@ func badNamedTypes() string { return string([]byte(ms)) // want `string\(\[\]byte\(ms\)\) is a redundant round-trip; replace it with string\(ms\)` } +// aliasString is an alias for the predeclared string type, so round-trips +// through it are fully redundant. +type aliasString = string + +func badMixedNamedTypes() { + s := "hello" + var ms myString = "hello" + var as aliasString = "hello" + + // Outer conversion is a named string type: the outer conversion must stay. + _ = myString([]byte(s)) // want `myString\(\[\]byte\(s\)\) is a redundant round-trip; replace it with myString\(s\)` + // Argument is a named string type: an outer conversion is still needed. + _ = string([]byte(ms)) // want `string\(\[\]byte\(ms\)\) is a redundant round-trip; replace it with string\(ms\)` + // Alias of the predeclared string type: both conversions can be removed. + _ = aliasString([]byte(as)) // want `aliasString\(\[\]byte\(as\)\) is a redundant round-trip; both conversions can be removed` +} + // helperString is a regular function, not a type conversion — must not be flagged. func helperString(b []byte) string { return string(b) } From fc0e810fb56a07ac7c868fe69c837ede6bf574a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 12:53:14 +0000 Subject: [PATCH 3/3] Unalias types in isExactString and cover alias-to-named string Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .../stringbytesroundtrip.go | 19 +++++++++---------- .../stringbytesroundtrip.go | 7 +++++++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/pkg/linters/stringbytesroundtrip/stringbytesroundtrip.go b/pkg/linters/stringbytesroundtrip/stringbytesroundtrip.go index 3f0a880a4b1..fc4f4a2200d 100644 --- a/pkg/linters/stringbytesroundtrip/stringbytesroundtrip.go +++ b/pkg/linters/stringbytesroundtrip/stringbytesroundtrip.go @@ -142,17 +142,16 @@ func isStringType(t types.Type) bool { return ok && basic.Kind() == types.String } -// isExactString reports whether t is the predeclared string type, not a named -// type whose underlying type is string. Unlike isStringType, which expects an -// already-.Underlying()-resolved type, isExactString must be given the raw type -// so it can tell string from `type MyString string`. That distinction matters -// because only a predeclared string can have both conversions removed; a named -// string type still needs an outer conversion. +// isExactString reports whether t denotes the predeclared string type, not a +// named type whose underlying type is string. Unlike isStringType, which +// expects an already-.Underlying()-resolved type, isExactString must be given +// the raw type so it can tell string from `type MyString string`. Aliases are +// resolved first, because an alias may denote either the predeclared string +// (`type A = string`) or a named string type (`type A = MyString`). That +// distinction matters because only the predeclared string can have both +// conversions removed; a named string type still needs an outer conversion. func isExactString(t types.Type) bool { - if _, named := t.(*types.Named); named { - return false - } - return isStringType(t.Underlying()) + return isStringType(types.Unalias(t)) } func isByteSliceType(t types.Type) bool { diff --git a/pkg/linters/stringbytesroundtrip/testdata/src/stringbytesroundtrip/stringbytesroundtrip.go b/pkg/linters/stringbytesroundtrip/testdata/src/stringbytesroundtrip/stringbytesroundtrip.go index a30076bd7d9..980b0331b1b 100644 --- a/pkg/linters/stringbytesroundtrip/testdata/src/stringbytesroundtrip/stringbytesroundtrip.go +++ b/pkg/linters/stringbytesroundtrip/testdata/src/stringbytesroundtrip/stringbytesroundtrip.go @@ -41,10 +41,15 @@ func badNamedTypes() string { // through it are fully redundant. type aliasString = string +// namedAlias is an alias for a named string type, so an outer conversion is +// still required even though its underlying type is string. +type namedAlias = myString + func badMixedNamedTypes() { s := "hello" var ms myString = "hello" var as aliasString = "hello" + var na namedAlias = "hello" // Outer conversion is a named string type: the outer conversion must stay. _ = myString([]byte(s)) // want `myString\(\[\]byte\(s\)\) is a redundant round-trip; replace it with myString\(s\)` @@ -52,6 +57,8 @@ func badMixedNamedTypes() { _ = string([]byte(ms)) // want `string\(\[\]byte\(ms\)\) is a redundant round-trip; replace it with string\(ms\)` // Alias of the predeclared string type: both conversions can be removed. _ = aliasString([]byte(as)) // want `aliasString\(\[\]byte\(as\)\) is a redundant round-trip; both conversions can be removed` + // Alias of a named string type: the outer conversion must stay. + _ = namedAlias([]byte(na)) // want `namedAlias\(\[\]byte\(na\)\) is a redundant round-trip; replace it with namedAlias\(na\)` } // helperString is a regular function, not a type conversion — must not be flagged.