Panic and false positives on Go 1.27 promoted-field keys in composite literals

Author: kamenlitchevCreated Sep 3, 2026Updated Sep 3, 2026
Labelsbugdependencies

Welcome

  • Yes, I'm using a binary release within 2 latest releases. Only such installations are supported.
  • Yes, I've searched similar issues on GitHub and didn't find any.
  • Yes, I've read the typecheck section of the FAQ.
  • Yes, I've tried with the standalone linter if available (e.g., gocritic, go vet, etc.).
  • I agree to follow this project's Code of Conduct

How did you install golangci-lint?

go install

Description of the problem

Go 1.27 allows a composite literal key to be any field selector, including a promoted field of an embedded struct (spec, "Composite literals"): Outer{A: "a"} now sets Outer.Inner.A directly, with no nested Inner{...}. skippedNamed assumes every literal key names a field of the outer struct, which no longer holds. This produces two distinct symptoms from one root cause.

Version of golangci-lint

2.13.2 built with go1.27.1

Configuration

.golangci.yml - every other linter off, to isolate this one:

yaml
version: "2"
linters:
  default: none
  enable:
    - exhaustruct_v5

Go environment

bash
go version go1.27.1 windows/amd64
set AR=ar
set CC=gcc
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_ENABLED=0
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set CXX=g++
set GCCGO=gccgo
set GO111MODULE=
set GOAMD64=v1
set GOARCH=amd64
set GOAUTH=netrc
set GOCACHEPROG=
set GODEBUG=
set GOEXE=.exe
set GOEXPERIMENT=
set GOFIPS140=off
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMOD=NUL
set GOOS=windows
set GOPACKAGESDRIVER=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTELEMETRY=local
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.27.1
set GOWORK=
set PKG_CONFIG=pkg-config

Verbose output of running

Verbose output

Standalone exhaustruct ./... (no flags, no config) — exit 2

panic: runtime error: makeslice: cap out of range

goroutine 52 [running]:
dev.gaijin.team/go/exhaustruct/v5/internal/structure.(*Struct).skippedNamed(0x3a4a671301b0, 0x3a4a6712e600, 0x0)
	dev.gaijin.team/go/exhaustruct/[email protected]/internal/structure/struct.go:138 +0x195
dev.gaijin.team/go/exhaustruct/v5/internal/structure.(*Struct).SkippedFields(0x3a4a671301b0?, 0x3a4a6712e600?, {0x3a4a6711660a?, 0x3a4a671301b0?})
	dev.gaijin.team/go/exhaustruct/[email protected]/internal/structure/struct.go:86 +0x6a
dev.gaijin.team/go/exhaustruct/v5/analyzer.literalVisitor.checkLiteral({0x3a4a66fbbc28, 0x3a4a6712e600, {0x3a4a67114280, 0x5, 0x8}}, {0x3a4a671301b0?, 0xb0?, 0x33?})
	dev.gaijin.team/go/exhaustruct/[email protected]/analyzer/missing-fields-visitor.go:270 +0xbd
dev.gaijin.team/go/exhaustruct/v5/analyzer.literalVisitor.process({0x3a4a66fbbc28, 0x3a4a6712e600, {0x3a4a67114280, 0x5, 0x8}})
	dev.gaijin.team/go/exhaustruct/[email protected]/analyzer/missing-fields-visitor.go:105 +0xdd
dev.gaijin.team/go/exhaustruct/v5/analyzer.(*missingFieldsVisitor).visit(0x3a4a67178328?, {0x7ff766bf6030?, 0x3a4a6712e600?}, 0x0?, {0x3a4a67114280?, 0x1?, 0x3a4a67114280?})
	dev.gaijin.team/go/exhaustruct/[email protected]/analyzer/missing-fields-visitor.go:54 +0x65
golang.org/x/tools/go/ast/inspector.(*Inspector).WithStack(0x3a4a6711a318, {0x3a4a66ef9c50?, 0x3a4a67102210?, 0x1?}, 0x3a4a66fbbc40)
	golang.org/x/[email protected]/go/ast/inspector/inspector.go:206 +0x186
dev.gaijin.team/go/exhaustruct/v5/analyzer.(*missingFieldsVisitor).run(...)
	dev.gaijin.team/go/exhaustruct/[email protected]/analyzer/missing-fields-visitor.go:40
dev.gaijin.team/go/exhaustruct/v5/analyzer.run(0x3a4a6716e0e0, 0x3a4a66f72af0, 0x3a4a67114200)
	dev.gaijin.team/go/exhaustruct/[email protected]/analyzer/analyzer.go:110 +0x1df
