
Incorrect operator precedence when using private fields with nullish coalescing assignment for targets <= es2021
DRANK
🔎 Search Terms "nullish coalescing assignment", "private properties", "private fields", "target es2021", "operator precedence" 🕗 Version & Regression Information This changed between versions 3.9.7 and 4.0.5 (with the introduction of nullish coalescing assignment) ⏯ Playground Link https://www.typescriptlang.org/play/?noUnusedLocals=true&noUnusedParameters=true&target=8&jsx=0&noFallthroughCasesInSwitch=true&useUnknownInCatchVariables=true&ts=5.8.0-dev.20250204&ssl=6&ssc=1&pln=9&pc=1#code/MYGwhgzhAEDCIwN4ChpugBwK4CMQEtgAFAJwHsMAuaAOywFscBTE6AH2ixoBMmAzfDSbcA3KnQBiDCXwA3MABcmpCtTqMW7Tj36DhY8WhpkVeJvQAUASmgp096AHpH0ACoALfDADuZEgGsYSGgmAA8MJmAlbmolEhowEgBPEPCSJih8MhpoMB5oBXcmWiwQAgh3aGAyMBAM4EEAc0MHZ1zMxpp6JhoFXPSQ+RAsRWFoGUb3BQBaBTJpur4FABpoCDICooKWBOTU6QyILJyvFvs2xvIsCO5c-K9obJAUpiGR6OhvIpyAA0KvAB02DwhBUGB+0AedDKXncALO6H+ECBuAIxHIGGgAH4sQBeaB8WoQYpY2ivFgeLzWaDUACMACYAMxiBzoNqUnx+QKrZjAMBYYmfYoC4oYRI9QoZDKUBFoJEAqQyeRKMHYvHQCyEhAksmyCmeCDU6j0gAMVhZ6AAvsgWtIyGZLDY7KynC4OQSwPgEDzIvzBZLoD8cbiIQ8FCQ8hBwB85rlAziIV9CJV3MEABIASQA4mmAKIAJVlrsw6WAwh6Zc2eU2xTiuxeaUOx3hLs2gMVclGquDHu12N1+qpNmNJotaGt1uQ1Ro6zqANATES1gM04gfVY+KE3jgCDEJABxlMdUde4PJnIDuXNtXZDnIDIjQsAHJjCX7ce1htCSQn+abfu7UvP8p2yWcmABe9HxfDZAI-RRcjKX8DD4LgomOAcSA5aw1HJVhnTldxyG3LdoFzEhyBIZ93QqMhSludJamecZFzKFIhD1VhUwwCIaAAQiQ5BLSAA (BTW: run it to see the problem) 💻 Code class Cls { publicProp: number | undefined; #privateProp: number | undefined; noProblem() { // This works as expected: ternary expression and the nullish coalescing // assignment are evaluated right-to-left, so the ternary expression is // grouped and is only evaluated when `this.publicProp` is nullish. this.publicProp ??= false ? neverThis() : 123; // This works, because we use parentheses: this.#privateProp ??= (false ? neverThis() : 20); } problem() { // This fails, because the `??=` is translated to a `??` which has HIGHER // precedence than the ternary expression. this.#privateProp ??= false ? neverThis() : 20; } } console.clear(); const r = new Cls; r.noProblem(); r.noProblem(); console.log('no problem so far'); r.problem(); console.log('no problem at all'); function neverThis(): never { throw new Error('This should really really never happen!'); } 🙁 Actual behavior The statement this.#privateProp ??= false ? neverThis() : 20; is translated into: __classPrivateFieldSet(this, _Cls_privateProp, __classPrivateFieldGet(this, _Cls_privateProp, "f") ?? false ? neverThis() : 20, "f"); The relevant piece here is: __classPrivateFieldGet(this, _Cls_privateProp, "f") ?? false ? neverThis() : 20 which is equivalent to: (__classPrivateFieldGet(this, _Cls_privateProp, "f") ?? false) ? neverThis() : 20 But it should have been: __classPrivateFieldGet(this, _Cls_privateProp, "f") ?? (false ? neverThis() : 20) 🙂 Expected behavior Expected correct operator precedence in transpiled code. Additional information about the issue This is no issue on targets greater than es2021, because of the native support of private fields.
👀👀👀github.com/microsoft/Type…F