Skip to content

Commit 1f6e927

Browse files
Closure Teamcopybara-github
authored andcommitted
Stop throwing JSC_CANNOT_CONVERT_YET: Member references this or super error for public fields.
This includes adding support for transpilation of `this` and `super` in static fields by renaming them with the correct class name when the static field is moved outside of the class it was originally declared in. PiperOrigin-RevId: 551554332
1 parent fc053fc commit 1f6e927

File tree

3 files changed

+212
-28
lines changed

3 files changed

+212
-28
lines changed

src/com/google/javascript/jscomp/NodeTraversal.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,27 @@ private static boolean isHoistScopeRootNode(Node n) {
12921292
}
12931293
}
12941294

1295+
/** Returns the closest scope binding the `this` or `super` keyword */
1296+
public @Nullable Node getClosestScopeRootNodeBindingThisOrSuper() {
1297+
for (int i = scopes.size() - 1; i >= 0; i--) {
1298+
Node rootNode = getNodeRootFromScopeObj(scopes.get(i));
1299+
switch (rootNode.getToken()) {
1300+
case FUNCTION:
1301+
if (rootNode.isArrowFunction()) {
1302+
continue;
1303+
}
1304+
return rootNode;
1305+
case MEMBER_FIELD_DEF:
1306+
case CLASS:
1307+
case MODULE_BODY:
1308+
case ROOT:
1309+
return rootNode;
1310+
default:
1311+
continue;
1312+
}
1313+
}
1314+
return null;
1315+
}
12951316

