13
Place,
14
ReactiveBlock,
15
ReactiveFunction,
16
- ReactiveInstruction,
16
ReactiveScopeBlock,
17
} from "../HIR/HIR";
19
-import { eachInstructionLValue } from "../HIR/visitors";
20
-import {
21
- ReactiveFunctionVisitor,
22
- eachReactiveValueOperand,
23
- visitReactiveFunction,
24
-} from "./visitors";
18
+import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
19
26
-/*
20
+/**
21
* Ensures that each named variable in the given function has a unique name
22
* that does not conflict with any other variables in the same block scope.
23
* Note that the scoping is based on the final inferred blocks, not the
24
* block scopes that were present in the original source. Thus variables
25
* that shadowed in the original source may end up with unique names in the
26
* output, if Forget would merge those two blocks into a single scope.
27
+ *
28
+ * Variables are renamed using their original name followed by a number,
29
+ * starting with 0 and incrementing until a unique name is found. Eg if the
30
+ * compiler collapses three scopes that each had their own `foo` declaration,
31
+ * they will be renamed to `foo`, `foo0`, and `foo1`, assuming that no conflicts'
32
+ * exist for `foo0` and `foo1`.
33
+ *
34
+ * For temporary values that are promoted to named variables, the starting name
35
+ * is "T0" for values that appear in JSX tag position and "t0" otherwise. If this
36
+ * name conflicts, the number portion increments until the name is unique (t1, t2, etc).
37
*/
38
export function renameVariables(fn: ReactiveFunction): void {
39
const scopes = new Scopes();
40
+ renameVariablesImpl(fn, new Visitor(), scopes);
41
+}
42
+
43
+function renameVariablesImpl(
44
+ fn: ReactiveFunction,
45
+ visitor: Visitor,
46
+ scopes: Scopes
47
+): void {
48
scopes.enter(() => {
49
for (const param of fn.params) {
50
if (param.kind === "Identifier") {
53
scopes.visit(param.place.identifier);
54
}
55
}
44
- visitReactiveFunction(fn, new Visitor(), scopes);
56
+ visitReactiveFunction(fn, visitor, scopes);
57
});
58
}
59
60
class Visitor extends ReactiveFunctionVisitor<Scopes> {
61
+ override visitLValue(_id: InstructionId, lvalue: Place, state: Scopes): void {
62
+ state.visit(lvalue.identifier);
63
+ }
64
override visitPlace(id: InstructionId, place: Place, state: Scopes): void {
65
state.visit(place.identifier);
66
}
69
this.traverseBlock(block, state);
70
});
71
}
57
- override visitInstruction(instr: ReactiveInstruction, state: Scopes): void {
58
- for (const operand of eachReactiveValueOperand(instr.value)) {
59
- state.visit(operand.identifier);
60
- }
61
- for (const operand of eachInstructionLValue(instr)) {
62
- this.visitPlace(instr.id, operand, state);
72
+
73
+ override visitScope(scope: ReactiveScopeBlock, state: Scopes): void {
74
+ for (const [_, declaration] of scope.scope.declarations) {
75
+ state.visit(declaration.identifier);
76
}
77
+ this.traverseScope(scope, state);
78
}
65
- override visitScope(scope: ReactiveScopeBlock, state: Scopes): void {
66
- /*
67
- * Intentionally bypass visitBlock() since scopes do not introduce a new
68
- * block scope
69
- */
70
- this.traverseBlock(scope.instructions, state);
79
+
80
+ override visitReactiveFunctionValue(
81
+ _id: InstructionId,
82
+ _dependencies: Place[],
83
+ _fn: ReactiveFunction,
84
+ _state: Scopes
85
+ ): void {
86
+ renameVariablesImpl(_fn, this, _state);
87
}
88
}
89
90
class Scopes {
75
- #nextId: number = 0;
76
- #seen: Set<IdentifierId> = new Set();
91
+ #seen: Map<IdentifierId, string> = new Map();
92
#stack: Array<Map<string, IdentifierId>> = [new Map()];
93
94
visit(identifier: Identifier): void {
80
- if (identifier.name === null || this.#seen.has(identifier.id)) {
95
+ const originalName = identifier.name;
96
+ if (originalName === null) {
97
+ return;
98
+ }
99
+ const mappedName = this.#seen.get(identifier.id);
100
+ if (mappedName !== undefined) {
101
+ identifier.name = mappedName;
102
return;
103
}
83
- this.#seen.add(identifier.id);
84
- let name = identifier.name;
104
+ let name = originalName;
105
+ let id = 0;
106
+ if (name.startsWith("#t")) {
107
+ name = `t${id++}`;
108
+ } else if (name.startsWith("#T")) {
109
+ name = `T${id++}`;
110
+ }
111
let previous = this.#lookup(name);
112
while (previous !== null) {
87
- name = `${identifier.name}$${this.#nextId++}`;
113
+ if (originalName.startsWith("#t")) {
114
+ name = `t${id++}`;
115
+ } else if (originalName.startsWith("#T")) {
116
+ name = `T${id++}`;
117
+ } else {
118
+ name = `${identifier.name}$${id++}`;
119
+ }
120
previous = this.#lookup(name);
121
}
122
identifier.name = name;
123
+ this.#seen.set(identifier.id, name);
124
this.#stack.at(-1)!.set(name, identifier.id);
125
}
126