Skip to content

Commit 543ee6d

Browse files
committed
fix: deduplicate enum constant names that collapse to the same identifier
buildEnums deduplicated on EnumReplace(v), but the final constant name is StructName(enumName+"_"+value), which collapses characters further (e.g. a trailing underscore). So enum values like "A+" and "A-" reduced to distinct EnumReplace outputs ("A" and "A_") that both mapped to the same constant name, producing duplicate Go constants that fail to compile. Dedup on the final constant name instead, falling back to value_<i> on collision. Fixes #4515
1 parent e209d86 commit 543ee6d

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

internal/codegen/golang/result.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,24 @@ func buildEnums(req *plugin.GenerateRequest, options *opts.Options) []Enum {
4141
seen := make(map[string]struct{}, len(enum.Vals))
4242
for i, v := range enum.Vals {
4343
value := EnumReplace(v)
44-
if _, found := seen[value]; found || value == "" {
44+
if value == "" {
4545
value = fmt.Sprintf("value_%d", i)
4646
}
47+
// Dedup on the final constant name, not on EnumReplace(v):
48+
// StructName further collapses characters (e.g. a trailing "_"),
49+
// so distinct EnumReplace outputs like "A" and "A_" (from "A+"
50+
// and "A-") can still map to the same constant name.
51+
name := StructName(enumName+"_"+value, options)
52+
if _, found := seen[name]; found {
53+
value = fmt.Sprintf("value_%d", i)
54+
name = StructName(enumName+"_"+value, options)
55+
}
4756
e.Constants = append(e.Constants, Constant{
48-
Name: StructName(enumName+"_"+value, options),
57+
Name: name,
4958
Value: v,
5059
Type: e.Name,
5160
})
52-
seen[value] = struct{}{}
61+
seen[name] = struct{}{}
5362
}
5463
enums = append(enums, e)
5564
}

internal/codegen/golang/result_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package golang
33
import (
44
"testing"
55

6+
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
67
"github.com/sqlc-dev/sqlc/internal/metadata"
78
"github.com/sqlc-dev/sqlc/internal/plugin"
89
)
@@ -76,3 +77,42 @@ func TestPutOutColumns_AlwaysTrueWhenQueryHasColumns(t *testing.T) {
7677
t.Error("should be true when we have columns")
7778
}
7879
}
80+
81+
// TestBuildEnums_DeduplicatesConstantNames covers enum values that differ only
82+
// by characters that get stripped or collapsed when building a Go identifier
83+
// (e.g. "A+" and "A-"), which previously produced duplicate constant names and
84+
// uncompilable output.
85+
func TestBuildEnums_DeduplicatesConstantNames(t *testing.T) {
86+
req := &plugin.GenerateRequest{
87+
Catalog: &plugin.Catalog{
88+
DefaultSchema: "public",
89+
Schemas: []*plugin.Schema{
90+
{
91+
Name: "public",
92+
Enums: []*plugin.Enum{
93+
{
94+
Name: "blood_group_type",
95+
Vals: []string{"A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"},
96+
},
97+
},
98+
},
99+
},
100+
},
101+
}
102+
103+
enums := buildEnums(req, &opts.Options{})
104+
if len(enums) != 1 {
105+
t.Fatalf("expected 1 enum, got %d", len(enums))
106+
}
107+
if got := len(enums[0].Constants); got != 8 {
108+
t.Fatalf("expected 8 constants, got %d", got)
109+
}
110+
111+
seen := make(map[string]string, 8)
112+
for _, c := range enums[0].Constants {
113+
if prev, dup := seen[c.Name]; dup {
114+
t.Errorf("duplicate constant name %q for values %q and %q", c.Name, prev, c.Value)
115+
}
116+
seen[c.Name] = c.Value
117+
}
118+
}

0 commit comments

Comments
 (0)