Repository navigation
[Python/JS/TS] Integer parsing accepts inputs .NET rejects (sign and 0x prefix with HexNumber, + before radix specifier) #4827
Description
Integer parsing accepts several inputs that .NET rejects with FormatException. These are all cases where Fable is more permissive than .NET, so unlike #4823 nothing crashes and no valid program misbehaves — but the targets disagree with .NET, and with each other in one case.
Splitting this out of the work on #4823/#4825: the neighbouring bugs where Fable was less capable than .NET (int " 0x11" and int "-0x11" failing to parse) are being fixed separately. These remaining ones are left alone deliberately, because tightening them could break code that currently works, so it seemed worth a maintainer decision rather than a drive-by change.
Divergences
All values below were measured, not inferred — .NET via dotnet fsi, Python via fable_library.core, JS via temp/fable-library-js/Int32.js.
| Input | .NET | Python | JS/TS |
|---|---|---|---|
Int32.Parse("-1F", NumberStyles.HexNumber) |
FormatException |
-31 |
-31 |
Int32.Parse("0x1F", NumberStyles.HexNumber) |
FormatException |
31 |
31 |
int "+0x11" |
FormatException |
ValueError ✅ |
17 ❌ |
- Sign accepted with
HexNumber. .NET'sNumberStyles.HexNumberdoes not includeAllowLeadingSign, so a sign is a format error. Both targets accept it. 0xprefix accepted withHexNumber. .NET never accepts a0xprefix inInt32.Parseat all — the prefix forms are an FSharp.Core feature ofint/int64/etc. on strings, not a BCL one. Both targets strip the prefix whenever the radix is non-decimal, so it is accepted here too.+before a radix specifier. FSharp.Core consumes only-before looking for the0x/0o/0bspecifier, soint "+0x11"is a format error on .NET whileint "+11"is11. Python matches .NET; JS does not — its regex (^\s*([\+\-])?(0[xXoObB])?([0-9a-fA-F]+)\s*$,src/fable-library-ts/Int32.ts) allows either sign before the prefix group.
Reproduction
open System
open System.Globalization
printfn "%A" (try box (Int32.Parse("-1F", NumberStyles.HexNumber)) with e -> box e.GetType().Name)
printfn "%A" (try box (Int32.Parse("0x1F", NumberStyles.HexNumber)) with e -> box e.GetType().Name)
printfn "%A" (try box (int "+0x11") with e -> box e.GetType().Name)dotnet run # FormatException, FormatException, FormatException
dotnet fable Repro.fsproj --lang python -o out && python out/program.py
dotnet fable Repro.fsproj --lang javascript -o out && node out/program.jsWhere the behavior lives
- Python —
remove_prefixinsrc/fable-library-py/src/ints.rsstrips a0x/0o/0bprefix wheneverradix != 10, without consulting whether the radix came fromAllowHexSpecifieror from the prefix itself. The sign is likewise not checked against the style flags. - JS/TS —
isValidinsrc/fable-library-ts/Int32.tsmatches sign and prefix with one regex regardless of style, thengetRadixreturns 16 forAllowHexSpecifierwithout rejecting a prefix that was also present.
Making either strict would mean threading the style flags into the prefix/sign handling so that AllowHexSpecifier rejects both, and restricting the pre-specifier sign to -.
Scope checked
Verified on Python and JS/TS (which share the fable-library-ts implementation). Rust, Dart, PHP, and BEAM were not checked — they have separate parsing implementations and may or may not have the same gaps.
Worth noting
Fixing these is a behavioral tightening: code that currently parses "0x1F" with HexNumber would start throwing. Might be worth confirming the desired semantics (strict .NET parity vs. current lenience) before changing anything, and it may be reasonable to close this as intended behavior.
Activity
[-]Integer parsing accepts inputs .NET rejects (sign and 0x prefix with HexNumber, + before radix specifier)[/-][+][Python] Integer parsing accepts inputs .NET rejects (sign and 0x prefix with HexNumber, + before radix specifier)[/+][-][Python] Integer parsing accepts inputs .NET rejects (sign and 0x prefix with HexNumber, + before radix specifier)[/-][+][Python/JS/TS] Integer parsing accepts inputs .NET rejects (sign and 0x prefix with HexNumber, + before radix specifier)[/+]