/*
* 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.annotation.AnnotationHolder;
import com.intellij.plugins.haxe.lang.psi.*;
import com.intellij.plugins.haxe.lang.psi.impl.AbstractHaxeNamedComponent;
import com.intellij.plugins.haxe.lang.psi.impl.HaxeMethodImpl;
import com.intellij.plugins.haxe.util.HaxeAbstractEnumUtil;
import com.intellij.plugins.haxe.util.UsefulPsiTreeUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class HaxeTypeResolver {
@NotNull
static public ResultHolder getFieldOrMethodReturnType(@NotNull AbstractHaxeNamedComponent comp) {
return getFieldOrMethodReturnType(comp, null);
}
// @TODO: Check if cache works
@NotNull
static public ResultHolder getFieldOrMethodReturnType(@NotNull AbstractHaxeNamedComponent comp, @Nullable HaxeGenericResolver resolver) {
// @TODO: cache should check if any related type has changed, which return depends
if (comp.getContainingFile() == null) {
return SpecificHaxeClassReference.getUnknown(comp).createHolder();
}
long stamp = comp.getContainingFile().getModificationStamp();
if (comp._cachedType == null || comp._cachedTypeStamp != stamp) {
comp._cachedType = _getFieldOrMethodReturnType(comp, resolver);
comp._cachedTypeStamp = stamp;
}
return comp._cachedType;
}
@NotNull
static public ResultHolder getMethodFunctionType(PsiElement comp, @Nullable HaxeGenericResolver resolver) {
if (comp instanceof HaxeMethod) {
return ((HaxeMethod)comp).getModel().getFunctionType(resolver).createHolder();
}
// @TODO: error
return SpecificTypeReference.getInvalid(comp).createHolder();
}
@NotNull
static private ResultHolder _getFieldOrMethodReturnType(AbstractHaxeNamedComponent comp, @Nullable HaxeGenericResolver resolver) {
try {
if (comp instanceof PsiMethod) {
return getFunctionReturnType(comp);
} else if (comp instanceof HaxeFunctionLiteral) {
return getFunctionReturnType(comp);
} else {
return getFieldType(comp);
}
} catch (Throwable e) {
e.printStackTrace();
return SpecificTypeReference.getUnknown(comp).createHolder();
}
}
@NotNull
static private ResultHolder getFieldType(AbstractHaxeNamedComponent comp) {
//ResultHolder type = getTypeFromTypeTag(comp);
// Here detect assignment
final ResultHolder abstractEnumType = HaxeAbstractEnumUtil.getFieldType(comp);
if(abstractEnumType != null) {
return abstractEnumType;
}
if (comp instanceof HaxeVarDeclarationPart) {
ResultHolder result = null;
HaxeVarInit init = ((HaxeVarDeclarationPart)comp).getVarInit();
if (init != null) {
PsiElement child = init.getExpression();
final ResultHolder initType = HaxeTypeResolver.getPsiElementType(child);
HaxeVarDeclaration decl = ((HaxeVarDeclaration)comp.getParent());
boolean isConstant = false;
if (decl != null) {
isConstant = decl.hasModifierProperty(HaxePsiModifier.INLINE) && decl.isStatic();
}
result = isConstant ? initType : initType.withConstantValue(null);
}
HaxeTypeTag typeTag = ((HaxeVarDeclarationPart)comp).getTypeTag();
if (typeTag != null) {
final ResultHolder typeFromTag = getTypeFromTypeTag(typeTag, comp);
final Object initConstant = result != null ? result.getType().getConstant() : null;
result = typeFromTag.withConstantValue(initConstant);
}
if(result != null) {
return result;
}
}
return SpecificTypeReference.getUnknown(comp).createHolder();
}
@NotNull
static private ResultHolder getFunctionReturnType(AbstractHaxeNamedComponent comp) {
if (comp instanceof HaxeMethodImpl) {
HaxeTypeTag typeTag = ((HaxeMethodImpl)comp).getTypeTag();
if (typeTag != null) {
return getTypeFromTypeTag(typeTag, comp);
}
}
if (comp instanceof HaxeMethod) {
final HaxeExpressionEvaluatorContext context = getPsiElementType(((HaxeMethod)comp).getModel().getBodyPsi(), null);
return context.getReturnType();
} else if (comp instanceof HaxeFunctionLiteral) {
final HaxeExpressionEvaluatorContext context = getPsiElementType(comp.getLastChild(), null);
return context.getReturnType();
} else {
throw new RuntimeException("Can't get the body of a no PsiMethod");
}
}
@NotNull
static public ResultHolder getTypeFromTypeTag(@Nullable final HaxeTypeTag typeTag, @NotNull PsiElement context) {
if (typeTag != null) {
final HaxeTypeOrAnonymous typeOrAnonymous = typeTag.getTypeOrAnonymous();
final HaxeFunctionType functionType = typeTag.getFunctionType();
if (typeOrAnonymous != null) {
return getTypeFromTypeOrAnonymous(typeOrAnonymous);
}
//comp.getContainingFile().getNode().putUserData();
if (functionType != null) {
return getTypeFromFunctionType(functionType);
}
}
return SpecificTypeReference.getUnknown(context).createHolder();
}
@NotNull
static public ResultHolder getTypeFromTypeTag(AbstractHaxeNamedComponent comp, @NotNull PsiElement context) {
return getTypeFromTypeTag(PsiTreeUtil.getChildOfType(comp, HaxeTypeTag.class), context);
}
@NotNull
static public ResultHolder getTypeFromFunctionType(HaxeFunctionType type) {
ArrayList<ResultHolder> args = new ArrayList<ResultHolder>();
for (HaxeTypeOrAnonymous anonymous : type.getTypeOrAnonymousList()) {
args.add(getTypeFromTypeOrAnonymous(anonymous));
}
ResultHolder retval = args.get(args.size() - 1);
args.remove(args.size() - 1);
return new SpecificFunctionReference(args, retval, null, type).createHolder();
}
@NotNull
static public ResultHolder getTypeFromType(@NotNull HaxeType type) {
//System.out.println("Type:" + type);
//System.out.println("Type:" + type.getText());
HaxeReferenceExpression expression = type.getReferenceExpression();
HaxeClassReference reference = new HaxeClassReference(expression.getText(), expression);
HaxeTypeParam param = type.getTypeParam();
ArrayList<ResultHolder> references = new ArrayList<ResultHolder>();
if (param != null) {
for (HaxeTypeListPart part : param.getTypeList().getTypeListPartList()) {
for (HaxeTypeOrAnonymous anonymous : part.getTypeOrAnonymousList()) {
references.add(getTypeFromTypeOrAnonymous(anonymous));
}
}
}
//type.getTypeParam();
return SpecificHaxeClassReference.withGenerics(reference, references.toArray(ResultHolder.EMPTY)).createHolder();
}
@NotNull
static public ResultHolder getTypeFromTypeOrAnonymous(@NotNull HaxeTypeOrAnonymous typeOrAnonymous) {
// @TODO: Do a proper type resolving
HaxeType type = typeOrAnonymous.getType();
if (type != null) {
return getTypeFromType(type);
}
return SpecificTypeReference.getDynamic(typeOrAnonymous).createHolder();
}
@NotNull
static public ResultHolder getPsiElementType(PsiElement element) {
return getPsiElementType(element, null).result;
}
// @TODO: hack to avoid stack overflow, until a proper non-static fix is done
static private Set<PsiElement> processedElements = new HashSet<PsiElement>();
static private void checkMethod(PsiElement element, HaxeExpressionEvaluatorContext context) {
//final ResultHolder retval = context.getReturnType();
if (!(element instanceof HaxeMethod)) return;
final HaxeTypeTag typeTag = UsefulPsiTreeUtil.getChild(element, HaxeTypeTag.class);
ResultHolder expectedType = SpecificTypeReference.getDynamic(element).createHolder();
if (typeTag == null) {
final List<ReturnInfo> infos = context.getReturnInfos();
if (!infos.isEmpty()) {
expectedType = infos.get(0).type;
}
} else {
expectedType = getTypeFromTypeTag(typeTag, element);
}
if (expectedType == null) return;
for (ReturnInfo retinfo : context.getReturnInfos()) {
if (expectedType.canAssign(retinfo.type)) continue;
context.addError(
retinfo.element,
"Can't return " + retinfo.type + ", expected " + expectedType.toStringWithoutConstant()
);
}
}
@NotNull
static public HaxeExpressionEvaluatorContext getPsiElementType(PsiElement element, @Nullable AnnotationHolder holder) {
return evaluateFunction(new HaxeExpressionEvaluatorContext(element, holder));
}
@NotNull
static public HaxeExpressionEvaluatorContext evaluateFunction(@NotNull HaxeExpressionEvaluatorContext context) {
PsiElement element = context.root;
if (processedElements.contains(element)) {
context.result = SpecificHaxeClassReference.primitive("Dynamic", element).createHolder();
return context;
}
processedElements.add(element);
try {
HaxeExpressionEvaluator.evaluate(element, context);
checkMethod(element.getParent(), context);
for (HaxeExpressionEvaluatorContext lambda : context.lambdas) {
evaluateFunction(lambda);
}
return context;
} finally {
processedElements.remove(element);
}
}
static private SpecificHaxeClassReference createPrimitiveType(String type, PsiElement element, Object constant) {
return SpecificHaxeClassReference.withoutGenerics(new HaxeClassReference(type, element), constant);
}
}