Skip to content

Commit 084bf9a

Browse files
authored
Port SubtreeFacts (nee TransformFlags) from Strada (microsoft#707)
1 parent 2eb2d3f commit 084bf9a

27 files changed

+1150
-103
lines changed

internal/ast/ast.go

Lines changed: 949 additions & 47 deletions
Large diffs are not rendered by default.

internal/ast/subtreefacts.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package ast
2+
3+
import (
4+
"github.com/microsoft/typescript-go/internal/core"
5+
)
6+
7+
type SubtreeFacts int32
8+
9+
const (
10+
// Facts
11+
// - Flags used to indicate that a node or subtree contains syntax specific to a particular ECMAScript variant.
12+
13+
SubtreeContainsTypeScript SubtreeFacts = 1 << iota
14+
SubtreeContainsJsx
15+
SubtreeContainsESNext
16+
SubtreeContainsES2022
17+
SubtreeContainsES2021
18+
SubtreeContainsES2020
19+
SubtreeContainsES2019
20+
SubtreeContainsES2018
21+
SubtreeContainsES2017
22+
SubtreeContainsES2016
23+
24+
// Markers
25+
// - Flags used to indicate that a node or subtree contains a particular kind of syntax.
26+
27+
SubtreeContainsLexicalThis
28+
SubtreeContainsLexicalSuper
29+
SubtreeContainsRest
30+
SubtreeContainsObjectRestOrSpread
31+
SubtreeContainsAwait
32+
SubtreeContainsDynamicImport
33+
SubtreeContainsClassFields
34+
SubtreeContainsDecorators
35+
SubtreeContainsIdentifier
36+
37+
SubtreeFactsComputed // NOTE: This should always be last
38+
SubtreeFactsNone SubtreeFacts = 0
39+
40+
// Scope Exclusions
41+
// - Bitmasks that exclude flags from propagating out of a specific context
42+
// into the subtree flags of their container.
43+
44+
SubtreeExclusionsNode = SubtreeFactsComputed
45+
SubtreeExclusionsEraseable = ^SubtreeContainsTypeScript
46+
SubtreeExclusionsOuterExpression = SubtreeExclusionsNode
47+
SubtreeExclusionsPropertyAccess = SubtreeExclusionsNode
48+
SubtreeExclusionsElementAccess = SubtreeExclusionsNode
49+
SubtreeExclusionsArrowFunction = SubtreeExclusionsNode | SubtreeContainsAwait | SubtreeContainsObjectRestOrSpread
50+
SubtreeExclusionsFunction = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper | SubtreeContainsAwait | SubtreeContainsObjectRestOrSpread
51+
SubtreeExclusionsConstructor = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper | SubtreeContainsAwait | SubtreeContainsObjectRestOrSpread
52+
SubtreeExclusionsMethod = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper | SubtreeContainsAwait | SubtreeContainsObjectRestOrSpread
53+
SubtreeExclusionsAccessor = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper | SubtreeContainsAwait | SubtreeContainsObjectRestOrSpread
54+
SubtreeExclusionsProperty = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper
55+
SubtreeExclusionsClass = SubtreeExclusionsNode
56+
SubtreeExclusionsModule = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper
57+
SubtreeExclusionsObjectLiteral = SubtreeExclusionsNode | SubtreeContainsObjectRestOrSpread
58+
SubtreeExclusionsArrayLiteral = SubtreeExclusionsNode
59+
SubtreeExclusionsCall = SubtreeExclusionsNode
60+
SubtreeExclusionsNew = SubtreeExclusionsNode
61+
SubtreeExclusionsVariableDeclarationList = SubtreeExclusionsNode | SubtreeContainsObjectRestOrSpread
62+
SubtreeExclusionsParameter = SubtreeExclusionsNode
63+
SubtreeExclusionsCatchClause = SubtreeExclusionsNode | SubtreeContainsObjectRestOrSpread
64+
SubtreeExclusionsBindingPattern = SubtreeExclusionsNode | SubtreeContainsRest
65+
66+
// Masks
67+
// - Additional bitmasks
68+
69+
SubtreeContainsLexicalThisOrSuper = SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper
70+
)
71+
72+
func propagateEraseableSyntaxListSubtreeFacts(children *TypeArgumentList) SubtreeFacts {
73+
return core.IfElse(children != nil, SubtreeContainsTypeScript, SubtreeFactsNone)
74+
}
75+
76+
func propagateEraseableSyntaxSubtreeFacts(child *TypeNode) SubtreeFacts {
77+
return core.IfElse(child != nil, SubtreeContainsTypeScript, SubtreeFactsNone)
78+
}
79+
80+
func propagateObjectBindingElementSubtreeFacts(child *BindingElementNode) SubtreeFacts {
81+
facts := propagateSubtreeFacts(child)
82+
if facts&SubtreeContainsRest != 0 {
83+
facts &= ^SubtreeContainsRest
84+
facts |= SubtreeContainsObjectRestOrSpread
85+
}
86+
return facts
87+
}
88+
89+
func propagateBindingElementSubtreeFacts(child *BindingElementNode) SubtreeFacts {
90+
return propagateSubtreeFacts(child) & ^SubtreeContainsRest
91+
}
92+
93+
func propagateSubtreeFacts(child *Node) SubtreeFacts {
94+
if child == nil {
95+
return SubtreeFactsNone
96+
}
97+
return child.propagateSubtreeFacts()
98+
}
99+
100+
func propagateNodeListSubtreeFacts(children *NodeList, propagate func(*Node) SubtreeFacts) SubtreeFacts {
101+
if children == nil {
102+
return SubtreeFactsNone
103+
}
104+
facts := SubtreeFactsNone
105+
for _, child := range children.Nodes {
106+
facts |= propagate(child)
107+
}
108+
return facts
109+
}
110+
111+
func propagateModifierListSubtreeFacts(children *ModifierList) SubtreeFacts {
112+
if children == nil {
113+
return SubtreeFactsNone
114+
}
115+
return propagateNodeListSubtreeFacts(&children.NodeList, propagateSubtreeFacts)
116+
}

internal/transformers/commonjsmodule.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,10 @@ func (tx *CommonJSModuleTransformer) visit(node *ast.Node) *ast.Node {
136136

137137
// Visits source elements that are not top-level or top-level nested statements without ancestor tracking.
138138
func (tx *CommonJSModuleTransformer) visitNoStack(node *ast.Node, resultIsDiscarded bool) *ast.Node {
139-
// !!!
140-
////// This visitor does not need to descend into the tree if there is no dynamic import, destructuring assignment, or update expression
141-
////// as export/import statements are only transformed at the top level of a file.
142-
////if (!(node.transformFlags & (TransformFlags.ContainsDynamicImport | TransformFlags.ContainsDestructuringAssignment | TransformFlags.ContainsUpdateExpressionForIdentifier)) && !importsAndRequiresToRewriteOrShim?.length) {
143-
//// return node;
144-
////}
139+
// This visitor does not need to descend into the tree if there are no dynamic imports or identifiers in the subtree
140+
if !ast.IsSourceFile(node) && node.SubtreeFacts()&(ast.SubtreeContainsDynamicImport|ast.SubtreeContainsIdentifier) == 0 {
141+
return node
142+
}
145143

146144
switch node.Kind {
147145
case ast.KindSourceFile:
@@ -229,7 +227,7 @@ func (tx *CommonJSModuleTransformer) visitAssignmentPatternNoStack(node *ast.Nod
229227
func (tx *CommonJSModuleTransformer) visitSourceFile(node *ast.SourceFile) *ast.Node {
230228
if node.IsDeclarationFile ||
231229
!(ast.IsEffectiveExternalModule(node, tx.compilerOptions) ||
232-
containsDynamicImport(tx.emitContext, node) ||
230+
node.SubtreeFacts()&ast.SubtreeContainsDynamicImport != 0 ||
233231
ast.IsJsonSourceFile(node) && tx.compilerOptions.HasJsonModuleEmitEnabled() && len(tx.compilerOptions.OutFile) > 0) {
234232
return node.AsNode()
235233
}

internal/transformers/runtimesyntax.go

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (tx *RuntimeSyntaxTransformer) pushScope(node *ast.Node) (savedCurrentScope
6161
case ast.KindCaseBlock, ast.KindModuleBlock, ast.KindBlock:
6262
tx.currentScope = node
6363
tx.currentScopeFirstDeclarationsOfName = nil
64-
case ast.KindFunctionDeclaration, ast.KindClassDeclaration, ast.KindEnumDeclaration, ast.KindModuleDeclaration, ast.KindVariableDeclaration:
64+
case ast.KindFunctionDeclaration, ast.KindClassDeclaration, ast.KindEnumDeclaration, ast.KindModuleDeclaration, ast.KindVariableStatement:
6565
tx.recordDeclarationInScope(node)
6666
}
6767
return savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName
@@ -78,11 +78,16 @@ func (tx *RuntimeSyntaxTransformer) popScope(savedCurrentScope *ast.Node, savedC
7878

7979
// Visits each node in the AST
8080
func (tx *RuntimeSyntaxTransformer) visit(node *ast.Node) *ast.Node {
81-
savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName := tx.pushScope(node)
8281
grandparentNode := tx.pushNode(node)
8382
defer tx.popNode(grandparentNode)
83+
84+
savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName := tx.pushScope(node)
8485
defer tx.popScope(savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName)
8586

87+
if node.SubtreeFacts()&ast.SubtreeContainsTypeScript == 0 && (tx.currentNamespace == nil && tx.currentEnum == nil || node.SubtreeFacts()&ast.SubtreeContainsIdentifier == 0) {
88+
return node
89+
}
90+
8691
switch node.Kind {
8792
// TypeScript parameter property modifiers are elided
8893
case ast.KindPublicKeyword,
@@ -119,14 +124,33 @@ func (tx *RuntimeSyntaxTransformer) visit(node *ast.Node) *ast.Node {
119124

120125
// Records that a declaration was emitted in the current scope, if it was the first declaration for the provided symbol.
121126
func (tx *RuntimeSyntaxTransformer) recordDeclarationInScope(node *ast.Node) {
122-
name := node.Name()
123-
if name != nil && ast.IsIdentifier(name) {
124-
if tx.currentScopeFirstDeclarationsOfName == nil {
125-
tx.currentScopeFirstDeclarationsOfName = make(map[string]*ast.Node)
127+
switch node.Kind {
128+
case ast.KindVariableStatement:
129+
tx.recordDeclarationInScope(node.AsVariableStatement().DeclarationList)
130+
return
131+
case ast.KindVariableDeclarationList:
132+
for _, decl := range node.AsVariableDeclarationList().Declarations.Nodes {
133+
tx.recordDeclarationInScope(decl)
126134
}
127-
text := name.Text()
128-
if _, found := tx.currentScopeFirstDeclarationsOfName[text]; !found {
129-
tx.currentScopeFirstDeclarationsOfName[text] = node
135+
return
136+
case ast.KindArrayBindingPattern, ast.KindObjectBindingPattern:
137+
for _, element := range node.AsBindingPattern().Elements.Nodes {
138+
tx.recordDeclarationInScope(element)
139+
}
140+
return
141+
}
142+
name := node.Name()
143+
if name != nil {
144+
if ast.IsIdentifier(name) {
145+
if tx.currentScopeFirstDeclarationsOfName == nil {
146+
tx.currentScopeFirstDeclarationsOfName = make(map[string]*ast.Node)
147+
}
148+
text := name.Text()
149+
if _, found := tx.currentScopeFirstDeclarationsOfName[text]; !found {
150+
tx.currentScopeFirstDeclarationsOfName[text] = node
151+
}
152+
} else if ast.IsBindingPattern(name) {
153+
tx.recordDeclarationInScope(name)
130154
}
131155
}
132156
}

internal/transformers/typeeraser.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ func (tx *TypeEraserTransformer) elide(node *ast.Statement) *ast.Statement {
4040
}
4141

4242
func (tx *TypeEraserTransformer) visit(node *ast.Node) *ast.Node {
43-
// !!! TransformFlags were traditionally used here to skip over subtrees that contain no TypeScript syntax
43+
if node.SubtreeFacts()&ast.SubtreeContainsTypeScript == 0 {
44+
return node
45+
}
46+
4447
if ast.IsStatement(node) && ast.HasSyntacticModifier(node, ast.ModifierFlagsAmbient) {
4548
return tx.elide(node)
4649
}
@@ -260,6 +263,7 @@ func (tx *TypeEraserTransformer) visit(node *ast.Node) *ast.Node {
260263
return nil
261264
}
262265
return tx.factory.UpdateImportDeclaration(n, n.Modifiers(), importClause, n.ModuleSpecifier, n.Attributes)
266+
263267
case ast.KindImportClause:
264268
n := node.AsImportClause()
265269
if n.IsTypeOnly {
@@ -273,21 +277,28 @@ func (tx *TypeEraserTransformer) visit(node *ast.Node) *ast.Node {
273277
return nil
274278
}
275279
return tx.factory.UpdateImportClause(n, false /*isTypeOnly*/, name, namedBindings)
280+
276281
case ast.KindNamedImports:
277282
n := node.AsNamedImports()
283+
if len(n.Elements.Nodes) == 0 {
284+
// Do not elide a side-effect only import declaration.
285+
return node
286+
}
278287
elements := tx.visitor.VisitNodes(n.Elements)
279288
if !tx.compilerOptions.VerbatimModuleSyntax.IsTrue() && len(elements.Nodes) == 0 {
280289
// all import specifiers were elided
281290
return nil
282291
}
283292
return tx.factory.UpdateNamedImports(n, elements)
293+
284294
case ast.KindImportSpecifier:
285295
n := node.AsImportSpecifier()
286296
if n.IsTypeOnly {
287297
// elide type-only or unused imports
288298
return nil
289299
}
290300
return node
301+
291302
case ast.KindExportDeclaration:
292303
n := node.AsExportDeclaration()
293304
if n.IsTypeOnly {
@@ -303,21 +314,29 @@ func (tx *TypeEraserTransformer) visit(node *ast.Node) *ast.Node {
303314
}
304315
}
305316
return tx.factory.UpdateExportDeclaration(n, nil /*modifiers*/, false /*isTypeOnly*/, exportClause, tx.visitor.VisitNode(n.ModuleSpecifier), tx.visitor.VisitNode(n.Attributes))
317+
306318
case ast.KindNamedExports:
307319
n := node.AsNamedExports()
320+
if len(n.Elements.Nodes) == 0 {
321+
// Do not elide an empty export declaration.
322+
return node
323+
}
324+
308325
elements := tx.visitor.VisitNodes(n.Elements)
309326
if !tx.compilerOptions.VerbatimModuleSyntax.IsTrue() && len(elements.Nodes) == 0 {
310327
// all export specifiers were elided
311328
return nil
312329
}
313330
return tx.factory.UpdateNamedExports(n, elements)
331+
314332
case ast.KindExportSpecifier:
315333
n := node.AsExportSpecifier()
316334
if n.IsTypeOnly {
317335
// elide unused export
318336
return nil
319337
}
320338
return node
339+
321340
default:
322341
return tx.visitor.VisitEachChild(node)
323342
}

testdata/baselines/reference/submodule/compiler/es6ImportDefaultBindingFollowedWithNamedImport.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ exports.default = {};
3232
//// [es6ImportDefaultBindingFollowedWithNamedImport_1.js]
3333
"use strict";
3434
Object.defineProperty(exports, "__esModule", { value: true });
35-
require("./es6ImportDefaultBindingFollowedWithNamedImport_0");
3635
const es6ImportDefaultBindingFollowedWithNamedImport_0_1 = require("./es6ImportDefaultBindingFollowedWithNamedImport_0");
3736
var x1 = es6ImportDefaultBindingFollowedWithNamedImport_0_1.a;
3837
const es6ImportDefaultBindingFollowedWithNamedImport_0_2 = require("./es6ImportDefaultBindingFollowedWithNamedImport_0");

testdata/baselines/reference/submodule/compiler/es6ImportDefaultBindingFollowedWithNamedImport.js.diff

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
--- old.es6ImportDefaultBindingFollowedWithNamedImport.js
22
+++ new.es6ImportDefaultBindingFollowedWithNamedImport.js
3-
@@= skipped -31, +31 lines =@@
4-
//// [es6ImportDefaultBindingFollowedWithNamedImport_1.js]
5-
"use strict";
6-
Object.defineProperty(exports, "__esModule", { value: true });
7-
+require("./es6ImportDefaultBindingFollowedWithNamedImport_0");
8-
const es6ImportDefaultBindingFollowedWithNamedImport_0_1 = require("./es6ImportDefaultBindingFollowedWithNamedImport_0");
9-
var x1 = es6ImportDefaultBindingFollowedWithNamedImport_0_1.a;
10-
const es6ImportDefaultBindingFollowedWithNamedImport_0_2 = require("./es6ImportDefaultBindingFollowedWithNamedImport_0");
11-
@@= skipped -11, +12 lines =@@
3+
@@= skipped -42, +42 lines =@@
124
var x1 = es6ImportDefaultBindingFollowedWithNamedImport_0_4.x;
135
const es6ImportDefaultBindingFollowedWithNamedImport_0_5 = require("./es6ImportDefaultBindingFollowedWithNamedImport_0");
146
var x1 = es6ImportDefaultBindingFollowedWithNamedImport_0_5.m;

testdata/baselines/reference/submodule/compiler/es6ImportDefaultBindingFollowedWithNamedImportDts.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ exports.x11 = x11;
4949
"use strict";
5050
Object.defineProperty(exports, "__esModule", { value: true });
5151
exports.x6 = exports.x3 = exports.x5 = exports.x4 = exports.x2 = exports.x1 = void 0;
52-
require("./server");
5352
const server_1 = require("./server");
5453
exports.x1 = new server_1.a();
5554
const server_2 = require("./server");

testdata/baselines/reference/submodule/compiler/es6ImportDefaultBindingFollowedWithNamedImportDts.js.diff

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
Object.defineProperty(exports, "__esModule", { value: true });
5858
exports.x6 = exports.x3 = exports.x5 = exports.x4 = exports.x2 = exports.x1 = void 0;
5959
-var server_1 = require("./server");
60-
+require("./server");
6160
+const server_1 = require("./server");
6261
exports.x1 = new server_1.a();
6362
-var server_2 = require("./server");

testdata/baselines/reference/submodule/compiler/es6ImportDefaultBindingFollowedWithNamedImportInEs5.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ exports.m = exports.a;
3030
//// [es6ImportDefaultBindingFollowedWithNamedImportInEs5_1.js]
3131
"use strict";
3232
Object.defineProperty(exports, "__esModule", { value: true });
33-
require("./es6ImportDefaultBindingFollowedWithNamedImportInEs5_0");
3433
const es6ImportDefaultBindingFollowedWithNamedImportInEs5_0_1 = require("./es6ImportDefaultBindingFollowedWithNamedImportInEs5_0");
3534
var x1 = es6ImportDefaultBindingFollowedWithNamedImportInEs5_0_1.a;
3635
const es6ImportDefaultBindingFollowedWithNamedImportInEs5_0_2 = require("./es6ImportDefaultBindingFollowedWithNamedImportInEs5_0");

0 commit comments

Comments
 (0)