/* * Copyright 2000-2013 JetBrains s.r.o. * Copyright 2014-2015 AS3Boyan * Copyright 2014-2014 Elias Ku * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.plugins.haxe.model.type; import com.intellij.lang.ASTNode; import com.intellij.lang.annotation.Annotation; import com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypeSets; import com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypes; import com.intellij.plugins.haxe.lang.psi.*; import com.intellij.plugins.haxe.lang.psi.impl.AbstractHaxeNamedComponent; import com.intellij.plugins.haxe.model.HaxeClassModel; import com.intellij.plugins.haxe.model.HaxeMethodModel; import com.intellij.plugins.haxe.model.fixer.*; import com.intellij.plugins.haxe.util.HaxeJavaUtil; import com.intellij.plugins.haxe.util.HaxeResolveUtil; import com.intellij.plugins.haxe.util.HaxeStringUtil; import com.intellij.plugins.haxe.util.UsefulPsiTreeUtil; import com.intellij.psi.PsiCodeBlock; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiJavaToken; import com.intellij.psi.PsiReference; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; public class HaxeExpressionEvaluator { @NotNull static public HaxeExpressionEvaluatorContext evaluate(PsiElement element, HaxeExpressionEvaluatorContext context) { context.result = handle(element, context); return context; } @NotNull static private ResultHolder handle(final PsiElement element, final HaxeExpressionEvaluatorContext context) { try { return _handle(element, context); } catch (Throwable t) { t.printStackTrace(); return SpecificHaxeClassReference.getUnknown(element).createHolder(); } } @NotNull static private ResultHolder _handle(final PsiElement element, final HaxeExpressionEvaluatorContext context) { if (element == null) { System.out.println("getPsiElementType: " + element); return SpecificHaxeClassReference.getUnknown(element).createHolder(); } //System.out.println("Handling element: " + element.getClass()); if (element instanceof PsiCodeBlock) { context.beginScope(); ResultHolder type = SpecificHaxeClassReference.getUnknown(element).createHolder(); boolean deadCode = false; for (PsiElement childElement : element.getChildren()) { type = handle(childElement, context); if (deadCode) { //context.addWarning(childElement, "Unreachable statement"); context.addUnreachable(childElement); } if (childElement instanceof HaxeReturnStatement) { deadCode = true; } } context.endScope(); return type; } if (element instanceof HaxeReturnStatement) { PsiElement[] children = element.getChildren(); ResultHolder result = SpecificHaxeClassReference.getVoid(element).createHolder(); if (children.length >= 1) { result = handle(children[0], context); } context.addReturnType(result, element); return result; } if (element instanceof HaxeIterable) { return handle(((HaxeIterable)element).getExpression(), context); } if (element instanceof HaxeForStatement) { final HaxeComponentName name = ((HaxeForStatement)element).getComponentName(); final HaxeIterable iterable = ((HaxeForStatement)element).getIterable(); final PsiElement body = element.getLastChild(); context.beginScope(); try { final SpecificTypeReference iterableValue = handle(iterable, context).getType(); SpecificTypeReference type = iterableValue.getIterableElementType(iterableValue).getType(); if (iterableValue.isConstant()) { final Object constant = iterableValue.getConstant(); if (constant instanceof HaxeRange) { type = type.withRangeConstraint((HaxeRange)constant); } } if (name != null) { context.setLocal(name.getText(), new ResultHolder(type)); } return handle(body, context); //System.out.println(name); //System.out.println(iterable); } finally { context.endScope(); } } if (element instanceof HaxeNewExpression) { ResultHolder typeHolder = HaxeTypeResolver.getTypeFromType(((HaxeNewExpression)element).getType()); if (typeHolder.getType() instanceof SpecificHaxeClassReference) { final HaxeClassModel clazz = ((SpecificHaxeClassReference)typeHolder.getType()).getHaxeClassModel(); if (clazz != null) { HaxeMethodModel constructor = clazz.getConstructor(); if (constructor == null) { context.addError(element, "Class " + clazz.getName() + " doesn't have a constructor", new HaxeFixer("Create constructor") { @Override public void run() { // @TODO: Check arguments clazz.addMethod("new"); } }); } else { checkParameters(element, constructor, ((HaxeNewExpression)element).getExpressionList(), context); } } } return typeHolder.duplicate(); } if (element instanceof HaxeThisExpression) { //PsiReference reference = element.getReference(); //HaxeClassResolveResult result = HaxeResolveUtil.getHaxeClassResolveResult(element); HaxeClass ancestor = UsefulPsiTreeUtil.getAncestor(element, HaxeClass.class); if (ancestor == null) return SpecificTypeReference.getDynamic(element).createHolder(); HaxeClassModel model = ancestor.getModel(); if (model.isAbstract()) { HaxeTypeOrAnonymous type = model.getAbstractUnderlyingType(); if (type != null) { HaxeClass aClass = HaxeResolveUtil.tryResolveClassByQName(type); if (aClass != null) { return SpecificHaxeClassReference.withoutGenerics(new HaxeClassReference(aClass.getModel(), element), element).createHolder(); } } } return SpecificHaxeClassReference.primitive(ancestor.getQualifiedName(), element).createHolder(); } if (element instanceof HaxeIdentifier) { //PsiReference reference = element.getReference(); ResultHolder holder = context.get(element.getText()); if (holder == null) { context.addError(element, "Unknown variable", new HaxeCreateLocalVariableFixer(element.getText(), element)); return SpecificTypeReference.getDynamic(element).createHolder(); } return holder; //System.out.println("HaxeIdentifier:" + reference); } if (element instanceof HaxeCastExpression) { handle(((HaxeCastExpression)element).getExpression(), context); HaxeTypeOrAnonymous anonymous = ((HaxeCastExpression)element).getTypeOrAnonymous(); if (anonymous != null) { return HaxeTypeResolver.getTypeFromTypeOrAnonymous(anonymous); } else { return SpecificHaxeClassReference.getUnknown(element).createHolder(); } } if (element instanceof HaxeWhileStatement) { List<HaxeExpression> list = ((HaxeWhileStatement)element).getExpressionList(); SpecificTypeReference type = null; HaxeExpression lastExpression = null; for (HaxeExpression expression : list) { type = handle(expression, context).getType(); lastExpression = expression; } if (type == null) { type = SpecificTypeReference.getDynamic(element); } if (!type.isBool() && lastExpression != null) { context.addError( lastExpression, "While expression must be boolean", new HaxeCastFixer(lastExpression, type, SpecificHaxeClassReference.getBool(element)) ); } PsiElement body = element.getLastChild(); if (body != null) { //return SpecificHaxeClassReference.createArray(result); // @TODO: Check this return handle(body, context); } return SpecificHaxeClassReference.getUnknown(element).createHolder(); } if (element instanceof HaxeLocalVarDeclaration) { for (HaxeLocalVarDeclarationPart part : ((HaxeLocalVarDeclaration)element).getLocalVarDeclarationPartList()) { handle(part, context); } return SpecificHaxeClassReference.getUnknown(element).createHolder(); } if (element instanceof HaxeAssignExpression) { final PsiElement left = element.getFirstChild(); final PsiElement right = element.getLastChild(); if (left != null && right != null) { final ResultHolder leftResult = handle(left, context); final ResultHolder rightResult = handle(right, context); if (leftResult.isUnknown()) { leftResult.setType(rightResult.getType()); } leftResult.removeConstant(); final SpecificTypeReference leftValue = leftResult.getType(); final SpecificTypeReference rightValue = rightResult.getType(); //leftValue.mutateConstantValue(null); if (!leftResult.canAssign(rightResult)) { context.addError(element, "Can't assign " + rightValue + " to " + leftValue, new HaxeCastFixer(right, rightValue, leftValue)); } if (leftResult.isImmutable()) { context.addError(element, "Trying to change an immutable value"); } return rightResult; } return SpecificHaxeClassReference.getUnknown(element).createHolder(); } if (element instanceof HaxeLocalVarDeclarationPart) { final HaxeComponentName name = ((HaxeLocalVarDeclarationPart)element).getComponentName(); final HaxeVarInit init = ((HaxeLocalVarDeclarationPart)element).getVarInit(); final HaxeTypeTag typeTag = ((HaxeLocalVarDeclarationPart)element).getTypeTag(); ResultHolder result = SpecificHaxeClassReference.getUnknown(element).createHolder(); if (init != null) { result = handle(init, context); } if (typeTag != null) { result = HaxeTypeResolver.getTypeFromTypeTag(typeTag, element); } if (typeTag != null) { final ResultHolder tag = HaxeTypeResolver.getTypeFromTypeTag(typeTag, element); if (!tag.canAssign(result)) { result = tag.duplicate(); context.addError( element, "Can't assign " + result + " to " + tag, new HaxeTypeTagChangeFixer(typeTag, result.getType()), new HaxeTypeTagRemoveFixer(typeTag) ); } } if (name != null) { context.setLocal(name.getText(), result); } return result; } if (element instanceof HaxeVarInit) { return handle(((HaxeVarInit)element).getExpression(), context); } if (element instanceof HaxeReferenceExpression) { PsiElement[] children = element.getChildren(); ResultHolder typeHolder = handle(children[0], context); boolean resolved = true; for (int n = 1; n < children.length; n++) { String accessName = children[n].getText(); if (typeHolder.getType().isString() && typeHolder.getType().isConstant() && accessName.equals("code")) { String str = (String)typeHolder.getType().getConstant(); typeHolder = SpecificTypeReference.getInt(element, (str != null && str.length() >= 1) ? str.charAt(0) : -1).createHolder(); if (str == null || str.length() != 1) { context.addError(element, "String must be a single UTF8 char"); } } else { ResultHolder access = typeHolder.getType().access(accessName, context); if (access == null) { resolved = false; Annotation annotation = context.addError(children[n], "Can't resolve '" + accessName + "' in " + typeHolder.getType()); if (children.length == 1) { annotation.registerFix(new HaxeCreateLocalVariableFixer(accessName, element)); } else { annotation.registerFix(new HaxeCreateMethodFixer(accessName, element)); annotation.registerFix(new HaxeCreateFieldFixer(accessName, element)); } } typeHolder = access; } } // @TODO: this should be innecessary when code is working right! if (!resolved) { PsiReference reference = element.getReference(); if (reference != null) { PsiElement subelement = reference.resolve(); if (subelement instanceof AbstractHaxeNamedComponent) { typeHolder = HaxeTypeResolver.getFieldOrMethodReturnType((AbstractHaxeNamedComponent)subelement); } } } return (typeHolder != null) ? typeHolder : SpecificTypeReference.getDynamic(element).createHolder(); } if (element instanceof HaxeCallExpression) { HaxeCallExpression callelement = (HaxeCallExpression)element; HaxeExpression callLeft = ((HaxeCallExpression)element).getExpression(); SpecificTypeReference functionType = handle(callLeft, context).getType(); // @TODO: this should be innecessary when code is working right! if (functionType.isUnknown()) { if (callLeft instanceof HaxeReference) { PsiReference reference = callLeft.getReference(); if (reference != null) { PsiElement subelement = reference.resolve(); if (subelement instanceof HaxeMethod) { functionType = ((HaxeMethod)subelement).getModel().getFunctionType(); } } } } if (functionType.isUnknown()) { //System.out.println("Couldn't resolve " + callLeft.getText()); } List<HaxeExpression> parameterExpressions = null; if (callelement.getExpressionList() != null) { parameterExpressions = callelement.getExpressionList().getExpressionList(); } else { parameterExpressions = Collections.emptyList(); } if (functionType instanceof SpecificFunctionReference) { SpecificFunctionReference ftype = (SpecificFunctionReference)functionType; HaxeExpressionEvaluator.checkParameters(callelement, ftype, parameterExpressions, context); return ftype.getReturnType().duplicate(); } if (functionType.isDynamic()) { for (HaxeExpression expression : parameterExpressions) { handle(expression, context); } return functionType.withoutConstantValue().createHolder(); } // @TODO: resolve the function type return type return SpecificHaxeClassReference.getUnknown(element).createHolder(); } if (element instanceof HaxeLiteralExpression) { return handle(element.getFirstChild(), context); } if (element instanceof HaxeStringLiteralExpression) { // @TODO: check if it has string interpolation inside, in that case text is not constant return SpecificHaxeClassReference.primitive( "String", element, HaxeStringUtil.unescapeString(element.getText()) ).createHolder(); } if (element instanceof HaxeExpressionList) { ArrayList<ResultHolder> references = new ArrayList<ResultHolder>(); for (HaxeExpression expression : ((HaxeExpressionList)element).getExpressionList()) { references.add(handle(expression, context)); } return HaxeTypeUnifier.unifyHolders(references, element); } if (element instanceof HaxeArrayLiteral) { HaxeExpressionList list = ((HaxeArrayLiteral)element).getExpressionList(); if (list != null) { final List<HaxeExpression> list1 = list.getExpressionList(); if (list1.isEmpty()) { final PsiElement child = list.getFirstChild(); if ((child instanceof HaxeForStatement) || (child instanceof HaxeWhileStatement)) { return SpecificTypeReference.createArray(handle(child, context)).createHolder(); } } } ArrayList<SpecificTypeReference> references = new ArrayList<SpecificTypeReference>(); ArrayList<Object> constants = new ArrayList<Object>(); boolean allConstants = true; if (list != null) { for (HaxeExpression expression : list.getExpressionList()) { SpecificTypeReference type = handle(expression, context).getType(); if (!type.isConstant()) { allConstants = false; } else { constants.add(type.getConstant()); } references.add(type); } } ResultHolder elementTypeHolder = HaxeTypeUnifier.unify(references, element).withoutConstantValue().createHolder(); SpecificTypeReference result = SpecificHaxeClassReference.createArray(elementTypeHolder); if (allConstants) result = result.withConstantValue(constants); ResultHolder holder = result.createHolder(); return holder; } if (element instanceof PsiJavaToken) { IElementType type = ((PsiJavaToken)element).getTokenType(); if (type == HaxeTokenTypes.LITINT || type == HaxeTokenTypes.LITHEX || type == HaxeTokenTypes.LITOCT) { return SpecificHaxeClassReference.primitive("Int", element, Long.decode(element.getText())).createHolder(); } else if (type == HaxeTokenTypes.LITFLOAT) { return SpecificHaxeClassReference.primitive("Float", element, Double.parseDouble(element.getText())).createHolder(); } else if (type == HaxeTokenTypes.KFALSE || type == HaxeTokenTypes.KTRUE) { return SpecificHaxeClassReference.primitive("Bool", element, type == HaxeTokenTypes.KTRUE).createHolder(); } else if (type == HaxeTokenTypes.KNULL) { return SpecificHaxeClassReference.primitive("Dynamic", element, HaxeNull.instance).createHolder(); } else { //System.out.println("Unhandled token type: " + tokenType); return SpecificHaxeClassReference.getDynamic(element).createHolder(); } } if (element instanceof HaxeSuperExpression) { /* System.out.println("-------------------------"); final HaxeExpressionList list = HaxePsiUtils.getChildWithText(element, HaxeExpressionList.class); System.out.println(element); System.out.println(list); final List<HaxeExpression> parameters = (list != null) ? list.getExpressionList() : Collections.<HaxeExpression>emptyList(); final HaxeMethodModel method = HaxeJavaUtil.cast(HaxeMethodModel.fromPsi(element), HaxeMethodModel.class); if (method == null) { context.addError(element, "Not in a method"); } if (method != null) { final HaxeMethodModel parentMethod = method.getParentMethod(); if (parentMethod == null) { context.addError(element, "Calling super without parent constructor"); } else { System.out.println(element); System.out.println(parentMethod.getFunctionType()); System.out.println(parameters); checkParameters(element, parentMethod.getFunctionType(), parameters, context); //System.out.println(method); //System.out.println(parentMethod); } } return SpecificHaxeClassReference.getVoid(element); */ final HaxeMethodModel method = HaxeJavaUtil.cast(HaxeMethodModel.fromPsi(element), HaxeMethodModel.class); final HaxeMethodModel parentMethod = (method != null) ? method.getParentMethod() : null; if (parentMethod != null) { return parentMethod.getFunctionType().createHolder(); } context.addError(element, "Calling super without parent constructor"); return SpecificHaxeClassReference.getUnknown(element).createHolder(); } if (element instanceof HaxeIteratorExpression) { final List<HaxeExpression> list = ((HaxeIteratorExpression)element).getExpressionList(); if (list.size() >= 2) { final SpecificTypeReference left = handle(list.get(0), context).getType(); final SpecificTypeReference right = handle(list.get(1), context).getType(); Object constant = null; if (left.isConstant() && right.isConstant()) { constant = new HaxeRange( HaxeTypeUtils.getIntValue(left.getConstant()), HaxeTypeUtils.getIntValue(right.getConstant()) ); } return SpecificHaxeClassReference.getIterator(SpecificHaxeClassReference.getInt(element)).withConstantValue(constant) .createHolder(); } return SpecificHaxeClassReference.getUnknown(element).createHolder(); } if (element instanceof HaxeArrayAccessExpression) { final List<HaxeExpression> list = ((HaxeArrayAccessExpression)element).getExpressionList(); if (list.size() >= 2) { final SpecificTypeReference left = handle(list.get(0), context).getType(); final SpecificTypeReference right = handle(list.get(1), context).getType(); if (left.isArray()) { Object constant = null; if (left.isConstant()) { List array = (List)left.getConstant(); final HaxeRange constraint = right.getRangeConstraint(); HaxeRange arrayBounds = new HaxeRange(0, array.size()); if (right.isConstant()) { final int index = HaxeTypeUtils.getIntValue(right.getConstant()); if (arrayBounds.contains(index)) { constant = array.get(index); } else { context.addWarning(element, "Out of bounds " + index + " not inside " + arrayBounds); } } else if (constraint != null) { if (!arrayBounds.contains(constraint)) { context.addWarning(element, "Out of bounds " + constraint + " not inside " + arrayBounds); } } } return left.getArrayElementType().getType().withConstantValue(constant).createHolder(); } } return SpecificHaxeClassReference.getUnknown(element).createHolder(); } if (element instanceof HaxeFunctionLiteral) { HaxeParameterList params = ((HaxeFunctionLiteral)element).getParameterList(); if (params == null) { return SpecificHaxeClassReference.getInvalid(element).createHolder(); } LinkedList<ResultHolder> results = new LinkedList<ResultHolder>(); ResultHolder returnType = null; context.beginScope(); try { for (HaxeParameter parameter : params.getParameterList()) { ResultHolder vartype = HaxeTypeResolver.getTypeFromTypeTag(parameter.getTypeTag(), element); String name = parameter.getName(); if (name != null) { context.setLocal(name, vartype); } results.add(vartype); } context.addLambda(context.createChild(element.getLastChild())); returnType = HaxeTypeResolver.getTypeFromTypeTag(((HaxeFunctionLiteral)element).getTypeTag(), element); } finally { context.endScope(); } return new SpecificFunctionReference(results, returnType, null, element).createHolder(); } if (element instanceof HaxeIfStatement) { PsiElement[] children = element.getChildren(); if (children.length >= 1) { SpecificTypeReference expr = handle(children[0], context).getType(); if (!SpecificTypeReference.getBool(element).canAssign(expr)) { context.addError( children[0], "If expr " + expr + " should be bool", new HaxeCastFixer(children[0], expr, SpecificHaxeClassReference.getBool(element)) ); } if (expr.isConstant()) { context.addWarning(children[0], "If expression constant"); } if (children.length < 2) return SpecificHaxeClassReference.getUnknown(element).createHolder(); PsiElement eTrue = null; PsiElement eFalse = null; eTrue = children[1]; if (children.length >= 3) { eFalse = children[2]; } SpecificTypeReference tTrue = null; SpecificTypeReference tFalse = null; if (eTrue != null) tTrue = handle(eTrue, context).getType(); if (eFalse != null) tFalse = handle(eFalse, context).getType(); if (expr.isConstant()) { if (expr.getConstantAsBool()) { if (tFalse != null) { context.addUnreachable(eFalse); } } else { if (tTrue != null) { context.addUnreachable(eTrue); } } } return HaxeTypeUnifier.unify(tTrue, tFalse, element).createHolder(); } } if (element instanceof HaxeParenthesizedExpression) { return handle(element.getChildren()[0], context); } if (element instanceof HaxeTernaryExpression) { HaxeExpression[] list = ((HaxeTernaryExpression)element).getExpressionList().toArray(new HaxeExpression[0]); return HaxeTypeUnifier.unify(handle(list[1], context).getType(), handle(list[2], context).getType(), element).createHolder(); } if (element instanceof HaxePrefixExpression) { HaxeExpression expression = ((HaxePrefixExpression)element).getExpression(); if (expression == null) { return handle(element.getFirstChild(), context); } else { ResultHolder typeHolder = handle(expression, context); SpecificTypeReference type = typeHolder.getType(); if (type.getConstant() != null) { String operatorText = getOperator(element, HaxeTokenTypeSets.OPERATORS); return type.withConstantValue(HaxeTypeUtils.applyUnaryOperator(type.getConstant(), operatorText)).createHolder(); } return typeHolder; } } if ( (element instanceof HaxeAdditiveExpression) || (element instanceof HaxeBitwiseExpression) || (element instanceof HaxeShiftExpression) || (element instanceof HaxeLogicAndExpression) || (element instanceof HaxeLogicOrExpression) || (element instanceof HaxeCompareExpression) || (element instanceof HaxeMultiplicativeExpression) ) { PsiElement[] children = element.getChildren(); String operatorText; if (children.length == 3) { operatorText = children[1].getText(); return HaxeOperatorResolver.getBinaryOperatorResult( element, handle(children[0], context).getType(), handle(children[2], context).getType(), operatorText, context ).createHolder(); } else { operatorText = getOperator(element, HaxeTokenTypeSets.OPERATORS); return HaxeOperatorResolver.getBinaryOperatorResult( element, handle(children[0], context).getType(), handle(children[1], context).getType(), operatorText, context ).createHolder(); } } System.out.println("Unhandled " + element.getClass()); return SpecificHaxeClassReference.getDynamic(element).createHolder(); } static private void checkParameters( final PsiElement callelement, final HaxeMethodModel method, final List<HaxeExpression> arguments, final HaxeExpressionEvaluatorContext context ) { checkParameters(callelement, method.getFunctionType(), arguments, context); } static private void checkParameters( PsiElement callelement, SpecificFunctionReference ftype, List<HaxeExpression> parameterExpressions, HaxeExpressionEvaluatorContext context ) { List<ResultHolder> parameterTypes = ftype.getParameters(); int len = Math.min(parameterTypes.size(), parameterExpressions.size()); for (int n = 0; n < len; n++) { ResultHolder type = parameterTypes.get(n); HaxeExpression expression = parameterExpressions.get(n); ResultHolder value = handle(expression, context); if (!type.canAssign(value)) { context.addError( expression, "Can't assign " + value + " to " + type, new HaxeCastFixer(expression, value.getType(), type.getType()) ); } } //System.out.println(ftype.getDebugString()); // More parameters than expected if (parameterExpressions.size() > parameterTypes.size()) { for (int n = parameterTypes.size(); n < parameterExpressions.size(); n++) { context.addError(parameterExpressions.get(n), "Unexpected argument"); } } // Less parameters than expected else if (parameterExpressions.size() < ftype.getNonOptionalArgumentsCount()) { context.addError(callelement, "Less arguments than expected"); } } static private String getOperator(PsiElement element, TokenSet set) { ASTNode operatorNode = element.getNode().findChildByType(set); if (operatorNode == null) return ""; return operatorNode.getText(); } }