Skip to content

Don't emit unnecessary unchecked scope for native-int literals - #773

Merged
tannergooding merged 2 commits into
dotnet:mainfrom
tannergooding:tannergooding-fix-unnecessary-unchecked-literals
Jul 14, 2026
Merged

Don't emit unnecessary unchecked scope for native-int literals#773
tannergooding merged 2 commits into
dotnet:mainfrom
tannergooding:tannergooding-fix-unnecessary-unchecked-literals

Conversation

@tannergooding

Copy link
Copy Markdown
Member

Fixes #709.

A bare integer literal whose target type resolves to a native-sized integer (nint/nuint) was wrapped in an unchecked scope whenever its value fell outside the 32-bit range, even in expression contexts where the literal keeps its natural C# type and is never narrowed.

The root cause is that on an LP64 target unsigned long maps to nuint, so the 18446744073709551615UL literal in SDL_size_add_check_overflow is treated as nuint and IsUnchecked applies the nuint 32-bit-range check (> uint.MaxValue) to it. But the narrowing (nuint)/(nint) cast -- the only thing that would actually overflow on a 32-bit target -- is only emitted for VarDecl initializers (see the IsPrevContextDecl<VarDecl> gate in UncheckStmt). In a plain expression the literal just keeps its natural ulong type, so no unchecked is needed.

This restricts the native-int range check in the IntegerLiteral path of IsUnchecked to the VarDecl context, which is symmetric with the existing cast-emission gate:

  • SDL_size_add_check_overflow (expression) now emits if (b > (18446744073709551615U) - a).
  • SIZE_MAX and friends (VarDecl init) still emit unchecked((nuint)(18446744073709551615U)).

The existing CLongDefinesRegressionTestUnix baseline test (added in #699 to track this) is reused as the regression; its expected output is updated to the ideal (unchecked removed) and the now-stale "not ideal yet" comments are refreshed. The case is inherently Unix-only -- it requires the unsigned long to nuint mapping -- so it keeps its [Platform("unix")] gate.

Verified with dotnet build -c Release (0 warnings) and dotnet test -c Release (generator suite: 3770 passed, 0 failed). The Unix-gated regression was also confirmed by temporarily un-gating it and running against the Unix-typed baseline harness on a Windows host.

tannergooding and others added 2 commits July 13, 2026 23:00
A bare integer literal whose target type resolves to a native-sized integer
(nint/nuint) was wrapped in an unchecked scope whenever its value fell outside
the 32-bit range, even in expression contexts where the literal keeps its
natural C# type and is never narrowed. The narrowing (nint)/(nuint) cast is
only emitted for VarDecl initializers, so restrict the native-int range check
to that context.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…exts

The literal-only check missed the constant-evaluation path (the CXEvalResult
overload reached via ParenExpr/BinaryOperator .Handle.Evaluate), which still
saw the un-widened nuint on an LP64 host and re-emitted the unchecked scope.
Widen nint/nuint to long/ulong once at the single Stmt funnel so every
recursive and constant-folded path is consistent.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@tannergooding
tannergooding merged commit 8585d95 into dotnet:main Jul 14, 2026
14 checks passed
@tannergooding
tannergooding deleted the tannergooding-fix-unnecessary-unchecked-literals branch July 14, 2026 22:46
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.

Edge case where unnecessary unchecked scope is generated for integer literals

1 participant