Evaluate enum constants with go/constant - #24
Merged
Conversation
Previously the generator understood only iota, plain literals and a binary expression with one of + - * / around iota. Anything else silently produced 0: shifts, bitwise operators, parentheses, references to other constants, and every literal that is not plain decimal, since the literal converter scanned with %d and stopped at the first character it did not understand. After this change constant expressions are evaluated with go/constant, which covers the whole integer operator set and keeps values exact, so a uint64 enum past MaxInt64 keeps its value. A spec without an expression now repeats the expression of the previous spec, the rule the language itself uses, instead of replaying a recorded operation. Values that cannot be evaluated, or that do not fit the underlying type, are reported as errors instead of becoming 0.
The complement of an unsigned constant depends on the width of its type, so ^perm(0) of a uint8 based enum is 255 and not -1. Values now carry the builtin type they were converted to, and a conversion reports the values that type cannot hold, matching what the compiler accepts. Constants declared inside a function are no longer collected: they are not in scope for the enum values and a local name would shadow the package level one. Arithmetic on constants with a fractional literal is evaluated exactly, as the language does, so 1.5 * 2 is 3 rather than an error, and len of a string literal is supported.
A typed operand decides the type of an operation, so x / 2.0 is an integer division when x is a typed integer, and a value reached through a float keeps its exact result. A typed float holds its value at the width of its type, both after a conversion and after each operation. Conversions to float and string are evaluated rather than refused, len works on any string constant, min and max are evaluated, and a type declared by the package is followed before a builtin of the same name, through an alias chain of any length. A right shift is no longer capped: shifting past the width of a value settles at 0, or -1 when it is negative, which is what the compiler computes. The cap on a left shift stays, it bounds the size of the value being built. The complement of uint and uintptr is refused, it is a different value on a 32 and a 64 bit target while the generated file holds one number.
The type a constant is declared with shapes its value: a float one holds it at the width of that type, so a constant declared float32 rounds the way the compiler rounds it. min and max return their result in the type of a typed argument, and the underlying type of the enum is read through parentheses, so type code (uint8) is recognised as unsigned and rejects a negative value.
umputun
approved these changes
Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Previously the generator recognised only
iota, a plain literal, and a binary expression combiningiotawith one of+,-,*or/. Every other constant expression silently became zero, because the evaluator returned an error that was then discarded in favour of a zero value. That covered shifts, bitwise operators, parentheses, references to other constants, and every literal that is not plain decimal, since the literal converter scanned with%dand stopped at the first character it did not recognise. An enum written asgenerated
value: 0for all three constants, and0x10,0b1010and1_000became zero in the same way. With-getterthe duplicate zeroes failed generation instead.On one source file, the same constants before and after:
permRead,permWrite,permExec1 << iotapermAll^perm(0)permBasebase, declared as0x10permCalcperm(base/2 + len("ab"))permNeg1_0 - 2The
-1in the last row was written into auint8field, so that file did not compile at all. Every value in the last column is the one the compiler computes for the same constant, checked by generating the enum and comparingIndex()against the original constant in a test.After this change constant expressions are evaluated with
go/constant. The whole integer operator set is supported, together with literals in any base, conversions,len,min,max, and references to other constants declared in the package. The type a constant carries is followed through the expression, since it decides several results:^perm(0)complements within the width ofperm, a typed integer divides as an integer, and a constant declaredfloat32holds its value at the width of that type. Values are kept exact rather than narrowed toint, so auint64enum aboveMaxInt64keeps its value. A constant declared without an expression repeats the expression of the preceding one, which is the rule the language itself applies, instead of replaying a recorded operation. Each of these cases was checked against the value the compiler computes for the same constant, in a differential comparison over 38 expressions and by generating enums and comparingIndex()against the original constant.Values that cannot be evaluated, and values that do not fit the underlying type of the enum, are now reported as errors rather than becoming zero. Two things are deliberately left out and named in the error: a constant of another package, which would need the type checker and a resolvable import graph, and the constant built-ins other than
len, which do not appear in enum declarations. The complement ofint,uintanduintptris refused as well, since its value depends on the width of the target architecture while the generated file holds one number.What changes for existing users:
Index()andGet...ByIDchange accordingly, so a project that persisted those integers needs to consider a migration.