Skip to content

Commit cf55a5d

Browse files
committed
Fixing resolve of extension methods from EnumValueTools and EnumTools
Fixing TypeParameters support for `bind()``
1 parent dfb3ef9 commit cf55a5d

File tree

12 files changed

+245
-46
lines changed

12 files changed

+245
-46
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# Changelog
22
## 1.6.6
3-
* Bugfix : fixed issue where Class references where treated as instance references in new expressions
3+
* Bugfix: Fixed issue where Class references where treated as instance references in new expressions
4+
* Fixed: Extension methods from `EnumValueTools` and `EnumTools` are now resolved without using imports (same as the compiler)
5+
* Fixed: TypeParameters are now kept when using `bind()` on method references.
6+
7+
Known issues :
8+
- Extension methods from `EnumValueTools` and `EnumTools` are not included in completions without using statement.
9+
410

511
## 1.6.5
612
* Added: Cppia target for OpenFL/Lime builds

src/main/java/com/intellij/plugins/haxe/lang/psi/HaxeResolver.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
import java.util.concurrent.atomic.AtomicInteger;
5757

5858
import static com.intellij.plugins.haxe.model.evaluator.HaxeExpressionEvaluator.findObjectLiteralType;
59+
import static com.intellij.plugins.haxe.model.type.SpecificTypeReference.CLASS;
60+
import static com.intellij.plugins.haxe.model.type.SpecificTypeReference.ENUM;
5961
import static com.intellij.plugins.haxe.util.HaxeDebugLogUtil.traceAs;
6062
import static com.intellij.plugins.haxe.util.HaxeResolveUtil.searchInSameFileForEnumValues;
6163
import static com.intellij.plugins.haxe.util.HaxeStringUtil.elide;
@@ -1676,6 +1678,7 @@ private List<? extends PsiElement> resolveChain(HaxeReference lefthandExpression
16761678
SpecificHaxeClassReference classType = result == null || result.isUnknown() ? null : result.getClassType();
16771679
HaxeClass haxeClass = classType != null ? classType.getHaxeClass() : null;
16781680

1681+
16791682
// To avoid incorrect extension method results we avoid any results where we don't know type of left reference.
16801683
// this is important as recursion guards might prevent us from getting the type and returning a different result depending on
16811684
// whether or not we got the type is bad and causes issues.
@@ -1731,11 +1734,26 @@ private List<? extends PsiElement> resolveChain(HaxeReference lefthandExpression
17311734
if (fileModel != null) {
17321735
usingModels.addAll(fileModel.getUsingModels());
17331736
}
1737+
SpecificHaxeClassReference extensionType = classType;
1738+
// check if reference is to a Class or Enum and if so wrap in Class<> or Enum<> so we
1739+
// can match stuff like methods in EnumTools and/or other extensions for Enum/Class types.
1740+
if (leftReference != null) {
1741+
if (leftReference.getParent() instanceof HaxeReferenceExpression parent) {
1742+
// make sure our HaxeReferenceExpression is the fist element in parent (ex. MyClass.someMember)
1743+
// TODO what about fully qualified or partial qualified (Module.Type)?
1744+
if (parent.getFirstChild() == leftReference && !(parent.getParent() instanceof HaxeReferenceExpression)) {
1745+
HaxeClassModel model = haxeClass.getModel();
1746+
if (leftReference.textMatches(model.getName())) {
1747+
extensionType = SpecificHaxeClassReference.getStdClass(haxeClass.isEnum() ? ENUM : CLASS, leftReference, new ResultHolder[]{new ResultHolder(classType)});
1748+
}
1749+
}
1750+
}
1751+
}
17341752

17351753
HaxeMethodModel foundMethod = null;
17361754
for (int i = usingModels.size() - 1; i >= 0; --i) {
17371755
foundMethod = usingModels.get(i)
1738-
.findExtensionMethod(identifier, classType);
1756+
.findExtensionMethod(identifier, extensionType);
17391757
if (null != foundMethod && !foundMethod.HasNoUsingMeta()) {
17401758

17411759
if (log.isTraceEnabled()) log.trace("Found method in 'using' import: " + foundMethod.getName());

src/main/java/com/intellij/plugins/haxe/lang/psi/impl/HaxeReferenceImpl.java

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -184,27 +184,11 @@ public PsiElement resolve() {
184184
}
185185

186186
public boolean resolveIsStaticExtension() {
187-
if (this instanceof HaxeCallExpression) {
188-
PsiReference referenceChain = this.getFirstChild().getReference();
189-
if (referenceChain instanceof HaxeReferenceExpression referenceExpression) {
190-
PsiElement method = referenceExpression.resolve();
191-
if (method instanceof HaxeMethod haxeMethod) {
192-
if (!haxeMethod.isStatic()) return false; // only static methods can be extensions (compiler: Cannot access static field XXX from a class instance)
193-
194-
PsiElement ChainBeforeMethod = referenceExpression.getChildren()[0];
195-
if (ChainBeforeMethod instanceof HaxeIdentifier) return false; // not chain, got method identifer
196-
if (ChainBeforeMethod instanceof HaxeReferenceExpression referenceExpression1) {
197-
PsiElement caller = referenceExpression1.resolve();
198-
if (caller == method) return false; // probably a function bind or similar
199-
// todo find using import statement for methods declaring class and confirm "using"
200-
return !(caller instanceof HaxeClass || caller instanceof HaxeImportAlias);
201-
}else {
202-
return true;
203-
}
204-
}
205-
}
187+
if (this instanceof HaxeCallExpression callExpression) {
188+
return HaxeReferenceUtil.isStaticExtension(callExpression);
189+
} else {
190+
return false;
206191
}
207-
return false;
208192
}
209193

210194

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.intellij.plugins.haxe.lang.psi.impl;
2+
3+
import com.intellij.plugins.haxe.lang.psi.*;
4+
import com.intellij.plugins.haxe.model.HaxeBaseMemberModel;
5+
import com.intellij.plugins.haxe.model.HaxeClassModel;
6+
import com.intellij.plugins.haxe.model.evaluator.HaxeExpressionEvaluator;
7+
import com.intellij.plugins.haxe.model.type.ResultHolder;
8+
import com.intellij.plugins.haxe.model.type.SpecificHaxeClassReference;
9+
import com.intellij.psi.PsiElement;
10+
import com.intellij.psi.PsiReference;
11+
12+
import static com.intellij.plugins.haxe.model.type.SpecificTypeReference.CLASS;
13+
import static com.intellij.plugins.haxe.model.type.SpecificTypeReference.ENUM;
14+
15+
public class HaxeReferenceUtil {
16+
17+
public static boolean isStaticExtension(HaxeReferenceExpression referenceExpression) {
18+
PsiElement method = referenceExpression.resolve();
19+
if (method instanceof HaxeMethod haxeMethod) {
20+
if (!haxeMethod.isStatic()) return false; // only static methods can be extensions (compiler: Cannot access static field XXX from a class instance)
21+
22+
PsiElement ChainBeforeMethod = referenceExpression.getChildren()[0];
23+
if (ChainBeforeMethod instanceof HaxeIdentifier) return false; // not chain, got method identifier
24+
if (ChainBeforeMethod instanceof HaxeReferenceExpression referenceExpression1) {
25+
PsiElement caller = referenceExpression1.resolve();
26+
if (caller == method) return false; // probably a function bind or similar
27+
ResultHolder callerType = HaxeExpressionEvaluator.evaluateWithRecursionGuard(referenceExpression1).result;
28+
if(callerType.getClassType() != null) {
29+
HaxeClassModel haxeClassModel = callerType.getClassType().getHaxeClassModel();
30+
if(haxeClassModel != null) {
31+
HaxeBaseMemberModel member = haxeClassModel.getMember(((HaxeMethod) method).getName(), null);
32+
if (member == null) return true;
33+
}
34+
}
35+
return !(caller instanceof HaxeClass || caller instanceof HaxeImportAlias);
36+
}else {
37+
return true;
38+
}
39+
}
40+
return false;
41+
}
42+
43+
public static boolean isStaticExtension(HaxeCallExpression callExpression) {
44+
PsiReference referenceChain = callExpression.getFirstChild().getReference();
45+
if (referenceChain instanceof HaxeReferenceExpression referenceExpression) {
46+
return isStaticExtension(referenceExpression);
47+
}
48+
return false;
49+
}
50+
51+
52+
public static ResultHolder wrapTypeInClassOrEnum(PsiElement element, HaxeClass haxeClass) {
53+
// wrap in Class<> or Enum<>
54+
SpecificHaxeClassReference originalClass = SpecificHaxeClassReference.withoutGenerics(haxeClass.getModel().getReference());
55+
SpecificHaxeClassReference wrappedClass =
56+
SpecificHaxeClassReference.getStdClass(haxeClass.isEnum() ? ENUM : CLASS, element,
57+
new ResultHolder[]{new ResultHolder(originalClass)});
58+
return wrappedClass.createHolder();
59+
}
60+
}

src/main/java/com/intellij/plugins/haxe/model/evaluator/HaxeExpressionEvaluatorHandlers.java

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@
3434
import org.jetbrains.annotations.Nullable;
3535

3636
import java.util.*;
37+
import java.util.stream.Collectors;
3738
import java.util.stream.Stream;
3839

3940
import static com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypeSets.ONLY_COMMENTS;
4041
import static com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypes.KUNTYPED;
4142
import static com.intellij.plugins.haxe.lang.psi.impl.HaxeReferenceImpl.getLiteralClassName;
4243
import static com.intellij.plugins.haxe.lang.psi.impl.HaxeReferenceImpl.tryToFindTypeFromCallExpression;
44+
import static com.intellij.plugins.haxe.lang.psi.impl.HaxeReferenceUtil.isStaticExtension;
45+
import static com.intellij.plugins.haxe.lang.psi.impl.HaxeReferenceUtil.wrapTypeInClassOrEnum;
4346
import static com.intellij.plugins.haxe.model.evaluator.HaxeExpressionUsageUtil.searchReferencesForTypeParameters;
4447
import static com.intellij.plugins.haxe.model.evaluator.HaxeExpressionUsageUtil.tryToFindTypeFromUsage;
4548
import static com.intellij.plugins.haxe.model.evaluator.callexpression.HaxeCallExpressionUtil.isBindCall;
@@ -301,7 +304,7 @@ static ResultHolder handleReferenceExpression( HaxeExpressionEvaluatorContext co
301304
SpecificHaxeClassReference classType = typeHolder.getClassType();
302305
if(classType != null) {
303306
HaxeClass haxeClass = classType.getHaxeClass();
304-
if(haxeClass != null) typeHolder = wrapTypeInClassOrEnum(element, haxeClass, haxeClass.getModel());
307+
if(haxeClass != null) typeHolder = wrapTypeInClassOrEnum(element, haxeClass);
305308
}
306309
}
307310
}
@@ -331,7 +334,7 @@ static ResultHolder handleReferenceExpression( HaxeExpressionEvaluatorContext co
331334
if (expression.isPureClassReferenceOf(haxeClass)) {
332335
// make sure its not an import statement
333336
if (PsiTreeUtil.getParentOfType(expression, HaxeImportStatement.class) == null) {
334-
typeHolder = wrapTypeInClassOrEnum(element, haxeClass, model);
337+
typeHolder = wrapTypeInClassOrEnum(element, haxeClass);
335338
}
336339
}
337340
}
@@ -394,6 +397,25 @@ else if (subelement instanceof HaxeMethod haxeMethod) {
394397

395398
SpecificFunctionReference type = haxeMethod.getModel().getFunctionType(isFromCallExpression ? localResolver : localResolver.withoutAssignHint());
396399
if (!isFromCallExpression) {
400+
if( reference instanceof HaxeReferenceExpression referenceExpression) {
401+
// if this is a reference to an extension method we need to bind the callie type
402+
if(isStaticExtension(referenceExpression)) {
403+
List<HaxeArgument> argumentsToKeep = type.getArguments();
404+
HaxeGenericResolver bindResolver = null;
405+
// if this is from a method we might have typeParameters and need to "bind" these if they are present in the callie
406+
if(type.method != null) {
407+
List<SpecificTypeReference> params = argumentsToKeep.stream().map(HaxeArgument::getType).map(ResultHolder::getType).collect(Collectors.toList());
408+
params.removeFirst();
409+
SpecificTypeReference callieType = tryToFindPreviousTypeInChainForExtensionMethods(element.getFirstChild());
410+
params.addFirst(callieType);
411+
HaxeCallExpressionContext tmpContext = HaxeCallExpressionUtil.createContextForMethodCall(params, type.method, null);
412+
HaxeCallExpressionEvaluation evaluate = tmpContext.evaluate();
413+
bindResolver = evaluate.getCallExpressionResolver();
414+
}
415+
argumentsToKeep.removeFirst(); // remove first argument as this should be the callie
416+
type = type.performMethodBind(argumentsToKeep, bindResolver);
417+
}
418+
}
397419
// expression is referring to the method not calling it.
398420
// assign hint should be used for substituting parameters instead of being used as return type
399421
type = resolver.substituteTypeParamsWithAssignHintTypes(type);
@@ -484,17 +506,21 @@ else if (typeHolder == null || typeHolder.isUnknown()) {
484506
//return SpecificTypeReference.getDynamic(element).createHolder();
485507
}
486508

487-
private static ResultHolder wrapTypeInClassOrEnum(HaxeReferenceExpression element, HaxeClass haxeClass, HaxeClassModel model) {
488-
ResultHolder typeHolder;
489-
// wrap in Class<> or Enum<>
490-
SpecificHaxeClassReference originalClass = SpecificHaxeClassReference.withoutGenerics(model.getReference());
491-
SpecificHaxeClassReference wrappedClass =
492-
SpecificHaxeClassReference.getStdClass(haxeClass.isEnum() ? ENUM : CLASS, element,
493-
new ResultHolder[]{new ResultHolder(originalClass)});
494-
typeHolder = wrappedClass.createHolder();
495-
return typeHolder;
509+
private static SpecificTypeReference tryToFindPreviousTypeInChainForExtensionMethods(PsiElement firstChild) {
510+
ResultHolder result = evaluateWithRecursionGuard(firstChild).result;
511+
if(result == null) return createUnknown(firstChild).getType();
512+
// check if extension method is on a pure references and if so wrap it in class/enum
513+
if(result.getType() instanceof SpecificHaxeClassReference classReference) {
514+
String className = classReference.getClassName();
515+
if (className != null &&firstChild.textMatches(className)) {
516+
HaxeClass haxeClass = classReference.getHaxeClass();
517+
if(haxeClass != null) return wrapTypeInClassOrEnum(firstChild, haxeClass).getType();
518+
}
519+
}
520+
return result.getType();
496521
}
497522

523+
498524
private static boolean isReificationExpression(HaxeReferenceExpression element) {
499525
return false;
500526
}
@@ -1467,7 +1493,11 @@ static ResultHolder handleCallExpression(
14671493
SpecificTypeReference assignHintType = assignHint == null ? null : assignHint.getType();
14681494
HaxeCallExpressionContext callExpressionContext = HaxeCallExpressionUtil.createContextForMethodCall(callExpression, assignHintType, methodModel.getMethod());
14691495
HaxeCallExpressionEvaluation evaluate = callExpressionContext.evaluate();
1470-
functionType = evaluate.getFunctionType(methodModel);
1496+
if(evaluate.isValid()) {
1497+
functionType = evaluate.getFunctionType(methodModel);
1498+
}else {
1499+
functionType = createUnknown(callExpression).getType();
1500+
}
14711501
}else {
14721502
SpecificTypeReference callieRef = tryGetCallieType(callExpression);
14731503
if (callieRef instanceof SpecificHaxeClassReference classReference && !classReference.isUnknown()) {
@@ -1639,6 +1669,8 @@ static ResultHolder handleCallExpression(
16391669
private static ResultHolder tryHandleFunctionBind(SpecificFunctionReference functionReference, HaxeCallExpression callExpression) {
16401670
List<HaxeArgument> arguments = functionReference.getArguments();
16411671
List<HaxeArgument> argumentsToKeep = new ArrayList<>();
1672+
List<SpecificTypeReference> tmParamList = new ArrayList<>();
1673+
boolean canHaveTypeParameters = functionReference.method != null;
16421674

16431675
HaxeCallExpressionList expressionList = callExpression.getExpressionList();
16441676
if (expressionList == null) {
@@ -1648,9 +1680,10 @@ private static ResultHolder tryHandleFunctionBind(SpecificFunctionReference func
16481680
argumentsToKeep.add(argument);
16491681
}
16501682
}
1651-
return functionReference.performMethodBind(argumentsToKeep).createHolder();
1683+
return functionReference.performMethodBind(argumentsToKeep, null).createHolder();
16521684
} else {
16531685
List<HaxeExpression> expressions = expressionList.getExpressionList();
1686+
16541687
for (int i = 0; i < arguments.size(); i++) {
16551688
HaxeExpression expr = expressions.size() > i ? expressions.get(i) : null;
16561689
HaxeArgument argument = arguments.get(i);
@@ -1661,12 +1694,25 @@ private static ResultHolder tryHandleFunctionBind(SpecificFunctionReference func
16611694
if (expr == null) {
16621695
if (!optional) {
16631696
argumentsToKeep.add(argument);
1697+
if (canHaveTypeParameters) tmParamList.add(argument.getType().getType());
16641698
}
16651699
} else if (expr.textMatches("_")) {
16661700
argumentsToKeep.add(argument);
1701+
if (canHaveTypeParameters) tmParamList.add(argument.getType().getType());
1702+
}else {
1703+
if (canHaveTypeParameters) {
1704+
ResultHolder expressionType = evaluateWithRecursionGuard(expr).result;
1705+
tmParamList.add(expressionType.getType());
1706+
}
16671707
}
16681708
}
1669-
return functionReference.performMethodBind(argumentsToKeep).createHolder();
1709+
if (canHaveTypeParameters) {
1710+
HaxeCallExpressionContext context = HaxeCallExpressionUtil.createContextForMethodCall(tmParamList, functionReference.method, null);
1711+
HaxeCallExpressionEvaluation evaluate = context.evaluate();
1712+
HaxeGenericResolver callExpressionResolver = evaluate.getCallExpressionResolver();
1713+
return functionReference.performMethodBind(argumentsToKeep, callExpressionResolver).createHolder();
1714+
}
1715+
return functionReference.performMethodBind(argumentsToKeep, null).createHolder();
16701716
}
16711717
}
16721718

src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionContext.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,16 @@ private HaxeCallExpressionEvaluation evaluate(boolean trackErrors, PsiElement so
161161
evaluation.addError("Extension methods require at least one parameter", sourceExpression);
162162
return evaluation.validationFailed();
163163
}
164-
SpecificTypeReference expectedCallieType = parameters.get(parameterCounter++).getType();
164+
SpecificTypeReference expectedCallieType = parameters.getFirst().getType();
165165
if (!expectedCallieType.canAssign(callie)) {
166166
// todo better error message, use bundle and show types
167167
if (trackErrors) evaluation.addError("Can not use extension method, wrong type", sourceExpression);
168168
return evaluation.validationFailed();
169169
}
170+
// while it might be a waste to re-evaluate the callie assignability
171+
// we do it here because we need to keep track if typeParameters
172+
// perhaps the logic above can be moved down into the argument/parameter check loop
173+
arguments.addFirst(new CallExpressionArgumentModel(callie.context, callie));
170174
}
171175

172176
CallExpressionArgumentModel argumentModel = null;

0 commit comments

Comments
 (0)