Skip to content

Commit

Permalink
proper handling of monomorphism (+tests)
Browse files Browse the repository at this point in the history
  • Loading branch information
m0rkeulv committed Sep 15, 2024
1 parent 2596fd1 commit 593d356
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class HaxeCallExpressionUtil {
// (amongst the problem is mixed parameter classes and method needing reference for type resolve)
@NotNull
public static CallExpressionValidation checkMethodCall(@NotNull HaxeCallExpression callExpression, @NotNull HaxeMethod method) {
return checkMethodCall(callExpression, method, false);
}
public static CallExpressionValidation checkMethodCall(@NotNull HaxeCallExpression callExpression, @NotNull HaxeMethod method, boolean isFirstRef) {
CallExpressionValidation validation = new CallExpressionValidation();
validation.isMethod = true;

Expand Down Expand Up @@ -99,6 +102,12 @@ public static CallExpressionValidation checkMethodCall(@NotNull HaxeCallExpressi
}
}
}
if(callieType.isUnknown() && !validation.isStaticExtension && !isFirstRef ) {
//TODO hack?
// if callie is unknown we prohibit class TypeParameters unless first reference flag
// reason: we need to prevent incorrect typeParameters when evaluating monomorphs and recursion guards can cause callie to be null
validation.unknownCallie = true;
}
HaxeGenericResolver resolver = HaxeGenericResolverUtil.appendCallExpressionGenericResolver(callExpression, classTypeResolver);

