flesh out type inferrence some more, rename some of the NodePath methods to be less ambiguous, remove dead Binding type methods

This commit is contained in:
Sebastian McKenzie 2015-06-01 14:01:14 +01:00
parent de652dc747
commit 7d2c6525d8
7 changed files with 130 additions and 76 deletions

View File

@ -5,7 +5,7 @@ export var metadata = {
};
export function ForOfStatement(node, parent, scope, file) {
if (this.get("right").isTypeGeneric("Array")) {
if (this.get("right").isTypeAnnotationGeneric("Array")) {
return _ForOfStatementArray.call(this, node, scope, file);
}
}

View File

@ -11,10 +11,10 @@ export function insertBefore(nodes) {
if (this.parentPath.isExpressionStatement() || this.parentPath.isLabeledStatement()) {
return this.parentPath.insertBefore(nodes);
} else if (this.isType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) {
} else if (this.isNodeType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) {
if (this.node) nodes.push(this.node);
this.replaceExpressionWithStatements(nodes);
} else if (this.isType("Statement") || !this.type) {
} else if (this.isNodeType("Statement") || !this.type) {
this._maybePopFromStatements(nodes);
if (Array.isArray(this.container)) {
return this._containerInsertBefore(nodes);
@ -82,14 +82,14 @@ export function insertAfter(nodes) {
if (this.parentPath.isExpressionStatement() || this.parentPath.isLabeledStatement()) {
return this.parentPath.insertAfter(nodes);
} else if (this.isType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) {
} else if (this.isNodeType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) {
if (this.node) {
var temp = this.scope.generateDeclaredUidIdentifier();
nodes.unshift(t.expressionStatement(t.assignmentExpression("=", temp, this.node)));
nodes.push(t.expressionStatement(temp));
}
this.replaceExpressionWithStatements(nodes);
} else if (this.isType("Statement") || !this.type) {
} else if (this.isNodeType("Statement") || !this.type) {
this._maybePopFromStatements(nodes);
if (Array.isArray(this.container)) {
return this._containerInsertAfter(nodes);

View File

@ -110,12 +110,12 @@ export function replaceWith(replacement, whateverAllowed) {
}
// replacing a statement with an expression so wrap it in an expression statement
if (this.isType("Statement") && t.isExpression(replacement) && !this.canHaveVariableDeclarationOrExpression()) {
if (this.isNodeType("Statement") && t.isExpression(replacement) && !this.canHaveVariableDeclarationOrExpression()) {
replacement = t.expressionStatement(replacement);
}
// replacing an expression with a statement so let's explode it
if (this.isType("Expression") && t.isStatement(replacement)) {
if (this.isNodeType("Expression") && t.isStatement(replacement)) {
return this.replaceExpressionWithStatements([replacement]);
}

View File

@ -3,11 +3,22 @@ import isNumber from "lodash/lang/isNumber";
import isString from "lodash/lang/isString";
import * as t from "../../types";
const BOOLEAN_BINARY_OPERATORS = ["==", "===", "!=", "!==", ">", "<", ">=", "<="];
const NUMBER_BINARY_OPERATORS = ["-", "/", "*", "**", "&", "|"];
/**
* Description
*/
export function getTypeAnnotation(): {
export function getTypeAnnotation() {
return this.getTypeAnnotationInfo().annotation;
}
/**
* Description
*/
export function getTypeAnnotationInfo(): {
inferred: boolean;
annotation: ?Object;
} {
@ -24,23 +35,23 @@ export function getTypeAnnotation(): {
if (!type) {
info.inferred = true;
type = this.inferType(this);
type = this.inferTypeAnnotation();
}
if (type) {
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
info.annotation = type;
}
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
info.annotation = type;
return info;
}
/**
* Description
* Resolves `NodePath` pointers until it resolves to an absolute path. ie. a data type instead of a
* call etc. If a data type can't be resolved then the last path we were at is returned.
*/
export function resolve(resolved?): ?NodePath {
// detect infinite recursion
// todo: possibly have a max length on this just to be safe
if (resolved && resolved.indexOf(this) >= 0) return;
// we store all the paths we've "resolved" in this array to prevent infinite recursion
@ -53,9 +64,15 @@ export function resolve(resolved?): ?NodePath {
} else {
// otherwise it's a request for a pattern and that's a bit more tricky
}
} else if (this.isIdentifier()) {
} else if (this.isReferencedIdentifier()) {
var binding = this.scope.getBinding(this.node.name);
if (!binding || !binding.constant) return;
if (!binding) return;
// reassigned so we can't really resolve it
if (!binding.constant) return;
// todo - lookup module in dependency graph
if (binding.kind === "module") return;
if (binding.path === this) {
return this;
@ -94,43 +111,121 @@ export function resolve(resolved?): ?NodePath {
}
/**
* Description
* Infer the type of the current `NodePath`.
*
* NOTE: This is not cached. Use `getTypeAnnotation()` which is cached.
*/
export function inferType(path: NodePath): ?Object {
path = path.resolve();
export function inferTypeAnnotation(force) {
return this._inferTypeAnnotation(force) || t.anyTypeAnnotation();
}
export function _inferTypeAnnotation(force?: boolean): ?Object {
var path = this.resolve();
if (!path) return;
if (path.isType("RestElement") || path.parentPath.isType("RestElement") || path.isType("ArrayExpression")) {
if (path.isNodeType("RestElement") || path.parentPath.isNodeType("RestElement") || path.isNodeType("ArrayExpression")) {
return t.genericTypeAnnotation(t.identifier("Array"));
}
if (path.parentPath.isType("TypeCastExpression")) {
if (path.parentPath.isNodeType("TypeCastExpression")) {
return path.parentPath.node.typeAnnotation;
}
if (path.isType("TypeCastExpression")) {
if (path.parentPath.isNodeType("ReturnStatement") && !force) {
return path.parentPath.inferTypeAnnotation();
}
if (path.isNodeType("ReturnStatement")) {
var funcPath = this.findParent((node, path) => path.isFunction());
if (!funcPath) return;
var returnType = funcPath.node.returnType;
if (returnType) {
return returnType;
} else {
return this.get("argument").inferTypeAnnotation(true);
}
}
if (path.isNodeType("NewExpression")) {
// todo
}
if (path.isNodeType("Identifier") && path.node.name === "undefined") {
return t.voidTypeAnnotation();
}
if (path.isNodeType("TypeCastExpression")) {
return path.node.typeAnnotation;
}
if (path.isType("ObjectExpression")) {
if (path.isNodeType("ObjectExpression")) {
return t.genericTypeAnnotation(t.identifier("Object"));
}
if (path.isType("Function")) {
if (path.isNodeType("Function")) {
return t.identifier("Function");
}
if (path.isType("Literal")) {
var value = path.node.value;
if (isString(value)) return t.stringTypeAnnotation();
if (isNumber(value)) return t.numberTypeAnnotation();
if (isBoolean(value)) return t.booleanTypeAnnotation();
if (path.isNodeType("BinaryExpression")) {
var operator = path.node.operator;
if (NUMBER_BINARY_OPERATORS.indexOf(operator) >= 0) {
// these operators always result in numbers
return t.numberTypeAnnotation();
} else if (BOOLEAN_BINARY_OPERATORS.indexOf(operator) >= 0) {
return t.booleanTypeAnnotation();
} else if (operator === "+") {
var right = this.get("right").getTypeAnnotation();
var left = this.get("left").getTypeAnnotation();
if (t.isNumberTypeAnnotation(left) && t.isNumberTypeAnnotation(right)) {
// both numbers so this will be a number
return t.numberTypeAnnotation();
} else if (t.isStringTypeAnnotation(left) && t.isStringTypeAnnotation(right)) {
// both strings so this will be a string
return t.stringTypeAnnotation();
} else {
// unsure if left and right are both strings or numbers so stay on the safe side
return t.unionTypeAnnotation([
t.stringTypeAnnotation(),
t.numberTypeAnnotation()
]);
}
}
}
if (path.isType("CallExpression")) {
if (path.isNodeType("LogicalExpression")) {
// todo: create UnionType of left and right annotations
}
if (path.isNodeType("UpdateExpression")) {
var operator = path.node.operator;
if (operator === "++" || operator === "--") {
return t.numberTypeAnnotation();
}
}
if (path.isNodeType("UnaryExpression") && path.node.prefix) {
var operator = path.node.operator;
if (operator === "!") {
return t.booleanTypeAnnotation();
} else if (operator === "+" || operator === "-") {
return t.numberTypeAnnotation();
}
}
if (path.isNodeType("Literal")) {
var value = path.node.value;
if (typeof value === "string") return t.stringTypeAnnotation();
if (typeof value === "number") return t.numberTypeAnnotation();
if (typeof value === "boolean") return t.booleanTypeAnnotation();
if (path.node.regex) return t.genericTypeAnnotation(t.identifier("RegExp"));
}
if (path.isNodeType("CallExpression")) {
var callee = path.get("callee").resolve();
if (callee && callee.isType("Function")) return callee.node.returnType;
if (callee && callee.isNodeType("Function")) return callee.node.returnType;
}
}
@ -138,8 +233,8 @@ export function inferType(path: NodePath): ?Object {
* Description
*/
export function isTypeGeneric(genericName: string, opts = {}): boolean {
var typeInfo = this.getTypeAnnotation();
export function isTypeAnnotationGeneric(genericName: string, opts = {}): boolean {
var typeInfo = this.getTypeAnnotationInfo();
var type = typeInfo.annotation;
if (!type) return false;

View File

@ -99,7 +99,7 @@ export function equals(key, value): boolean {
* been removed yet we still internally know the type and need it to calculate node replacement.
*/
export function isType(type: string): boolean {
export function isNodeType(type: string): boolean {
return t.isType(this.type, type);
}

View File

@ -14,42 +14,6 @@ export default class Binding {
this.kind = kind;
}
/**
* Description
*/
setTypeAnnotation() {
var typeInfo = this.path.getTypeAnnotation();
this.typeAnnotationInferred = typeInfo.inferred;
this.typeAnnotation = typeInfo.annotation;
}
/**
* Description
*/
isTypeGeneric(): boolean {
return this.path.isTypeGeneric(...arguments);
}
/**
* Description
*/
assignTypeGeneric(type: Object, params?) {
var typeParams = null;
if (params) params = t.typeParameterInstantiation(params);
this.assignType(t.genericTypeAnnotation(t.identifier(type), typeParams));
}
/**
* Description
*/
assignType(type: Object) {
this.typeAnnotation = type;
}
/**
* Description
*/
@ -57,11 +21,6 @@ export default class Binding {
reassign(path) {
this.constant = false;
this.constantViolations.push(path);
if (this.typeAnnotationInferred) {
// destroy the inferred typeAnnotation
this.typeAnnotation = null;
}
}
/**

View File

@ -380,7 +380,7 @@ export default class Scope {
if (t.isIdentifier(node)) {
var binding = this.getBinding(node.name);
if (binding && binding.constant && binding.isTypeGeneric("Array")) return node;
if (binding && binding.constant && binding.path.isTypeAnnotationGeneric("Array")) return node;
}
if (t.isArrayExpression(node)) {