12961317
public ScopeCreator getScopeCreator() {
12971318
return scopeCreator;
@@ -1360,8 +1381,7 @@ public void reportCodeChange(Node n) {
13601381
*
13611382
* <p>e.g. returns null if {@link #traverseInnerNode(Node, Node, AbstractScope)} was used
13621383
*/
1363-
@Nullable
1364-
Node getCurrentScript() {
1384+
@Nullable Node getCurrentScript() {
13651385
return currentScript;
13661386
}
13671387

src/com/google/javascript/jscomp/RewriteClassMembers.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,6 @@ public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
9595
return false;
9696
case MEMBER_FIELD_DEF:
9797
checkState(!classStack.isEmpty());
98-
if (NodeUtil.referencesEnclosingReceiver(n)) {
99-
t.report(n, TranspilationUtil.CANNOT_CONVERT_YET, "Member references this or super");
100-
classStack.peek().cannotConvert = true;
101-
break;
102-
}
10398
classStack.peek().enterField(n);
10499
break;
105100
case BLOCK:
@@ -123,7 +118,7 @@ public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
123118
// constructor(x) {}
124119
// }
125120
// Either using scopes to be more precise or just doing renaming for all conflicting
126-
// constructor declarations would addresss this issue.
121+
// constructor declarations would address this issue.
127122
record.potentiallyRecordNameInRhs(n);
128123
}
129124
break;
@@ -153,6 +148,22 @@ public void visit(NodeTraversal t, Node n, Node parent) {
153148
case MEMBER_FIELD_DEF:
154149
classStack.peek().exitField();
155150
return;
151+
case THIS:
152+
Node rootNode = t.getClosestScopeRootNodeBindingThisOrSuper();
153+
if (rootNode.isMemberFieldDef() && rootNode.isStaticMember()) {
154+
Node className = rootNode.getGrandparent().getFirstChild().cloneNode();
155+
n.replaceWith(className);
156+
t.reportCodeChange(className);
157+
}
158+
return;
159+
case SUPER:
160+
rootNode = t.getClosestScopeRootNodeBindingThisOrSuper();
161+
if (rootNode.isMemberFieldDef() && rootNode.isStaticMember()) {
162+
Node superclassName = rootNode.getGrandparent().getChildAtIndex(1).cloneNode();
163+
n.replaceWith(superclassName);
164+
t.reportCodeChange(superclassName);
165+
}
166+
return;
156167
default:
157168
return;
158169
}

test/com/google/javascript/jscomp/RewriteClassMembersTest.java

Lines changed: 173 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public void testCannotConvertYet() {
9191

9292
testError(
9393
lines(
94-
"class C extends B{", //
94+
"class C extends B {", //
9595
" static {",
9696
" let x = super.y",
9797
" }",
@@ -165,14 +165,6 @@ public void testCannotConvertYet() {
165165
"}"),
166166
TranspilationUtil.CANNOT_CONVERT_YET); // `var` in static block
167167

168-
testError(
169-
lines(
170-
"class C {", //
171-
" static x = 1;",
172-
" static y = this.x;",
173-
"}"),
174-
TranspilationUtil.CANNOT_CONVERT_YET); // `this` in static field
175-
176168
testError(
177169
lines(
178170
"let c = class C {", //
@@ -196,14 +188,6 @@ public void testCannotConvertYet() {
196188
"})"),
197189
TranspilationUtil.CANNOT_CONVERT_YET); // not class decl
198190

199-
testError(
200-
lines(
201-
"class C {", //
202-
" x = 1;",
203-
" y = this.x;",
204-
"}"),
205-
TranspilationUtil.CANNOT_CONVERT_YET); // `this` in public field
206-
207191
testError(
208192
lines(
209193
"foo(class C {", //
@@ -288,10 +272,169 @@ public void testCannotConvertYet() {
288272
"}" // testing that the correct number of diagnostics are thrown
289273
)),
290274
error(TranspilationUtil.CANNOT_CONVERT_YET),
291-
error(TranspilationUtil.CANNOT_CONVERT_YET),
292275
error(TranspilationUtil.CANNOT_CONVERT_YET));
293276
}
294277

278+
@Test
279+
public void testThisInNonStaticPublicField() {
280+
test(
281+
lines(
282+
"class A {", //
283+
" b = 'word';",
284+
" c = this.b;",
285+
"}"),
286+
lines(
287+
"class A {",
288+
" constructor() {",
289+
" this.b = 'word';",
290+
" this.c = this.b;",
291+
" }",
292+
"}"));
293+
294+
test(
295+
lines(
296+
"let obj = { bar() { return 9; } };", //
297+
"class D {",
298+
" e = obj;",
299+
" f = this.e.bar() * 4;",
300+
"}"),
301+
lines(
302+
"let obj = { bar() { return 9; } };",
303+
"class D {",
304+
" constructor() {",
305+
" this.e = obj;",
306+
" this.f = this.e.bar() * 4;",
307+
" }",
308+
"}"));
309+
310+
test(
311+
lines(
312+
"class Foo {", //
313+
" y = 'apple';",
314+
" x = () => { return this.y + ' and banana'; };",
315+
"}"),
316+
lines(
317+
"class Foo {",
318+
" constructor() {",
319+
" this.y = 'apple';",
320+
" this.x = () => { return this.y + ' and banana'; };",
321+
" }",
322+
"}"));
323+
324+
test(
325+
lines(
326+
"class Bar {", //
327+
" x = () => { this.method(); };",
328+
" method() {}",
329+
"}"),
330+
lines(
331+
"class Bar {",
332+
" constructor() {",
333+
" this.x = () => { this.method(); };",
334+
" }",
335+
" method() {}",
336+
"}"));
337+
}
338+
339+
@Test
340+
public void testSuperInNonStaticPublicField() {
341+
test(
342+
lines(
343+
"class Foo {",
344+
" x() {",
345+
" return 3;",
346+
" }",
347+
"}",
348+
"class Bar extends Foo {",
349+
" y = 1 + super.x();",
350+
"}"),
351+
lines(
352+
"class Foo {",
353+
" x() {",
354+
" return 3;",
355+
" }",
356+
"}",
357+
"class Bar extends Foo {",
358+
" constructor() {",
359+
" super(...arguments);",
360+
" this.y = 1 + super.x();",
361+
" }",
362+
"}"));
363+
}
364+
365+
@Test
366+
public void testThisInStaticField() {
367+
test(
368+
lines(
369+
"class C {", //
370+
" static x = 2;",
371+
" static y = () => this.x;",
372+
"}"),
373+
lines(
374+
"class C {}", //
375+
"C.x = 2;",
376+
"C.y = () => C.x;"));
377+
378+
test(
379+
lines(
380+
"class F {", //
381+
" static a = 'there';",
382+
" static b = this.c() + this.a;",
383+
" static c() { return 'hi'; }",
384+
"}"),
385+
lines(
386+
"class F {", //
387+
" static c() { return 'hi'; }",
388+
"}",
389+
"F.a = 'there';",
390+
"F.b = F.c() + F.a;"));
391+
}
392+
393+
@Test
394+
public void testSuperInStaticField() {
395+
test(
396+
lines(
397+
"class Foo {",
398+
" static x() {",
399+
" return 5;",
400+
" }",
401+
" static y() {",
402+
" return 20;",
403+
" }",
404+
"}",
405+
"class Bar extends Foo {",
406+
" static z = () => super.x() + 12 + super.y();",
407+
"}"),
408+
lines(
409+
"class Foo {",
410+
" static x() {",
411+
" return 5;",
412+
" }",
413+
" static y() {",
414+
" return 20;",
415+
" }",
416+
"}",
417+
"class Bar extends Foo {}",
418+
"Bar.z = () => Foo.x() + 12 + Foo.y();"));
419+
420+
test(
421+
lines(
422+
"class Bar {",
423+
" static a = { method1() {} };",
424+
" static b = { method2() { super.method1(); } };",
425+
"}",
426+
"Object.setPrototypeOf = function(c, d) {}",
427+
"Object.setPrototypeOf(Foo.b, Foo.a);",
428+
"Foo.b.method2();"),
429+
lines(
430+
"class Bar {}",
431+
"Bar.a = { method1() {} };",
432+
"Bar.b = { method2() { super.method1(); } };",
433+
"Object.setPrototypeOf = function(c, d) {}",
434+
"Object.setPrototypeOf(Foo.b, Foo.a);",
435+
"Foo.b.method2();"));
436+
}
437+
295438
@Test
296439
public void testClassStaticBlocksNoFieldAssign() {
297440
test(
@@ -1243,9 +1386,19 @@ public void testInstanceInitializerDoesntShadowConstructorDeclaration() {
12431386
@Test
12441387
public void testInstanceFieldInitializersDontBleedOut() {
12451388
test(
1246-
lines("class C {", " y = z", " method() { x; }", " constructor(x) {}", "}"),
12471389
lines(
1248-
"class C {", " method() { x; }", " constructor(x) {", " this.y = z;", " }", "}"));
1390+
"class C {", //
1391+
" y = z",
1392+
" method() { x; }",
1393+
" constructor(x) {}",
1394+
"}"),
1395+
lines(
1396+
"class C {", //
1397+
" method() { x; }",
1398+
" constructor(x) {",
1399+
" this.y = z;",
1400+
" }",
1401+
"}"));
12491402
}
12501403

12511404
@Test

0 commit comments

Comments
 (0)