From 497d026a7cfe9d6ea54c83aa562a633daa71222b Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 10 Sep 2023 17:23:04 +0800 Subject: [PATCH 1/7] Update aliased function name in ec-evaluator --- src/ec-evaluator/interpreter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ec-evaluator/interpreter.ts b/src/ec-evaluator/interpreter.ts index e12999731..cb35fdd22 100644 --- a/src/ec-evaluator/interpreter.ts +++ b/src/ec-evaluator/interpreter.ts @@ -195,7 +195,9 @@ function evaluateImports( } declareIdentifier(context, spec.local.name, node, environment) - defineVariable(context, spec.local.name, functions[spec.imported.name], true, node) + const importedObj = functions[spec.imported.name] + Object.defineProperty(importedObj, 'name', { value: spec.local.name }) + defineVariable(context, spec.local.name, importedObj, true, node) } } }) From 5541f1cd07bcbb9621cd6194317e8a915ff65f93 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 10 Sep 2023 17:24:43 +0800 Subject: [PATCH 2/7] Update aliased function name in transpiler --- src/transpiler/transpiler.ts | 38 ++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/transpiler/transpiler.ts b/src/transpiler/transpiler.ts index e14b9acc7..afa946574 100644 --- a/src/transpiler/transpiler.ts +++ b/src/transpiler/transpiler.ts @@ -109,21 +109,41 @@ export async function transformImportDeclarations( } const declNodes = nodes.flatMap(({ specifiers }) => - specifiers.map(spec => { + specifiers.flatMap(spec => { assert(spec.type === 'ImportSpecifier', `Expected ImportSpecifier, got ${spec.type}`) if (checkImports && !(spec.imported.name in docs!)) { throw new UndefinedImportError(spec.imported.name, moduleName, spec) } - // Convert each import specifier to its corresponding local variable declaration - return create.constantDeclaration( - spec.local.name, - create.memberExpression( - create.identifier(`${useThis ? 'this.' : ''}${namespaced}`), - spec.imported.name + return [ + // Convert each import specifier to its corresponding local variable declaration + create.constantDeclaration( + spec.local.name, + create.memberExpression( + create.identifier(`${useThis ? 'this.' : ''}${namespaced}`), + spec.imported.name + ) + ), + // Update the specifier's name property with the new name. This is so that calling + // `function.name` will return the aliased name. Equivalent to: + // Object.defineProperty(spec.imported.name, 'name', { value: spec.local.name }); + create.expressionStatement( + create.callExpression( + create.memberExpression(create.identifier('Object'), 'defineProperty'), + [ + create.memberExpression( + create.identifier(`${useThis ? 'this.' : ''}${namespaced}`), + spec.imported.name + ), + create.literal('name'), + create.objectExpression([ + create.property('value', create.literal(spec.local.name)) + ]) + ] + ) ) - ) + ] }) ) @@ -749,6 +769,7 @@ export function transpile( return transpileToFullJS(program, context, fullImportOptions, true) } else if (context.variant == Variant.NATIVE) { + console.log('hihihi2') const fullImportOptions = { checkImports: true, loadTabs: true, @@ -757,6 +778,7 @@ export function transpile( } return transpileToFullJS(program, context, fullImportOptions, false) } else { + console.log('hihihi') const fullImportOptions = { checkImports: true, loadTabs: true, From af8ece4bacf202cab74375594ddc54cab5ccce02 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 10 Sep 2023 17:27:30 +0800 Subject: [PATCH 3/7] Remove unnecessary non-null assertion --- src/runner/sourceRunner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runner/sourceRunner.ts b/src/runner/sourceRunner.ts index 4a5310475..b7027d3e2 100644 --- a/src/runner/sourceRunner.ts +++ b/src/runner/sourceRunner.ts @@ -284,7 +284,7 @@ export async function sourceRunner( return runNative(program, context, theOptions) } - return runInterpreter(program!, context, theOptions) + return runInterpreter(program, context, theOptions) } export async function sourceFilesRunner( From 4d1f21d396a517cfc0aae080662dbc5b98353fa8 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 10 Sep 2023 17:44:18 +0800 Subject: [PATCH 4/7] Remove test console.log statements --- src/transpiler/transpiler.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/transpiler/transpiler.ts b/src/transpiler/transpiler.ts index afa946574..8e363890e 100644 --- a/src/transpiler/transpiler.ts +++ b/src/transpiler/transpiler.ts @@ -769,7 +769,6 @@ export function transpile( return transpileToFullJS(program, context, fullImportOptions, true) } else if (context.variant == Variant.NATIVE) { - console.log('hihihi2') const fullImportOptions = { checkImports: true, loadTabs: true, @@ -778,7 +777,6 @@ export function transpile( } return transpileToFullJS(program, context, fullImportOptions, false) } else { - console.log('hihihi') const fullImportOptions = { checkImports: true, loadTabs: true, From 9feb406f6d720402c28bb13bd564b98d05afe952 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 10 Sep 2023 18:15:54 +0800 Subject: [PATCH 5/7] Fix typing * Replace typecase with `satisfies` operator * Update return type --- src/transpiler/transpiler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/transpiler/transpiler.ts b/src/transpiler/transpiler.ts index 8e363890e..3177eb194 100644 --- a/src/transpiler/transpiler.ts +++ b/src/transpiler/transpiler.ts @@ -63,7 +63,7 @@ export async function transformImportDeclarations( context?: Context, nativeId?: es.Identifier, useThis: boolean = false -): Promise<[string, es.VariableDeclaration[], es.Program['body']]> { +): Promise<[string, (es.VariableDeclaration | es.ExpressionStatement)[], es.Program['body']]> { const [importNodes, otherNodes] = partition(program.body, isImportDeclaration) if (importNodes.length === 0) return ['', [], otherNodes] @@ -147,11 +147,11 @@ export async function transformImportDeclarations( }) ) - return [moduleName, { text, nodes: declNodes, namespaced }] as [ + return [moduleName, { text, nodes: declNodes, namespaced }] satisfies [ string, { text: string - nodes: es.VariableDeclaration[] + nodes: (es.VariableDeclaration | es.ExpressionStatement)[] namespaced: string } ] From cb8226e2ec365250de859c77c13b92011f6ce4e5 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 10 Sep 2023 18:57:24 +0800 Subject: [PATCH 6/7] Fix tests --- src/infiniteLoops/instrument.ts | 4 +- src/infiniteLoops/runtime.ts | 1 + src/transpiler/__tests__/modules.ts | 60 +++++++++++++++++++++++++---- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/infiniteLoops/instrument.ts b/src/infiniteLoops/instrument.ts index 6b3af9749..8cc755e14 100644 --- a/src/infiniteLoops/instrument.ts +++ b/src/infiniteLoops/instrument.ts @@ -587,7 +587,9 @@ async function handleImports(programs: es.Program[]): Promise<[string, string[]] } ) program.body = (importsToAdd as es.Program['body']).concat(otherNodes) - const importedNames = importsToAdd.flatMap(node => + const importedNames = ( + importsToAdd.filter(node => node.type === 'VariableDeclaration') as es.VariableDeclaration[] + ).flatMap(node => node.declarations.map( decl => ((decl.init as es.MemberExpression).object as es.Identifier).name ) diff --git a/src/infiniteLoops/runtime.ts b/src/infiniteLoops/runtime.ts index 83783e251..1d767460a 100644 --- a/src/infiniteLoops/runtime.ts +++ b/src/infiniteLoops/runtime.ts @@ -268,6 +268,7 @@ function prepareBuiltins(oldBuiltins: Map) { } } newBuiltins.set('undefined', undefined) + newBuiltins.set('Object', Object) return newBuiltins } diff --git a/src/transpiler/__tests__/modules.ts b/src/transpiler/__tests__/modules.ts index 0bba8c24c..d40396974 100644 --- a/src/transpiler/__tests__/modules.ts +++ b/src/transpiler/__tests__/modules.ts @@ -11,12 +11,30 @@ import { transformImportDeclarations, transpile } from '../transpiler' jest.mock('../../modules/moduleLoaderAsync') jest.mock('../../modules/moduleLoader') +// One import declaration is transformed into two AST nodes +const IMPORT_DECLARATION_NODE_COUNT = 2 + +test('Transform import declarations to correct number of nodes', async () => { + const code = stripIndent` + import { foo } from "one_module"; + ` + const context = mockContext(Chapter.SOURCE_4) + const program = parse(code, context)! + const [, importNodes] = await transformImportDeclarations(program, new Set(), { + wrapSourceModules: true, + loadTabs: false, + checkImports: true + }) + expect(importNodes.length).toEqual(IMPORT_DECLARATION_NODE_COUNT) +}) + test('Transform import declarations into variable declarations', async () => { const code = stripIndent` import { foo } from "one_module"; import { bar } from "another_module"; foo(bar); ` + const identifiers = ['foo', 'bar'] as const const context = mockContext(Chapter.SOURCE_4) const program = parse(code, context)! const [, importNodes] = await transformImportDeclarations(program, new Set(), { @@ -25,11 +43,33 @@ test('Transform import declarations into variable declarations', async () => { checkImports: true }) - expect(importNodes[0].type).toBe('VariableDeclaration') - expect((importNodes[0].declarations[0].id as Identifier).name).toEqual('foo') + identifiers.forEach((ident, index) => { + const idx = IMPORT_DECLARATION_NODE_COUNT * index + expect(importNodes[idx].type).toBe('VariableDeclaration') + const node = importNodes[idx] as VariableDeclaration + expect((node.declarations[0].id as Identifier).name).toEqual(ident) + }) +}) + +test('Transform import declarations with correctly aliased names (expression statements)', async () => { + const code = stripIndent` + import { foo } from "one_module"; + import { bar as alias } from "another_module"; + foo(bar); + ` + const aliases = ['foo', 'alias'] as const + const context = mockContext(Chapter.SOURCE_4) + const program = parse(code, context)! + const [, importNodes] = await transformImportDeclarations(program, new Set(), { + wrapSourceModules: true, + loadTabs: false, + checkImports: true + }) - expect(importNodes[1].type).toBe('VariableDeclaration') - expect((importNodes[1].declarations[0].id as Identifier).name).toEqual('bar') + aliases.forEach((_, index) => { + const idx = IMPORT_DECLARATION_NODE_COUNT * index + 1 + expect(importNodes[idx].type).toBe('ExpressionStatement') + }) }) test('Transpiler accounts for user variable names when transforming import statements', async () => { @@ -54,7 +94,10 @@ test('Transpiler accounts for user variable names when transforming import state expect(importNodes[0].type).toBe('VariableDeclaration') expect( - ((importNodes[0].declarations[0].init as MemberExpression).object as Identifier).name + ( + ((importNodes[0] as VariableDeclaration).declarations[0].init as MemberExpression) + .object as Identifier + ).name ).toEqual('__MODULE__1') expect(varDecl0.type).toBe('VariableDeclaration') @@ -63,9 +106,12 @@ test('Transpiler accounts for user variable names when transforming import state expect(varDecl1.type).toBe('VariableDeclaration') expect(((varDecl1 as VariableDeclaration).declarations[0].init as Literal).value).toEqual('test1') - expect(importNodes[1].type).toBe('VariableDeclaration') + expect(importNodes[2].type).toBe('VariableDeclaration') expect( - ((importNodes[1].declarations[0].init as MemberExpression).object as Identifier).name + ( + ((importNodes[2] as VariableDeclaration).declarations[0].init as MemberExpression) + .object as Identifier + ).name ).toEqual('__MODULE__3') }) From aca7039d01f408844c5917a4c6f02305499f2fee Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 10 Sep 2023 18:59:30 +0800 Subject: [PATCH 7/7] Replace `satisfies` with `as` `satisfies` is unfortunately only introduced in TS 4.9. --- src/transpiler/transpiler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transpiler/transpiler.ts b/src/transpiler/transpiler.ts index 3177eb194..0fb6535d7 100644 --- a/src/transpiler/transpiler.ts +++ b/src/transpiler/transpiler.ts @@ -147,7 +147,7 @@ export async function transformImportDeclarations( }) ) - return [moduleName, { text, nodes: declNodes, namespaced }] satisfies [ + return [moduleName, { text, nodes: declNodes, namespaced }] as [ string, { text: string