Skip to content

x/tools/gopls/internal/analysis/modernize: append([]T{}, x...) -> slices.Clone(x) fix may unsoundly return nil #73557

Closed
@aronatkins

Description

@aronatkins

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.

Activity

added
ToolsThis label describes issues relating to any tools in the x/tools repository.
goplsIssues related to the Go language server, gopls.
on Apr 30, 2025
added this to the Unreleased milestone on Apr 30, 2025
changed the title [-]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[/+] on Apr 30, 2025
added
BugReportIssues describing a possible bug in the Go implementation.
on Apr 30, 2025
gopherbot

gopherbot commented on May 1, 2025

@gopherbot
Contributor

Change https://go.dev/cl/669295 mentions this issue: gopls/internal/analysis/modernize: preserve non-nil behavior when simplifying append([]T{}, s...)

adonovan

adonovan commented on May 5, 2025

@adonovan
Member

Here's a recap of the behavior of the relevant operations wrt nil:

  • []T(nil) is nil
  • T{} is non-nil
  • x[:i:i] has the nilness of x.
  • append(x, y...) preserves the nilness of x if the result is empty.
  • slices.Clip(x) preserves the nilness of x (undocumented).
  • slices.Clone(s) preserves the nilness of x (undocumented).
  • slices.Concat(slices...) is nil if the result is empty (undocumented).

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

gopherbot commented on Jun 2, 2025

@gopherbot
Contributor

Change https://go.dev/cl/678135 mentions this issue: gopls/internal/analysis/modernize: add TODOs for nilness problem

adonovan

adonovan commented on Jun 2, 2025

@adonovan
Member

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:

append(x, y...) where len(x)=cap(x)=0 -> Clone(y)
  x is nil
        y is nil => append returns nil, Clone returns nil (OK)
        y is empty => append returns nil, Clone returns non-nil (FAIL)
  x is empty
        y is nil => append returns non-nil, Clone returns nil (FAIL)
        y is empty => append returns non-nil, Clone returns non-nil (OK)

I plan to document the problem in the code for now, and punt this to gopls/v0.20.

aronatkins

aronatkins commented on Jun 2, 2025

@aronatkins
Author

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.

self-assigned this
on Jun 2, 2025
adonovan

adonovan commented on Jun 2, 2025

@adonovan
Member

Fair enough. I'll disable it for now.

gopherbot

gopherbot commented on Jun 2, 2025

@gopherbot
Contributor

Change https://go.dev/cl/678117 mentions this issue: gopls/internal/analysis/modernize: disable appendclipped

added a commit that references this issue on Jun 2, 2025
f12067d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

BugReportIssues describing a possible bug in the Go implementation.ToolsThis label describes issues relating to any tools in the x/tools repository.goplsIssues related to the Go language server, gopls.

Type

No type

Projects

No projects

Relationships

None yet

    Development

    No branches or pull requests

      Participants

      @aronatkins@adonovan@gopherbot@gabyhelp

      Issue actions

        x/tools/gopls/internal/analysis/modernize: append([]T{}, x...) -> slices.Clone(x) fix may unsoundly return nil · Issue #73557 · golang/go