int parameterCounter = 0;
Expand All @@ -120,7 +129,7 @@ public static CallExpressionValidation checkMethodCall(@NotNull HaxeCallExpressi
// when resolving parameters
HaxeGenericResolver parameterResolver = resolver.withoutUnknowns();

TypeParameterTable typeParamTable = createTypeParameterConstraintTable(method, resolver);
TypeParameterTable typeParamTable = createTypeParameterConstraintTable(method, resolver, validation.unknownCallie);

if (validation.isStaticExtension) {
// this might not work for literals, need to handle those in a different way
Expand Down Expand Up @@ -519,7 +528,7 @@ public static CallExpressionValidation checkConstructor(HaxeNewExpression newExp

resolver = HaxeGenericResolverUtil.appendCallExpressionGenericResolver(newExpression, resolver);

TypeParameterTable typeParamTable = createTypeParameterConstraintTable(constructor.getMethod(), resolver);
TypeParameterTable typeParamTable = createTypeParameterConstraintTable(constructor.getMethod(), resolver, validation.unknownCallie);


int parameterCounter = 0;
Expand Down Expand Up @@ -964,6 +973,7 @@ private static boolean isExternRestClass(SpecificHaxeClassReference classReferen

@Data
public static class CallExpressionValidation {
public boolean unknownCallie = false;
Map<Integer, Integer> argumentToParameterIndex = new HashMap<>();
Map<Integer, ResultHolder> argumentIndexToType = new HashMap<>();
Map<Integer, ResultHolder> parameterIndexToType = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
public class TypeParameterUtil {

@NotNull
public static TypeParameterTable createTypeParameterConstraintTable(HaxeMethod method, HaxeGenericResolver resolver) {
public static TypeParameterTable createTypeParameterConstraintTable(HaxeMethod method, HaxeGenericResolver resolver,
boolean prohibitClassTypeParameters) {

TypeParameterTable typeParamTable = new TypeParameterTable();

Expand All @@ -31,7 +32,7 @@ public static TypeParameterTable createTypeParameterConstraintTable(HaxeMethod m
}
if (method.isConstructor()) {
HaxeClassModel declaringClass = method.getModel().getDeclaringClass();
if (declaringClass != null) {
if (declaringClass != null && !prohibitClassTypeParameters) {
params = declaringClass.getGenericParams();
for (HaxeGenericParamModel model : params) {
ResultHolder constraint = model.getConstraint(resolver);
Expand All @@ -47,7 +48,9 @@ public static TypeParameterTable createTypeParameterConstraintTable(HaxeMethod m
ResultHolder constraint = model.getConstraint(resolver);
// make sure we do not add if method type parameter with the same name is present
if(!typeParamTable.contains(model.getName(), ResolveSource.METHOD_TYPE_PARAMETER)) {
typeParamTable.put(model.getName(), constraint, ResolveSource.CLASS_TYPE_PARAMETER);
if(!prohibitClassTypeParameters) {
typeParamTable.put(model.getName(), constraint, ResolveSource.CLASS_TYPE_PARAMETER);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import static com.intellij.plugins.haxe.model.evaluator.HaxeExpressionEvaluatorHandlers.*;
import static com.intellij.plugins.haxe.model.evaluator.HaxeExpressionEvaluatorHandlers.handleWithRecursionGuard;
import static com.intellij.plugins.haxe.model.evaluator.HaxeExpressionUsageUtil.findUsageAsParameterInFunctionCall;
import static com.intellij.plugins.haxe.model.evaluator.HaxeExpressionUsageUtil.searchReferencesForTypeParameters;
import static com.intellij.plugins.haxe.model.type.HaxeMacroTypeUtil.getTypeDefinition;

@CustomLog
Expand Down Expand Up @@ -114,7 +115,7 @@ private static void cleanUp() {
// keep package protected, don't use this one outside code running from evaluate()
// if used outside it can mess up the result cache
@NotNull
static ResultHolder handle(final PsiElement element,
static ResultHolder handle(@NotNull final PsiElement element,
final HaxeExpressionEvaluatorContext context,
final HaxeGenericResolver resolver) {
try {
Expand All @@ -125,7 +126,7 @@ static ResultHolder handle(final PsiElement element,
}
catch (NullPointerException e) {
// Make sure that these get into the log, because the GeneralHighlightingPass swallows them.
log.error("Error evaluating expression type for element " + element.toString(), e);
log.error("Error evaluating expression type for element " + element, e);
throw e;
}
catch (ProcessCanceledException e) {
Expand All @@ -141,7 +142,9 @@ static ResultHolder handle(final PsiElement element,
return createUnknown(element != null ? element : context.root);
}

@NotNull
// if recursion guard is triggered value will be null
// this is intentional as providing an unknown result instead can break monomorphs
@Nullable
static ResultHolder _handle(final PsiElement element,
final HaxeExpressionEvaluatorContext context,
HaxeGenericResolver optionalResolver) {
Expand Down Expand Up @@ -246,7 +249,8 @@ static ResultHolder _handle(final PsiElement element,
if (element instanceof HaxeReference) {

if (element instanceof HaxeNewExpression expression) {
return handleNewExpression(context, resolver, expression);
ResultHolder holder = handleNewExpression(context, resolver, expression);
return findMissingTypeParametersIfNecessary(context, resolver, expression, holder);
}

if (element instanceof HaxeThisExpression thisExpression) {
Expand Down Expand Up @@ -320,7 +324,7 @@ static ResultHolder _handle(final PsiElement element,
return handleVarInit(context, resolver, varInit);
}

if(element instanceof HaxeObjectLiteralElement objectLiteralElement) {
if(element instanceof HaxeObjectLiteralElement objectLiteralElement && objectLiteralElement.getExpression() != null) {
return handle(objectLiteralElement.getExpression(), context, resolver);
}

Expand Down Expand Up @@ -428,6 +432,31 @@ static ResultHolder _handle(final PsiElement element,
return createUnknown(element);
}

private static @Nullable ResultHolder findMissingTypeParametersIfNecessary(HaxeExpressionEvaluatorContext context,
HaxeGenericResolver resolver,
HaxeNewExpression expression,
ResultHolder typeHolder) {
// if new expression is missing typeParameters try to resolve from usage
HaxeType type = expression.getType();
if (type.getTypeParam() == null && typeHolder.getClassType() != null && typeHolder.getClassType().getSpecifics().length > 0) {
HaxePsiField fieldDeclaration = PsiTreeUtil.getParentOfType(expression, HaxePsiField.class);
if (fieldDeclaration != null && fieldDeclaration.getTypeTag() == null) {
SpecificHaxeClassReference classType = typeHolder.getClassType();
// if class does not have any generics there no need to search for references
if (classType != null && classType.getSpecifics().length > 0) {
HaxeComponentName componentName = fieldDeclaration.getComponentName();
if(componentName != null) {
ResultHolder searchResult = searchReferencesForTypeParameters(componentName, context, resolver, typeHolder);
if (!searchResult.isUnknown()) {
return searchResult;
}
}
}
}
}
return typeHolder;
}

private static ResultHolder handeTypeCheckExpr(PsiElement element, HaxeTypeCheckExpr expr, HaxeGenericResolver resolver) {
if (expr.getTypeOrAnonymous() != null) {
return HaxeTypeResolver.getTypeFromTypeOrAnonymous(expr.getTypeOrAnonymous(), resolver);
Expand Down Expand Up @@ -535,24 +564,34 @@ public static ResultHolder searchReferencesForType(final HaxeComponentName compo
) {
List<PsiReference> references = referenceSearch(componentName, searchScopePsi);
ResultHolder lastValue = null;
for (PsiReference reference : references) {
ResultHolder possibleType = checkSearchResult(context, resolver, reference, componentName, hint);
if(possibleType != null) {
int continueFrom = 0;
for (int i = 0, size = references.size(); i < size; i++) {
PsiReference reference = references.get(i);
ResultHolder possibleType = checkSearchResult(context, resolver, reference, componentName, hint, i == 0);
if (possibleType != null) {
if (!possibleType.isUnknown()) {
if (lastValue == null) {
lastValue = possibleType;
continueFrom = i+1;
}
if (!lastValue.isDynamic()) {
// monomorphs should only use the first real value found (right?)
//NOTE: don't use unify here (will break function type from usage)
if (!lastValue.isFunctionType()) break;
boolean canAssign = lastValue.canAssign(possibleType);
if (canAssign) lastValue = possibleType;
if (canAssign) {
lastValue = possibleType;
continueFrom = i+1;
}
}
}
}
}
if (lastValue != null && !lastValue.isUnknown()) {
if(lastValue.containsTypeParameters()) {
ResultHolder holder = searchReferencesForTypeParameters(componentName, context, resolver, lastValue, continueFrom);
if (!holder.isUnknown()) return holder;
}
return lastValue;
}

Expand Down Expand Up @@ -581,15 +620,17 @@ public static List<PsiReference> referenceSearch(final HaxeComponentName compone
@Nullable
private static ResultHolder checkSearchResult(HaxeExpressionEvaluatorContext context, HaxeGenericResolver resolver, PsiReference reference,
HaxeComponentName originalComponent,
@Nullable ResultHolder hint) {
@Nullable ResultHolder hint, boolean firstReference) {

if (originalComponent.getParent() == reference) return null;
if (reference instanceof HaxeExpression expression) {
if (expression.getParent() instanceof HaxeAssignExpression assignExpression) {
HaxeExpression rightExpression = assignExpression.getRightExpression();
ResultHolder result = handle(rightExpression, context, resolver);
if (!result.isUnknown()) {
return result;
if(rightExpression != null) {
ResultHolder result = handle(rightExpression, context, resolver);
if (!result.isUnknown()) {
return result;
}
}
HaxeExpression leftExpression = assignExpression.getLeftExpression();
if (leftExpression instanceof HaxeReferenceExpression referenceExpression) {
Expand Down Expand Up @@ -676,7 +717,7 @@ private static ResultHolder checkSearchResult(HaxeExpressionEvaluatorContext con
final HaxeReference leftReference = PsiTreeUtil.getChildOfType(callExpression.getExpression(), HaxeReference.class);
if (leftReference == reference) {
if (resolved instanceof HaxeMethod method ) {
HaxeCallExpressionUtil.CallExpressionValidation validation = HaxeCallExpressionUtil.checkMethodCall(callExpression, method);
HaxeCallExpressionUtil.CallExpressionValidation validation = HaxeCallExpressionUtil.checkMethodCall(callExpression, method, firstReference);
ResultHolder hintResolved = validation.getResolver().resolve(hint);
if (hintResolved != null && hintResolved.getType() != hintResolved.getType()) return hintResolved;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.intellij.plugins.haxe.model.evaluator;

import com.intellij.openapi.util.RecursionGuard;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.plugins.haxe.model.type.HaxeGenericResolver;
import com.intellij.plugins.haxe.model.type.ResultHolder;
import com.intellij.plugins.haxe.model.type.SpecificTypeReference;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;

Expand All @@ -19,21 +22,27 @@
public class HaxeExpressionEvaluatorCacheService {

private volatile Map<EvaluationKey, ResultHolder> cacheMap = new ConcurrentHashMap<>();
public boolean skipCaching = false;
public static boolean skipCaching = false;// just convenience flag for debugging

public @NotNull ResultHolder handleWithResultCaching(final PsiElement element,

public @NotNull ResultHolder handleWithResultCaching(@NotNull final PsiElement element,
final HaxeExpressionEvaluatorContext context,
final HaxeGenericResolver resolver) {

if(skipCaching) return _handle(element, context, resolver);
if(skipCaching){
ResultHolder holder = _handle(element, context, resolver);
if(holder == null) return SpecificTypeReference.getUnknown(element).createHolder();
return holder;
}

EvaluationKey key = new EvaluationKey(element, resolver == null ? "NO_RESOLVER" : resolver.toCacheString());
if (cacheMap.containsKey(key)) {
return cacheMap.get(key);
}
else {
ResultHolder holder = _handle(element, context, resolver);
if (!holder.isUnknown()) {
if(holder == null) return SpecificTypeReference.getUnknown(element).createHolder();
if (!holder.isUnknown() && !holder.containsUnknownTypeParameters()) {
cacheMap.put(key, holder);
}
return holder;
Expand Down
Loading

0 comments on commit 593d356

Please sign in to comment.