dev.gaijin.team/go/exhaustruct/v5/analyzer.NewAnalyzer.func2(0x3a4a6716e0e0)
	dev.gaijin.team/go/exhaustruct/[email protected]/analyzer/analyzer.go:35 +0x4f
golang.org/x/tools/go/analysis/checker.(*Action).execOnce.func3(...)
	golang.org/x/[email protected]/go/analysis/checker/checker.go:359
golang.org/x/tools/go/analysis/checker.(*Action).execOnce(0x3a4a6712c320)
	golang.org/x/[email protected]/go/analysis/checker/checker.go:380 +0xbfc
sync.(*Once).doSlow(0x3a4a6712e340?, 0x7ff76679f0e0?)
	sync/once.go:78 +0xab
sync.(*Once).Do(...)
	sync/once.go:69
golang.org/x/tools/go/analysis/checker.(*Action).exec(...)
	golang.org/x/[email protected]/go/analysis/checker/checker.go:258
golang.org/x/tools/go/analysis/checker.execAll.func1(0x3a4a66ef9f98?)
	golang.org/x/[email protected]/go/analysis/checker/checker.go:246 +0x45
created by golang.org/x/tools/go/analysis/checker.execAll in goroutine 1
	golang.org/x/[email protected]/go/analysis/checker/checker.go:252 +0x167

golangci-lint cache clean && golangci-lint run -v ./... — exit 3

level=info msg="golangci-lint has version 2.13.2 built with go1.27.1"
level=info msg="[config_reader] Used config file .golangci.yml"
level=info msg="[config_reader] Module name \"exbug\""
level=info msg="[goenv] Read go env for 55.8393ms: map[string]string{\"GOCACHE\":\"...\", \"GOROOT\":\"...\"}"
level=info msg="[lintersdb] Active 1 linters: [exhaustruct_v5]"
level=info msg="[loader] Go packages loading at mode 8767 (deps|exports_file|files|imports|name|compiled_files|types_sizes) took 673.6954ms"
level=info msg="[runner/filename_unadjuster] Pre-built 0 adjustments in 0s"
level=info msg="[linters_context/goanalysis] analyzers took 0s with top 10 stages: typecheck: 0s, inspect: 0s"
level=error msg="[runner] Panic: exhaustruct_v5: package \"exbug\" (isInitialPkg: true, needAnalyzeSource: true): runtime error: makeslice: cap out of range: goroutine 180 [running]:\nruntime/debug.Stack()\n\t...\ndev.gaijin.team/go/exhaustruct/v5/internal/structure.(*Struct).skippedNamed(...)\n\t.../internal/structure/struct.go:138 +0x195\n..."
level=warning msg="[runner] Can't run linter goanalysis_metalinter: goanalysis_metalinter: exhaustruct_v5: package \"exbug\" (isInitialPkg: true, needAnalyzeSource: true): runtime error: makeslice: cap out of range"
level=info msg="[runner] processing took 0s with stages: path_absoluter: 0s, fixer: 0s, ..."
level=info msg="[runner] linters took 2.5762ms with stages: goanalysis_metalinter: 2.5762ms"
level=error msg="Running error: can't run linter goanalysis_metalinter\ngoanalysis_metalinter: exhaustruct_v5: package \"exbug\" (isInitialPkg: true, needAnalyzeSource: true): runtime error: makeslice: cap out of range"
level=info msg="Memory: 9 samples, avg is 42.9MB, max is 43.3MB"
level=info msg="Execution took 732.1109ms"

The panic inside the level=error line is the same stack as above, with golangci-lint's goanalysis frames wrapped around it.

A minimal reproducible example or link to a public repository

### 1. Panic when promoted keys outnumber the outer struct's own fields
go
package exbug

type Inner struct{ A, B string }

type Outer struct{ Inner }

func New() Outer {
	return Outer{A: "a", B: "b"}
}

Compiles cleanly under Go 1.27. exhaustruct ./... panics:

panic: runtime error: makeslice: cap out of range

goroutine 52 [running]:
dev.gaijin.team/go/exhaustruct/v5/internal/structure.(*Struct).skippedNamed(...)
	internal/structure/struct.go:138
dev.gaijin.team/go/exhaustruct/v5/internal/structure.(*Struct).SkippedFields(...)
	internal/structure/struct.go:86
dev.gaijin.team/go/exhaustruct/v5/analyzer.literalVisitor.checkLiteral(...)
	analyzer/missing-fields-visitor.go:270

2. False positive when the counts happen not to go negative

type Inner struct{ A string }

type Outer struct {
	Inner

	C string
}

func New() Outer {
	return Outer{A: "a", C: "c"}
}

Reports exbug.Outer is missing field Inner, though the literal initializes every field.

Validation

  • Yes, I've included all the information above (example, version, config, etc.).

Supporter