Closed
Description
Go version
go version go1.24.2 darwin/arm64, golang.org/x/tools/gopls v0.18.1
Output of go env
in your module/workspace:
AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/aron/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/aron/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/f5/d_lvj8s17bx46zzhfr5gqywc0000gp/T/go-build2208041228=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/Users/aron/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/aron/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/aron/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.2'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
Original code:
package main
import (
"fmt"
)
func main() {
var m []string
s := append([]string{}, m[0:]...)
fmt.Printf("%#v\n", s)
}
Prior to any modification, running the program:
go run ./main.go
#> []string{}
What did you see happen?
Adjusting this code with the modernizer modifies its behavior.
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest --fix ./main.go
After modification, running the program:
go run ./main.go
#> []string(nil)
The modified contents:
package main
import (
"fmt"
"slices"
)
func main() {
var m []string
s := slices.Clone(m[0:])
fmt.Printf("%#v\n", s)
}
What did you expect to see?
I expected the result of the program to be unchanged.
Workaround: Detect when m
or s
is nil
and use a []string{}
value rather than the slices.Clone()
result.
Metadata
Metadata
Assignees
Labels
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
[-]x/tools/gopls: modernizer slices.Clone replacement results in nil slice[/-][+]x/tools/gopls/internal/analysis/modernize: append([]T{}, x...) -> slices.Clone(x) fix may unsoundly return nil[/+]gabyhelp commentedon Apr 30, 2025
Related Issues
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
gopherbot commentedon May 1, 2025
Change https://go.dev/cl/669295 mentions this issue:
gopls/internal/analysis/modernize: preserve non-nil behavior when simplifying append([]T{}, s...)
adonovan commentedon May 5, 2025
Here's a recap of the behavior of the relevant operations wrt nil:
The inconsistency of Concat is frustrating, but perhaps too late to change.
Given that the nilness behavior of the slices package is undocumented, I wonder whether this modernizer's fixes can ever be safely offered, since they could change in future (though perhaps any such change to the slices package would be rejected as too risky). Assuming the current semantics of the slices package are here to stay, we should not offer the slices.Concat fix if we cannot prove its result is non-empty.
gopherbot commentedon Jun 2, 2025
Change https://go.dev/cl/678135 mentions this issue:
gopls/internal/analysis/modernize: add TODOs for nilness problem
adonovan commentedon Jun 2, 2025
The nilness postconditions of slices package are now all documented (see https://go.dev/cl/671996).
That said, it's not clear to me that the appendclipped modernizer can realistically be fixed. It's already complicated, and now we'll have a combinatoric explosion of cases, each of which requires yet more information about the operands (nilness): Consider just one transformation:
I plan to document the problem in the code for now, and punt this to gopls/v0.20.
aronatkins commentedon Jun 2, 2025
My original example was extracted from a much larger program, which saw test failures as a result of the nil/non-nil change. I ran the modernizer across the whole codebase; this issue was the only fallout.
My vote would be to suppress this rewrite in any situation where the validity is questionable. If that means the rewrite never occurs because there's insufficient information about the incoming values, so be it.
gopls/internal/analysis/modernize: add TODOs for nilness problem
adonovan commentedon Jun 2, 2025
Fair enough. I'll disable it for now.
gopherbot commentedon Jun 2, 2025
Change https://go.dev/cl/678117 mentions this issue:
gopls/internal/analysis/modernize: disable appendclipped
gopls/internal/analysis/modernize: disable appendclipped