Summary
In the source generator, integral enum underlying values are formatted into generated source using the ambient culture. EnumValueToString pins InvariantCulture for the float/double cases but falls back to a culture-sensitive object.ToString() for the integral cases. The result is written verbatim into the generated C# source, so on a build host configured with a non-invariant culture the generated output can differ for negative or large enum values (e.g. a non-ASCII negative sign), undermining deterministic / reproducible builds.
Root cause
src/PolyType.SourceGenerator/Parser/Parser.ModelMapper.cs:791-797
private static string EnumValueToString(object underlyingValue)
=> underlyingValue switch
{
float f => f.ToString("R", System.Globalization.CultureInfo.InvariantCulture),
double d => d.ToString("R", System.Globalization.CultureInfo.InvariantCulture),
_ => underlyingValue.ToString(), // ambient culture for byte/sbyte/short/ushort/int/uint/long/ulong
};
The boxed integral value's ToString() uses CultureInfo.CurrentCulture's NumberFormatInfo, whose negative sign (and other formatting) is not guaranteed to be ASCII/invariant.
This value flows into the emitted model and is written directly into source:
- Constructed at
Parser.ModelMapper.cs:36
Members = enumModel.Members.ToImmutableEquatableDictionary(m => m.Key, m => EnumValueToString(m.Value)),
- Emitted at
src/PolyType.SourceGenerator/SourceFormatter/SourceFormatter.Enum.cs:57
writer.WriteLine($"""["{member.Key}"] = {member.Value},""");
Impact
- Reproducibility / determinism: generated source for an enum with negative or large underlying values can vary by build-host culture. This is a (minor, low-likelihood) violation of the generator's "deterministic, culture-independent generation" property.
- Not a runtime-data / untrusted-input concern — the enum metadata is control-plane.
Repro sketch
Build a project containing an enum with a negative underlying value (e.g. enum E : int { A = -1 }) on a host with a culture whose NumberFormatInfo.NegativeSign is non-ASCII (some ar-* cultures). The generated member dictionary entry can contain a non-ASCII negative sign instead of -.
Suggested fix
Format the integral fallback with InvariantCulture as well, e.g.:
_ => ((IFormattable)underlyingValue).ToString(null, System.Globalization.CultureInfo.InvariantCulture),
(all the boxed integral primitives implement IFormattable). A regression test that formats a negative-valued enum under a non-invariant CultureInfo.CurrentCulture and asserts ASCII output would lock this down.
Found during a threat-model review of the core programming model and the two built-in shape providers.
Summary
In the source generator, integral enum underlying values are formatted into generated source using the ambient culture.
EnumValueToStringpinsInvariantCulturefor thefloat/doublecases but falls back to a culture-sensitiveobject.ToString()for the integral cases. The result is written verbatim into the generated C# source, so on a build host configured with a non-invariant culture the generated output can differ for negative or large enum values (e.g. a non-ASCII negative sign), undermining deterministic / reproducible builds.Root cause
src/PolyType.SourceGenerator/Parser/Parser.ModelMapper.cs:791-797The boxed integral value's
ToString()usesCultureInfo.CurrentCulture'sNumberFormatInfo, whose negative sign (and other formatting) is not guaranteed to be ASCII/invariant.This value flows into the emitted model and is written directly into source:
Parser.ModelMapper.cs:36src/PolyType.SourceGenerator/SourceFormatter/SourceFormatter.Enum.cs:57Impact
Repro sketch
Build a project containing an enum with a negative underlying value (e.g.
enum E : int { A = -1 }) on a host with a culture whoseNumberFormatInfo.NegativeSignis non-ASCII (somear-*cultures). The generated member dictionary entry can contain a non-ASCII negative sign instead of-.Suggested fix
Format the integral fallback with
InvariantCultureas well, e.g.:(all the boxed integral primitives implement
IFormattable). A regression test that formats a negative-valued enum under a non-invariantCultureInfo.CurrentCultureand asserts ASCII output would lock this down.Found during a threat-model review of the core programming model and the two built-in shape providers.