Browse Source

more changes

value-tracking
Rich-Harris 8 years ago
parent
commit
821c3963ea
  1. 7
      src/Bundle.js
  2. 10
      src/Declaration.js
  3. 11
      src/ast/Node.js
  4. 20
      src/ast/nodes/ArrayExpression.js
  5. 2
      src/ast/nodes/CallExpression.js
  6. 2
      src/ast/nodes/ExportNamedDeclaration.js
  7. 58
      src/ast/nodes/FunctionDeclaration.js
  8. 2
      src/ast/nodes/Identifier.js
  9. 39
      src/ast/nodes/MemberExpression.js
  10. 36
      src/ast/nodes/ObjectExpression.js
  11. 4
      src/ast/nodes/VariableDeclarator.js
  12. 65
      src/ast/nodes/shared/FunctionValue.js
  13. 1
      src/ast/scopes/ModuleScope.js
  14. 2
      src/ast/scopes/Scope.js
  15. 11
      src/ast/values.js
  16. 4
      test/form/effect-in-for-of-loop-in-functions/_expected/amd.js
  17. 4
      test/form/effect-in-for-of-loop-in-functions/_expected/cjs.js
  18. 4
      test/form/effect-in-for-of-loop-in-functions/_expected/es.js
  19. 4
      test/form/effect-in-for-of-loop-in-functions/_expected/iife.js
  20. 4
      test/form/effect-in-for-of-loop-in-functions/_expected/umd.js
  21. 8
      test/form/effect-in-for-of-loop-in-functions/main.js
  22. 1
      test/form/namespace-optimization/_config.js

7
src/Bundle.js

