Skip to content

[Python/JS/TS] Integer parsing accepts inputs .NET rejects (sign and 0x prefix with HexNumber, + before radix specifier) #4827

Description

@dbrattli

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
  1. Sign accepted with HexNumber. .NET's NumberStyles.HexNumber does not include AllowLeadingSign, so a sign is a format error. Both targets accept it.
  2. 0x prefix accepted with HexNumber. .NET never accepts a 0x prefix in Int32.Parse at all — the prefix forms are an FSharp.Core feature of int/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.
  3. + before a radix specifier. FSharp.Core consumes only - before looking for the 0x/0o/0b specifier, so int "+0x11" is a format error on .NET while int "+11" is 11. 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.js

Where the behavior lives

  • Pythonremove_prefix in src/fable-library-py/src/ints.rs strips a 0x/0o/0b prefix whenever radix != 10, without consulting whether the radix came from AllowHexSpecifier or from the prefix itself. The sign is likewise not checked against the style flags.
  • JS/TSisValid in src/fable-library-ts/Int32.ts matches sign and prefix with one regex regardless of style, then getRadix returns 16 for AllowHexSpecifier without 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions