Skip to content

Commit 9346bc9

Browse files
committed
fix #1600: "++" and "--" on class private fields
1 parent 5c10033 commit 9346bc9

File tree

6 files changed

+253
-81
lines changed

6 files changed

+253
-81
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
As of this release, esbuild will now parse a superset of ES5 and ES6+ and will now quote identifier names when possible if it's not considered to be a valid identifier name in either ES5 or ES6+. In other words, a union of ES5 and ES6 rules is used for parsing and the intersection of ES5 and ES6 rules is used for printing.
1515

16+
* Fix `++` and `--` on class private fields when used with big integers ([#1600](https://github.com/evanw/esbuild/issues/1600))
17+
18+
Previously when esbuild lowered class private fields (e.g. `#foo`) to older JavaScript syntax, the transform of the `++` and `--` was not correct if the value is a big integer such as `123n`. The transform in esbuild is similar to Babel's transform which [has the same problem](https://github.com/babel/babel/issues/13756). Specifically, the code was transformed into code that either adds or subtracts the number `1` and `123n + 1` throws an exception in JavaScript. This problem has been fixed so this should now work fine starting with this release.
19+
1620
## 0.12.27
1721

1822
* Update JavaScript syntax feature compatibility tables ([#1594](https://github.com/evanw/esbuild/issues/1594))

internal/bundler/snapshots/snapshots_lower.txt

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -481,11 +481,10 @@ class Foo {
481481
__privateAdd(this, _x, void 0);
482482
}
483483
unary() {
484-
var _a, _b;
485-
__privateSet(this, _x, (_a = +__privateGet(this, _x)) + 1), _a;
486-
__privateSet(this, _x, (_b = +__privateGet(this, _x)) - 1), _b;
487-
__privateSet(this, _x, +__privateGet(this, _x) + 1);
488-
__privateSet(this, _x, +__privateGet(this, _x) - 1);
484+
__privateWrapper(this, _x)._++;
485+
__privateWrapper(this, _x)._--;
486+
++__privateWrapper(this, _x)._;
487+
--__privateWrapper(this, _x)._;
489488
}
490489
binary() {
491490
var _a;
@@ -518,11 +517,10 @@ class Foo {
518517
__privateAdd(this, _x, void 0);
519518
}
520519
unary() {
521-
var _a, _b;
522-
__privateSet(this, _x, (_a = +__privateGet(this, _x)) + 1), _a;
523-
__privateSet(this, _x, (_b = +__privateGet(this, _x)) - 1), _b;
524-
__privateSet(this, _x, +__privateGet(this, _x) + 1);
525-
__privateSet(this, _x, +__privateGet(this, _x) - 1);
520+
__privateWrapper(this, _x)._++;
521+
__privateWrapper(this, _x)._--;
522+
++__privateWrapper(this, _x)._;
523+
--__privateWrapper(this, _x)._;
526524
}
527525
binary() {
528526
var _a;
@@ -555,11 +553,10 @@ class Foo {
555553
__privateAdd(this, _x, void 0);
556554
}
557555
unary() {
558-
var _a, _b;
559-
__privateSet(this, _x, (_a = +__privateGet(this, _x)) + 1), _a;
560-
__privateSet(this, _x, (_b = +__privateGet(this, _x)) - 1), _b;
561-
__privateSet(this, _x, +__privateGet(this, _x) + 1);
562-
__privateSet(this, _x, +__privateGet(this, _x) - 1);
556+
__privateWrapper(this, _x)._++;
557+
__privateWrapper(this, _x)._--;
558+
++__privateWrapper(this, _x)._;
559+
--__privateWrapper(this, _x)._;
563560
}
564561
binary() {
565562
__privateSet(this, _x, 1);
@@ -676,11 +673,10 @@ var Foo = class {
676673
__privateSet(fn(), _prop, 2, prop_set);
677674
}
678675
unary(fn) {
679-
var _a, _b, _c, _d, _e, _f;
680-
__privateSet(_a = fn(), _prop, (_b = +__privateGet(_a, _prop, prop_get)) + 1, prop_set), _b;
681-
__privateSet(_c = fn(), _prop, (_d = +__privateGet(_c, _prop, prop_get)) - 1, prop_set), _d;
682-
__privateSet(_e = fn(), _prop, +__privateGet(_e, _prop, prop_get) + 1, prop_set);
683-
__privateSet(_f = fn(), _prop, +__privateGet(_f, _prop, prop_get) - 1, prop_set);
676+
__privateWrapper(fn(), _prop, prop_set, prop_get)._++;
677+
__privateWrapper(fn(), _prop, prop_set, prop_get)._--;
678+
++__privateWrapper(fn(), _prop, prop_set, prop_get)._;
679+
--__privateWrapper(fn(), _prop, prop_set, prop_get)._;
684680
}
685681
binary(fn) {
686682
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
@@ -736,11 +732,10 @@ var Foo = class {
736732
__privateSet(fn(), _prop, 2, prop_set);
737733
}
738734
unary(fn) {
739-
var _a, _b, _c, _d, _e, _f;
740-
__privateSet(_a = fn(), _prop, (_b = +__privateGet(_a, _prop, prop_get)) + 1, prop_set), _b;
741-
__privateSet(_c = fn(), _prop, (_d = +__privateGet(_c, _prop, prop_get)) - 1, prop_set), _d;
742-
__privateSet(_e = fn(), _prop, +__privateGet(_e, _prop, prop_get) + 1, prop_set);
743-
__privateSet(_f = fn(), _prop, +__privateGet(_f, _prop, prop_get) - 1, prop_set);
735+
__privateWrapper(fn(), _prop, prop_set, prop_get)._++;
736+
__privateWrapper(fn(), _prop, prop_set, prop_get)._--;
737+
++__privateWrapper(fn(), _prop, prop_set, prop_get)._;
738+
--__privateWrapper(fn(), _prop, prop_set, prop_get)._;
744739
}
745740
binary(fn) {
746741
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
@@ -796,11 +791,10 @@ var Foo = class {
796791
__privateSet(fn(), _prop, 2, prop_set);
797792
}
798793
unary(fn) {
799-
var _a, _b, _c, _d, _e, _f;
800-
__privateSet(_a = fn(), _prop, (_b = +__privateGet(_a, _prop, prop_get)) + 1, prop_set), _b;
801-
__privateSet(_c = fn(), _prop, (_d = +__privateGet(_c, _prop, prop_get)) - 1, prop_set), _d;
802-
__privateSet(_e = fn(), _prop, +__privateGet(_e, _prop, prop_get) + 1, prop_set);
803-
__privateSet(_f = fn(), _prop, +__privateGet(_f, _prop, prop_get) - 1, prop_set);
794+
__privateWrapper(fn(), _prop, prop_set, prop_get)._++;
795+
__privateWrapper(fn(), _prop, prop_set, prop_get)._--;
796+
++__privateWrapper(fn(), _prop, prop_set, prop_get)._;
797+
--__privateWrapper(fn(), _prop, prop_set, prop_get)._;
804798
}
805799
binary(fn) {
806800
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;

internal/js_parser/js_parser.go

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11553,24 +11553,9 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
1155311553
////////////////////////////////////////////////////////////////////////////////
1155411554
// All assignment operators below here
1155511555

11556-
case js_ast.UnOpPreDec:
11556+
case js_ast.UnOpPreDec, js_ast.UnOpPreInc, js_ast.UnOpPostDec, js_ast.UnOpPostInc:
1155711557
if target, loc, private := p.extractPrivateIndex(e.Value); private != nil {
11558-
return p.lowerPrivateSetUnOp(target, loc, private, js_ast.BinOpSub, false), exprOut{}
11559-
}
11560-
11561-
case js_ast.UnOpPreInc:
11562-
if target, loc, private := p.extractPrivateIndex(e.Value); private != nil {
11563-
return p.lowerPrivateSetUnOp(target, loc, private, js_ast.BinOpAdd, false), exprOut{}
11564-
}
11565-
11566-
case js_ast.UnOpPostDec:
11567-
if target, loc, private := p.extractPrivateIndex(e.Value); private != nil {
11568-
return p.lowerPrivateSetUnOp(target, loc, private, js_ast.BinOpSub, true), exprOut{}
11569-
}
11570-
11571-
case js_ast.UnOpPostInc:
11572-
if target, loc, private := p.extractPrivateIndex(e.Value); private != nil {
11573-
return p.lowerPrivateSetUnOp(target, loc, private, js_ast.BinOpAdd, true), exprOut{}
11558+
return p.lowerPrivateSetUnOp(target, loc, private, e.Op), exprOut{}
1157411559
}
1157511560
}
1157611561
}

internal/js_parser/js_parser_lower.go

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,35 +1084,53 @@ func (p *parser) lowerPrivateSet(
10841084
}
10851085
}
10861086

1087-
func (p *parser) lowerPrivateSetUnOp(target js_ast.Expr, loc logger.Loc, private *js_ast.EPrivateIdentifier, op js_ast.OpCode, isSuffix bool) js_ast.Expr {
1088-
targetFunc, targetWrapFunc := p.captureValueWithPossibleSideEffects(target.Loc, 2, target, valueDefinitelyNotMutated)
1089-
target = targetFunc()
1090-
1091-
// Load the private field and then use the unary "+" operator to force it to
1092-
// be a number. Otherwise the binary "+" operator may cause string
1093-
// concatenation instead of addition if one of the operands is not a number.
1094-
value := js_ast.Expr{Loc: target.Loc, Data: &js_ast.EUnary{
1095-
Op: js_ast.UnOpPos,
1096-
Value: p.lowerPrivateGet(targetFunc(), loc, private),
1097-
}}
1087+
func (p *parser) lowerPrivateSetUnOp(target js_ast.Expr, loc logger.Loc, private *js_ast.EPrivateIdentifier, op js_ast.OpCode) js_ast.Expr {
1088+
kind := p.symbols[private.Ref.InnerIndex].Kind
10981089

1099-
if isSuffix {
1100-
// "target.#private++" => "__privateSet(target, #private, _a = +__privateGet(target, #private) + 1), _a"
1101-
valueFunc, valueWrapFunc := p.captureValueWithPossibleSideEffects(value.Loc, 2, value, valueDefinitelyNotMutated)
1102-
assign := valueWrapFunc(targetWrapFunc(p.lowerPrivateSet(target, loc, private, js_ast.Expr{Loc: target.Loc, Data: &js_ast.EBinary{
1103-
Op: op,
1104-
Left: valueFunc(),
1105-
Right: js_ast.Expr{Loc: target.Loc, Data: &js_ast.ENumber{Value: 1}},
1106-
}})))
1107-
return js_ast.JoinWithComma(assign, valueFunc())
1090+
// Determine the setter, if any
1091+
var setter js_ast.Expr
1092+
switch kind {
1093+
case js_ast.SymbolPrivateSet, js_ast.SymbolPrivateStaticSet,
1094+
js_ast.SymbolPrivateGetSetPair, js_ast.SymbolPrivateStaticGetSetPair:
1095+
ref := p.privateSetters[private.Ref]
1096+
p.recordUsage(ref)
1097+
setter = js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}
11081098
}
11091099

1110-
// "++target.#private" => "__privateSet(target, #private, +__privateGet(target, #private) + 1)"
1111-
return targetWrapFunc(p.lowerPrivateSet(target, loc, private, js_ast.Expr{Loc: target.Loc, Data: &js_ast.EBinary{
1112-
Op: op,
1113-
Left: value,
1114-
Right: js_ast.Expr{Loc: target.Loc, Data: &js_ast.ENumber{Value: 1}},
1115-
}}))
1100+
// Determine the getter, if any
1101+
var getter js_ast.Expr
1102+
switch kind {
1103+
case js_ast.SymbolPrivateGet, js_ast.SymbolPrivateStaticGet,
1104+
js_ast.SymbolPrivateGetSetPair, js_ast.SymbolPrivateStaticGetSetPair:
1105+
ref := p.privateGetters[private.Ref]
1106+
p.recordUsage(ref)
1107+
getter = js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}
1108+
}
1109+
1110+
// Only include necessary arguments
1111+
args := []js_ast.Expr{
1112+
target,
1113+
{Loc: loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
1114+
}
1115+
if setter.Data != nil {
1116+
args = append(args, setter)
1117+
}
1118+
if getter.Data != nil {
1119+
if setter.Data == nil {
1120+
args = append(args, js_ast.Expr{Loc: loc, Data: js_ast.ENullShared})
1121+
}
1122+
args = append(args, getter)
1123+
}
1124+
1125+
// "target.#private++" => "__privateWrapper(target, #private, private_set, private_get)._++"
1126+
return js_ast.Expr{Loc: loc, Data: &js_ast.EUnary{
1127+
Op: op,
1128+
Value: js_ast.Expr{Loc: target.Loc, Data: &js_ast.EDot{
1129+
Target: p.callRuntime(target.Loc, "__privateWrapper", args),
1130+
NameLoc: target.Loc,
1131+
Name: "_",
1132+
}},
1133+
}}
11161134
}
11171135

11181136
func (p *parser) lowerPrivateSetBinOp(target js_ast.Expr, loc logger.Loc, private *js_ast.EPrivateIdentifier, op js_ast.OpCode, value js_ast.Expr) js_ast.Expr {
@@ -1305,31 +1323,31 @@ func (p *parser) lowerPrivateInAssign(expr js_ast.Expr) (js_ast.Expr, bool) {
13051323
}
13061324

13071325
case *js_ast.EIndex:
1308-
// "[a.#b] = [c]" => "[__privateAssign(a, #b)._] = [c]"
1326+
// "[a.#b] = [c]" => "[__privateWrapper(a, #b)._] = [c]"
13091327
if private, ok := e.Index.Data.(*js_ast.EPrivateIdentifier); ok && p.privateSymbolNeedsToBeLowered(private) {
13101328
var target js_ast.Expr
13111329

13121330
switch p.symbols[private.Ref.InnerIndex].Kind {
13131331
case js_ast.SymbolPrivateSet, js_ast.SymbolPrivateStaticSet,
13141332
js_ast.SymbolPrivateGetSetPair, js_ast.SymbolPrivateStaticGetSetPair:
1315-
// "this.#setter" => "__privateAssign(this, #setter, setter_set)"
1333+
// "this.#setter" => "__privateWrapper(this, #setter, setter_set)"
13161334
fnRef := p.privateSetters[private.Ref]
13171335
p.recordUsage(fnRef)
1318-
target = p.callRuntime(expr.Loc, "__privateAssign", []js_ast.Expr{
1336+
target = p.callRuntime(expr.Loc, "__privateWrapper", []js_ast.Expr{
13191337
e.Target,
13201338
{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
13211339
{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: fnRef}},
13221340
})
13231341

13241342
default:
1325-
// "this.#field" => "__privateAssign(this, #field)"
1326-
target = p.callRuntime(expr.Loc, "__privateAssign", []js_ast.Expr{
1343+
// "this.#field" => "__privateWrapper(this, #field)"
1344+
target = p.callRuntime(expr.Loc, "__privateWrapper", []js_ast.Expr{
13271345
e.Target,
13281346
{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: private.Ref}},
13291347
})
13301348
}
13311349

1332-
// "__privateAssign(this, #field)" => "__privateAssign(this, #field)._"
1350+
// "__privateWrapper(this, #field)" => "__privateWrapper(this, #field)._"
13331351
expr.Data = &js_ast.EDot{Target: target, Name: "_", NameLoc: expr.Loc}
13341352
didLower = true
13351353
}

internal/runtime/runtime.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,11 @@ func code(isES6 bool) string {
255255
setter ? setter.call(obj, value) : member.set(obj, value)
256256
return value
257257
}
258-
export var __privateAssign = (obj, member, setter) => {
259-
return { set _(value) { __privateSet(obj, member, value, setter) } }
258+
export var __privateWrapper = (obj, member, setter, getter) => {
259+
return {
260+
set _(value) { __privateSet(obj, member, value, setter) },
261+
get _() { return __privateGet(obj, member, getter) },
262+
}
260263
}
261264
export var __privateMethod = (obj, member, method) => {
262265
__accessCheck(obj, member, 'access private method')

0 commit comments

Comments
 (0)