Skip to content

Unify numeric types API across all targets#12825

Open
Simn wants to merge 26 commits intodevelopmentfrom
haxe-numeric
Open

Unify numeric types API across all targets#12825
Simn wants to merge 26 commits intodevelopmentfrom
haxe-numeric

Conversation

@Simn
Copy link
Member

@Simn Simn commented Mar 16, 2026

At the moment all targets mostly just do their own thing when it comes to numeric types. While some like haxe.Int64 are somewhat nicely abstracted in the user-facing code, others are really only available via their target-specific type. I'd like to look into cleaning this up to have a unified API.

Initial analysis:


Numeric types are inconsistently available and named across Haxe targets. This PR documents the current state and proposes a unification path.

Current State

  • Int precision varies per target: 31-bit (Neko), 32-bit (C++/JVM/HL), 53-bit (JS), unbounded (Python), platform-dependent (PHP)
  • haxe.Int64 is the only well-unified fixed-width type — 4 targets use native overrides, 6 use emulated high/low pair
  • Platform-specific types are fragmented: C++ has full set (cpp.Int8cpp.UInt64, cpp.Float32/Float64), JVM has partial (jvm.Int8, jvm.Int16, jvm.Int64), HL uses different naming (hl.I64, hl.F32, hl.UI8), scripting targets have nothing
  • Missing from haxe.*: No cross-platform Int8, Int16, UInt8, UInt16, UInt64, Float32
  • UInt is only truly unsigned on Flash; simulated elsewhere
  • Single (Float32) exists as @:coreType on only 3 targets

Proposal

Follow the proven haxe.Int64 pattern (cross-platform abstract + native _std overrides) to add:

Type Emulated Fallback Native On
haxe.Int8 abstract(Int) + masking C++, JVM
haxe.Int16 abstract(Int) + masking C++, JVM
haxe.UInt8 abstract(Int) + masking C++, HL
haxe.UInt16 abstract(Int) + masking C++, HL
haxe.UInt32 extend existing UInt C++
haxe.UInt64 high/low pair C++, Eval
haxe.Float32 abstract(Float) (lossy) C++, JVM, HL
haxe.Float64 alias for Float All

Open Questions

  • Should Int be pinned to 32-bit or documented as "at least 32-bit"?
  • Runtime masking cost for emulated small types on scripting targets — acceptable or compile-time-only?
  • Deprecation timeline for platform-specific types (cpp.Int8haxe.Int8, hl.I64haxe.Int64, Singlehaxe.Float32)
  • Phasing: likely start with UInt8/UInt16 (binary protocols) and Float32 (graphics/games)

Research

Surveyed Rust, Go, Swift, Kotlin, C# — all provide complete fixed-width type sets consistent across platforms. Swift's protocol hierarchy (NumericBinaryIntegerFixedWidthInteger) is the most sophisticated generic approach. Kotlin Multiplatform normalizes all numeric types in common code without needing expect/actual.

@Simn
Copy link
Member Author

Simn commented Mar 16, 2026

@copilot Please do a survey of the current situation for all our targets. Observe and summarize how they are represented for all of them, and then make a suggestion on how we could go about unifying this properly. Research how other language standard libraries handle this to get a sense for best practices.

Copy link
Contributor

Copilot AI commented Mar 16, 2026

@Simn I've opened a new pull request, #12826, to work on those changes. Once the pull request is ready, I'll request review from you.

…a thin @:op wrapper (#12826)

* Initial plan

* Refactor Int64: extract Int64Native, disable platform overrides, fix JVM generator

- Create std/haxe/numeric/Int64Native.hx with cross-platform emulation
- Refactor std/haxe/Int64.hx to use haxe.numeric.Int64Native as underlying type
- Disable platform-specific Int64 overrides (cpp, jvm, hl, eval)
- Remove haxe.Int64 -> TLong mapping from JVM generator
- Remove special Int64.make() optimization from JVM generator
- Fix JVM stdlib Jvm.hx to work with emulated Int64

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Refactor Int64: move implementation logic to Int64Native, make Int64 a thin @:op wrapper

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Address review: remove __Int64 typedef and IMPL, move Int64Helper to haxe.numeric, add JVM Int64Native abstract shadow

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Add HL Int64Native shadow, import cleanup, forward isInt64, re-add genjvm mapping, delete .disabled files

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Add C++ and Eval Int64Native shadows wrapping native platform types

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Fix C++ CPPIA inline requirement and docgen Int64 merge incompatibility

- C++ Int64Native: Make set_high, set_low, and new unconditionally inline
  (they modify `this` which requires inline even in CPPIA mode)
- Convert cross-platform Int64Native from class to abstract over Int64Data
  (fixes docgen "could not merge" error between macro/lua targets where
  Int64Native was abstract on native targets but class on emulation)
- Create Int64Data.hx as backing class with standalone toString()
- Update 6 platform-specific Type.hx files to reference Int64Data

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Add hxcpp bootstrapping to copilot setup and document C++ compilation in instructions

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Fix docgen merge: skip Int64Native in XmlParser merge, add @:dox(hide), remove differing doc comments

The haxe.rtti.XmlParser merge logic already had a hardcoded skip for the old
haxe._Int64.NativeInt64 path. Added haxe.numeric.Int64Native to the same skip
since it's intentionally different per platform (abstract over different native
types). Also added @:dox(hide) to all Int64Native implementations and removed
platform-specific doc comments that caused "documentation is different" errors.

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Remove accidentally committed dox build artifacts, add to .gitignore

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* this should be better

