Skip to content

Return false for #x in on static private fields before initialization - #18265

Merged
nicolo-ribaudo merged 3 commits into
babel:mainfrom
hoobnn:fix/static-private-in-before-init
Oct 2, 2026
Merged

nicolo-ribaudo merged 3 commits into
babel:mainfrom
hoobnn:fix/static-private-in-before-init

Conversation

@hoobnn

@hoobnn hoobnn commented Oct 2, 2026

Copy link
Copy Markdown
Contributor
Q A
Fixed Issues? Fixes #18262
Patch: Bug Fix? Yes
Major: Breaking Change? No
Minor: New Feature? No
Tests Added + Pass? Yes
Documentation PR Link
Any Dependency Changes? No
License MIT

For a static private field, #x in obj compiles to checkInRHS(obj) === Class. But the field's value lives in var _x = { _: value }, which isn't assigned until the class body has finished evaluating. So any code that runs before that point (a static block, an earlier static field initializer, or anything after the field's initializer has thrown) gets true, even though natively the class doesn't have the field yet.

This PR adds && _x !== void 0 to the check for static private fields. checkInRHS still runs first, so a non-object right-hand side throws as before, and it's only evaluated once. Static private methods and accessors are left as they are, because the class already has them before any field initializer runs. Under noUninitializedPrivateFieldAccess the storage holds the raw value, so undefined doesn't mean "not initialized yet"; in that case the check is skipped and the output doesn't change. privateFieldsAsProperties / privateFieldsAsSymbols go through the hasOwnProperty path and aren't affected.

Tests:

  • private/static-field-initialization (exec): brand checks on a static field, a field with no initializer, a static method and a static getter, before and after each initializer. It also checks that the RHS is evaluated once and that the getter is never called.
  • private/half-constructed-static (exec): returns false after the field's initializer throws.
  • assumption-noUninitializedPrivateFieldAccess/static-field (output): no extra check under the assumption.
  • Six existing output fixtures now include && _x !== void 0. private-loose/static-shadow and to-native-fields/static-shadow override their plugins to transform-class-properties, so their change is the spec-mode one; actual loose-mode output is unchanged.

With only the new fixtures, the private-in suite has 8 failures. With the change it passes (67 tests), and so does babel-helper-create-class-features-plugin (17). I checked this on Linux and macOS.

One known gap: when the same class declaration is evaluated more than once, for example in a loop, var _x is shared between evaluations, so only the first one is fixed. Natively you get [false, true, false, true]; with this PR it's [false, true, true, true], and before it was [true, true, true, true]. This comes from how static private field storage is hoisted, which affects reads and writes as well, so I've kept it out of this PR.

This also extends noUninitializedPrivateFieldAccess to cover in checks. If you'd like, I can open a PR on the website to update the docs for that assumption.

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

Thanks for opening this pull request! 🎉

We really appreciate you taking the time to contribute, @hoobnn.

A maintainer will take a look as soon as they can. In the meantime, please make sure that:

  • the description explains what changed and why
  • any related issues are linked
  • existing tests still pass

If anything needs adjusting we'll leave comments here. Thanks again!

@babel-bot

babel-bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/62220

@pkg-pr-new

pkg-pr-new Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

commit: 219bf51

@nicolo-ribaudo nicolo-ribaudo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you!

Comment thread packages/babel-helper-create-class-features-plugin/src/fields.ts Outdated
@hoobnn

hoobnn commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Thank you!

Done, thanks!

@nicolo-ribaudo nicolo-ribaudo left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Very minor, but could you also add a test for the loop case that you mentioned, with a comment saying that that output is actually wrong?

Btw, your Claude is adding to the commit messages links to sessions that are not actually accessible.

@nicolo-ribaudo nicolo-ribaudo added the PR: Bug Fix 🐛 A type of pull request used for our changelog categories label Oct 2, 2026
hoobnn and others added 3 commits October 2, 2026 17:33
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hoobnn
hoobnn force-pushed the fix/static-private-in-before-init branch from 0b4ddd0 to 219bf51 Compare October 2, 2026 09:33
JLHwung

This comment was marked as outdated.

@nicolo-ribaudo

nicolo-ribaudo commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

@JLHwung I was also confused, but that assumption already exists :) https://babeljs.io/docs/assumptions#nouninitializedprivatefieldaccess

(if you merge this PR, please squash-merge replacing Co-authored-by with Assisted-by)

@JLHwung

JLHwung commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

@JLHwung I was also confused, but that assumption already exists :) https://babeljs.io/docs/assumptions#nouninitializedprivatefieldaccess

(if you merge this PR, please squash-merge replacing Co-authored-by with Assisted-by)

Oh my bad. I didn't realize we already have this assumption. In that case this PR is indeed a bug fix.

@nicolo-ribaudo
nicolo-ribaudo merged commit 5a9b954 into babel:main Oct 2, 2026
57 checks passed
@hoobnn
hoobnn deleted the fix/static-private-in-before-init branch October 3, 2026 02:05
brandhaug added a commit to brandhaug/b2b-saas-starter that referenced this pull request Oct 10, 2026
## pnpm-workspace.yaml (default)

## Dependency Updates

| Package | From | To | Type |
| --- | --- | --- | --- |
| `@babel/core` | 8.0.6 | 8.0.7 | patch |
| `@react-email/ui` | 6.11.0 | 6.11.1 | patch |
| `ai` | 7.0.128 | 7.0.130 | patch |
| `react-email` | 6.11.0 | 6.11.1 | patch |
| `shadcn` | 4.21.3 | 4.21.4 | patch |

## Release Notes

<details>
<summary><b>@<!---->babel/core</b> (8.0.6 → 8.0.7)</summary>

## v8.0.7 (2026-10-07)

Thanks @<!---->brunoborta, @<!---->CINC0S, @<!---->hoobnn, and
@<!---->jpradelle for your first PRs!

#### 🐛 Bug Fix
* `babel-helper-create-regexp-features-plugin`,
`babel-plugin-transform-dotall-regex`,
`babel-plugin-transform-unicode-sets-regex`, `babel-preset-env`
* [#18295](babel/babel#18295) Bump regexpu-core
to 6.5.3 ([@<!---->JLHwung](https://github.com/JLHwung))
* `babel-plugin-transform-for-of`
* [#18292](babel/babel#18292) Skip `for await`
in `transform-for-of`
([@<!---->brunoborta](https://github.com/brunoborta))
* `babel-helpers`, `babel-plugin-transform-async-generator-functions`,
`babel-runtime-corejs3`
* [#18283](babel/babel#18283) Fix
`asyncIterator` helper forwarding `.throw()` to `.return()`
([@<!---->brunoborta](https://github.com/brunoborta))
* `babel-parser`
* [#18288](babel/babel#18288) fix(parser): allow
await as a function expression name inside async arrow
([@<!---->JLHwung](https://github.com/JLHwung))
* `babel-helpers`, `babel-plugin-transform-class-properties`,
`babel-plugin-transform-modules-systemjs`, `babel-runtime-corejs3`
* [#18284](babel/babel#18284) Guard
`@@toPrimitive` usage in toPrimitive helper
([@<!---->nicolo-ribaudo](https://github.com/nicolo-ribaudo))
* `babel-plugin-transform-block-scoping`
* [#18195](babel/babel#18195) fix: closures in
for-loop init capture wrong binding
([@<!---->joelle-a-dev](https://github.com/joelle-a-dev))
* `babel-helper-create-class-features-plugin`,
`babel-plugin-transform-private-property-in-object`
* [#18265](babel/babel#18265) Return false for
`#x in` on static private fields before initialization
([@<!---->hoobnn](https://github.com/hoobnn))
* `babel-traverse`
* [#18151](babel/babel#18151) Evaluate
TypeScript expression wrappers in `NodePath#evaluate`
([@<!---->CINC0S](https://github.com/CINC0S))
  * [#18250](https://github

…[full notes](https://github.com/babel/babel/releases/tag/v8.0.7)

</details>

<details>
<summary><b>react-email</b> (6.11.0 → 6.11.1)</summary>

### Patch Changes

* 54769fc: Prevent `email export` from deleting the project, the home
directory, the email templates or the static assets when `--outDir`
points at them.
* 6926bac: `<Markdown>` now applies `markdownCustomStyles.th` to table
header cells. Header cells were previously styled with `td`, so any `th`
styles were ignored.

</details>

<details>
<summary><b>shadcn</b> (4.21.3 → 4.21.4)</summary>

### Patch Changes

- [#12189](shadcn-ui/ui#12189)
[`76fd499595ba7cc01f49d159bd7ac82d7aca8e63`](shadcn-ui/ui@76fd499)
Thanks [@<!---->shadcn](https://github.com/shadcn)! - Drop the ts-morph
dependency from @<!---->shadcn/registry.

- [#12184](shadcn-ui/ui#12184)
[`995c2cfff44c373bd210d451089726ec097ab917`](shadcn-ui/ui@995c2cf)
Thanks [@<!---->shadcn](https://github.com/shadcn)! - Run the icons and
asChild transformers without ts-morph.

- [#12160](shadcn-ui/ui#12160)
[`6efecd8fe9aa167886fe2cc0c05c5623a5bb5670`](shadcn-ui/ui@6efecd8)
Thanks [@<!---->shadcn](https://github.com/shadcn)! - Edit the Next.js
layout for font items without ts-morph, and skip it with a warning
instead of writing a broken layout.

- [#12188](shadcn-ui/ui#12188)
[`efa11781f756c86debb0392fbea4fe468250b41b`](shadcn-ui/ui@efa1178)
Thanks [@<!---->shadcn](https://github.com/shadcn)! - Rewrite imports
and crawl file imports without ts-morph.

- [#12177](shadcn-ui/ui#12177)
[`e8c3143b1cd191280befcd6c9538284bb43399a8`](shadcn-ui/ui@e8c3143)
Thanks [@<!---->shadcn](https://github.com/shadcn)! - Edit
tailwind.config without ts-morph, and skip it with a warning instead of
writing a broken config.

- [#12183](shadcn-ui/ui#12183)
[`f56bbd7282f0116a925a601cb6d7e0c3fede448b`](shadcn-ui/ui@f56bbd7)
Thanks [@<!---->shadcn](https://github.com/shadcn)! - Run the rsc,
import, CSS variable, cleanup, font and menu transformers without
ts-morph.

- [#12185](shadcn-ui/ui#12185)
[`4a90344c42dc9ae47494179065219377ff5f2a8f`](https://github.com/shadcn-ui/u

…[full
notes](https://github.com/shadcn-ui/ui/releases/tag/shadcn%404.21.4)

</details>

---
*This PR was auto-generated by
[catalog-update-action](https://github.com/brandhaug/catalog-update-action).*

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: Bug Fix 🐛 A type of pull request used for our changelog categories

Projects

None yet

Development

Successfully merging this pull request may close these issues.

static private field exists before it is actually defined

4 participants

Sponsor
SponsoredKunjungi sekarang
Promo