@ -151,7 +151,14 @@ export default class Bundle {
const declaration = entryModule.traceExport( name );
declaration.exportName = name;
if ( !declaration.markChildrenIndiscriminately ) {
console.log( `declaration`, declaration )
}
// TODO do we need both of these?
declaration.activate();
declaration.markChildrenIndiscriminately();
if ( declaration.isNamespace ) {
declaration.needsNamespaceBlock = true;

10
src/Declaration.js

@ -79,6 +79,12 @@ export class SyntheticNamespaceDeclaration {
return this.name;
}
markChildrenIndiscriminately () {
forOwn( this.originals, original => {
original.markChildrenIndiscriminately();
});
}
renderBlock ( es, legacy, indentString ) {
const members = keys( this.originals ).map( name => {
const original = this.originals[ name ];
@ -142,6 +148,10 @@ export class ExternalDeclaration {
return es ? this.safeName : `${this.module.name}.${this.name}`;
}
markChildrenIndiscriminately () {
// noop
}
markReturnStatements () {
// noop
}

11
src/ast/Node.js

@ -86,7 +86,7 @@ export default class Node {
markChildren () {
function visit ( node ) {
node.mark();
node.mark(); // TODO should that be markChildren?
if ( node.type === 'BlockStatement' ) return;
node.eachChild( visit );
@ -95,6 +95,15 @@ export default class Node {
visit( this );
}
markChildrenIndiscriminately () {
function visit ( node ) {
node.mark();
node.eachChild( visit );
}
visit( this );
}
render ( code, es ) {
this.eachChild( child => child.render( code, es ) );
}

20
src/ast/nodes/ArrayExpression.js

@ -1,8 +1,26 @@
import Node from '../Node.js';
import { ARRAY } from '../values.js';
import { ARRAY, unknown } from '../values.js';
class ArrayValue {
constructor ( node ) {
this.node = node;
this.values = node.elements.map( element => element.run() );
}
getProperty ( name ) {
return unknown; // TODO return values, or prototype methods etc
}
setProperty ( name, value ) {
// TODO
}
}
export default class ArrayExpression extends Node {
gatherPossibleValues ( values ) {
values.add( ARRAY );
}
run () {
return new ArrayValue( this );
}
}

2
src/ast/nodes/CallExpression.js

@ -69,6 +69,6 @@ export default class CallExpression extends Node {
throw new Error( `${this.callee} does not have call method` );
}
return this.callee.call( this.arguments );
return this.callee.call( null, this.arguments );
}
}

2
src/ast/nodes/ExportNamedDeclaration.js

@ -6,10 +6,12 @@ export default class ExportNamedDeclaration extends Node {
this.isExportDeclaration = true;
if ( this.declaration ) this.declaration.initialise( scope );
if ( this.specifiers ) this.specifiers.forEach( specifier => specifier.initialise( scope ) );
}
bind () {
if ( this.declaration ) this.declaration.bind();
if ( this.specifiers ) this.specifiers.forEach( specifier => specifier.bind() );
}
render ( code, es ) {

58
src/ast/nodes/FunctionDeclaration.js

@ -1,16 +1,6 @@
import Node from '../Node.js';
import { unknown } from '../values.js';
class AsyncFunctionReturnValue {
constructor ( value ) {
this.value = value;
}
getProperty () {
// TODO express promise semantics somehow?
return unknown;
}
}
import FunctionValue from './shared/FunctionValue.js';
export default class FunctionDeclaration extends Node {
activate () {
@ -30,38 +20,6 @@ export default class FunctionDeclaration extends Node {
this.body.bind( scope );
}
call ( context, args ) {
if ( this.isCalling ) return; // recursive functions
this.isCalling = true;
let returnValue;
this.body.scope.initialise();
args.forEach( ( arg, i ) => {
const param = this.params[i];
if ( !param ) return;
if ( param.type !== 'Identifier' ) {
throw new Error( 'TODO desctructuring' );
}
this.body.scope.setValue( param.name, arg );
});
for ( const node of this.body.body ) {
node.run();
if ( node.type === 'ReturnStatement' ) {
returnValue = node.argument ? node.argument.run() : undefined; // TODO represent undefined
break;
}
}
this.isCalling = false;
return this.async ? new AsyncFunctionReturnValue( returnValue ) : returnValue;
}
gatherPossibleValues ( values ) {
values.add( this );
}
@ -70,6 +28,10 @@ export default class FunctionDeclaration extends Node {
return this.name;
}
getProperty () {
return unknown; // TODO handle added properties, plus things like call, apply, length
}
hasEffects () {
return false;
}
@ -87,10 +49,6 @@ export default class FunctionDeclaration extends Node {
this.body.initialise();
}
markReturnStatements () {
this.returnStatements.forEach( statement => statement.mark() );
}
render ( code, es ) {
if ( !this.module.bundle.treeshake || this.activated ) {
super.render( code, es );
@ -99,9 +57,7 @@ export default class FunctionDeclaration extends Node {
}
}
run ( scope ) {
if ( this.parent.type === 'ExportDefaultDeclaration' ) {
super.run( scope );
}
run () {
return new FunctionValue( this );
}
}

2
src/ast/nodes/Identifier.js

@ -30,7 +30,7 @@ export default class Identifier extends Node {
}
}
call ( args ) {
call ( context, args ) {
const callee = this.scope.getValue( this.name );
if ( !callee.call ) {
throw new Error( `${callee} does not have call method (${this})` );

39
src/ast/nodes/MemberExpression.js

@ -30,7 +30,6 @@ export default class MemberExpression extends Node {
bind ( scope ) {
// if this resolves to a namespaced declaration, prepare
// to replace it
// TODO this code is a bit inefficient
const keypath = new Keypath( this );
if ( !keypath.computed && keypath.root.type === 'Identifier' ) {
@ -72,10 +71,19 @@ export default class MemberExpression extends Node {
}
}
call ( args ) {
call ( context, args ) {
if ( this.declaration ) {
return this.declaration.call( undefined, args );
}
const objectValue = this.object.run();
const propValue = this.computed ? this.property.run() : this.property.name;
if ( !objectValue.getProperty ) {
console.log( objectValue );
throw new Error( `${objectValue} does not have getProperty method` );
}
const value = objectValue.getProperty( propValue );
return value.call( objectValue, args );
@ -86,12 +94,21 @@ export default class MemberExpression extends Node {
}
mark () {
if ( this.declaration ) {
this.declaration.activate();
}
this.object.mark();
super.mark();
}
markReturnStatements () {
// TODO???
if ( this.declaration ) {
this.declaration.markReturnStatements();
} else {
// TODO this seems wrong. nothing should ever 'run' twice
this.run().markReturnStatements();
}
}
render ( code, es ) {
@ -108,15 +125,25 @@ export default class MemberExpression extends Node {
}
run () {
if ( this.declaration ) this.declaration.activate();
super.run();
if ( this.declaration ) {
return this.declaration;
}
const objectValue = this.object.run();
const propValue = this.computed ? this.property.run() : this.property.name;
return objectValue.getProperty( propValue );
}
setValue ( value ) {
const objectValue = this.object.run();
const propValue = this.computed ? this.property.run() : this.property.name;
if ( !objectValue.setProperty ) {
console.log( objectValue );
throw new Error( `${objectValue} does not have setProperty method` );
}
objectValue.setProperty( propValue, value );
}
}

36
src/ast/nodes/ObjectExpression.js

@ -1,5 +1,35 @@
import Node from '../Node.js';
import { OBJECT } from '../values.js';
import { blank } from '../../utils/object.js';
import { OBJECT, unknown } from '../values.js';
class ObjectValue {
constructor ( node ) {
this.node = node;
this.values = blank();
node.properties.forEach( prop => {
let key = prop.key;
if ( prop.computed ) {
key = key.run();
if ( key === unknown ) {
throw new Error( 'TODO unknown computed props' );
}
}
this.values[ key ] = prop.value.run();
});
}
getProperty ( name ) {
return this.values[ name ];
}
setProperty ( name, value ) {
this.values[ name ] = value;
}
}
export default class ObjectExpression extends Node {
gatherPossibleValues ( values ) {
@ -15,4 +45,8 @@ export default class ObjectExpression extends Node {
}
}
}
run () {
return new ObjectValue( this );
}
}

4
src/ast/nodes/VariableDeclarator.js

@ -38,6 +38,10 @@ class DeclaratorProxy {
return `exports.${this.exportName}`;
}
markChildrenIndiscriminately () {
this.declarator.markChildrenIndiscriminately();
}
toString () {
return this.name;
}

65
src/ast/nodes/shared/FunctionValue.js

@ -0,0 +1,65 @@
import { blank } from '../../../utils/object.js';
import { unknown } from '../../values.js';
class AsyncFunctionReturnValue {
constructor ( value ) {
this.value = value;
}
getProperty () {
// TODO express promise semantics somehow?
return unknown;
}
}
export default class FunctionValue {
constructor ( node ) {
this.node = node;
this.values = blank();
}
call ( context, args ) {
if ( this.node.isCalling ) return; // recursive functions
this.node.isCalling = true;
let returnValue;
this.node.body.scope.initialise();
args.forEach( ( arg, i ) => {
const param = this.node.params[i];
if ( !param ) return;
if ( param.type !== 'Identifier' ) {
throw new Error( 'TODO desctructuring' );
}
this.node.body.scope.setValue( param.name, arg );
});
for ( const node of this.node.body.body ) {
node.run();
if ( node.type === 'ReturnStatement' ) {
returnValue = node.argument ? node.argument.run() : undefined; // TODO represent undefined
break;
}
}
this.node.isCalling = false;
return this.node.async ? new AsyncFunctionReturnValue( returnValue ) : returnValue;
}
getProperty ( name ) {
return this.values[ name ]; // TODO .length etc
}
markReturnStatements () {
this.node.returnStatements.forEach( statement => statement.mark() );
}
setProperty ( name, value ) {
// TODO unknown names
this.values[ name ] = value;
}
}

1
src/ast/scopes/ModuleScope.js

@ -65,6 +65,7 @@ export default class ModuleScope extends Scope {
const imported = this.module.imports[ name ];
if ( imported ) {
if ( imported.module.isExternal ) return unknown;
if ( imported.name === '*' ) return imported.module.namespace();
const exported = imported.module.exports[ imported.name ];
const exportedName = exported.localName === 'default' && exported.identifier ? exported.identifier : exported.localName; // TODO this is a mess

2
src/ast/scopes/Scope.js

@ -127,7 +127,7 @@ export default class Scope {
} else if ( declaration.type === 'ClassDeclaration' ) {
this.values[ name ] = TDZ_VIOLATION;
} else if ( declaration.type === 'FunctionDeclaration' ) {
this.values[ name ] = declaration;
this.values[ name ] = declaration.run();
} else {
console.log( declaration )
throw new Error( 'well this is odd' );

11
src/ast/values.js

@ -12,20 +12,29 @@ export class TdzViolation {
}
export class Undefined {
getProperty () {
return unknown; // TODO warn here?
}
}
export class UnknownValue {
call ( context, args ) {
args.forEach( arg => {
// TODO call functions (and children of objects...) with unknown arguments
arg.markChildrenIndiscriminately();
});
return unknown;
}
getValue () {
return unknown;
}
getProperty () {
return unknown;
}
markReturnStatements () {
// noop?
}

4
test/form/effect-in-for-of-loop-in-functions/_expected/amd.js

@ -3,7 +3,7 @@ define(function () { 'use strict';
const items = [{}, {}, {}];
function a () {
for ( const item of items.children ) {
for ( const item of items ) {
item.foo = 'a';
}
}
@ -12,7 +12,7 @@ define(function () { 'use strict';
function c () {
let item;
for ( item of items.children ) {
for ( item of items ) {
item.bar = 'c';
}
}

4
test/form/effect-in-for-of-loop-in-functions/_expected/cjs.js

@ -3,7 +3,7 @@
const items = [{}, {}, {}];
function a () {
for ( const item of items.children ) {
for ( const item of items ) {
item.foo = 'a';
}
}
@ -12,7 +12,7 @@ a();
function c () {
let item;
for ( item of items.children ) {
for ( item of items ) {
item.bar = 'c';
}
}

4
test/form/effect-in-for-of-loop-in-functions/_expected/es.js

@ -1,7 +1,7 @@
const items = [{}, {}, {}];
function a () {
for ( const item of items.children ) {
for ( const item of items ) {
item.foo = 'a';
}
}
@ -10,7 +10,7 @@ a();
function c () {
let item;
for ( item of items.children ) {
for ( item of items ) {
item.bar = 'c';
}
}

4
test/form/effect-in-for-of-loop-in-functions/_expected/iife.js

@ -4,7 +4,7 @@
const items = [{}, {}, {}];
function a () {
for ( const item of items.children ) {
for ( const item of items ) {
item.foo = 'a';
}
}
@ -13,7 +13,7 @@
function c () {
let item;
for ( item of items.children ) {
for ( item of items ) {
item.bar = 'c';
}
}

4
test/form/effect-in-for-of-loop-in-functions/_expected/umd.js

@ -7,7 +7,7 @@
const items = [{}, {}, {}];
function a () {
for ( const item of items.children ) {
for ( const item of items ) {
item.foo = 'a';
}
}
@ -16,7 +16,7 @@
function c () {
let item;
for ( item of items.children ) {
for ( item of items ) {
item.bar = 'c';
}
}

8
test/form/effect-in-for-of-loop-in-functions/main.js

@ -1,7 +1,7 @@
const items = [{}, {}, {}];
function a () {
for ( const item of items.children ) {
for ( const item of items ) {
item.foo = 'a';
}
}
@ -9,7 +9,7 @@ function a () {
a();
function b () {
for ( const item of items.children ) {
for ( const item of items ) {
// do nothing
}
}
@ -18,7 +18,7 @@ b();
function c () {
let item;
for ( item of items.children ) {
for ( item of items ) {
item.bar = 'c';
}
}
@ -27,7 +27,7 @@ c();
function d () {
let item;
for ( item of items.children ) {
for ( item of items ) {
// do nothing
}
}

1
test/form/namespace-optimization/_config.js

@ -1,3 +1,4 @@
module.exports = {
solo: true,
description: 'it does static lookup optimization of internal namespaces'
};

Loading…
Cancel
Save