Skip to content

Warn when a fraction is stored into a variable inferred from an integer literal - #544

Merged
ASDAlexander77 merged 4 commits into
mainfrom
warn-integer-literal-variable-truncation
Oct 11, 2026
Merged

ASDAlexander77 merged 4 commits into
mainfrom
warn-integer-literal-variable-truncation

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Problem

Integer literals are s32 in tslang, so let s = 0 is an integer and a fraction stored into it is dropped silently:

let s = 0;
s += 1.5;
print(s);              // 1 (TypeScript: 1.5)

function sum(values: number[]): number {
    let a = 0;
    for (...) a += values[i];
    return a;          // sum([1.5, 0, 2]) gives 3, not 3.5
}

Reported in docs/bugs/native-ts-graphics-findings.md, issue 3. Inferring s32 from integer literals is deliberate, and this PR keeps it. The report asks for what this PR adds: a warning when ordinary TypeScript code changes meaning because of it.

Change

When a store into a variable converts a value of type number (or another float type) to the variable's integer type, and that type was inferred from an integer literal, it warns:

global.ts:3:1: warning: a 'number' is stored in a 's32', so any fraction is dropped: the variable's type was
inferred from an integer literal. Declare it 'number' (e.g. `let s: number = 0`) to keep fractions

No warning for:

  • a whole-number constant (s = 3.0);
  • a variable whose integer type was declared (let d: s32 = 0; d = 2.5), since that truncation was asked for;
  • a variable inferred from 0.0, or declared number.

How it works: adjustLocalVariableType/adjustGlobalVariableType note that the type was inferred from an integer literal (no type given, and the initializer has a literal integer type). registerVariable marks the storage (ts.Variable, or ts.Global for a global) with INTEGER_LITERAL_INFERRED_ATTR_NAME, and mlirGenSaveLogicOneItem checks that mark before it casts the stored value. This covers =, += and the other compound assignments. Code generation doesn't change.

Stores from inside a closure (a.forEach(v => { s += v; })) are warned about too.

Testing

  • New warning-text tests: test-compile-integer-literal-truncation-global and -local require the warning at the store's line and column; -quiet fails on any warning, for the cases listed above. The current release prints no warning, so -global and -local fail on it.
  • Full ctest: 3916/3916 pass.
  • Noise check: compiled every file in tslang/test/tester/tests (about 630). 27 of them warn, in 1 to 5 places each. All are real number-to-s32 stores into a let x = 0 variable, such as x /= 5, total = total + n.v, glb1 += x and dist = v - Math.sqrt(disc). The compiler can't know whether those values have a fraction at run time, which is why the message says "any fraction". If that's too noisy for code that relies on the integer inference on purpose, the fix is to declare the type (let x: s32 = 0), which turns the warning off.

🤖 Generated with Claude Code

ASDAlexander77 and others added 3 commits October 11, 2026 13:33
Integer literals are s32, so `let s = 0` is an integer and `s += 1.5`,
`s = s + x` or `let a = 0; a += values[i]` drop the fraction without a
word, where TypeScript keeps it. The inference stays; storing a number
with a fraction into such a variable now warns, and says to declare it
`number`. A whole-number constant (`s = 3.0`), a variable with a declared
integer type and one inferred from `0.0` are not warned about.

The declaration marks its storage (ts.Variable or ts.Global) with
INTEGER_LITERAL_INFERRED_ATTR_NAME; the store checks the mark.

Reported in docs/bugs/native-ts-graphics-findings.md (issue 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…variable-truncation

# Conflicts:
#	tslang/test/tester/CMakeLists.txt
…variable-truncation

# Conflicts:
#	tslang/test/tester/CMakeLists.txt
@ASDAlexander77
ASDAlexander77 merged commit 189a53a into main Oct 11, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the warn-integer-literal-variable-truncation branch October 11, 2026 13:17
ASDAlexander77 added a commit that referenced this pull request Oct 12, 2026
…558)

Every repro in docs/bugs passes on main, or fails as TypeScript does:
the native_ts_graphics findings were fixed by #535-#544 and #549 (the
`let s = 0` one by design, with a warning since #544), testFallThrough,
generic_in_generic and array_of_ifaces_cast by #538, #542 and #543, and
bug14 and bug21 aren't valid TypeScript. Each fix has its regression
test in tslang/test/tester, and the findings document is in the history
and in those PRs, so the folder goes.

Its interop notes stay, in docs/c-cpp-header-import.md: a `string`
returned by a C function is its `char*`, not a copy; `number` is a
`double`; and a nullable `Ref<T>` field is spelled `Ref<T> | null`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant