-
Notifications
You must be signed in to change notification settings - Fork 566
Proposal: Allow Same Names Across Different Enum Types
Extend the Simplified Enum Type Declaration feature to permit multiple enum types to share identical underlying constant values when they use the same constant name, instead of treating this as a redeclaration error.
XGo's enum type declaration uses the following syntax:
type Identifier const (
Name1 [= Expr1]
Name2 [= Expr2]
...
)-
Identifierbecomes a new named type. - Each
Namebecomes a package-level constant of typeIdentifier. -
Exprfollows the same rules as ordinaryconstdeclarations, includingiotasupport and the "repeat previous expression" shorthand.
For example:
type Weekday const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)This expands to underlying type int and generates:
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)Because each enum constant is a package-level identifier, it is accessed directly by name — Sunday, Monday, etc. — not via a type-qualified form like Weekday.Sunday.
Because enum constants are package-level identifiers, two enum types in the same package that both declare a constant with the same name will currently produce a redeclaration error. In practice, however, it is very common for different enum types to independently use the same sentinel name — most typically None — with identical underlying values. For example:
type RepeatMode const (
None = ""
Once = "Once"
Daily = "Daily"
Weekly = "Weekly"
Monthly = "Monthly"
Yearly = "Yearly"
)
type MonthlyRepeatOnDay const (
None = ""
Sunday = "Sunday"
Monday = "Monday"
Tuesday = "Tuesday"
Wednesday = "Wednesday"
Thursday = "Thursday"
Friday = "Friday"
Saturday = "Saturday"
Day = "day"
Weekday = "weekday"
WeekendDay = "weekend day"
)Both types declare None = "". This is intentional: each type independently models the concept of "not set" with the same natural sentinel. The current prohibition forces awkward workarounds — distinct sentinel strings, extra wrapper types, or giving up enum types entirely — all of which compromise readability and domain correctness.
Two or more enum type declarations in the same package may declare constants with the same name, subject to the following rules:
- If a name
Nis declared in multiple enum types and all declarations assign the same constant valueV, the declarations are merged:Nis emitted once as an untyped constant with valueV, and each enum type gains a typed alias forN. - If a name
Nis declared in multiple enum types but with different underlying values, it is a compile-time error. - Within a single enum type, all constant names must remain unique.
-
The shared constant's value must be of kind
intorstring. Values of any other kind (e.g.float64,complex128,bool) are not permitted as shared enum constants and result in a compile-time error.
Legal — None = "" appears in both types with the same value:
type RepeatMode const (
None = ""
Once = "Once"
...
)
type MonthlyRepeatOnDay const (
None = ""
Sunday = "Sunday"
...
)Illegal — None appears in both types but with different values:
type RepeatMode const (
None = "none"
)
type MonthlyRepeatOnDay const (
None = "no repeat" // error: "None" already declared with value "none"
)Illegal — the shared constant's value is not of kind int or string:
type Foo const (
None = false // bool — cannot be shared
Bar = true
)
type Baz const (
None = false // error: shared constant "None" must be of kind int or string
Qux = true
)The XGo source:
type RepeatMode const (
None = ""
Once = "Once"
Daily = "Daily"
Weekly = "Weekly"
Monthly = "Monthly"
Yearly = "Yearly"
)
type MonthlyRepeatOnDay const (
None = ""
Sunday = "Sunday"
Monday = "Monday"
Tuesday = "Tuesday"
Wednesday = "Wednesday"
Thursday = "Thursday"
Friday = "Friday"
Saturday = "Saturday"
Day = "day"
Weekday = "weekday"
WeekendDay = "weekend day"
)generates:
type RepeatMode string
type MonthlyRepeatOnDay string
const None = "" // untyped string constant; usable as both RepeatMode and MonthlyRepeatOnDay
const (
_None_1 RepeatMode = ""
Once RepeatMode = "Once"
Daily RepeatMode = "Daily"
Weekly RepeatMode = "Weekly"
Monthly RepeatMode = "Monthly"
Yearly RepeatMode = "Yearly"
)
const (
_None_2 MonthlyRepeatOnDay = ""
Sunday MonthlyRepeatOnDay = "Sunday"
Monday MonthlyRepeatOnDay = "Monday"
Tuesday MonthlyRepeatOnDay = "Tuesday"
Wednesday MonthlyRepeatOnDay = "Wednesday"
Thursday MonthlyRepeatOnDay = "Thursday"
Friday MonthlyRepeatOnDay = "Friday"
Saturday MonthlyRepeatOnDay = "Saturday"
Day MonthlyRepeatOnDay = "day"
Weekday MonthlyRepeatOnDay = "weekday"
WeekendDay MonthlyRepeatOnDay = "weekend day"
)The shared name None is emitted as a single untyped string constant. Each participating enum type also gets a renamed typed constant (_None_1, _None_2, …) at the original position within its own const block, allowing reflection-based tooling (e.g. auto-generated String() methods) to associate the value with the name "None" inside each type's value set. The renamed constants are unexported and not intended for direct use.
The shared name itself may also be iota-based, as long as it evaluates to the same int value in all participating types. When None is the first entry in each enum, it evaluates to 0 in both, so sharing is valid. The two types may differ in their explicit underlying type annotation (e.g. uint8(iota) vs plain iota):
type Direction const (
None = uint8(iota) // 0
North // 1
South // 2
East // 3
West // 4
)
type Priority const (
None = iota // 0
Low // 1
High // 2
)
var d Direction = None
var p Priority = None
echo d, pgenerates:
type Direction uint8
type Priority int
const None = 0 // untyped integer constant; assignable to both Direction and Priority
const (
_None_1 Direction = uint8(iota) // 0
North // 1
South // 2
East // 3
West // 4
)
const (
_None_2 Priority = iota // 0
Low // 1
High // 2
)
var d Direction = None
var p Priority = None
fmt.Println(d, p)None being emitted as an untyped constant means it is assignable to any enum type whose underlying type is compatible, just like an untyped numeric or string literal. However, the two enum types themselves remain distinct named types and are not mutually assignable:
var r RepeatMode = None // OK: untyped "" is assignable to RepeatMode
var m MonthlyRepeatOnDay = None // OK: untyped "" is assignable to MonthlyRepeatOnDay
r = m // compile error: cannot use MonthlyRepeatOnDay as RepeatModeNone is a single package-level identifier. There is no ambiguity: it resolves to the untyped constant "" (or 0, etc.) regardless of which enum type is expected in context. Type checking then validates the assignment against the target type as usual.
-
String()/ value-to-name converters. If XGo auto-generatesString()methods or reverse-lookup tables for enum types, each type's converter should return its own type-local name ("None") regardless of the shared underlying value. This is straightforward since converters are per-type. -
Cross-package sharing. This proposal covers same-package declarations only. Enum constants from another package are accessed via the package qualifier (
pkg.None) and do not interact with local declarations.
Allowing multiple enum types to share the same constant name when the underlying values are identical removes a common friction point in domain modeling. The rule is minimal: same name + same value + value kind must be int or string; any deviation is a compile-time error. The implementation is straightforward — emit the shared name as a single untyped constant — and full type safety is preserved because the enum types themselves remain distinct named types.