Skip to content

Improve numeric handling - #6

Merged
bea4dev merged 8 commits into
ProjectTesca:mainfrom
ureishi:improve-numeric-handling
Sep 16, 2026
Merged

bea4dev merged 8 commits into
ProjectTesca:mainfrom
ureishi:improve-numeric-handling

Conversation

@ureishi

@ureishi ureishi commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Improve numeric handling to more closely match C# 9 behavior.

  • Apply numeric promotion before calling Udon operators, including small integer operands and mixed signed/unsigned comparisons.
  • Implement remainder operations for uint, long, and ulong, preserving exception behavior.
  • Preserve the heap types of small integer metadata constants.
  • Correct compound assignment validation and operator selection for constant expressions.
  • Emit floating-point infinity metadata in a format accepted by Unity Mono.

Environment

  • OS: Windows 11
  • Unity: 2022.3.22f1
  • VRChat SDK: 3.10.5

Notes

This pull request was prepared with assistance from OpenAI Codex (GPT-6), including implementation, test preparation, and investigation.

Please feel free to revise the code, tests, commit organization, or wording to better match the project's coding style and conventions.

Apply C# numeric promotion before selecting and calling Udon operators, so
small integer operands are converted to the types expected by the externs.
Share unary and binary promotion rules between checking and code generation.

Promote small integer unary operands to int, uint negation to long, and
mixed signed integer/uint operands to long. Reject incompatible numeric
pairs and invalid shift operands instead of emitting unusable calls.
Convert char through int for floating-point and decimal conversions, since
the direct Convert overloads do not support those conversions.

Add code-generation checks and SDK smoke coverage for promoted result types
and signed/uint comparisons in both operand orders. Extend the emulator's
UInt32-to-Int64 conversion support. Honor DOTNET_ROOT in the test reference
lookup so these tests can also run with a configured Windows .NET runtime.
Lower uint, long and ulong remainder to division, multiplication and
subtraction when a remainder extern is unavailable. Route division by zero
through generated exception handling so user code can catch it.

Keep this contribution within C# 9, as required by Unity 2022.3. Support
wide remainder compound assignments and ulong complement using XOR.
Retain ordinary Int32 shift emulation for C# 9 operator tests.

Add codegen and SDK smoke cases for negative remainders, wide values and
caught exceptions. The long.MinValue overflow case is covered by the
separate fix long remainder overflow commit.
Preserve sbyte, byte, short and ushort metadata constants as their declared
heap types instead of initializing their slots with boxed Int32 values.
A numerically correct value with the wrong boxed type can fail at a Udon
extern call or when the value is consumed through object.

Add typed HeapInit variants, serialize their metadata kinds, and decode
those kinds to the corresponding CLR types in the Unity importer. Keep the
emulator's existing Int32 representation for small integer values.

Inspect generated heap initializers directly in the compiler regression
test, since emulator values alone cannot detect this type mismatch. Add
SDK smoke assertions for both the values and their actual boxed types.
Validate predefined numeric compound assignments instead of accepting any
numeric target merely because the result can be narrowed back to it.
When result narrowing is needed, require the right operand to convert
implicitly to the target type, with the shift exception and fitting int
constant conversions handled explicitly.

Add separate compilation checks for invalid operator and operand pairs,
including byte += long, byte += 300 and char += int. List each source body
in an array so one shared checking path exercises every rejection case;
the assertion includes the rejected body to identify a failure.

Change the existing char test from c += 1 to c += (char)1. C# has no
implicit int-to-char constant conversion, so the original expression was
invalid. The explicit cast keeps the intended test: promote both operands
to int for addition, then convert the compound assignment result to char.
Accept fitting signed integer constants as unsigned compound-assignment
operands and perform the operation in the selected unsigned type. Avoid
converting a large unsigned left operand to a signed type before arithmetic.

Recognize unary signs and parentheses when reading integer literals, so a
case such as sbyte a = 1; a += (-1) remains valid. Apply the corresponding
constant-aware operand selection in checking and code generation.

Add an unchecked wrapping regression starting at ulong.MaxValue with
+=, -= and *=, plus a compilation check for the parenthesized negative
constant. The boundary value exposes an erroneous signed conversion.
Raise a catchable OverflowException for long.MinValue % -1, matching Unity
Mono, instead of returning zero from the lowered remainder operation.
Keep this check separate from the DivideByZeroException path.

Update the compiler and SDK smoke tests to catch the overflow and verify
that execution reaches the catch body. Adjust the SDK expected result to
match the new sentinel value rather than the previously expected zero.
Retain the numeric operand types selected by semantic checking and pass
that information through to code generation. Re-inferring an operator
from an Int32 constant heap slot can otherwise discard the constant
conversion that made an unsigned operation valid.

Evaluate integral constant expressions used for operator binding, including
const locals and fields, parentheses, unary/binary operations and integral
casts. Track const local values separately from ordinary initialized locals,
bound recursive evaluation, and preserve integer width for shift evaluation.
Merge the recorded promotions across checked files and retain fallback
promotion for synthesized operations that have no checked expression.

Add regressions using a const field, a const local and arithmetic constant
expressions with ulong operations and comparisons. Also check byte compound
assignment with a const local and a masked constant shift. These tests cover
the supported binding cases, not every C# constant-expression form.
Write positive and negative floating-point infinity as Infinity and
-Infinity in compiler-generated heap metadata. Rust's Debug formatting
uses inf and -inf, which Unity 2022.3 Mono's invariant-culture Parse rejects.
The compiler owns this interchange format; keep the Unity importer unchanged.

Preserve the existing finite-value and NaN formatting for both Single and
Double. Previously generated inf metadata must be regenerated with the
updated compiler.

Add a serialization regression covering both types, both infinities, NaN,
a finite value and signed zeros. Add eight Unity decoder cases checking
the emitted spellings and exact boxed types through the existing importer.
@bea4dev

bea4dev commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

thanks! LGTM!

@bea4dev
bea4dev merged commit 5a6f6b4 into ProjectTesca:main Sep 16, 2026
2 checks passed
@ureishi
ureishi deleted the improve-numeric-handling branch September 16, 2026 09:28
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.

2 participants