* Apply typedef pattern to JVM/HL/C++, fix CPPIA inline, fix Type.hx Int64Data refs, clean up eval @:op

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* what do you mean?

* inline things

* docgen is a nuisance

* more CPPIA inlining

* add the host class too while we're at it

* close enough

* remove bad inlining because this isn't going to work for CPPIA

* Fix CPPIA: use extern abstract + #if !scriptable inline pattern, add getHigh/getLow static functions

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* CPPIA: use properties instead of getHigh/getLow workaround, remove #if cpp from Int64.hx

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
Co-authored-by: Simon Krajewski <simon@haxe.org>
@Simn
Copy link
Member Author

Simn commented Mar 17, 2026

@copilot In 1f84a42 we have reworked Int64 to a unified system. Let's now do the same for Int32 to verify that the system is flexible:

  • haxe/Int32.hx becomes the thin @:op layer which defers to the native implementation.
  • That native implementation sits in haxe/numeric/Int32Native.hx. The main version is target-agnostic and should be an abstract(Int) with the appropriate masking.
  • Targets that have a native Int32 type shadow Int32Native.hx and define an abstract over the native type with the same public API, avoiding any unnecessary operations.
  • Neko needs a custom implementation because it's Int type is 31 bits.

I'm not sure how well tested Int32 is in our unit tests, so let's also take this opportunity to add unit/src/unit/haxe/Int32.hx with a good test coverage. If you come across test failures on some targets, please just #if those tests out for now and add a comment so we can look into that later.

Copy link
Contributor

Copilot AI commented Mar 17, 2026

@Simn I've opened a new pull request, #12828, to work on those changes. Once the pull request is ready, I'll request review from you.

…#12828)

* Initial plan

* Refactor Int32 to use Int32Native pattern matching Int64 architecture

- Create std/haxe/numeric/Int32Native.hx as cross-platform abstract(Int) with masking
- Create JVM, HL, C++, Neko target overrides in _std directories
- Refactor std/haxe/Int32.hx to thin @:op layer delegating to Int32Native
- Add comprehensive TestInt32 unit test class
- All tests passing on interp, JS, JVM, Neko, Python, PHP

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Address code review: add comments for Neko negation and C++ test skip

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Address PR review: fix test #if, remove Neko override, remove target docs and @:coreApi

- Move #if !cpp to cover entire testArrayIndexWithInt32 function (avoids utest "no assertions")
- Remove Neko-specific Int32Native override (cross-platform version works correctly via Float auto-promotion)
- Remove doc comments from target-specific Int32Native files (docs belong on main Int32 type)
- Remove unnecessary @:coreApi(check = Off) from private types (Implied check handles these)
- Add platform representation docs to main Int32.hx

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Fix CPPIA neg overflow, deduplicate Int32Native with Int32Direct, remove @:coreApi from Int64Native, add issue regression tests

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
@Simn
Copy link
Member Author

Simn commented Mar 17, 2026

@copilot Before we work on more types we need to discuss what to do with the unsigned variants. We'll definitely need to define central haxe/UInt64.hx and such for each of them, but the question is if we can avoid the code duplication on the native side by working with a single implementation per integer size. I haven't given this much thought yet, so go ahead and see if we can implement UInt64 on top of NativeInt64 or if that comes with complications.

Copy link
Contributor

Copilot AI commented Mar 17, 2026

@Simn I've opened a new pull request, #12834, to work on those changes. Once the pull request is ready, I'll request review from you.


package haxe.numeric;

