| /******************************************************************************* |
| * Copyright (c) 2010 xored software, Inc. |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * xored software, Inc. - initial API and Implementation (Alex Panchenko) |
| *******************************************************************************/ |
| package org.eclipse.dltk.internal.javascript.ti; |
| |
| import java.io.StringReader; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| |
| import org.eclipse.dltk.ast.ASTNode; |
| import org.eclipse.dltk.compiler.problem.IProblemCategory; |
| import org.eclipse.dltk.compiler.problem.IProblemIdentifier; |
| import org.eclipse.dltk.internal.javascript.validation.JavaScriptValidations; |
| import org.eclipse.dltk.internal.javascript.validation.ValidationMessages; |
| import org.eclipse.dltk.javascript.ast.ArrayInitializer; |
| import org.eclipse.dltk.javascript.ast.AsteriskExpression; |
| import org.eclipse.dltk.javascript.ast.BinaryOperation; |
| import org.eclipse.dltk.javascript.ast.BooleanLiteral; |
| import org.eclipse.dltk.javascript.ast.BreakStatement; |
| import org.eclipse.dltk.javascript.ast.CallExpression; |
| import org.eclipse.dltk.javascript.ast.CaseClause; |
| import org.eclipse.dltk.javascript.ast.CatchClause; |
| import org.eclipse.dltk.javascript.ast.CommaExpression; |
| import org.eclipse.dltk.javascript.ast.Comment; |
| import org.eclipse.dltk.javascript.ast.ConditionalOperator; |
| import org.eclipse.dltk.javascript.ast.ConstStatement; |
| import org.eclipse.dltk.javascript.ast.ContinueStatement; |
| import org.eclipse.dltk.javascript.ast.DecimalLiteral; |
| import org.eclipse.dltk.javascript.ast.DefaultXmlNamespaceStatement; |
| import org.eclipse.dltk.javascript.ast.DoWhileStatement; |
| import org.eclipse.dltk.javascript.ast.EmptyExpression; |
| import org.eclipse.dltk.javascript.ast.EmptyStatement; |
| import org.eclipse.dltk.javascript.ast.Expression; |
| import org.eclipse.dltk.javascript.ast.ForEachInStatement; |
| import org.eclipse.dltk.javascript.ast.ForInStatement; |
| import org.eclipse.dltk.javascript.ast.ForStatement; |
| import org.eclipse.dltk.javascript.ast.FunctionStatement; |
| import org.eclipse.dltk.javascript.ast.GetAllChildrenExpression; |
| import org.eclipse.dltk.javascript.ast.GetArrayItemExpression; |
| import org.eclipse.dltk.javascript.ast.GetLocalNameExpression; |
| import org.eclipse.dltk.javascript.ast.Identifier; |
| import org.eclipse.dltk.javascript.ast.IfStatement; |
| import org.eclipse.dltk.javascript.ast.JSDeclaration; |
| import org.eclipse.dltk.javascript.ast.JSNode; |
| import org.eclipse.dltk.javascript.ast.JSScope; |
| import org.eclipse.dltk.javascript.ast.LabelledStatement; |
| import org.eclipse.dltk.javascript.ast.NewExpression; |
| import org.eclipse.dltk.javascript.ast.NullExpression; |
| import org.eclipse.dltk.javascript.ast.ObjectInitializer; |
| import org.eclipse.dltk.javascript.ast.ObjectInitializerPart; |
| import org.eclipse.dltk.javascript.ast.ParenthesizedExpression; |
| import org.eclipse.dltk.javascript.ast.PropertyExpression; |
| import org.eclipse.dltk.javascript.ast.PropertyInitializer; |
| import org.eclipse.dltk.javascript.ast.RegExpLiteral; |
| import org.eclipse.dltk.javascript.ast.ReturnStatement; |
| import org.eclipse.dltk.javascript.ast.Script; |
| import org.eclipse.dltk.javascript.ast.Statement; |
| import org.eclipse.dltk.javascript.ast.StatementBlock; |
| import org.eclipse.dltk.javascript.ast.StringLiteral; |
| import org.eclipse.dltk.javascript.ast.SwitchComponent; |
| import org.eclipse.dltk.javascript.ast.SwitchStatement; |
| import org.eclipse.dltk.javascript.ast.ThisExpression; |
| import org.eclipse.dltk.javascript.ast.ThrowStatement; |
| import org.eclipse.dltk.javascript.ast.TryStatement; |
| import org.eclipse.dltk.javascript.ast.UnaryOperation; |
| import org.eclipse.dltk.javascript.ast.VariableDeclaration; |
| import org.eclipse.dltk.javascript.ast.VariableStatement; |
| import org.eclipse.dltk.javascript.ast.VoidExpression; |
| import org.eclipse.dltk.javascript.ast.WhileStatement; |
| import org.eclipse.dltk.javascript.ast.WithStatement; |
| import org.eclipse.dltk.javascript.ast.XmlAttributeIdentifier; |
| import org.eclipse.dltk.javascript.ast.XmlExpressionFragment; |
| import org.eclipse.dltk.javascript.ast.XmlFragment; |
| import org.eclipse.dltk.javascript.ast.XmlLiteral; |
| import org.eclipse.dltk.javascript.ast.XmlTextFragment; |
| import org.eclipse.dltk.javascript.ast.YieldOperator; |
| import org.eclipse.dltk.javascript.core.JavaScriptPlugin; |
| import org.eclipse.dltk.javascript.core.JavaScriptProblems; |
| import org.eclipse.dltk.javascript.internal.core.RRecordMember; |
| import org.eclipse.dltk.javascript.parser.ISuppressWarningsState; |
| import org.eclipse.dltk.javascript.parser.JSParser; |
| import org.eclipse.dltk.javascript.parser.PropertyExpressionUtils; |
| import org.eclipse.dltk.javascript.parser.jsdoc.JSDocTag; |
| import org.eclipse.dltk.javascript.parser.jsdoc.JSDocTags; |
| import org.eclipse.dltk.javascript.typeinference.IAssignProtection; |
| import org.eclipse.dltk.javascript.typeinference.IFunctionValueCollection; |
| import org.eclipse.dltk.javascript.typeinference.IValueCollection; |
| import org.eclipse.dltk.javascript.typeinference.IValueReference; |
| import org.eclipse.dltk.javascript.typeinference.PhantomValueReference; |
| import org.eclipse.dltk.javascript.typeinference.ReferenceKind; |
| import org.eclipse.dltk.javascript.typeinference.ReferenceLocation; |
| import org.eclipse.dltk.javascript.typeinference.ValueReferenceUtil; |
| import org.eclipse.dltk.javascript.typeinfo.CommonSuperTypeFinder; |
| import org.eclipse.dltk.javascript.typeinfo.E4XTypes; |
| import org.eclipse.dltk.javascript.typeinfo.GenericMethodTypeInferencer; |
| import org.eclipse.dltk.javascript.typeinfo.IMemberEvaluator; |
| import org.eclipse.dltk.javascript.typeinfo.IModelBuilder; |
| import org.eclipse.dltk.javascript.typeinfo.IModelBuilder.IMethod; |
| import org.eclipse.dltk.javascript.typeinfo.IModelBuilder.IParameter; |
| import org.eclipse.dltk.javascript.typeinfo.IModelBuilder.IVariable; |
| import org.eclipse.dltk.javascript.typeinfo.IModelBuilderExtension; |
| import org.eclipse.dltk.javascript.typeinfo.IRArrayType; |
| import org.eclipse.dltk.javascript.typeinfo.IRClassType; |
| import org.eclipse.dltk.javascript.typeinfo.IRConstructor; |
| import org.eclipse.dltk.javascript.typeinfo.IRFunctionType; |
| import org.eclipse.dltk.javascript.typeinfo.IRLocalType; |
| import org.eclipse.dltk.javascript.typeinfo.IRMapType; |
| import org.eclipse.dltk.javascript.typeinfo.IRMethod; |
| import org.eclipse.dltk.javascript.typeinfo.IRProperty; |
| import org.eclipse.dltk.javascript.typeinfo.IRRecordMember; |
| import org.eclipse.dltk.javascript.typeinfo.IRRecordType; |
| import org.eclipse.dltk.javascript.typeinfo.IRSimpleType; |
| import org.eclipse.dltk.javascript.typeinfo.IRType; |
| import org.eclipse.dltk.javascript.typeinfo.IRTypeDeclaration; |
| import org.eclipse.dltk.javascript.typeinfo.IRVariable; |
| import org.eclipse.dltk.javascript.typeinfo.ITypeInferenceListener; |
| import org.eclipse.dltk.javascript.typeinfo.ITypeNames; |
| import org.eclipse.dltk.javascript.typeinfo.JSTypeSet; |
| import org.eclipse.dltk.javascript.typeinfo.RModelBuilder; |
| import org.eclipse.dltk.javascript.typeinfo.RTypes; |
| import org.eclipse.dltk.javascript.typeinfo.ReferenceSource; |
| import org.eclipse.dltk.javascript.typeinfo.TypeInfoManager; |
| import org.eclipse.dltk.javascript.typeinfo.TypeMode; |
| import org.eclipse.dltk.javascript.typeinfo.TypeUtil; |
| import org.eclipse.dltk.javascript.typeinfo.model.ArrayType; |
| import org.eclipse.dltk.javascript.typeinfo.model.FunctionType; |
| import org.eclipse.dltk.javascript.typeinfo.model.GenericMethod; |
| import org.eclipse.dltk.javascript.typeinfo.model.JSType; |
| import org.eclipse.dltk.javascript.typeinfo.model.MapType; |
| import org.eclipse.dltk.javascript.typeinfo.model.Parameter; |
| import org.eclipse.dltk.javascript.typeinfo.model.ParameterKind; |
| import org.eclipse.dltk.javascript.typeinfo.model.ParameterizedType; |
| import org.eclipse.dltk.javascript.typeinfo.model.RecordType; |
| import org.eclipse.dltk.javascript.typeinfo.model.Type; |
| import org.eclipse.dltk.javascript.typeinfo.model.TypeKind; |
| import org.eclipse.dltk.javascript.typeinfo.model.TypeVariableClassType; |
| import org.eclipse.dltk.javascript.typeinfo.model.TypeVariableReference; |
| import org.eclipse.dltk.javascript.typeinfo.model.UnionType; |
| import org.eclipse.dltk.javascript.typeinfo.model.util.TypeInfoModelSwitch; |
| import org.eclipse.emf.ecore.EObject; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.InputSource; |
| |
| public class TypeInferencerVisitor extends TypeInferencerVisitorBase { |
| |
| public TypeInferencerVisitor(ITypeInferenceContext context) { |
| super(context); |
| } |
| |
| private static class ForwardDeclaration { |
| final JSMethod method; |
| final IValueReference reference; |
| |
| public ForwardDeclaration(JSMethod method, IValueReference reference) { |
| this.method = method; |
| this.reference = reference; |
| } |
| } |
| |
| private final Map<FunctionStatement, ForwardDeclaration> forwardDeclarations = new IdentityHashMap<FunctionStatement, ForwardDeclaration>(); |
| |
| @Override |
| public void initialize() { |
| super.initialize(); |
| forwardDeclarations.clear(); |
| } |
| |
| private final Stack<Branching> branchings = new Stack<Branching>(); |
| |
| private class Branching { |
| public void end() { |
| branchings.remove(this); |
| } |
| } |
| |
| protected Branching branching() { |
| final Branching branching = new Branching(); |
| branchings.add(branching); |
| return branching; |
| } |
| |
| public ReferenceSource getSource() { |
| final ReferenceSource source = context.getSource(); |
| return source != null ? source : ReferenceSource.UNKNOWN; |
| } |
| |
| protected void assign(IValueReference dest, IValueReference src) { |
| IRType destType = JavaScriptValidations.typeOf(dest); |
| if (destType != null && isXML(destType)) { |
| IRType srcType = JavaScriptValidations.typeOf(src); |
| if (srcType != null && !isXML(srcType)) |
| return; |
| } |
| if (branchings.isEmpty()) { |
| dest.setValue(src); |
| } else { |
| dest.addValue(src, false); |
| } |
| } |
| |
| private static boolean isXML(IRType srcType) { |
| return (srcType instanceof IRSimpleType) |
| && (ITypeNames.XML.equals(srcType.getName()) || ITypeNames.XMLLIST |
| .equals(srcType.getName())); |
| } |
| |
| @Override |
| public IValueReference visitArrayInitializer(ArrayInitializer node) { |
| if (node.getItems().isEmpty()) { |
| return new ConstantValue(RTypes.arrayOf()); |
| } |
| final Set<IRType> types = new HashSet<IRType>(); |
| for (ASTNode astNode : node.getItems()) { |
| if (astNode instanceof StringLiteral) { |
| types.add(RTypes.STRING); |
| } else if (astNode instanceof DecimalLiteral) { |
| types.add(RTypes.NUMBER); |
| } else if (astNode instanceof BooleanLiteral) { |
| types.add(RTypes.BOOLEAN); |
| } else if (astNode instanceof NullExpression |
| || astNode instanceof EmptyExpression) { |
| // ignore |
| } else { |
| final IValueReference child = visit(astNode); |
| if (child != null && child.exists()) { |
| for (IRType type : JavaScriptValidations.getTypes(child)) { |
| if (type == null) { |
| JavaScriptPlugin.error(buildNodeErrorMessage(node) |
| + " - item type is null"); |
| continue; |
| } |
| types.add(type.normalize()); |
| } |
| } |
| // TODO (alex) else add(Object) ? |
| } |
| } |
| if (types.isEmpty()) { |
| return new ConstantValue(RTypes.arrayOf()); |
| } else { |
| if (types.size() == 1) { |
| return new ConstantValue(RTypes.arrayOf(context, |
| CommonSuperTypeFinder.evaluate(context, types))); |
| } else { |
| return new ConstantValue(RTypes.arrayOf(context, |
| RTypes.union(types))); |
| } |
| } |
| } |
| |
| @Override |
| public IValueReference visitAsteriskExpression(AsteriskExpression node) { |
| return new XMLListValue(peekContext()); |
| } |
| |
| @Override |
| public IValueReference visitBinaryOperation(BinaryOperation node) { |
| final IValueReference left = visit(node.getLeftExpression()); |
| final int op = node.getOperation(); |
| if (JSParser.ASSIGN == op) { |
| if (left != null) { |
| for (IModelBuilder modelBuilder : context.getModelBuilders()) { |
| if (modelBuilder instanceof IModelBuilderExtension) { |
| ((IModelBuilderExtension) modelBuilder) |
| .processAssignment(node.getLeftExpression(), |
| left); |
| } |
| } |
| } |
| if (left != null && left.exists()) { |
| if (left.getParent() instanceof ThisValue) { |
| // this is an override, make sure that left is really |
| // created. copy over the variable (so that visibility is used) |
| Object variable = left |
| .getAttribute(IReferenceAttributes.R_VARIABLE); |
| left.getParent().createChild(left.getName()); |
| if (variable != null) { |
| left.setAttribute(IReferenceAttributes.R_VARIABLE, |
| variable); |
| } |
| Expression property = node.getLeftExpression(); |
| if (property instanceof PropertyExpression) { |
| left.setLocation(ReferenceLocation.create(getSource(), |
| property.sourceStart(), property.sourceEnd(), |
| ((PropertyExpression) property).getProperty() |
| .sourceStart(), |
| ((PropertyExpression) property).getProperty() |
| .sourceEnd())); |
| } |
| } |
| left.setAttribute(IReferenceAttributes.RESOLVING, Boolean.TRUE); |
| final IValueReference r; |
| try { |
| r = visit(node.getRightExpression()); |
| } finally { |
| left.setAttribute(IReferenceAttributes.RESOLVING, null); |
| } |
| return visitAssign(left, r, node); |
| } else { |
| return visitAssign(left, visit(node.getRightExpression()), node); |
| } |
| } |
| final IValueReference right = visit(node.getRightExpression()); |
| if (left == null && right instanceof ConstantValue) { |
| return right; |
| } else if (op == JSParser.LAND) { |
| return coalesce(right, left); |
| } else if (op == JSParser.GT || op == JSParser.GTE || op == JSParser.LT |
| || op == JSParser.LTE || op == JSParser.NSAME |
| || op == JSParser.SAME || op == JSParser.NEQ |
| || op == JSParser.EQ) { |
| return ConstantValue.of(RTypes.BOOLEAN); |
| } else if (isNumber(left) && isNumber(right)) { |
| return ConstantValue.of(RTypes.NUMBER); |
| } else if (op == JSParser.ADD) { |
| if (isString(left) || isString(right)) { |
| return ConstantValue.of(RTypes.STRING); |
| } |
| return left; |
| } else if (JSParser.INSTANCEOF == op) { |
| return ConstantValue.of(RTypes.BOOLEAN); |
| } else if (JSParser.LOR == op) { |
| final JSTypeSet typeSet = JSTypeSet.create(); |
| if (left != null) { |
| typeSet.addAll(left.getDeclaredTypes()); |
| typeSet.addAll(left.getTypes()); |
| } |
| if (right != null) { |
| typeSet.addAll(right.getDeclaredTypes()); |
| typeSet.addAll(right.getTypes()); |
| } |
| return new ConstantValue(typeSet); |
| } else { |
| // TODO handle other operations |
| return null; |
| } |
| } |
| |
| private static IValueReference coalesce(IValueReference v1, |
| IValueReference v2) { |
| return v1 != null ? v1 : v2; |
| } |
| |
| private boolean isNumber(IValueReference ref) { |
| if (ref != null) { |
| if (ref.getTypes().contains(RTypes.NUMBER)) |
| return true; |
| if (RTypes.NUMBER.equals(ref.getDeclaredType())) |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean isString(IValueReference ref) { |
| if (ref != null) { |
| if (ref.getTypes().contains(RTypes.STRING)) |
| return true; |
| if (RTypes.STRING.equals(ref.getDeclaredType())) |
| return true; |
| } |
| return false; |
| } |
| |
| protected IValueReference visitAssign(IValueReference left, |
| IValueReference right, BinaryOperation node) { |
| if (left != null) { |
| if (node.getLeftExpression() instanceof PropertyExpression) { |
| final PropertyExpression property = (PropertyExpression) node |
| .getLeftExpression(); |
| if (property.getProperty() instanceof Identifier |
| && !left.exists()) { |
| if (property.getObject() instanceof ThisExpression) { |
| if (isFunctionDeclaration(property)) { |
| left.setKind(ReferenceKind.FUNCTION); |
| // this functions is part of an object ('this' |
| // assignment). Set the this of that function to the |
| // parent local type function. |
| IValueCollection scope = (IValueCollection) right |
| .getAttribute(IReferenceAttributes.FUNCTION_SCOPE); |
| IValueCollection context = peekContext(); |
| if (scope != null |
| && scope.getThis().getDeclaredType() == null |
| && context instanceof IFunctionValueCollection) { |
| String name = ((IFunctionValueCollection) context) |
| .getFunctionName(); |
| scope.getThis().setDeclaredType( |
| RTypes.localType(name, context |
| .getParent().getChild(name))); |
| } |
| } |
| else { |
| left.setKind(ReferenceKind.FIELD); |
| final Comment comment = JSDocSupport |
| .getComment(node); |
| final JSDocTags tags = parseTags(comment); |
| final JSDocTag typeTag = tags.get(JSDocTag.TYPE); |
| if (typeTag != null) { |
| final JSType type = getDocSupport().parseType( |
| typeTag, false, getProblemReporter()); |
| if (type != null) { |
| setIRType(left, type.toRType(context), true); |
| } |
| } |
| if (comment != null) { |
| IValueReference namedChild = extractNamedChild( |
| left, property.getProperty()); |
| String name = namedChild.getName(); |
| final JSVariable variable = new JSVariable(name); |
| getDocSupport().parseAccessModifiers(variable, |
| tags, reporter); |
| if (variable.getVisibility() != null) { |
| left.setAttribute( |
| IReferenceAttributes.R_VARIABLE, |
| RModelBuilder.create(context, |
| variable)); |
| } |
| } |
| } |
| left.setLocation(ReferenceLocation.create(getSource(), |
| property.sourceStart(), property.sourceEnd(), |
| property.getProperty().sourceStart(), property |
| .getProperty().sourceEnd())); |
| } else { |
| final IValueReference leftParent = left.getParent(); |
| if (leftParent != null) { |
| final IRType declaredType = leftParent |
| .getDeclaredType(); |
| if (declaredType != null |
| && !declaredType.isExtensible() |
| && !declaredType.isJavaScriptObject()) { |
| // skip assignment |
| return right; |
| } else { |
| // if the assignment is done on the prototype property |
| // then make sure it is not IRProperty (the one from Object itself) |
| // replace that with a AnonymousValue |
| Object attribute = leftParent |
| .getAttribute(IReferenceAttributes.ELEMENT); |
| if (attribute instanceof IRProperty |
| && ((IRProperty) attribute).getName() |
| .equals(IRLocalType.PROTOTYPE_PROPERTY)) { |
| leftParent.getParent() |
| .createChild( |
| IRLocalType.PROTOTYPE_PROPERTY) |
| .setValue(new AnonymousValue()); |
| } |
| |
| } |
| } |
| } |
| } else { |
| // create a new object if prototype is directly set to record type value. |
| Object attribute = left |
| .getAttribute(IReferenceAttributes.ELEMENT); |
| if (attribute instanceof IRProperty |
| && ((IRProperty) attribute).getName().equals( |
| IRLocalType.PROTOTYPE_PROPERTY)) { |
| left.getParent() |
| .createChild(IRLocalType.PROTOTYPE_PROPERTY) |
| .setValue(new AnonymousValue()); |
| } |
| } |
| } |
| if (IValueReference.ARRAY_OP.equals(left.getName()) |
| && node.getLeftExpression() instanceof GetArrayItemExpression) { |
| GetArrayItemExpression arrayItemExpression = (GetArrayItemExpression) node |
| .getLeftExpression(); |
| IValueReference namedChild = extractNamedChild( |
| left.getParent(), arrayItemExpression.getIndex()); |
| if (namedChild != null) { |
| assign(namedChild, right); |
| } else { |
| assign(left, right); |
| } |
| } else { |
| if (!hasUnknowParentFunctionCall(left)) |
| assign(left, right); |
| } |
| } |
| return right; |
| } |
| |
| private boolean hasUnknowParentFunctionCall(IValueReference reference) { |
| IValueReference parent = reference.getParent(); |
| while (parent != null) { |
| if (parent.getName().equals(IValueReference.FUNCTION_OP) |
| && !parent.exists()) |
| return true; |
| parent = parent.getParent(); |
| } |
| return false; |
| } |
| |
| @Override |
| public IValueReference visitBooleanLiteral(BooleanLiteral node) { |
| return ConstantValue.of(RTypes.BOOLEAN); |
| } |
| |
| @Override |
| public IValueReference visitBreakStatement(BreakStatement node) { |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitCallExpression(CallExpression node) { |
| final IValueReference reference = visit(node.getExpression()); |
| final List<ASTNode> args = node.getArguments(); |
| final IValueReference[] arguments = new IValueReference[args.size()]; |
| for (int i = 0; i < args.size(); ++i) { |
| arguments[i] = visit(args.get(i)); |
| } |
| if (reference != null) { |
| final List<IRMethod> methods = ValueReferenceUtil.extractElements( |
| reference, IRMethod.class); |
| if (methods != null && methods.size() == 1) { |
| final IRMethod method = methods.get(0); |
| IValueReference ref = checkSpecialJavascriptFunctionCalls( |
| reference, |
| arguments, method); |
| if (ref != null) |
| return ref; |
| if (method.isGeneric()) { |
| final IRType type = evaluateGenericCall(method, arguments); |
| return ConstantValue.of(type); |
| } else { |
| return ConstantValue.of(method.getType()); |
| } |
| } else { |
| final IRType expressionType = JavaScriptValidations |
| .typeOf(reference); |
| if (expressionType != null) { |
| if (expressionType instanceof IRFunctionType) { |
| return ConstantValue |
| .of(((IRFunctionType) expressionType) |
| .getReturnType()); |
| } else if (expressionType instanceof IRClassType) { |
| final IRTypeDeclaration target = ((IRClassType) expressionType) |
| .getDeclaration(); |
| if (target != null) { |
| final IRConstructor constructor = target |
| .getStaticConstructor(); |
| if (constructor != null |
| && constructor.getType() != null) { |
| return new ConstantValue(constructor.getType()); |
| } |
| } |
| } |
| } |
| } |
| return reference.getChild(IValueReference.FUNCTION_OP); |
| } else { |
| return null; |
| } |
| } |
| |
| protected IValueReference checkSpecialJavascriptFunctionCalls( |
| final IValueReference reference, final IValueReference[] arguments, |
| final IRMethod method) { |
| if (method.getName() != null) { |
| if (reference.getParent() != null |
| && RTypes.FUNCTION.getDeclaration().equals( |
| method.getDeclaringType())) { |
| |
| if ("call".equals(method.getName()) |
| || "apply".equals(method.getName())) { |
| Object x = reference.getParent().getAttribute( |
| IReferenceAttributes.ELEMENT); |
| if (x == null) |
| x = reference.getParent().getAttribute( |
| IReferenceAttributes.R_METHOD); |
| if (x instanceof IRMethod) { |
| return ConstantValue.of(((IRMethod) x).getType()); |
| } |
| } else if ("bind".equals(method.getName())) { |
| return reference.getParent(); |
| } |
| } else if (method.getName().equals("create") |
| && RTypes.OBJECT.getDeclaration().equals( |
| method.getDeclaringType()) && arguments.length > 0) { |
| AnonymousValue value = new AnonymousValue(); |
| IValue argumentValue = ((IValueProvider) arguments[0]) |
| .getValue(); |
| if (argumentValue != null) |
| value.getValue().addValue(argumentValue); |
| if (arguments.length == 2) { |
| JSTypeSet types = arguments[1].getTypes(); |
| for (IRType type : types) { |
| if (type instanceof IRRecordType) { |
| List<IRRecordMember> newMembers = new ArrayList<IRRecordMember>(); |
| Collection<IRRecordMember> members = ((IRRecordType) type) |
| .getMembers(); |
| for (IRRecordMember member : members) { |
| if (member.getType() instanceof IRRecordType) { |
| IRRecordMember valueMember = ((IRRecordType) member |
| .getType()).getMember("value"); |
| if (valueMember != null) { |
| newMembers.add(new RRecordMember(member |
| .getName(), valueMember |
| .getType(), valueMember |
| .getSource())); |
| } |
| valueMember = ((IRRecordType) member |
| .getType()).getMember("get"); |
| if (valueMember != null) { |
| IRType valueType = valueMember |
| .getType(); |
| if (valueType instanceof IRFunctionType) { |
| valueType = ((IRFunctionType) valueType) |
| .getReturnType(); |
| } |
| newMembers.add(new RRecordMember(member |
| .getName(), valueType, |
| valueMember.getSource())); |
| } |
| } |
| } |
| if (newMembers.size() > 0) { |
| value.addValue(ConstantValue.of(RTypes |
| .recordType(newMembers)), true); |
| } |
| } |
| } |
| } |
| return value; |
| } |
| } |
| return null; |
| } |
| |
| protected IRType evaluateGenericCall(IRMethod rMethod, |
| IValueReference[] arguments) { |
| assert rMethod.isGeneric(); |
| final GenericMethod method = (GenericMethod) rMethod.getSource(); |
| final GenericMethodTypeInferencer methodTypeInferencer = new GenericMethodTypeInferencer( |
| context, method); |
| for (int i = 0; i < arguments.length; ++i) { |
| final Parameter parameter = method.getParameterFor(i); |
| if (parameter != null) { |
| // parameter count is checked in ValidationVisitor |
| // TODO (alex) can be pre-evaluated in GenericParameter objects. |
| if (TypeUtil.containsTypeVariables(parameter.getType())) { |
| final IValueReference argument = arguments[i]; |
| JSTypeSet argTypes; |
| if (argument != null) { |
| argTypes = argument.getDeclaredTypes(); |
| if (argTypes.isEmpty()) { |
| argTypes = argument.getTypes(); |
| } |
| } else { |
| argTypes = JSTypeSet.emptySet(); |
| } |
| methodTypeInferencer.capture(parameter.getType(), argTypes); |
| } else { |
| // TODO (alex) check parameter compatibility |
| } |
| } |
| } |
| return RTypes.create(methodTypeInferencer, method.getType()); |
| } |
| |
| public static final TypeInfoModelSwitch<Boolean> GENERIC_TYPE_EXPRESSION = new TypeInfoModelSwitch<Boolean>() { |
| @Override |
| public Boolean doSwitch(EObject theEObject) { |
| return theEObject != null ? super.doSwitch(theEObject) : null; |
| } |
| |
| @Override |
| public Boolean caseJSType(JSType object) { |
| return Boolean.FALSE; |
| } |
| |
| @Override |
| public Boolean caseTypeVariableReference(TypeVariableReference object) { |
| return Boolean.TRUE; |
| } |
| |
| @Override |
| public Boolean caseTypeVariableClassType(TypeVariableClassType object) { |
| return Boolean.TRUE; |
| } |
| |
| @Override |
| public Boolean caseArrayType(ArrayType object) { |
| return doSwitch(object.getItemType()); |
| } |
| |
| @Override |
| public Boolean caseMapType(MapType object) { |
| final Boolean result = doSwitch(object.getKeyType()); |
| if (result == Boolean.TRUE) { |
| return result; |
| } |
| return doSwitch(object.getValueType()); |
| } |
| |
| @Override |
| public Boolean caseParameterizedType(ParameterizedType object) { |
| for (JSType type : object.getActualTypeArguments()) { |
| final Boolean result = doSwitch(type); |
| if (result == Boolean.TRUE) { |
| return result; |
| } |
| } |
| return Boolean.FALSE; |
| } |
| |
| @Override |
| public Boolean caseUnionType(UnionType object) { |
| for (JSType type : object.getTargets()) { |
| final Boolean result = doSwitch(type); |
| if (result == Boolean.TRUE) { |
| return result; |
| } |
| } |
| return Boolean.FALSE; |
| } |
| |
| @Override |
| public Boolean caseFunctionType(FunctionType object) { |
| for (Parameter parameter : object.getParameters()) { |
| final Boolean result = doSwitch(parameter.getType()); |
| if (result == Boolean.TRUE) { |
| return result; |
| } |
| } |
| return doSwitch(object.getReturnType()); |
| } |
| }; |
| |
| @Override |
| public IValueReference visitCommaExpression(CommaExpression node) { |
| return visit(node.getItems()); |
| } |
| |
| @Override |
| public IValueReference visitConditionalOperator(ConditionalOperator node) { |
| visit(node.getCondition()); |
| return merge(visit(node.getTrueValue()), visit(node.getFalseValue())); |
| } |
| |
| protected static final IAssignProtection PROTECT_CONST = new IAssignProtection() { |
| public IProblemIdentifier problemId() { |
| return JavaScriptProblems.REASSIGNMENT_OF_CONSTANT; |
| } |
| |
| public String problemMessage() { |
| return ValidationMessages.ReassignmentOfConstant; |
| } |
| }; |
| |
| @Override |
| public IValueReference visitConstDeclaration(ConstStatement node) { |
| final IValueCollection context = peekContext(); |
| for (VariableDeclaration declaration : node.getVariables()) { |
| final IValueReference reference = context.getChild(declaration |
| .getVariableName()); |
| assert reference.exists(); |
| initializeVariable(reference, declaration); |
| } |
| return null; |
| } |
| |
| protected IValueReference createVariable(IValueCollection context, |
| VariableDeclaration declaration) { |
| final Identifier identifier = declaration.getIdentifier(); |
| final String varName = identifier.getName(); |
| final IValueReference reference = context.createChild(varName); |
| final JSVariable variable = new JSVariable( |
| declaration.getVariableName()); |
| for (IModelBuilder extension : this.context.getModelBuilders()) { |
| extension.processVariable(declaration, variable, reporter, |
| getTypeChecker()); |
| } |
| if (reporter != null) { |
| final ISuppressWarningsState state = reporter.getSuppressWarnings(); |
| if (state != null) { |
| variable.addSuppressedWarning(state.asCategory()); |
| } |
| } |
| if (listeners != null) { |
| for (ITypeInferenceListener listener : listeners) { |
| listener.variableParsed(variable); |
| } |
| } |
| reference.setAttribute(IReferenceAttributes.VARIABLE, variable); |
| |
| reference.setKind(inFunction() ? ReferenceKind.LOCAL |
| : ReferenceKind.GLOBAL); |
| reference.setLocation(ReferenceLocation.create(getSource(), |
| declaration.sourceStart(), declaration.sourceEnd(), |
| identifier.sourceStart(), identifier.sourceEnd())); |
| |
| if (variable.getTypeDef() instanceof RecordType) { |
| RecordType type = (RecordType) variable.getTypeDef(); |
| type.setTypeName(varName); |
| this.context.registerRecordType(type); |
| } |
| return reference; |
| } |
| |
| protected void initializeVariable(final IValueReference reference, |
| VariableDeclaration declaration) { |
| if (declaration.getInitializer() != null) { |
| final IValueReference assignment; |
| reference |
| .setAttribute(IReferenceAttributes.RESOLVING, Boolean.TRUE); |
| try { |
| assignment = visit(declaration.getInitializer()); |
| } finally { |
| reference.setAttribute(IReferenceAttributes.RESOLVING, null); |
| } |
| if (assignment != null) { |
| final IRVariable variable = (IRVariable) reference |
| .getAttribute(IReferenceAttributes.R_VARIABLE); |
| if (variable != null && variable.getType() != null) { |
| // if declared type specified then just add it as a value on |
| // top of what we already have. So that we don't clear the |
| // current one. |
| reference.addValue(assignment, false); |
| } else { |
| // assign only if no declared type specified |
| assign(reference, assignment); |
| } |
| if (assignment.getKind() == ReferenceKind.FUNCTION |
| && reference.getAttribute(IReferenceAttributes.METHOD) != null) |
| reference.setKind(ReferenceKind.FUNCTION); |
| } |
| } |
| } |
| |
| @Override |
| public IValueReference visitContinueStatement(ContinueStatement node) { |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitDecimalLiteral(DecimalLiteral node) { |
| return ConstantValue.of(RTypes.NUMBER); |
| } |
| |
| @Override |
| public IValueReference visitDefaultXmlNamespace( |
| DefaultXmlNamespaceStatement node) { |
| visit(node.getValue()); |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitDoWhileStatement(DoWhileStatement node) { |
| visit(node.getCondition()); |
| visit(node.getBody()); |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitEmptyExpression(EmptyExpression node) { |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitEmptyStatement(EmptyStatement node) { |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitForEachInStatement(ForEachInStatement node) { |
| IValueReference itemReference = visit(node.getItem()); |
| IValueReference iteratorReference = visit(node.getIterator()); |
| Set<IRType> typeSet = JavaScriptValidations.getTypes(iteratorReference); |
| if (!typeSet.isEmpty()) { |
| IRType type = null; |
| // try to get the best type, just take the first one, and if the the |
| // latter just have Any or None types skip those. |
| for (IRType irType : typeSet) { |
| if (type == null) { |
| type = irType; |
| } else if (irType instanceof IRArrayType) { |
| IRType itemType = ((IRArrayType) irType).getItemType(); |
| if (itemType != RTypes.none() && itemType != RTypes.any()) { |
| type = irType; |
| } |
| } else if (irType instanceof IRMapType) { |
| IRType itemType = ((IRMapType) irType).getValueType(); |
| if (itemType != RTypes.none() && itemType != RTypes.any()) { |
| type = irType; |
| } |
| } |
| } |
| |
| if (type instanceof IRArrayType |
| && JavaScriptValidations.typeOf(itemReference) == null) { |
| final IRType itemType = ((IRArrayType) type).getItemType(); |
| setIRType(itemReference, itemType, true); |
| } else if (type instanceof IRMapType |
| && JavaScriptValidations.typeOf(itemReference) == null) { |
| final IRType itemType = ((IRMapType) type).getValueType(); |
| setIRType(itemReference, itemType, true); |
| } else if (ITypeNames.XMLLIST.equals(type.getName())) { |
| itemReference.setDeclaredType(E4XTypes.XML); |
| } |
| } |
| visit(node.getBody()); |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitForInStatement(ForInStatement node) { |
| final IValueReference item = visit(node.getItem()); |
| if (item != null) { |
| assign(item, ConstantValue.of(RTypes.STRING)); |
| } |
| visit(node.getIterator()); |
| visit(node.getBody()); |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitForStatement(ForStatement node) { |
| if (node.getInitial() != null) |
| visit(node.getInitial()); |
| if (node.getCondition() != null) |
| visit(node.getCondition()); |
| if (node.getStep() != null) |
| visit(node.getStep()); |
| if (node.getBody() != null) |
| visit(node.getBody()); |
| return null; |
| } |
| |
| private void initializeFunction(JSMethod method, IValueReference function) { |
| function.setLocation(method.getLocation()); |
| function.setKind(ReferenceKind.FUNCTION); |
| function.setDeclaredType(RTypes.FUNCTION); |
| function.setAttribute(IReferenceAttributes.METHOD, method); |
| function.setAttribute(IReferenceAttributes.RESOLVING, Boolean.TRUE); |
| } |
| |
| @Override |
| public IValueReference visitFunctionStatement(FunctionStatement node) { |
| final JSMethod method; |
| final IValueReference result; |
| final ForwardDeclaration forward = forwardDeclarations.remove(node); |
| if (forward != null) { |
| method = forward.method; |
| result = forward.reference; |
| } else { |
| assert !node.isDeclaration(); |
| method = createMethod(node); |
| result = new AnonymousValue(); |
| initializeFunction(method, result); |
| result.setAttribute(IReferenceAttributes.R_METHOD, |
| RModelBuilder.create(getContext(), method)); |
| } |
| final ThisValue thisValue = new ThisValue(); |
| if (method.getThisType() != null) { |
| thisValue.setDeclaredType(this.context.contextualize(method |
| .getThisType())); |
| } else if (method.getExtendsType() != null) { |
| IRType thisType = this.context.contextualize(method |
| .getExtendsType()); |
| thisValue.setDeclaredType(thisType); |
| if (thisType instanceof IRLocalType) { |
| IValueReference prototype = result |
| .createChild(IRLocalType.PROTOTYPE_PROPERTY); |
| prototype.setDeclaredType(context.getType(ITypeNames.OBJECT) |
| .toRType(context)); |
| Set<String> directChildren = ((IRLocalType) thisType) |
| .getDirectChildren(); |
| for (String child : directChildren) { |
| prototype.createChild(child).setDeclaredType( |
| context.getType(ITypeNames.FUNCTION).toRType( |
| context)); |
| } |
| } |
| |
| } else { |
| // if this is a "this.property" assignment then take over the this |
| // of the parent. |
| if (node.getParent() instanceof BinaryOperation) { |
| BinaryOperation bo = (BinaryOperation) node.getParent(); |
| if (bo.getLeftExpression() instanceof PropertyExpression |
| && ((PropertyExpression) bo.getLeftExpression()) |
| .getObject() instanceof ThisExpression) { |
| IValueCollection context = peekContext(); |
| if (context instanceof IFunctionValueCollection) { |
| String name = ((IFunctionValueCollection) context) |
| .getFunctionName(); |
| thisValue.setDeclaredType(RTypes.localType(name, |
| context.getParent().getChild(name))); |
| } |
| } |
| } |
| } |
| final IValueCollection function = new FunctionValueCollection( |
| peekContext(), method.getName(), thisValue, |
| node.isInlineBlock()); |
| |
| for (IParameter parameter : method.getParameters()) { |
| final IValueReference refArg = function.createChild(parameter |
| .getName()); |
| refArg.setKind(ReferenceKind.ARGUMENT); |
| setTypeImpl(refArg, parameter.getType()); |
| refArg.setLocation(parameter.getLocation()); |
| } |
| result.setAttribute(IReferenceAttributes.FUNCTION_SCOPE, function); |
| enterContext(function); |
| Set<IProblemIdentifier> suppressed = null; |
| try { |
| if (reporter != null && !method.getSuppressedWarnings().isEmpty()) { |
| suppressed = new HashSet<IProblemIdentifier>(); |
| for (IProblemCategory category : method.getSuppressedWarnings()) { |
| suppressed.addAll(category.contents()); |
| } |
| reporter.pushSuppressWarnings(suppressed); |
| } |
| visitFunctionBody(node); |
| } finally { |
| if (reporter != null && suppressed != null) { |
| reporter.popSuppressWarnings(); |
| } |
| leaveContext(); |
| result.setAttribute(IReferenceAttributes.RESOLVING, null); |
| } |
| final IValueReference returnValue = result |
| .getChild(IValueReference.FUNCTION_OP); |
| returnValue.addValue(function.getReturnValue(), true); |
| setTypeImpl(returnValue, method.getType()); |
| return result; |
| } |
| |
| /** |
| * Creates "declaration" model for the specified function AST node. Is |
| * called once before processing this function. |
| * |
| * @param node |
| * @return |
| */ |
| protected JSMethod createMethod(FunctionStatement node) { |
| final JSMethod method = new JSMethod(node, getSource()); |
| for (IModelBuilder extension : context.getModelBuilders()) { |
| extension.processMethod(node, method, reporter, getTypeChecker()); |
| } |
| if (node.isInlineBlock() && method.getParameterCount() > 0) { |
| final IParameter last = method.getParameters().get( |
| method.getParameterCount() - 1); |
| if (last.getType() == null |
| && last.getKind() == ParameterKind.NORMAL |
| && ITypeNames.UNDEFINED.equals(last.getName())) { |
| last.setKind(ParameterKind.OPTIONAL); |
| } |
| } |
| if (listeners != null) { |
| for (ITypeInferenceListener listener : listeners) { |
| listener.methodParsed(method); |
| } |
| } |
| return method; |
| } |
| |
| public void visitFunctionBody(FunctionStatement node) { |
| handleDeclarations(node); |
| visit(node.getBody()); |
| } |
| |
| public void setType(IValueReference value, JSType type, boolean lazyEnabled) { |
| setTypeImpl(value, type, lazyEnabled); |
| } |
| |
| private void setTypeImpl(IValueReference value, JSType type) { |
| setTypeImpl(value, type, true); |
| } |
| |
| private void setTypeImpl(IValueReference value, JSType type, |
| boolean lazyEnabled) { |
| if (type == null) { |
| return; |
| } |
| final IRType rt = context.contextualize(type); |
| setIRType(value, rt, lazyEnabled); |
| } |
| |
| /** |
| * @param value |
| * @param rt |
| * @param lazyEnabled |
| */ |
| private void setIRType(IValueReference value, final IRType rt, |
| boolean lazyEnabled) { |
| if (rt instanceof IRSimpleType) { |
| final Type t = ((IRSimpleType) rt).getTarget(); |
| if (t.getKind() != TypeKind.UNKNOWN) { |
| value.setDeclaredType(rt); |
| if (value instanceof IValueProvider) { |
| for (IMemberEvaluator evaluator : TypeInfoManager |
| .getMemberEvaluators()) { |
| final IValueCollection collection = evaluator.valueOf( |
| context, t); |
| if (collection != null) { |
| if (collection instanceof IValueProvider) { |
| ((IValueProvider) value).getValue().addValue( |
| ((IValueProvider) collection) |
| .getValue()); |
| } |
| } |
| } |
| } |
| } else if (lazyEnabled) { |
| value.addValue(new LazyTypeReference(context, t.getName(), |
| peekContext()), false); |
| } |
| } else { |
| value.setDeclaredType(rt); |
| } |
| } |
| |
| @Override |
| public IValueReference visitGetAllChildrenExpression( |
| GetAllChildrenExpression node) { |
| return new XMLListValue(peekContext()); |
| } |
| |
| @Override |
| public IValueReference visitGetArrayItemExpression( |
| GetArrayItemExpression node) { |
| final IValueReference array = visit(node.getArray()); |
| visit(node.getIndex()); |
| if (array != null) { |
| // always just create the ARRAY_OP child (for code completion) |
| IValueReference child = array.getChild(IValueReference.ARRAY_OP); |
| IRType arrayType = null; |
| if (array.getDeclaredType() != null) { |
| arrayType = TypeUtil.extractArrayItemType(array |
| .getDeclaredType()); |
| } else { |
| JSTypeSet types = array.getTypes(); |
| if (types.size() > 0) |
| arrayType = TypeUtil.extractArrayItemType(types.toRType()); |
| } |
| if (arrayType != null && child.getDeclaredType() == null) { |
| setIRType(child, arrayType, true); |
| } |
| if (node.getIndex() instanceof StringLiteral) { |
| IValueReference namedChild = extractNamedChild(array, |
| node.getIndex()); |
| if (namedChild.exists()) { |
| child = namedChild; |
| if (arrayType != null && child.getDeclaredType() == null) { |
| child.setDeclaredType(arrayType); |
| } |
| } |
| } |
| return child; |
| } |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitGetLocalNameExpression( |
| GetLocalNameExpression node) { |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitIdentifier(Identifier node) { |
| return peekContext().getChild(node.getName()); |
| |
| } |
| |
| private Boolean evaluateCondition(Expression condition) { |
| if (condition instanceof BooleanLiteral) { |
| return Boolean.valueOf(((BooleanLiteral) condition).getText()); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public IValueReference visitIfStatement(IfStatement node) { |
| visit(node.getCondition()); |
| visitIfStatements(node); |
| return null; |
| } |
| |
| protected void visitIfStatements(IfStatement node) { |
| final List<Statement> statements = new ArrayList<Statement>(2); |
| Statement onlyBranch = null; |
| final Boolean condition = evaluateCondition(node.getCondition()); |
| if ((condition == null || condition.booleanValue()) |
| && node.getThenStatement() != null) { |
| statements.add(node.getThenStatement()); |
| if (condition != null && condition.booleanValue()) { |
| onlyBranch = node.getThenStatement(); |
| } |
| } |
| if ((condition == null || !condition.booleanValue()) |
| && node.getElseStatement() != null) { |
| statements.add(node.getElseStatement()); |
| if (condition != null && !condition.booleanValue()) { |
| onlyBranch = node.getElseStatement(); |
| } |
| } |
| if (!statements.isEmpty()) { |
| if (statements.size() == 1) { |
| if (statements.get(0) == onlyBranch) { |
| visit(statements.get(0)); |
| } else { |
| final Branching branching = branching(); |
| visit(statements.get(0)); |
| branching.end(); |
| } |
| } else { |
| final Branching branching = branching(); |
| final List<NestedValueCollection> collections = new ArrayList<NestedValueCollection>( |
| statements.size()); |
| for (Statement statement : statements) { |
| final NestedValueCollection nestedCollection = new NestedValueCollection( |
| peekContext()); |
| enterContext(nestedCollection); |
| visit(statement); |
| leaveContext(); |
| collections.add(nestedCollection); |
| } |
| NestedValueCollection.mergeTo(peekContext(), collections); |
| branching.end(); |
| } |
| } |
| } |
| |
| @Override |
| public IValueReference visitLabelledStatement(LabelledStatement node) { |
| if (node.getStatement() != null) |
| visit(node.getStatement()); |
| return null; |
| } |
| |
| protected static class AnonymousNewValue extends AnonymousValue { |
| @Override |
| public IValueReference getChild(String name) { |
| if (name.equals(IValueReference.FUNCTION_OP)) |
| return this; |
| return super.getChild(name); |
| } |
| |
| @Override |
| public void setValue(IValueReference value) { |
| if (value instanceof ThisValue) { |
| // make sure a copy is created so that the this values of |
| // various instances are not shared over those instances. |
| IValue val = createValue(); |
| if (val != null) |
| val.addValue(((ThisValue) value).getValue()); |
| } else { |
| super.setValue(value); |
| } |
| } |
| } |
| |
| public static class VisitNewResult { |
| IValueReference typeValue; |
| IValueReference[] arguments; |
| IValueReference value; |
| |
| public IValueReference getValue() { |
| return value; |
| } |
| |
| public IValueReference getTypeValue() { |
| return typeValue; |
| } |
| |
| public IValueReference[] getArguments() { |
| return arguments; |
| } |
| } |
| |
| protected VisitNewResult visitNew(NewExpression node) { |
| final VisitNewResult result = new VisitNewResult(); |
| Expression objectClass = node.getObjectClass(); |
| if (objectClass instanceof CallExpression) { |
| final CallExpression call = (CallExpression) objectClass; |
| result.arguments = new IValueReference[call.getArguments().size()]; |
| int index = 0; |
| for (ASTNode argument : call.getArguments()) { |
| result.arguments[index++] = visit(argument); |
| } |
| objectClass = call.getExpression(); |
| } else { |
| result.arguments = new IValueReference[0]; |
| } |
| result.typeValue = visit(objectClass); |
| |
| if (result.typeValue != null) { |
| if (result.typeValue.getKind() == ReferenceKind.FUNCTION) { |
| Object fs = result.typeValue |
| .getAttribute(IReferenceAttributes.FUNCTION_SCOPE); |
| if (fs instanceof IValueCollection |
| && ((IValueCollection) fs).getThis() != null) { |
| result.value = new AnonymousNewValue(); |
| result.value.setValue(((IValueCollection) fs).getThis()); |
| result.value.setKind(ReferenceKind.TYPE); |
| String className = PropertyExpressionUtils |
| .getPath(objectClass); |
| IRMethod method = (IRMethod) result.typeValue |
| .getAttribute(IReferenceAttributes.R_METHOD); |
| if (method != null) { |
| // if a method is found, then check if that |
| // method name 'MyObject' is in the |
| // classname 'xxx.yyy.MyObject' then use the full |
| // classname else use the methodname |
| // (new myVar() when var myVar = MyObject) |
| String methodName = method.getName(); |
| if (className == null |
| || !className.endsWith(methodName)) { |
| className = methodName; |
| } |
| } |
| if (className != null && !className.equals("<anonymous>")) { |
| result.value.setDeclaredType(RTypes.localType( |
| className, result.typeValue)); |
| } else { |
| result.value.setDeclaredType(RTypes.OBJECT); |
| } |
| } |
| } else if (result.typeValue.exists()) { |
| for (IRType type : result.typeValue.getDeclaredTypes()) { |
| if (type instanceof IRClassType) { |
| result.value = new AnonymousNewValue(); |
| result.value.setKind(ReferenceKind.TYPE); |
| result.value.setDeclaredType(((IRClassType) type) |
| .newItemType()); |
| return result; |
| } |
| } |
| for (IRType type : result.typeValue.getTypes()) { |
| if (type instanceof IRClassType) { |
| result.value = new AnonymousNewValue(); |
| result.value.setKind(ReferenceKind.TYPE); |
| result.value.setDeclaredType(((IRClassType) type) |
| .newItemType()); |
| return result; |
| } |
| } |
| } |
| } |
| if (result.value == null) { |
| final String className = PropertyExpressionUtils |
| .getPath(objectClass); |
| IValueCollection contextValueCollection = peekContext(); |
| if (className != null) { |
| Type knownType = context.getKnownType(className, TypeMode.CODE); |
| if (knownType != null) { |
| result.value = new AnonymousNewValue(); |
| result.value.setValue(new ConstantValue(RTypes.simple( |
| context, knownType))); |
| result.value.setKind(ReferenceKind.TYPE); |
| } else { |
| result.value = new LazyTypeReference(context, className, |
| contextValueCollection); |
| } |
| } else { |
| result.value = new AnonymousNewValue(); |
| result.value.setValue(ConstantValue.of(RTypes.OBJECT)); |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public IValueReference visitNewExpression(NewExpression node) { |
| return visitNew(node).getValue(); |
| } |
| |
| @Override |
| public IValueReference visitNullExpression(NullExpression node) { |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitObjectInitializer(ObjectInitializer node) { |
| final List<IRRecordMember> members = new ArrayList<IRRecordMember>(node |
| .getInitializers().size()); |
| for (ObjectInitializerPart part : node.getInitializers()) { |
| if (part instanceof PropertyInitializer) { |
| final PropertyInitializer pi = (PropertyInitializer) part; |
| final String childName = PropertyExpressionUtils.nameOf(pi |
| .getName()); |
| if (childName == null) { // just in case |
| visit(pi.getValue()); |
| continue; |
| } |
| final JSDocTags tags = parseTags(pi.getName() |
| .getDocumentation()); |
| |
| final IValueReference value = visit(pi.getValue()); |
| if (value != null) { |
| final IRMethod method = (IRMethod) value |
| .getAttribute(IReferenceAttributes.R_METHOD); |
| if (method != null |
| && (pi.getValue() instanceof FunctionStatement || tags |
| .get(JSDocTag.TYPE) == null)) { |
| if (method.getSource() instanceof IMethod) { |
| final IMethod m = (IMethod) method.getSource(); |
| final ReferenceLocation loc = m.getLocation(); |
| m.setLocation(ReferenceLocation.create(getSource(), |
| loc.getDeclarationStart(), loc |
| .getDeclarationEnd(), pi.getName() |
| .sourceStart(), pi.getName() |
| .sourceEnd())); |
| } |
| IRType returnType = method.getType(); |
| if (returnType == null) { |
| returnType = JavaScriptValidations.typeOf(value |
| .getChild(IValueReference.FUNCTION_OP)); |
| } |
| members.add(new RRecordMember(childName, RTypes |
| .functionType(context, method.getParameters(), |
| returnType), method.getSource())); |
| continue; |
| } |
| } |
| final JSVariable source; |
| if (!(pi.getValue() instanceof FunctionStatement)) { |
| source = new JSVariable(); |
| source.setName(childName); |
| source.setLocation(ReferenceLocation.create(getSource(), pi |
| .getName().sourceStart(), pi.getName().sourceEnd())); |
| if (!tags.isEmpty()) { |
| final JSDocSupport jsdocSupport = getDocSupport(); |
| if (jsdocSupport != null) { |
| jsdocSupport.parseType(source, tags, |
| JSDocSupport.TYPE_TAGS, reporter, |
| getTypeChecker()); |
| jsdocSupport.parseDeprecation(source, tags, |
| reporter); |
| } |
| } |
| } else { |
| source = null; |
| } |
| final IRType type; |
| if (source != null && source.getType() != null) { |
| type = RTypes.create(context, source.getType()); |
| // TODO (alex) validate evaluated type? |
| } else { |
| type = JavaScriptValidations.typeOf(value); |
| } |
| members.add(new RRecordMember(childName, type != null ? type |
| : RTypes.any(), source)); |
| } else { |
| // TODO handle get/set methods |
| } |
| } |
| return ConstantValue.of(RTypes.recordType(members)); |
| } |
| |
| private JSDocTags parseTags(final Comment documentation) { |
| return documentation != null ? JSDocSupport.parse(documentation) |
| : JSDocTags.EMPTY; |
| } |
| |
| private JSDocSupport getDocSupport() { |
| for (IModelBuilder modelBuilder : this.context.getModelBuilders()) { |
| if (modelBuilder instanceof JSDocSupport) { |
| return (JSDocSupport) modelBuilder; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitParenthesizedExpression( |
| ParenthesizedExpression node) { |
| return visit(node.getExpression()); |
| } |
| |
| @Override |
| public IValueReference visitPropertyExpression(PropertyExpression node) { |
| final IValueReference object = visit(node.getObject()); |
| return extractNamedChild(object, node.getProperty()); |
| } |
| |
| protected IValueReference extractNamedChild(IValueReference parent, |
| Expression name) { |
| if (parent != null) { |
| final String nameStr; |
| if (name instanceof Identifier) { |
| nameStr = ((Identifier) name).getName(); |
| IRType parentType = JavaScriptValidations.typeOf(parent); |
| if (parentType != null && isXML(parentType)) { |
| IValueReference child = parent.getChild(nameStr); |
| if (child != null && child.getDeclaredType() == null) { |
| child.setDeclaredType(E4XTypes.XML); |
| return child; |
| } |
| } |
| } else if (name instanceof StringLiteral) { |
| nameStr = ((StringLiteral) name).getValue(); |
| } else if (name instanceof XmlAttributeIdentifier) { |
| if (((XmlAttributeIdentifier) name).getExpression() instanceof AsteriskExpression) { |
| return visitAsteriskExpression((AsteriskExpression) ((XmlAttributeIdentifier) name) |
| .getExpression()); |
| } else { |
| nameStr = ((XmlAttributeIdentifier) name) |
| .getAttributeName(); |
| if (nameStr == null) { |
| // TODO (alex) .@[expression] syntax |
| return null; |
| } |
| IValueReference child = parent.getChild(nameStr); |
| if (child != null && child.getDeclaredType() == null) { |
| child.setDeclaredType(E4XTypes.XML); |
| return child; |
| } |
| } |
| } else if (name instanceof AsteriskExpression) { |
| return visitAsteriskExpression((AsteriskExpression) name); |
| } else if (name instanceof ParenthesizedExpression) { |
| visitParenthesizedExpression((ParenthesizedExpression) name); |
| return parent; |
| } else { |
| return null; |
| } |
| return parent.getChild(nameStr); |
| } |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitRegExpLiteral(RegExpLiteral node) { |
| return new ConstantValue(RTypes.REGEXP); |
| } |
| |
| @Override |
| public IValueReference visitReturnStatement(ReturnStatement node) { |
| if (node.getValue() != null) { |
| final IValueReference value = visit(node.getValue()); |
| if (value != null) { |
| final IValueReference returnValue = peekContext() |
| .getReturnValue(); |
| if (returnValue != null) { |
| returnValue.addValue(value, |
| !(value instanceof LazyTypeReference)); |
| } |
| } |
| return value; |
| } |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitScript(Script node) { |
| handleDeclarations(node); |
| return visit(node.getStatements()); |
| } |
| |
| private void handleDeclarations(JSScope scope) { |
| ArrayList<IValueReference> variables = new ArrayList<IValueReference>(); |
| ArrayList<ForwardDeclaration> forwardDecls = new ArrayList<ForwardDeclaration>(); |
| final IValueCollection context = peekContext(); |
| for (JSDeclaration declaration : scope.getDeclarations()) { |
| if (declaration instanceof FunctionStatement) { |
| final FunctionStatement funcNode = (FunctionStatement) declaration; |
| assert funcNode.isDeclaration(); |
| final JSMethod method = createMethod(funcNode); |
| final IValueReference function = context.createChild(method |
| .getName()); |
| initializeFunction(method, function); |
| ForwardDeclaration fd = new ForwardDeclaration(method, function); |
| forwardDecls.add(fd); |
| forwardDeclarations.put(funcNode, fd); |
| } else if (declaration instanceof VariableDeclaration) { |
| final VariableDeclaration varDeclaration = (VariableDeclaration) declaration; |
| final IValueReference var = createVariable(context, |
| varDeclaration); |
| if (varDeclaration.getParent() instanceof ConstStatement) { |
| var.setAttribute(IAssignProtection.ATTRIBUTE, PROTECT_CONST); |
| } |
| variables.add(var); |
| } |
| } |
| for (ForwardDeclaration decl : forwardDecls) { |
| decl.reference.setAttribute(IReferenceAttributes.R_METHOD, |
| RModelBuilder.create(getContext(), decl.method)); |
| if (decl.method.isConstructor()) { |
| String name = decl.method.getName(); |
| |
| if (scope instanceof FunctionStatement) { |
| JSNode parent = (FunctionStatement) scope; |
| while (parent != null) { |
| if (parent instanceof FunctionStatement) { |
| FunctionStatement fs = (FunctionStatement) parent; |
| if (fs.getName() != null) { |
| name = fs.getName().getName() + '.' + name; |
| } else { |
| parent = fs.getParent(); |
| if (parent instanceof BinaryOperation) { |
| Expression leftExpression = ((BinaryOperation) parent) |
| .getLeftExpression(); |
| if (leftExpression instanceof PropertyExpression |
| && ((PropertyExpression) leftExpression) |
| .getObject() instanceof ThisExpression |
| && ((PropertyExpression) leftExpression) |
| .getProperty() instanceof Identifier) { |
| name = ((Identifier) ((PropertyExpression) leftExpression) |
| .getProperty()).getName() |
| + '.' |
| + name; |
| } |
| } |
| } |
| } |
| parent = parent.getParent(); |
| } |
| } |
| // this will create a local type |
| this.context.getType(name); |
| } |
| } |
| |
| for (IValueReference reference : variables) { |
| final IVariable var = (IVariable) reference |
| .getAttribute(IReferenceAttributes.VARIABLE); |
| if (var == null) { |
| // attribute was lost or new value was not created as |
| // something predefined was already there. |
| continue; |
| } |
| final IRVariable rvar = RModelBuilder.create(getContext(), var); |
| reference.setAttribute(IReferenceAttributes.R_VARIABLE, rvar); |
| if (rvar.getType() != null) { |
| setIRType(reference, rvar.getType(), true); |
| // typed - make sure it wasn't initialized with the phantom |
| // value. |
| reference.removeReference(PhantomValueReference.REFERENCE); |
| } |
| |
| } |
| |
| } |
| |
| @Override |
| public IValueReference visitStatementBlock(StatementBlock node) { |
| for (Statement statement : node.getStatements()) { |
| visit(statement); |
| } |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitStringLiteral(StringLiteral node) { |
| return ConstantValue.of(RTypes.STRING); |
| } |
| |
| @Override |
| public IValueReference visitSwitchStatement(SwitchStatement node) { |
| if (node.getCondition() != null) |
| visit(node.getCondition()); |
| for (SwitchComponent component : node.getCaseClauses()) { |
| if (component instanceof CaseClause) { |
| visit(((CaseClause) component).getCondition()); |
| } |
| visit(component.getStatements()); |
| } |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitThisExpression(ThisExpression node) { |
| return peekContext().getThis(); |
| } |
| |
| @Override |
| public IValueReference visitThrowStatement(ThrowStatement node) { |
| if (node.getException() != null) |
| visit(node.getException()); |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitTryStatement(TryStatement node) { |
| visit(node.getBody()); |
| for (CatchClause catchClause : node.getCatches()) { |
| final NestedValueCollection collection = new NestedValueCollection( |
| peekContext()); |
| final Identifier id = catchClause.getException(); |
| final IValueReference var = collection.createChild(id.getName()); |
| final JSDocTags tags = parseTags(id.getDocumentation()); |
| final JSElement variable = new JSElement(id.getName()); |
| getDocSupport().parseType(variable, tags, JSDocSupport.TYPE_TAGS, |
| reporter, getTypeChecker()); |
| var.setDeclaredType(variable.getType() != null ? context |
| .contextualize(variable.getType()) : RTypes.ERROR); |
| |
| enterContext(collection); |
| if (catchClause.getFilterExpression() != null) { |
| visit(catchClause.getFilterExpression()); |
| } |
| if (catchClause.getStatement() != null) { |
| visit(catchClause.getStatement()); |
| } |
| leaveContext(); |
| } |
| if (node.getFinally() != null) { |
| visit(node.getFinally().getStatement()); |
| } |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitUnaryOperation(UnaryOperation node) { |
| if (node.getOperation() == JSParser.NOT) { |
| visit(node.getExpression()); |
| return ConstantValue.of(RTypes.BOOLEAN); |
| } else if (node.getOperation() == JSParser.DELETE) { |
| final IValueReference value = visit(node.getExpression()); |
| if (value != null) { |
| value.delete(false); |
| } |
| return ConstantValue.of(RTypes.BOOLEAN); |
| } else if (node.getOperation() == JSParser.TYPEOF) { |
| visit(node.getExpression()); |
| return ConstantValue.of(RTypes.STRING); |
| } else if (node.getOperation() == JSParser.VOID) { |
| visit(node.getExpression()); |
| return null; |
| } else { |
| return visit(node.getExpression()); |
| } |
| } |
| |
| @Override |
| public IValueReference visitVariableStatement(VariableStatement node) { |
| final IValueCollection collection = peekContext(); |
| IValueReference result = null; |
| for (VariableDeclaration declaration : node.getVariables()) { |
| result = collection.getChild(declaration.getVariableName()); |
| assert result.exists(); |
| initializeVariable(result, declaration); |
| } |
| return result; |
| } |
| |
| @Override |
| public IValueReference visitVoidExpression(VoidExpression node) { |
| visit(node.getExpression()); |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitWhileStatement(WhileStatement node) { |
| if (node.getCondition() != null) |
| visit(node.getCondition()); |
| if (node.getBody() != null) |
| visit(node.getBody()); |
| return null; |
| } |
| |
| @Override |
| public IValueReference visitWithStatement(WithStatement node) { |
| final IValueReference with = visit(node.getExpression()); |
| if (with != null) { |
| final WithValueCollection withCollection = new WithValueCollection( |
| peekContext(), with); |
| enterContext(withCollection); |
| visit(node.getStatement()); |
| leaveContext(); |
| } else { |
| visit(node.getStatement()); |
| } |
| return null; |
| } |
| |
| private static final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory |
| .newInstance(); |
| private DocumentBuilder docBuilder; |
| |
| /** |
| * @return |
| * @throws ParserConfigurationException |
| */ |
| private DocumentBuilder getDocumentBuilder() |
| throws ParserConfigurationException { |
| if (docBuilder == null) |
| docBuilder = docBuilderFactory.newDocumentBuilder(); |
| return docBuilder; |
| } |
| |
| @Override |
| public IValueReference visitXmlLiteral(XmlLiteral node) { |
| IValueReference xmlValueReference = new ConstantValue(E4XTypes.XML); |
| |
| if (xmlValueReference instanceof IValueProvider) { |
| IValue xmlValue = ((IValueProvider) xmlValueReference).getValue(); |
| List<XmlFragment> fragments = node.getFragments(); |
| StringBuilder xml = new StringBuilder(); |
| for (XmlFragment xmlFragment : fragments) { |
| if (xmlFragment instanceof XmlTextFragment) { |
| String xmlText = ((XmlTextFragment) xmlFragment).getXml(); |
| if (xmlText.equals("<></>")) |
| continue; |
| if (xmlText.startsWith("<>") && xmlText.endsWith("</>")) { |
| xmlText = "<xml>" |
| + xmlText.substring(2, xmlText.length() - 3) |
| + "</xml>"; |
| } |
| xml.append(xmlText); |
| } else if (xmlFragment instanceof XmlExpressionFragment) { |
| Expression expression = ((XmlExpressionFragment) xmlFragment) |
| .getExpression(); |
| visit(expression); |
| if (xml.charAt(xml.length() - 1) == '<' |
| || xml.subSequence(xml.length() - 2, xml.length()) |
| .equals("</")) { |
| if (expression instanceof Identifier) { |
| xml.append(((Identifier) expression).getName()); |
| } else { |
| xml.setLength(0); |
| break; |
| } |
| } else |
| xml.append("\"\" "); |
| } |
| } |
| |
| if (xml.length() > 0) { |
| try { |
| DocumentBuilder docBuilder = getDocumentBuilder(); |
| Document doc = docBuilder.parse(new InputSource( |
| new StringReader(xml.toString()))); |
| NodeList nl = doc.getChildNodes(); |
| if (nl.getLength() == 1) { |
| Node item = nl.item(0); |
| NamedNodeMap attributes = item.getAttributes(); |
| for (int a = 0; a < attributes.getLength(); a++) { |
| Node attribute = attributes.item(a); |
| xmlValue.createChild("@" + attribute.getNodeName(), |
| 0); |
| } |
| createXmlChilds(xmlValue, item.getChildNodes()); |
| } else { |
| System.err.println("root should be 1 child?? " + xml); |
| } |
| } catch (Exception e) { |
| } |
| } |
| } |
| return xmlValueReference; |
| } |
| |
| /** |
| * @param xmlType |
| * @param xmlValue |
| * @param nl |
| */ |
| private void createXmlChilds(IValue xmlValue, NodeList nl) { |
| for (int i = 0; i < nl.getLength(); i++) { |
| Node item = nl.item(i); |
| if (item.getNodeType() == Node.TEXT_NODE) { |
| String value = item.getNodeValue(); |
| if (value == null || "".equals(value.trim())) { |
| continue; |
| } |
| } |
| IValue nodeValue = xmlValue.createChild(item.getNodeName(), 0); |
| nodeValue.setDeclaredType(E4XTypes.XML); |
| NamedNodeMap attributes = item.getAttributes(); |
| if (attributes != null) { |
| for (int a = 0; a < attributes.getLength(); a++) { |
| Node attribute = attributes.item(a); |
| nodeValue.createChild("@" + attribute.getNodeName(), 0); |
| } |
| } |
| createXmlChilds(nodeValue, item.getChildNodes()); |
| } |
| } |
| |
| @Override |
| public IValueReference visitXmlPropertyIdentifier( |
| XmlAttributeIdentifier node) { |
| return new ConstantValue(E4XTypes.XML); |
| } |
| |
| @Override |
| public IValueReference visitYieldOperator(YieldOperator node) { |
| final IValueReference value = visit(node.getExpression()); |
| if (value != null) { |
| final IValueReference reference = peekContext().getReturnValue(); |
| if (reference != null) { |
| reference.addValue(value, true); |
| } |
| } |
| return null; |
| } |
| |
| public static boolean isFunctionDeclaration(Expression expression) { |
| PropertyExpression pe = null; |
| if (expression instanceof PropertyExpression) |
| pe = (PropertyExpression) expression; |
| else if (expression.getParent() instanceof PropertyExpression) |
| pe = (PropertyExpression) expression.getParent(); |
| if (pe != null && pe.getObject() instanceof ThisExpression |
| && pe.getParent() instanceof BinaryOperation) { |
| return ((BinaryOperation) pe.getParent()).getRightExpression() instanceof FunctionStatement; |
| } |
| return false; |
| } |
| |
| } |