Documentation

Arx Preview Contracts

Arx Preview Contracts

These safety and compatibility decisions describe the non-stable 2026-preview edition (language draft 0.1.0). They are not a complete language specification. The capability matrix is the single support summary; executable conformance lives in the compiler tests.

Arithmetic and evaluation

Boolean evaluation

packages/arx/tests/arx/test_datatypes.x contains the black-box conformance case: the skipped operand deliberately asserts false. IRx also verifies that lowering uses conditional blocks and a merge value rather than eager LLVM and/or instructions.

Boolean negation accepts ! and not. Word not binds below comparisons and above and/or: not a == b means not (a == b), whereas !a == b means (!a) == b. Parenthesize word negation in tighter operands. Neither spelling adds numeric truthiness. Nullable/Series masks keep their existing eager, null-propagating operations rather than scalar short-circuiting.

Prefix increment and decrement

Use ++value or --value to update a mutable numeric variable by one. Both expressions yield the updated value:

var value: i32 = 3
var increased: i32 = ++value  # value and increased are now 4
var decreased: i32 = --value  # value and decreased are now 3

Postfix value++ and value-- are not supported. Use prefix mutation or an ordinary assignment such as value = value + 1 or value = value - 1. Integer mutation follows the checked overflow rules below.

Compound assignment

+=, -=, *=, /=, and %= update a declared mutable numeric variable:

var start: i32 = 207
var amount: i32 = 5
start -= amount
start %= 100  # start is now 2

Targets must be identifiers, not fields or subscripts. Boolean, string, collection, and nullable operands are unsupported. Compound assignment is a statement, not an expression for arguments, conditions, or returns. It is also allowed in a count-loop update, where it must target that loop's induction variable.

The old target value is loaded before evaluating the RHS once, and the result is stored once. Operand promotion and storage must both be lossless: there is no implicit narrowing or wrapping. Integer arithmetic follows the checked rules below, including truncating division and dividend-sign remainder. A failed operation or RHS conversion does not perform the compound store; independent RHS side effects are not rolled back. For example, x += ++x adds the old x to its incremented value, and an overflow in the addition retains that prior increment.

Fixed-width integer overflow

Fixed-width signed and unsigned 8–64-bit integer +, -, *, negation, and supported increment/decrement operations are checked in every build. IRx fixed-width range induction is checked too; the stdlib range helper uses wider endpoint arithmetic to terminate safely on an overshooting step. Overflow fails before storing or publishing a result; an unused expression is still checked. Provably evaluated invalid constant operations are semantic errors. Untaken branches and short-circuited operands do not execute checks.

Implicit numeric conversions preserve the entire source type's domain. Mixed operands use the smallest supported lossless common domain: i32 + u32 computes in i64, and i32 + f32 in f64; i64 + u64 and i64 + f64 require an explicit conversion. A destination annotation never widens a computation retroactively. Same-type templates keep their selected type; widen operands or select math.square<i64>(narrow) before multiplication, not afterward.

Source integer literals use the declared/argument/peer type when representable, otherwise default to i32, then i64. Uncontextualized floating literals default to f32. Decimal literals retain their exact value until type selection, then round directly to the chosen binary format, nearest with ties to even. Explicitly typed ASTx literals are never recontextualized. Generic inference uses concrete arguments before contextualizing literal arguments, independently of argument order; literals do not make conflicting concrete same-T arguments compatible.

cast(value, Type) checks integer range, including equal-width signedness changes. Float-to-integer casts truncate toward zero, then require a finite, representable result. Explicit integer-to-float and float-narrowing casts may round; finite input must not become infinity. Existing floating NaN, infinity, signed zero, and ordinary IEEE floating arithmetic are preserved. Implicit integer-to-float conversions may not discard integer precision.

Intentional modulo arithmetic requires wrapping_add, wrapping_sub, wrapping_mul, or wrapping_cast(value, IntegerType). Binary wrapping operands must have the same integer type, or an explicit lossless specialization such as wrapping_add<i64>(narrow, 1). Boolean and floating operands are rejected. These forms do not change surrounding expressions' checked behavior.

Integer division and remainder failures

Signed integer / truncates toward zero. Scalar % is the remainder from that quotient, so -5 / 100 == 0 and -5 % 100 == -5, unlike Python's floor-modulo result 95. %, *, and / have equal precedence and associate left. For a positive integer modulus m, compute r = value % m and add m only when r < 0; unlike (r + m) % m, this avoids overflowing on an already positive remainder. The compiled datatype tests exercise this workflow.

Integer division and remainder reject zero divisors and signed minimum with negative one before LLVM executes the operation. Remainder supports signed, unsigned, and floating non-nullable scalars, not collections, nullable values, or vectors. Integer SIMD division checks every supplied active lane; no masked source-vector API is implied. Floating-point division follows IEEE behavior; floating % uses truncating remainder (like fmod), preserves the dividend's sign including negative zero, and produces NaN for a zero divisor.

Dynamic integer zero divisors raise ZeroDivisionError; out-of-range arithmetic and conversions raise OverflowError, at every optimization level. Diagnostic codes remain ARX-RUNTIME-ARITHMETIC-001 and ARX-RUNTIME-CONVERSION-001. These failures propagate through generated cleanup edges and can be caught; uncaught exceptions produce structured reports and a nonzero exit status. Statically invalid expressions remain compile-time diagnostics. See the exception support boundary.

Nullable and Series arithmetic retains checked Arrow kernels, status checking, null propagation, and checked reduction accumulators. Nullable scalar casts skip invalid payloads entirely. Tensor element operations use scalar checks; bulk tensor arithmetic and nonidentity Series/tensor casts are rejected rather than reinterpreting storage. List length/search results must fit their public i32 domain. Numeric runtime unions require lossless shared storage; generic union bounds are membership constraints and need no runtime storage. Raw FFI internals and external native functions remain outside these language guarantees.

Ownership

The runtime ownership contract owns the detailed create/borrow/move/release rules for lists, strings, files, and class references. Dynamic lists are move-only and cleaned up on ordinary lexical exits, not process-lifetime allocations. Shared class references follow a different model. Use the class lifecycle guide for construction and field ownership, and the file guide for shared cursor and close behavior.

IRx cleanup paths for break/continue or generators do not establish Arx source support for those constructs. Fatal errors do not generally unwind caller frames. Do not depend on automatic cleanup to confirm successful writer finalization; follow the CSV or Parquet close contract.

Modules and failures

Module graph failures

One entry module identifies a compiler-owned, canonical dependency graph. Missing modules are IRX-S011 failures and import cycles are IRX-S012 failures; a cycle is never executed or initialized partially. A canonical module is resolved and analyzed once per compilation session. Module-level runtime initialization order remains outside the stable subset until Arx admits and specifies executable module-level statements.

Compiler and process failures

Expected source/compiler failures exit the CLI with status 1, CLI usage failures use status 2, and sanitized internal compiler failures use status 70. --traceback is an explicit developer opt-in. Compiled assertions raise AssertionError. Uncaught exceptions emit ARX_EXCEPTION records with stable operation codes and ARX_EXCEPTION_FRAME records for recorded call sites, then exit nonzero. The test runner and ArxPy decode these reports without treating completed subprocess failures as Python compiler errors. Unrecoverable native or exception-machinery allocation failures retain the ARX_RUNTIME_FAIL fallback; they do not promise recovery or cleanup.

Compatibility identifiers

The language draft and preview edition do not grant source compatibility. Before Arx 2.0, replace APIs within the requested scope and update callers, tests, source contracts, and necessary guides together. Record intentional breaking changes in concise release notes rather than parallel deprecated API documentation. Native ABI producers and consumers must remain aligned.

Diagnostics contract

Stable compiler failures will have a code, phase, message, and a trustworthy optional source location. Normal CLI use renders expected Arx and IRx failures without a Python traceback and exits nonzero. Unexpected internal failures are not relabeled as user errors.

The source loader emits ARX-SOURCE-*, the lexer emits ARX-LEX-*, the parser emits ARX-PARSE-*, and IRx formats its diagnostic families as IRX-*. --diagnostic-format json emits schema version 1 for structured compiler-phase failures. The complete stable-code catalog remains GA work; schema versions must be checked rather than inferred.

Decisions still required

A stable specification still needs floating-point non-finite/NaN and narrowing rules, broader Unicode operations, general resource/error propagation, FFI and artifact guarantees, and a versioned conformance suite. Do not promote a preview behavior to a compatibility promise without that evidence.

At Arx 2.0, explicitly define source/package/native ABI compatibility, edition changes, any deprecation policy, supported toolchains, and release support periods. The pre-2.0 policy must not silently continue afterward.

Search documentation

Type to search.