typedef Int64Native = Int64NativeImpl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can follow a similar pattern here like Int32Direct and Int32Native so that the class implementation of Int64 is available for hl-legacy32

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That hinges on whether or not we have to drag hl-legacy32 into Haxe 5 in the first place. But I would like to review this part again anyway because the code duplication between native targets annoys me and it's largely because they have different native underlying types.

// Use ~x+1 (two's complement) rather than unary minus.
// On CPPIA, unary minus on Int can return a value wider than 32 bits,
// while bitwise NOT and addition stay within the native 32-bit int range.
return cast(~(x : Int) + 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this bug is cppia only then it sounds like it should be fixed in the cppia runtime rather than something that should have a workaround here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but my rate-limit on CPPIA patience has been reached for this week.

@Simn
Copy link
Member Author

Simn commented Mar 17, 2026

I think the missing piece of the puzzle here is that the cross-target implementation should also typedef to something else which has the actual implementation. That way even targets can use parts of that (like the string parsing) and we don't need to duplicate so much.

That still doesn't solve the abstract NativeInt64Impl duplications, but looking at the current implementation of those there are actually subtle differences anyway, so perhaps this is fine for now.

Simn and others added 2 commits March 17, 2026 15:00
…gh native layer (#12834)

* Initial plan

* Implement UInt64 on top of Int64Native, sharing the same backing type

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Route UInt64 operations through Int64Native, remove toStr, fix increment style

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
@Simn
Copy link
Member Author

Simn commented Mar 17, 2026

Actually I suppose that's pretty much what we already have anyway because this shared code lives in the Int64Helper and UInt64Helper classes, and the native versions simply call that. So I guess the current state is fine and the only part left to discuss in that regard is the HL legacy situation.

@Simn
Copy link
Member Author

Simn commented Mar 17, 2026

@copilot From #12834:

Possible gaps to discuss:

  • Should UInt64 have isUInt64(val:Dynamic):Bool? (Int64 has isInt64, but they share the same runtime type)

I don't think this is needed for now because arguably even isInt64 should already not exist and is a workaround for there not being a good cross-target value we can use in value is Here. Which makes me think that there should be, but I'm unsure how to go about that. The obvious one would be Int64 and UInt64 itself, but I don't know if that's a good idea. Please let me know your thoughts!

  • Should Int64 also get toFloat/fromInt64?

Yes! This has been missing for a long time and recently came up in another context. Let's add this while we're here and make sure to properly test it.

  • Int64's toInt throws on overflow, UInt64's toInt silently truncates — should they be consistent?

If we can get this to behave consistently with minimal overhead then we should do it. Our other numeric types don't throw on overflow (I think? Let's verify that), so I don't see why Int64 should.


Also, the test discussion has reminded me of something else that I wanted to bring up, which is MIN/MAX constants for numeric data types. Please take a look if we can get away with simply adding static public final MAX fields to Int64/Int32 and the unsigned variants. This might need some #if magic to get it right for all targets but that seems acceptable. And, of course, test it! There are already some tests providing their own constants which should no longer be needed afterwards.

Copy link
Contributor

Copilot AI commented Mar 17, 2026

@Simn I've opened a new pull request, #12836, to work on those changes. Once the pull request is ready, I'll request review from you.

…/Int32/UInt64 (#12836)

* Initial plan

* Add toFloat, fix toInt truncation, add MIN/MAX to Int64/Int32/UInt64

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* it works for me like this

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
Co-authored-by: Simon Krajewski <simon@haxe.org>
@Simn
Copy link
Member Author

Simn commented Mar 17, 2026

@copilot I've noticed that Int32 has a bunch of @:op methods without expressions, but Int64 doesn't. Please check if these actually do something useful or if they can be removed. If they do something useful, let me know what so we can look into unifying the APIs between these two types more.

Copy link
Contributor

Copilot AI commented Mar 17, 2026

@Simn I've opened a new pull request, #12837, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 2 commits March 17, 2026 19:30
…ds; add `compare()` (#12837)

* Initial plan

* Replace body-less @:op methods in Int32 with explicit implementations; add compare()

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Redesign Int32 operator overloads: use type-parameter bounds, auto-promotions; remove exhaustive mixed-type permutations

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
@Simn
Copy link
Member Author

Simn commented Mar 17, 2026

@copilot Let's take a step back to review the current state of the generated code so that we make sure to avoid introducing performance regressions here. I've made a very simple test case to focus on a single thing for now:

@:analyzer(ignore)
@:keep
class NumericTests {

	static final i = 0;
	static final i32 = 0i32;
	static final i64 = 0i64;

	@:pure(false)
	static function use<T>(v:T) {}

	static function eqI64I32() {
		use(i64 == i32);
	}

}

function main() {}

Here's the before/after of the generated and decompiled JVM output:

 0 getstatic #10 <haxe/root/NumericTests.i64>
 3 getstatic #14 <haxe/root/NumericTests.i32>
 6 i2l
 7 lcmp
 8 ifne 15 (+7)
11 iconst_1
12 goto 16 (+4)
15 iconst_0
16 invokestatic #20 <java/lang/Boolean.valueOf>
19 invokestatic #24 <haxe/root/NumericTests.use>
22 return
 0 lconst_0
 1 lstore_0
 2 getstatic #10 <haxe/root/NumericTests.i32>
 5 i2l
 6 lstore_0
 7 lload_0
 8 lstore_0
 9 getstatic #14 <haxe/root/NumericTests.i64>
12 lload_0
13 lcmp
14 ifne 21 (+7)
17 iconst_1
18 goto 22 (+4)
21 iconst_0
22 invokestatic #20 <java/lang/Boolean.valueOf>
25 invokestatic #24 <haxe/root/NumericTests.use>
28 return

And here the same for HL:

	; source/Main.hx:13 (NumericTests.eqI64I32)
		r0 void
		r1 bool
		r2 i64
		r3 $NumericTests
		r4 i32
		r5 i64
		r6 dyn
		.13    @0 global 3, 3
		.13    @1 field 2,3[7]
		.13    @2 global 3, 3
		.13    @3 field 4,3[6]
		.13    @4 toint 5,4
		.13    @5 jnoteq 2,5,2
		.13    @6 true 1
		.13    @7 jalways 1
		.13    @8 false 1
		.13    @9 todyn 6,1
		.13    @A call 0, NumericTests.use(6)
		.13    @B ret 0
	; source/Main.hx:13 (NumericTests.eqI64I32)
		r0 void
		r1 i64
		r2 i32
		r3 $NumericTests
		r4 i64
		r5 bool
		r6 dyn
		.13    @0 global 3, 3
		.13    @1 field 2,3[6]
		.13    @2 toint 4,2
		.13    @3 mov 1,4
		.13    @4 global 3, 3
		.13    @5 field 4,3[7]
		.13    @6 jnoteq 4,1,2
		.13    @7 true 5
		.13    @8 jalways 1
		.13    @9 false 5
		.13    @A todyn 6,5
		.13    @B call 0, NumericTests.use(6)
		.13    @C ret 0

Please do a deep investigation of this and what causes the differences. Look into how the compiler handles abstract casts and type conversions in general, and also check out the two generators in that regard. See where improvements can be made and optimizations need to be applied. Focus on the compiler sources for that, no the standard library changes here.

You can use -D dump to generate the texpr data the compiler generates, and that also gives you the dump/hlcode.txt to inspect. You can also check out the generated JAR (install whatever tools you need, you should have access). This is a very important point which decides if our general approach here is feasible, so try your best!

@Simn
Copy link
Member Author

Simn commented Mar 20, 2026

Here's a summary of the design that has emerged:

  • Int and Float remain untouched. If anything we're going to lose the 32-bit restrictions on Int as a next step.
  • You can go into fixed-width "mode" via any of the Int32.fromInt or Int32.fromFloat functions. This conversion is explicit.
  • Within fixed-width types, conversions to the wider type (e.g. Int32 to Int64) are always implicitly allowed. Also, conversions between the unsigned and signed versions are implicitly allowed. I'm considering adding an optional warning for those.
  • Conversions in the other direction are explicit, e.g. int64.toInt32. This doesn't do any overflow checks and simply coerces the value.
  • Likewise, conversions back to Int/Float land are explicit via int64.toInt/int64.toFloat. This is true even for types where there's no risk of losing precision in order to avoid unwanted implicit transitive casting scenarios which currently happen on development.

This gives us a dual-path design that keeps the existing flexibility of Int/Float, but allows strictly defined numeric conversions for Int32 and such in places where it matters. There will be a perceived loss of convenience in that area because values can no longer be arbitrarily converted, but I'm prepared to argue that this is a major improvement.

Copilot AI and others added 2 commits March 20, 2026 18:30
…ng conversions for (U)Int32/64 (#12845)

* Initial plan

* Add Float comparison ops to Int32/Int64/UInt64; add @:to Float to Int64/UInt64

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Remove @:to Float from Int64/UInt64; integer cross-type comparisons use integer semantics

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Changes before error encountered

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Implement strict numeric type design: remove implicit float casts, add widening conversions, guard breaking tests

- Remove @:transitive from Int32, UInt32, Int64 (breaks implicit multi-step coercions)
- Remove @:to Float and all Float @:op overloads from Int32 and UInt32
- Add @:to Int to Int32 for explicit widening to platform Int
- Add @:from Int32, @:from UInt32 widening casts to Int64; @:from UInt32, @:from Int64 to UInt64
- Add same-size @:from casts (Int32↔UInt32, Int64↔UInt64)
- Add explicit toFloat(), toInt32(), toUInt32(), toInt64(), toUInt64(), fromFloat() to all types
- Fix stdlib that relied on removed implicit conversions: io/Bytes.hx, io/BytesBuffer.hx,
  io/Output.hx, io/UInt32Array.hx, js/_std/io/UInt32Array.hx, numeric/Int64Native.hx,
  numeric/UInt64Helper.hx, eval/_std/numeric/Int64Native.hx
- Guard all breaking tests behind #if loose_numeric_casts with noAssert() fallback:
  Issue2718, 2735, 2736, 2871, 3543, 3852, 5362 and TestInt32/TestUInt32/TestUInt32Array

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Fix bigint stdlib: change Vector<Int32> to Vector<Int>, shift params Int32→Int, tolerance UInt→Int

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Fix bigint (Vector<Int32>, switch cast, public API) and bytes flash conversions (UInt.toInt())

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* fixes

* dance around HL problems again

* avoid flash problems too

* it's 2026 and I'm editing flash externs, send help

* missed one

* try to fix the actual problem instead of doing this nonsense

* update hlcode tests

* I'm told this was fixed

* Add hlcode tests for all (U)Int32/64 ↔ Int conversions and mixed operations

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* delete more code

* deal with some tests

* remove flash @:transitive

* another one

* that one was needed

* update bytes public interface

* Fix _std overrides: update getInt32/setInt32/addInt32/i32ToFloat/floatToI32 to use Int32

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Fix flash CI failure: remove untyped in BytesOutput.getBytes, use bytes.length.toInt()

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* fix

* Add testStrictTypeChecking to TestInt32/UInt32/Int64/UInt64 asserting disallowed conversions cause type errors

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Remove @:to Int from Int32, update API functions to use Int32 (partial - more fixes needed)

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Fix Int64Native shift/toString after @:to Int removal, fix stdlib sites using readInt32/setInt32

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Fix HL target: Int32→hl.I64 in Int64Native, setI32 calls need .toInt(), fix hl/Bytes.fromAddress

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Fix unit tests: use .toInt() for Int32→Int conversions in tests and issue regressions

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Fix HL-specific TestInt32 edge cases: use .toInt() for Int32→Int comparisons

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Reorganize @:to/@:from casts per Simn's design: add back @:to Int on Int32, use @:to on source types instead of @:from on target types

Int32: @:to Int (public), @:to UInt32 (private), @:to Int64 (private)
UInt32: @:to Int32 (private), @:to UInt64 (private), toInt() (public, no @:to)
Int64: @:to UInt64 (private), toInt32() (public)
UInt64: @:to Int64 (private), toUInt32() (public)

Removed @:from fromInt32/fromUInt32/fromUInt64 from Int64 and UInt64.
Removed @:from fromUInt32/fromInt32 from Int32/UInt32.
Removed toUInt64() from Int32, toInt64() from UInt32 (long paths).
Removed toUInt32() from Int64, toInt32() from UInt64 (use toInt32().toUInt32()).

All tests pass: interp (11572), JS (11625), JVM (12340), HL (12003), flash compiles clean.

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* Fix formatting in Int64Native shr/ushr: consistent line breaks in ternary expressions

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

* avoid HL more

* Re-apply Int32 API signatures for "32" suffix functions: readInt32():Int32, i64ToDouble(low:Int32,high:Int32)

Since @:to Int is back on Int32, functions with "Int32" suffix should use
haxe.Int32 in their signatures. The @:to Int handles implicit coercion
where needed in function bodies.

Restored:
- Input.readInt32():Int32 and BytesInput override
- FPHelper.i64ToDouble(low:Int32, high:Int32) in all target overrides
  (cpp, eval, hl, lua, php, python)
- Python FileInput.readInt32():Int32 and IInput.readInt32():Int32

All tests pass: interp (11572), JS (11625), JVM (12340), HL (12003),
Python (11715), flash compiles clean, hlcode (38 tests).

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
Co-authored-by: Simon Krajewski <simon@haxe.org>
Simn added 3 commits March 20, 2026 20:26
# Conflicts:
#	src/generators/genhl.ml
#	std/haxe/UInt32.hx
#	std/hl/_std/haxe/UInt32.hx
#	tests/unit/src/unit/issues/Issue2990.hx
# Conflicts:
#	std/cpp/_std/haxe/Int64.hx
#	std/eval/_std/haxe/Int64.hx
#	std/haxe/Int64.hx
#	std/hl/_std/haxe/Int64.hx
#	std/jvm/_std/haxe/Int64.hx
@Simn
Copy link
Member Author

Simn commented Mar 20, 2026

Right, that test still fails on these targets because the expression is something like this:

@:implicitCast {
       var x = cast cast python.Syntax.code("{0} % {1}", -1756908916 + python.Syntax.opPow(2, 31), python.Syntax.opPow(2, 32)) - python.Syntax.opPow(2, 31);
       var this;
       this = x;
       cast cast this;
      }

The analyzer manages to turn the expression into a constant on other targets, but this is a bit much. This is one of these cases that would need better internal constant typing.

@Simn
Copy link
Member Author

Simn commented Mar 20, 2026

@copilot Something we still need to do here is change how the comparison operators are generated. It currently goes through the (u)compare functions, but on targets with native support we want to generate the native comparison operators. This means that these calls have to be dispatched to the Int32Native/Int64Native types instead.

Make sure to check the hlcode tests after updating this because it should change the results there.

Copy link
Contributor

Copilot AI commented Mar 20, 2026

@Simn I've opened a new pull request, #12855, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 2 commits March 20, 2026 22:46
…gen (#12855)

* Initial plan

* Dispatch comparison operators to Int32Native/Int64Native for native codegen

Add lt/lte/gt/gte (signed) and ult/ulte/ugt/ugte (unsigned) comparison
functions to Int32Native, Int32Direct, Int64Native, and all target-specific
overrides (JVM, HL, C++, eval).

Update Int32/Int64/UInt32/UInt64 abstract operators to call these native
functions directly instead of going through compare()/ucompare().

On targets with native support (JVM, HL), signed comparisons now generate
direct native comparison operators instead of the multi-branch
compare-return-{-1,0,1}-then-check pattern.

HL bytecode improvement for Int32 < Int: 19 → 11 instructions
HL bytecode improvement for Int64 < Int: 20 → 12 instructions

Updated hlcode test expectations accordingly.

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
Agent-Logs-Url: https://github.com/HaxeFoundation/haxe/sessions/00ae78d7-e1c3-49a6-8216-da0541560084

* Clarify hlcode test comment about unsigned comparison dispatch

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
Agent-Logs-Url: https://github.com/HaxeFoundation/haxe/sessions/00ae78d7-e1c3-49a6-8216-da0541560084

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
@Simn
Copy link
Member Author

Simn commented Mar 20, 2026

@copilot I just noticed that isZero and isNeg are static function, which is strange. Let's turn these into methods consistently because that makes more sense.

Let's also take this opportunity to align the order of functions on all four types. I think Int64 and UInt64 already match (double-check that), so let's align Int32 and UInt32 to that. Let's also add copy to those classes for consistency, and if you find anything else that's inconsistent let's deal with that too.

Copy link
Contributor

Copilot AI commented Mar 20, 2026

@Simn I've opened a new pull request, #12857, to work on those changes. Once the pull request is ready, I'll request review from you.

… fix HL opcode tests (#12857)

* Initial plan

* Change isZero/isNeg to instance methods, add copy() to Int32/UInt32, align API order, fix HL opcode tests

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
Agent-Logs-Url: https://github.com/HaxeFoundation/haxe/sessions/330c2464-c0e6-4f48-b8e5-899473797b40

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR advances the Haxe stdlib/compiler toward a unified, cross-target fixed-width numeric API by moving core numeric behavior behind haxe.numeric.* backends, adding haxe.UInt64, and updating tests/target stubs to match the new semantics.

Changes:

  • Refactors haxe.Int32, haxe.UInt32, and haxe.Int64 to delegate to haxe.numeric.Int32Native / haxe.numeric.Int64Native with target-specific _std overrides (JVM/HL/Eval/CPP).
  • Introduces haxe.UInt64 plus unsigned helper utilities, and updates multiple targets’ Type detection and runtime support to recognize the new backing implementation.
  • Expands/updates unit + codegen tests to lock in operator semantics, strict-cast behavior, and JS/HL codegen expectations.

Reviewed changes

Copilot reviewed 70 out of 71 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/unit/src/unit/teststd/haxe/io/TestUInt32Array.hx Updates UInt32Array tests (formatting + toFloat() usage) to match new UInt32 API.
tests/unit/src/unit/teststd/haxe/TestUInt32.hx Adds a comprehensive UInt32 behavior test suite (string/compare/ops/shifts/strict casts).
tests/unit/src/unit/teststd/haxe/TestInt32.hx Reworks Int32 tests to cover constants/overflow/ops/shifts/strict casts under new backend.
tests/unit/src/unit/issues/misc/Issue12415Abstract.hx Updates Int64 zero-check usage to new instance API style.
tests/unit/src/unit/issues/Issue6942.hx Migrates enum abstract from UInt to haxe.UInt32 and adjusts expectations.
tests/unit/src/unit/issues/Issue5362.hx Updates UInt mixing by using explicit toInt() conversion.
tests/unit/src/unit/issues/Issue4870.hx.disabled Adds a disabled regression scaffold referencing UInt behavior.
tests/unit/src/unit/issues/Issue3852.hx Gates UInt/Float mixed ops behind #if loose_numeric_casts.
tests/unit/src/unit/issues/Issue3543.hx Makes UInt division explicitly float-based via toFloat().
tests/unit/src/unit/issues/Issue2990.hx Updates typeof expectations to enforce integer semantics for UInt32 division/modulo.
tests/unit/src/unit/issues/Issue2871.hx Makes UInt-to-Int conversion explicit via toInt().
tests/unit/src/unit/issues/Issue2736.hx Gates UInt vs Float comparisons behind #if loose_numeric_casts.
tests/unit/src/unit/issues/Issue2735.hx Makes UInt-to-Float conversion explicit via toFloat().
tests/unit/src/unit/issues/Issue12415.hx Updates Int64 zero-check on nullable abstract via instance API.
tests/unit/src/unit/issues/Issue11248.hx Adjusts constants (UInt→Int) with comment about UInt32 typing friction.
tests/unit/src/unit/TestType.hx Reorders imports and updates Flash UInt typing expectation to use toInt().
tests/unit/src/unit/TestMain.hx Adds TestUInt64 to the unit test suite run list.
tests/unit/src/unit/TestInt64.hx Updates Int64 tests to new APIs (fromInt, toString, %), adds strict-cast tests.
tests/optimization/src/TestJs.hx Adds JS output test ensuring UInt32 Std.string uses unsigned formatting logic.
tests/hlcode/src/cases/UInt32Types.hx Adds HL bytecode “golden” tests documenting UInt32 compare/shift lowering.
tests/hlcode/src/cases/NumericTypes.hx Adds HL bytecode tests ensuring numeric abstracts don’t introduce temporaries.
std/python/_std/Type.hx Updates TInt64 runtime detection to use haxe.numeric.Int64Native.
std/php/_std/Type.hx Updates TInt64 runtime detection to use haxe.numeric.Int64Native.
std/neko/_std/Type.hx Updates TInt64 runtime detection to use haxe.numeric.Int64Native.
std/lua/_std/Type.hx Updates TInt64 runtime detection to use haxe.numeric.Int64Native.
std/lua/_lua/_hx_bit_clamp.lua Adjusts Lua int32 clamping logic by forcing integer conversion prior to bit ops.
std/jvm/_std/haxe/numeric/Int64Native.hx Adds JVM-native Int64Native backend (wrapping jvm.Int64).
std/jvm/_std/haxe/numeric/Int32Native.hx Adds JVM Int32Native override typedef to Int32Direct.
std/jvm/_std/haxe/Int64.hx Removes JVM-specific haxe.Int64 implementation in favor of unified backend.
std/jvm/Jvm.hx Changes toLong to return haxe.Int64 and adjusts incr/decr paths.
std/jvm/Int64.hx Simplifies JVM core jvm.Int64 extern ops set (removes Int-mixed variants).
std/js/_std/haxe/io/UInt32Array.hx Ensures JS UInt32Array set writes the underlying Int via toInt().
std/js/_std/Type.hx Updates TInt64 runtime detection to use haxe.numeric.Int64Native.
std/hl/types/ArrayObj.hx Updates unsigned bounds checks to use haxe.UInt32 casts.
std/hl/types/ArrayBytes.hx Updates unsigned bounds checks to use haxe.UInt32 casts.
std/hl/_std/haxe/numeric/Int64Native.hx Adds HL-native Int64Native backend (wrapping hl.I64) under version guard.
std/hl/_std/haxe/numeric/Int32Native.hx Adds HL Int32Native override typedef to Int32Direct.
std/hl/_std/haxe/UInt32.hx Removes HL-specific haxe.UInt32 implementation in favor of unified backend.
std/hl/I64.hx Adds @:from conversion from haxe.Int64 to hl.I64.
std/hl/GUID.hx Adds @:from conversion from haxe.Int64 to hl.GUID.
std/haxe/rtti/XmlParser.hx Updates private-impl path filtering to new Int64Native private type path.
std/haxe/numeric/UInt64Helper.hx Introduces shared unsigned-64 helper algorithms (div/mod/parse/toString/toFloat).
std/haxe/numeric/Int64Native.hx Adds default (emulated) Int64Native backend + unsigned helpers wiring.
std/haxe/numeric/Int64Helper.hx Introduces shared Int64 parse/fromFloat helper for Int64Native backends.
std/haxe/numeric/Int32Native.hx Adds default Int32Native backend with clamp + unsigned helpers (parse/udiv/utoString).
std/haxe/numeric/Int32Helper.hx Introduces shared Int32 parsing/unsigned division helpers.
std/haxe/numeric/Int32Direct.hx Adds no-masking Int32 backend for native-32-bit targets (C++/JVM/HL).
std/haxe/io/BytesOutput.hx Fixes Flash ByteArray UInt→Int conversions for length and Bytes construction.
std/haxe/io/BytesInput.hx Fixes Flash ByteArray UInt→Int conversions for position/length/read methods.
std/haxe/io/BytesBuffer.hx Fixes Flash ByteArray UInt→Int conversions in size and Bytes creation.
std/haxe/io/Bytes.hx Fixes Flash ByteArray UInt→Int conversions in compare/fromString/ofData.
std/haxe/UInt64.hx Adds the public cross-platform haxe.UInt64 abstract API.
std/haxe/UInt32.hx Refactors UInt32 to a cross-platform abstract over Int32Native with unsigned semantics.
std/haxe/Int64Helper.hx Updates Int64Helper to use new instance-style APIs (fromInt, isNeg).
std/haxe/Int32.hx Refactors Int32 to a cross-platform abstract over Int32Native with consistent ops.
std/flash/_std/Type.hx Updates TInt64 runtime detection to use haxe.numeric.Int64Native.
std/flash/Boot.hx Fixes Flash stage color UInt→Int conversion.
std/eval/integers/Int64.hx Adjusts eval Int64 interface visibility/ops exposure.
std/eval/_std/haxe/numeric/Int64Native.hx Adds eval-native Int64Native backend (wrapping eval.integers.Int64).
std/eval/_std/haxe/Int64.hx Removes eval-specific haxe.Int64 implementation in favor of unified backend.
std/cpp/cppia/HostClasses.hx Updates kept host class list to new Int64Native impl path; tidies extern set formatting.
std/cpp/_std/haxe/numeric/Int64Native.hx Adds C++ Int64Native backend using hxcpp helper intrinsics (incl. cppia surface).
std/cpp/_std/haxe/numeric/Int32Native.hx Adds C++ Int32Native override typedef to Int32Direct.
std/cpp/_std/haxe/Int64.hx Removes C++-specific haxe.Int64 implementation in favor of unified backend.
src/optimization/inline.ml Adds UInt32 case to Std.string inliner (noted as effectively unreachable).
src/generators/genjvm.ml Removes JVM codegen special-case for haxe.Int64.make constant folding.
src/generators/genhl.ml Disables UInt32-as-unsigned detection (commented out due to failures).

@Simn
Copy link
Member Author

Simn commented Mar 21, 2026

@copilot Please take a look at the still unresolved review comments!

Copy link
Contributor

Copilot AI commented Mar 21, 2026

@Simn I've opened a new pull request, #12859, to work on those changes. Once the pull request is ready, I'll request review from you.

…ation fix, test assertion fixes (#12859)

* Initial plan

* Address review comments: explain genhl.ml UInt32 design, fix Lua truncation, fix Int32/64 test assertions

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
Agent-Logs-Url: https://github.com/HaxeFoundation/haxe/sessions/01972f1a-bfdf-4e6b-94da-b1c2d5d377db

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
@Simn Simn marked this pull request as ready for review March 21, 2026 08:05
@Simn
Copy link
Member Author

Simn commented Mar 21, 2026

This is as good as it's gonna get. We still have the @:to Int on Int32 which is probably going to remain necessary. I do want to remove the @:from Int at some point, but that requires dealing with constants first.

As a next step we should probably look into adding @:forward.op to simplify all this a little and have the user-facing types be much more lightweight. And once we have that adding (U)Int8/16 should be quite simple.

@skial skial mentioned this pull request Mar 24, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants