Skip to content

Commit c6de056

Browse files
fix: #1714 steps appear as if capturing happens
1 parent 953a57c commit c6de056

File tree

8 files changed

+990
-436
lines changed

8 files changed

+990
-436
lines changed

src/tracer/__tests__/__snapshots__/tracer_full.ts.snap

Lines changed: 886 additions & 396 deletions
Large diffs are not rendered by default.

src/tracer/__tests__/tracer_debug.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,18 +93,17 @@ test('function calling', () => {
9393
*/
9494
test('general', () => {
9595
const code = `
96-
const f = x => {
97-
const x_1 = 1;
98-
return x_1 => x_2 + g();
99-
};
100-
const g = () => x + x_1;
101-
const x_2 = 0;
102-
const x_1 = 2;
103-
const x = 1;
104-
f(1)(1);
96+
function h(f, x) {
97+
function h(g, x) {
98+
return x <= 1 ? 1 : 3 * g(f, x - 1);
99+
}
100+
return x <= 1 ? 1 : 2 * f(h, x - 1);
101+
}
102+
h(h, 5);
103+
105104
`
106105
const program = parse(code, { ecmaVersion: 10 })!
107106
const steps = getSteps(convert(program), { stepLimit: 200 })
108107
const output = steps.map(stringifyWithExplanation)
109-
console.log(output)
108+
console.log(output.join('\n\n'))
110109
})

src/tracer/__tests__/tracer_full.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,20 @@ test("Church numerals", async () => {
13391339
`
13401340
const steps = await codify(acornParser(code))
13411341
expect(steps.join('\n')).toMatchSnapshot()
1342-
expect(steps.join('\n')).toMatchSnapshot()
13431342
expect(steps[steps.length - 1]).toEqual('true;\n[noMarker] Evaluation complete\n')
1343+
});
1344+
1345+
test("steps appear as if capturing happens #1714", async () => {
1346+
const code = `
1347+
function h(f, x) {
1348+
function h(g, x) {
1349+
return x <= 1 ? 1 : 3 * g(f, x - 1);
1350+
}
1351+
return x <= 1 ? 1 : 2 * f(h, x - 1);
1352+
}
1353+
h(h, 5);
1354+
`
1355+
const steps = await codify(acornParser(code))
1356+
expect(steps.join('\n')).toMatchSnapshot()
1357+
expect(steps[steps.length - 1]).toEqual('36;\n[noMarker] Evaluation complete\n')
13441358
});

src/tracer/nodes/Expression/ArrowFunctionExpression.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { StepperExpression, StepperPattern } from '..'
44
import { convert } from '../../generator'
55
import { getFreshName } from '../../utils'
66
import { StepperBlockStatement } from '../Statement/BlockStatement'
7+
import { StepperBlockExpression } from './BlockExpression'
78

89
export class StepperArrowFunctionExpression implements ArrowFunctionExpression, StepperBaseNode {
910
type: 'ArrowFunctionExpression'
@@ -32,7 +33,6 @@ export class StepperArrowFunctionExpression implements ArrowFunctionExpression,
3233
) {
3334
this.type = 'ArrowFunctionExpression'
3435
this.params = params
35-
this.body = body
3636
this.expression = expression
3737
this.generator = generator
3838
this.async = async
@@ -41,6 +41,23 @@ export class StepperArrowFunctionExpression implements ArrowFunctionExpression,
4141
this.trailingComments = trailingComments
4242
this.loc = loc
4343
this.range = range
44+
45+
// check whether the declarations will not collide with this.name
46+
// @ts-expect-error By nature of estree, block expression is a statement.
47+
if (body.type === "BlockStatement" && this.name !== undefined) {
48+
const repeatedNames = (body as StepperBlockExpression).scanAllDeclarationNames()
49+
.filter(name => name === this.name);
50+
const newNames = getFreshName([this.name], repeatedNames);
51+
let currentBlockExpression: StepperBlockExpression = body;
52+
for (var index in newNames) {
53+
currentBlockExpression = currentBlockExpression
54+
.rename(repeatedNames[index], newNames[index]) as StepperBlockExpression
55+
}
56+
// @ts-expect-error By nature of estree, block expression is a statement.
57+
this.body = currentBlockExpression;
58+
} else {
59+
this.body = body;
60+
}
4461
}
4562

4663
static create(node: ArrowFunctionExpression) {

src/tracer/nodes/Expression/BlockExpression.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { StepperBaseNode } from '../../interface'
22
import { StepperExpression, StepperPattern, undefinedNode } from '..'
33
import { convert } from '../../generator'
44
import { redex } from '../..'
5-
import { StepperVariableDeclaration, StepperVariableDeclarator } from '../Statement/VariableDeclaration'
5+
import { StepperVariableDeclaration } from '../Statement/VariableDeclaration'
66
import { assignMuTerms, getFreshName } from '../../utils'
77
import { StepperReturnStatement } from '../Statement/ReturnStatement'
88
import { StepperStatement } from '../Statement'
@@ -168,9 +168,14 @@ export class StepperBlockExpression implements StepperBaseNode {
168168

169169
scanAllDeclarationNames(): string[] {
170170
return this.body
171-
.filter(ast => ast.type === 'VariableDeclaration')
172-
.flatMap((ast: StepperVariableDeclaration) => ast.declarations)
173-
.map((ast: StepperVariableDeclarator) => ast.id.name);
171+
.filter(ast => ast.type === 'VariableDeclaration' || ast.type === 'FunctionDeclaration')
172+
.flatMap((ast: StepperVariableDeclaration | StepperFunctionDeclaration) => {
173+
if (ast.type === 'VariableDeclaration') {
174+
return ast.declarations.map(ast => ast.id.name);
175+
} else { // Function Declaration
176+
return [(ast as StepperFunctionDeclaration).id.name];
177+
}
178+
})
174179
}
175180

176181
freeNames(): string[] {

src/tracer/nodes/Program.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import { StepperStatement } from './Statement'
55
import { StepperExpression, StepperPattern, undefinedNode } from '.'
66

77
import {
8-
StepperVariableDeclaration,
9-
StepperVariableDeclarator
8+
StepperVariableDeclaration
109
} from './Statement/VariableDeclaration'
1110
import { redex } from '..'
1211
import { assignMuTerms } from '../utils'
@@ -156,9 +155,14 @@ export class StepperProgram implements Program, StepperBaseNode {
156155

157156
scanAllDeclarationNames(): string[] {
158157
return this.body
159-
.filter(ast => ast.type === 'VariableDeclaration')
160-
.flatMap((ast: StepperVariableDeclaration) => ast.declarations)
161-
.map((ast: StepperVariableDeclarator) => ast.id.name)
158+
.filter(ast => ast.type === 'VariableDeclaration' || ast.type === 'FunctionDeclaration')
159+
.flatMap((ast: StepperVariableDeclaration | StepperFunctionDeclaration) => {
160+
if (ast.type === 'VariableDeclaration') {
161+
return ast.declarations.map(ast => ast.id.name);
162+
} else { // Function Declaration
163+
return [(ast as StepperFunctionDeclaration).id.name];
164+
}
165+
})
162166
}
163167

164168
freeNames(): string[] {

src/tracer/nodes/Statement/BlockStatement.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { StepperExpression, StepperPattern, undefinedNode } from '..'
44
import { StepperStatement } from '.'
55
import { convert } from '../../generator'
66
import { redex } from '../..'
7-
import { StepperVariableDeclaration, StepperVariableDeclarator } from './VariableDeclaration'
7+
import { StepperVariableDeclaration } from './VariableDeclaration'
88
import { assignMuTerms, getFreshName } from '../../utils'
99
import { StepperFunctionDeclaration } from './FunctionDeclaration'
1010
import { StepperReturnStatement } from './ReturnStatement'
@@ -200,9 +200,14 @@ export class StepperBlockStatement implements BlockStatement, StepperBaseNode {
200200

201201
scanAllDeclarationNames(): string[] {
202202
return this.body
203-
.filter(ast => ast.type === 'VariableDeclaration')
204-
.flatMap((ast: StepperVariableDeclaration) => ast.declarations)
205-
.map((ast: StepperVariableDeclarator) => ast.id.name)
203+
.filter(ast => ast.type === 'VariableDeclaration' || ast.type === 'FunctionDeclaration')
204+
.flatMap((ast: StepperVariableDeclaration | StepperFunctionDeclaration) => {
205+
if (ast.type === 'VariableDeclaration') {
206+
return ast.declarations.map(ast => ast.id.name);
207+
} else { // Function Declaration
208+
return [(ast as StepperFunctionDeclaration).id.name];
209+
}
210+
})
206211
}
207212

208213
freeNames(): string[] {

src/tracer/nodes/Statement/FunctionDeclaration.ts

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { convert } from '../../generator'
77
import { redex } from '../..'
88
import { StepperArrowFunctionExpression } from '../Expression/ArrowFunctionExpression'
99
import { getFreshName } from '../../utils'
10+
import { StepperVariableDeclaration } from './VariableDeclaration'
1011

1112
export class StepperFunctionDeclaration implements FunctionDeclaration, StepperBaseNode {
1213
type: 'FunctionDeclaration'
@@ -33,16 +34,23 @@ export class StepperFunctionDeclaration implements FunctionDeclaration, StepperB
3334
) {
3435
this.type = 'FunctionDeclaration'
3536
this.id = id
36-
this.body = body
3737
this.params = params
3838
this.generator = generator
3939
this.async = async
4040
this.leadingComments = leadingComments
4141
this.trailingComments = trailingComments
4242
this.loc = loc
4343
this.range = range
44-
}
4544

45+
46+
const repeatedNames = body.scanAllDeclarationNames().filter(name => name === this.id.name);
47+
const newNames = getFreshName([this.id.name], repeatedNames)
48+
let currentBlockStatement = body
49+
for (var index in newNames) {
50+
currentBlockStatement = currentBlockStatement.rename(repeatedNames[index], newNames[index])
51+
}
52+
this.body = currentBlockStatement;
53+
}
4654
static create(node: FunctionDeclaration) {
4755
return new StepperFunctionDeclaration(
4856
convert(node.id) as StepperIdentifier,
@@ -94,25 +102,37 @@ export class StepperFunctionDeclaration implements FunctionDeclaration, StepperB
94102
scanAllDeclarationNames(): string[] {
95103
const paramNames = this.params.map(param => param.name)
96104
const bodyDeclarations = this.body.body
97-
.filter(stmt => stmt.type === 'VariableDeclaration')
98-
.flatMap(decl => (decl as any).declarations.map((d: any) => d.id.name))
99-
105+
.filter(ast => ast.type === 'VariableDeclaration' || ast.type === 'FunctionDeclaration')
106+
.flatMap((ast: StepperVariableDeclaration | StepperFunctionDeclaration) => {
107+
if (ast.type === 'VariableDeclaration') {
108+
return ast.declarations.map(ast => ast.id.name)
109+
} else {
110+
// Function Declaration
111+
return [(ast as StepperFunctionDeclaration).id.name]
112+
}
113+
})
100114
return [...paramNames, ...bodyDeclarations]
101115
}
102116

103-
substitute(id: StepperPattern, value: StepperExpression, upperBoundName?: string[]): StepperBaseNode {
117+
substitute(
118+
id: StepperPattern,
119+
value: StepperExpression,
120+
upperBoundName?: string[]
121+
): StepperBaseNode {
104122
const valueFreeNames = value.freeNames()
105123
const scopeNames = this.scanAllDeclarationNames()
106124
const repeatedNames = valueFreeNames.filter(name => scopeNames.includes(name))
107125

108-
var currentFunction: StepperFunctionDeclaration = this;
109-
let protectedNamesSet = new Set([this.allNames(), upperBoundName ?? []].flat());
110-
repeatedNames.forEach(name => protectedNamesSet.delete(name));
111-
const protectedNames = Array.from(protectedNamesSet);
112-
const newNames = getFreshName(repeatedNames, protectedNames);
126+
var currentFunction: StepperFunctionDeclaration = this
127+
let protectedNamesSet = new Set([this.allNames(), upperBoundName ?? []].flat())
128+
repeatedNames.forEach(name => protectedNamesSet.delete(name))
129+
const protectedNames = Array.from(protectedNamesSet)
130+
const newNames = getFreshName(repeatedNames, protectedNames)
113131
for (var index in newNames) {
114-
currentFunction = currentFunction
115-
.rename(repeatedNames[index], newNames[index]) as StepperFunctionDeclaration
132+
currentFunction = currentFunction.rename(
133+
repeatedNames[index],
134+
newNames[index]
135+
) as StepperFunctionDeclaration
116136
}
117137

118138
if (currentFunction.scanAllDeclarationNames().includes(id.name)) {
@@ -141,12 +161,12 @@ export class StepperFunctionDeclaration implements FunctionDeclaration, StepperB
141161
const paramNames = this.params
142162
.filter(param => param.type === 'Identifier')
143163
.map(param => param.name)
144-
return Array.from(new Set([paramNames, this.body.allNames()].flat()));
164+
return Array.from(new Set([paramNames, this.body.allNames()].flat()))
145165
}
146166

147167
rename(before: string, after: string): StepperFunctionDeclaration {
148168
return new StepperFunctionDeclaration(
149-
this.id,
169+
this.id.rename(before, after),
150170
this.body.rename(before, after) as unknown as StepperBlockStatement,
151171
this.params.map(param => param.rename(before, after))
152172
)

0 commit comments

Comments
 (0)