Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions contrib/typescript-55091/PR_BODY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
Fixes #55091

### Problem

Enum members whose initializers evaluate to `Infinity`, `-Infinity`, or `NaN` are
emitted as the global identifiers of those names. Identifiers resolve in the scope of
the *emitted* code rather than at the enum declaration, so a local binding that shadows
`Infinity` or `NaN` silently changes the member's value at runtime:

```ts
{
let Infinity = 3;
enum A {
X = 1 / 0
}
console.log(A.X); // 3, expected Infinity
}
```

emits

```js
A[A["X"] = Infinity] = "X";
```

This compiles with no diagnostic. `const enum` members are protected here by TS2477 and
TS2478, but plain enums are not, so the value is wrong with nothing to indicate it.

### Change

`constantExpression` now emits `1 / 0`, `-1 / 0`, and `0 / 0` for these three values.
They evaluate to exactly the same numbers and cannot be shadowed, which is the same
reasoning behind emitting `void 0` rather than `undefined`.

The `-Infinity` case prints as `-1 / 0` rather than `-(1 / 0)`; unary minus binds tighter
than division, so no parentheses are needed.

### Testing

I added `tests/cases/conformance/enums/enumComputedNonFiniteShadowed.ts`, covering all
three values both under a shadowing binding and without one.

By hand, I built the compiler from this branch and ran the case above through it. Before
the change the emitted program printed `Pos: 3 Neg: -3 Nan: 7`; after it, it printed
`Pos: Infinity Neg: -Infinity Nan: NaN`.

Five existing emit baselines change, in each case only `Infinity`/`NaN` becoming the
division form: `enumConstantMembers`, `enumNaNValues`, `enumAutoIncrementValue`,
`fakeInfinity2`, and `fakeInfinity3`. No `.types`, `.symbols`, or `.d.ts` baseline
changes, since checking is untouched. `enumShadowedInfinityNaN` is unaffected, as that
test references the identifiers directly rather than computing a constant.

I ran `go test ./...` under `tsc/`: 62 packages pass. `internal/astnav` fails in my
environment because it loads `node_modules/typescript/lib/typescript.js`, which I had not
installed; I confirmed it fails identically with this change stashed. I did not run the
`npx hereby` wrappers for that reason, but `gofmt -l` and `go vet ./internal/transformers/...`
are both clean.

### Key points

The tradeoff is emit readability: `E[E["a"] = 1 / 0] = "a"` reads less clearly than
`= Infinity` for the common, non-shadowed case. I chose the unconditional form because it
is correct without needing to resolve `Infinity`/`NaN` at the emit site. If you would
rather keep the identifier when it is demonstrably not shadowed, I am happy to rework it
that way.

This does not address declaration emit, which has the same underlying problem: the
committed `fakeInfinity3.d.ts` baseline still carries TS1066 and TS2749 errors. That
looked like a separate change, so I left it out rather than widen this one.

### AI assistance

This patch was written with AI assistance (Claude Code). I have read and understand the
change and will be responding to review feedback myself.
82 changes: 82 additions & 0 deletions contrib/typescript-55091/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# microsoft/TypeScript #55091 - submission package

Status: **prepared, not submitted.** Submission is a manual step for the repository
owner, for the reasons in "Why you submit this" below.

## Record

| Field | Value |
|---|---|
| Organization | Microsoft |
| Repository | microsoft/TypeScript |
| Issue | [#55091](https://github.com/microsoft/TypeScript/issues/55091) - `Help Wanted`, unassigned, milestone `Dormant` |
| Prior art | [#55107](https://github.com/microsoft/TypeScript/pull/55107) by Andarist, closed 2026-03-24 in the post-6.0 mass closure, not rejected on merit |
| Base commit | `9b323de` era clone of `main` (Go compiler; verify against latest before submitting) |
| Files changed | 1 source, 1 new test, 8 baselines |
| Diff | +43 / -24 in source; baselines mechanical |
| PR body | `PR_BODY.md` |
| Patch | `fix.patch` |

## The bug

`tsc/internal/transformers/tstransforms/utilities.go`, `constantExpression()` emitted
bare `Infinity` / `-Infinity` / `NaN` identifiers for enum members whose initializers
fold to those values. Identifiers resolve in the emitted code's scope, so shadowing
changes the value silently. Reproduced on published TypeScript 7.0.2:

```
$ cat repro.ts
{
let Infinity = 3;
enum A { X = 1 / 0 }
console.log(A.X);
}
$ tsc --target es2020 repro.ts && node repro.js
3 # expected: Infinity
```

No diagnostic is issued. `const enum` is guarded by TS2477/TS2478; plain `enum` is not.

## Verification performed

- `go build ./...` under `tsc/` - clean
- `go test ./internal/testrunner/ -run TestLocal` - passes with updated baselines
- `go test ./...` - 62 packages pass; `internal/astnav` fails on a missing
`node_modules/typescript`, confirmed identical with the fix stashed
- `gofmt -l` clean, `go vet ./internal/transformers/...` clean
- Built the patched compiler and diffed real emit/runtime before and after

Not run: `npx hereby test` / `lint` / `check:format`, because npm dependencies were not
installed in the build environment. Run these before submitting.

## Why you submit this

microsoft/TypeScript `CONTRIBUTING.md` states that a pull request is acceptable only if
"a specific human operator has chosen this specific issue, intends to shepherd the change
through review themselves, and will be the one responding to feedback in their own
personal workflow." It also requires that AI assistance be disclosed in the PR
description, and closes undisclosed AI-authored PRs without review. `PR_BODY.md` contains
that disclosure - keep it.

So: read the diff until you can defend it, then submit it yourself and answer review
yourself. That is the path the project actually accepts.

## Steps

1. Read `fix.patch` and make sure you can explain why `1 / 0` is correct and why the
identifier form was not.
2. Fork `microsoft/TypeScript`, clone your fork, branch.
3. `git am < fix.patch` (or `git apply`), rebase onto current `main`, re-run the tests
above plus the `hereby` wrappers.
4. Push and open the PR using `PR_BODY.md`.
5. Note the PR template requires an associated issue in the `Backlog` milestone; #55091
is currently `Dormant`. Expect maintainers to re-milestone it or to say the change is
not wanted right now - that is a real possibility worth accepting up front.

## Honest assessment

Merge probability is moderate, not high. In favour: the bug is real, silent, reproducible
on the shipping compiler, the fix is small, and the approach has precedent. Against: a
maintainer called this scenario unlikely on the earlier PR, the issue sits in `Dormant`,
and the emit-readability tradeoff may draw a request to make it conditional. It is a
legitimate contribution either way.
Loading