Skip to content

Fix invalid declaration emit for constants whose value is Infinity or NaN - #64761

Open
Sid Chaudhary (sidcraftscode) wants to merge 1 commit into
microsoft:mainfrom
sidcraftscode:fix/dts-non-finite-literals
Open

Sid Chaudhary (sidcraftscode) wants to merge 1 commit into
microsoft:mainfrom
sidcraftscode:fix/dts-non-finite-literals

Conversation

@sidcraftscode

Copy link
Copy Markdown

Fixes #64760

Problem

A numeric literal that overflows, such as 1e400, has the number literal type Infinity. Declaration emit writes constants of that type with their literal value, which produces declaration files that don't type-check:

// m.ts
export const a = 1e400;
export const b = -1e400;
export class C {
    static readonly e = 1e400;
}
export default 1.7976931348623159e308;
// m.d.ts, from tsc --declaration on main (b5c34ba)
export declare const a = Infinity;
export declare const b = -Infinity;
export declare class C {
    static readonly e = Infinity;
}
declare const _default = Infinity;
export default _default;

Each initializer is an error when the .d.ts is checked: TS1254: A 'const' initializer in an ambient context must be a string or numeric literal or literal enum reference. Infinity is an identifier, not a literal.

This isn't a TS 7 regression. TypeScript 6.0.3 (Playground) emits the same initializers, and declare const _default: Infinity; for the default export.

Fix

In tsc/internal/checker/emitresolver.go:

  • IsLiteralConstDeclaration returns false when the declaration's type is a non-enum number literal whose value is Infinity, -Infinity or NaN, so the declaration gets a type annotation instead of an initializer.
  • CreateLiteralConstValue returns nil for those values. The export default path calls it directly and already falls back to a type annotation on nil. This replaces the branches that built the Infinity and NaN identifiers, which produced exactly the invalid initializers above.

Output after the fix:

export declare const a: number;
export declare const b: number;
export declare class C {
    static readonly e: number;
}
declare const _default: number;
export default _default;

Enum members with NaN or Infinity values are unaffected (declarationEmitEnumNaN.ts). So are references to them: export const g = E.A still emits = E.A, which is a valid initializer.

An alternative would be to reuse the source text (= 1e400), which keeps the literal type. It needs a fallback anyway, since the type can come from an expression that isn't a numeric literal. I went with number to keep the change small. Happy to switch if you prefer.

Related: #42905 (Infinity in type positions, fixed in 7.1) and #63831 (-1e500 as const emitted as -Infinity). The type-position case in #63831 goes through the node builder and isn't touched here. #56272 discusses whether overflowing literals should have an Infinity literal type at all. This fix doesn't depend on that outcome: either way, the emitted .d.ts must type-check.

Tests

  • New compiler test declarationEmitNonFiniteNumberLiteral.ts. It covers const, negative values, the smallest overflowing literal (1.7976931348623159e308), a large finite literal that must keep its value (1e308), static readonly and readonly properties, export default, and references to NaN and Infinity enum members. Without the fix, the harness's .d.ts re-check records 6 TS1254 errors (//// [DtsFileErrors]). With it, there are none.
  • Every other declarationEmit*, enum, fakeInfinity1 and nanEquality compiler test is unchanged (599 tests).
  • go test ./... in tsc passes: 64 packages, no failures (golang:1-bookworm in Docker). internal/testrunner was run on its own with -parallel 4, because the Docker VM's 4 GB ran o

… NaN

A numeric literal that overflows, such as 1e400, has the number literal
type Infinity. Declaration emit wrote these constants with their literal
value, producing `declare const a = Infinity;`. That is not a valid
ambient initializer (TS1254: it must be a string or numeric literal or a
literal enum reference), so the emitted .d.ts did not type-check. The
same happened for -Infinity, readonly class properties and
`export default 1e400`.

These declarations now get a `number` type annotation instead. Enum
member references whose value is NaN or Infinity are unchanged, since
`declare const x = E.A;` is valid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 03:04
@typescript-automation typescript-automation Bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Oct 13, 2026
@sidcraftscode

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The focused emit fix correctly handles non-finite values and is supported by comprehensive regression baselines.

0 open findings

What changed in this PR

Fixes invalid declaration emit for non-finite numeric literal values by falling back to number annotations.

Changes:

  • Rejects Infinity and NaN as ambient constant initializers.
  • Preserves valid enum-member references and finite literals.
  • Adds comprehensive compiler regression coverage and baselines.
File Description
tsc/​internal/​checker/​emitresolver.go Handles non-finite literal values during declaration emit.
tsc/​testdata/​tests/​cases/​compiler/​declarationEmitNonFiniteNumberLiteral.ts Adds regression scenarios.
tsc/​testdata/​baselines/​reference/​compiler/​declarationEmitNonFiniteNumberLiteral.js Captures corrected declaration output.
tsc/​testdata/​baselines/​reference/​compiler/​declarationEmitNonFiniteNumberLiteral.types Records inferred types.
tsc/​testdata/​baselines/​reference/​compiler/​declarationEmitNonFiniteNumberLiteral.symbols Records symbol information.

🧠 Review effort: Balanced


💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

Declaration emit writes = Infinity initializers for overflowing number literals (invalid .d.ts, TS1254)

2 participants