Browse Source

Polish optimization of namespace lookups

* Explain better how optimization of namespace lookups are done in comments.
* Consider `hasReplacements` to be true if there exists any namespace replacements.
* Improve error messages slightly
better-aggressive
Oskar Segersvärd 9 years ago
parent
commit
0c83250fae
  1. 56
      src/Statement.js

56
src/Statement.js

@ -25,12 +25,19 @@ function isFunctionDeclaration ( node, parent ) {
if ( node.type === 'FunctionExpression' && parent.type === 'VariableDeclarator' ) return true; if ( node.type === 'FunctionExpression' && parent.type === 'VariableDeclarator' ) return true;
} }
// Extract the property access from a MemberExpression.
function property ( node ) {
return node.name ? `.${node.name}` : `[${node.value}]`;
}
// Recursively traverse the chain of member expressions from `node`,
// returning the access, e.g. `foo.bar[17]`
function chainedMemberExpression ( node ) { function chainedMemberExpression ( node ) {
if ( node.object.type === 'MemberExpression' ) { if ( node.object.type === 'MemberExpression' ) {
return chainedMemberExpression( node.object ) + '.' + node.property.name; return chainedMemberExpression( node.object ) + property( node.property );
} }
return node.object.name + '.' + node.property.name; return node.object.name + property( node.property );
} }
export default class Statement { export default class Statement {
@ -161,10 +168,10 @@ export default class Statement {
// /update expressions) need to be captured // /update expressions) need to be captured
let writeDepth = 0; let writeDepth = 0;
// Used to track // Used to track and optimize lookups through namespaces.
let topName; let localName; // The local name of the top-most imported namespace.
let currentMemberExpression = null; let topNode = null; // The top-node of the member expression.
let namespace = null; let namespace = null; // An instance of `Module`.
if ( !this.isImportDeclaration ) { if ( !this.isImportDeclaration ) {
walk( this.node, { walk( this.node, {
@ -184,13 +191,21 @@ export default class Statement {
if ( node._scope ) scope = scope.parent; if ( node._scope ) scope = scope.parent;
// Optimize namespace lookups, which manifest as MemberExpressions. // Optimize namespace lookups, which manifest as MemberExpressions.
if ( node.type === 'MemberExpression' && ( !currentMemberExpression || node.object === currentMemberExpression ) ) { if ( node.type === 'MemberExpression' && ( !topNode || node.object === topNode ) ) {
currentMemberExpression = node; // Ignore anything that doesn't begin with an identifier.
if ( !topNode && node.object.type !== 'Identifier') return;
topNode = node;
// If we don't already have a namespace,
// we aren't currently exploring any chain of member expressions.
if ( !namespace ) { if ( !namespace ) {
topName = node.object.name; localName = node.object.name;
const id = this.module.locals.lookup( topName );
// At first, we don't have a namespace, so we'll try to look one up.
const id = this.module.locals.lookup( localName );
// It only counts if it exists, is a module, and isn't external.
if ( !id || !id.isModule || id.isExternal ) return; if ( !id || !id.isModule || id.isExternal ) return;
namespace = id; namespace = id;
@ -225,16 +240,16 @@ export default class Statement {
namespace.mark(); namespace.mark();
namespace = null; namespace = null;
currentMemberExpression = null; topNode = null;
return; return;
} }
const id = namespace.exports.lookup( name ); const id = namespace.exports.lookup( name );
// If the namespace doesn't define the given name, // If the namespace doesn't export the given name,
// we can throw an error (even for nested namespaces). // we can throw an error (even for nested namespaces).
if ( !id ) { if ( !id ) {
throw new Error( `Module doesn't define "${name}"!` ); throw new Error( `Module "${namespace.id}" doesn't export "${name}"!` );
} }
// We can't resolve deeper. Replace the member chain. // We can't resolve deeper. Replace the member chain.
@ -244,18 +259,18 @@ export default class Statement {
} }
// FIXME: do this better // FIXME: do this better
// If we depend on this name... // If an earlier stage detected that we depend on this name...
if ( this.dependsOn[ topName ] ) { if ( this.dependsOn[ localName ] ) {
// ... decrement the count... // ... decrement the count...
if ( !--this.dependsOn[ topName ] ) { if ( !--this.dependsOn[ localName ] ) {
// ... and remove it if the count is 0. // ... and remove it if the count is 0.
delete this.dependsOn[ topName ]; delete this.dependsOn[ localName ];
} }
} }
this.namespaceReplacements.push( [ node, id ] ); this.namespaceReplacements.push( [ topNode, id ] );
namespace = null; namespace = null;
currentMemberExpression = null; topNode = null;
return; return;
} }
@ -486,7 +501,8 @@ export default class Statement {
topLevel = false; topLevel = false;
let newNames = blank(); let newNames = blank();
let hasReplacements; // Consider a scope to have replacements if there are any namespaceReplacements.
let hasReplacements = statement.namespaceReplacements.length > 0;
keys( names ).forEach( name => { keys( names ).forEach( name => {
if ( !scope.declarations[ name ] ) { if ( !scope.declarations[ name ] ) {

Loading…
Cancel
Save