diff --git a/src/main/java/com/intellij/plugins/haxe/lang/psi/HaxeResolver.java b/src/main/java/com/intellij/plugins/haxe/lang/psi/HaxeResolver.java index 1a776cf34..aae049776 100644 --- a/src/main/java/com/intellij/plugins/haxe/lang/psi/HaxeResolver.java +++ b/src/main/java/com/intellij/plugins/haxe/lang/psi/HaxeResolver.java @@ -113,7 +113,7 @@ public List resolve(@NotNull HaxeReference reference, bool List elements = skipCaching ? doResolve(reference, incompleteCode) : ResolveCache.getInstance(reference.getProject()) - .resolveWithCaching(reference, this::doResolve, false, incompleteCode); + .resolveWithCaching(reference, this::doResolve, true, incompleteCode); if (reportCacheMetrics) { if (skipCachingForDebug) { @@ -182,7 +182,7 @@ private List doResolve(@NotNull HaxeReference reference, b } private List doResolveInner(@NotNull HaxeReference reference, boolean incompleteCode, String referenceText) { - RecursionManager.markStack(); + if (reportCacheMetrics) { resolves.incrementAndGet(); } @@ -195,13 +195,15 @@ private List doResolveInner(@NotNull HaxeReference referen boolean isType = reference.getParent() instanceof HaxeType || PsiTreeUtil.getParentOfType(reference, HaxeTypeTag.class) != null; List result = checkIsTypeParameter(reference); + if (result == null) result = checkIsChain(reference); + if (result == null) result = checkIsAlias(reference); if (result == null) result = checkEnumMemberHints(reference); if (result == null) result = checkIsType(reference); if (result == null) result = checkIsFullyQualifiedStatement(reference); if (result == null) result = checkIsSuperExpression(reference); if (result == null) result = checkMacroIdentifier(reference); - if (result == null) result = checkIsChain(reference); +// if (result == null) result = checkIsChain(reference); if (result == null) result = checkIsAccessor(reference); if (result == null) result = checkIsSwitchVar(reference); if (result == null) result = checkByTreeWalk(reference); // Beware: This will also locate constraints in scope. diff --git a/src/main/java/com/intellij/plugins/haxe/model/HaxeEnumValueConstructorModel.java b/src/main/java/com/intellij/plugins/haxe/model/HaxeEnumValueConstructorModel.java index 78064cfc7..b20912133 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/HaxeEnumValueConstructorModel.java +++ b/src/main/java/com/intellij/plugins/haxe/model/HaxeEnumValueConstructorModel.java @@ -91,18 +91,18 @@ public SpecificFunctionReference getFunctionType(@Nullable HaxeGenericResolver r public ResultHolder getReturnType(@Nullable HaxeGenericResolver resolver) { HaxeClassModel declaringEnum = getDeclaringEnum(); - + if (declaringEnum != null) { HaxeClassReference superclassReference = new HaxeClassReference(declaringEnum, declaringEnum.haxeClass); if (resolver != null) { - SpecificHaxeClassReference reference = - SpecificHaxeClassReference.withGenerics(superclassReference, resolver.getSpecificsFor(declaringEnum.haxeClass)); - + @NotNull ResultHolder[] specificsFor = resolver.getSpecificsFor(declaringEnum.haxeClass); + SpecificHaxeClassReference reference = SpecificHaxeClassReference.withGenerics(superclassReference, specificsFor); return reference.createHolder(); } else { - SpecificHaxeClassReference reference = - SpecificHaxeClassReference.withoutGenerics(superclassReference); - return reference.createHolder(); + return declaringEnum.getInstanceType(); } + } + + return SpecificHaxeClassReference.getUnknown(basePsi).createHolder(); } diff --git a/src/main/java/com/intellij/plugins/haxe/model/evaluator/HaxeExpressionEvaluatorHandlers.java b/src/main/java/com/intellij/plugins/haxe/model/evaluator/HaxeExpressionEvaluatorHandlers.java index 5b2e89713..1aa5eea60 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/evaluator/HaxeExpressionEvaluatorHandlers.java +++ b/src/main/java/com/intellij/plugins/haxe/model/evaluator/HaxeExpressionEvaluatorHandlers.java @@ -1,5 +1,6 @@ package com.intellij.plugins.haxe.model.evaluator; +import com.esotericsoftware.kryo.kryo5.util.Null; import com.intellij.lang.ASTNode; import com.intellij.lang.annotation.AnnotationBuilder; import com.intellij.openapi.util.RecursionGuard; @@ -1370,6 +1371,9 @@ static ResultHolder handleCallExpression( // generateResolverFromScopeParents - making sure we got typeParameters from arguments/parameters HaxeGenericResolver localResolver = HaxeGenericResolverUtil.generateResolverFromScopeParents(callExpression); localResolver.addAll(resolver); + if(resolver.getAssignHint() != null) { + localResolver.setAssignHint(resolver.getAssignHint()); + } SpecificTypeReference functionType; if (callExpressionRef != null) { // can be null if the entire expression is a macro of callExpression @@ -1386,13 +1390,22 @@ static ResultHolder handleCallExpression( } } - functionType = handle(callExpressionRef, context, localResolver).getType(); - boolean varIsMacroFunction = isCallExpressionToMacroMethod(callExpressionRef); - boolean callIsFromMacroContext = isInMacroFunction(callExpressionRef); - if (varIsMacroFunction && !callIsFromMacroContext) { - ResultHolder holder = resolveMacroTypesForFunction(functionType.createHolder()); - functionType = holder.getFunctionType(); + HaxeMethodModel methodModel = tryGetMethodModel(callExpression); + if(methodModel != null) { + ResultHolder assignHint = resolver.getAssignHint(); + SpecificTypeReference assignHintType = assignHint == null ? null : assignHint.getType(); + HaxeCallExpressionContext callExpressionContext = HaxeCallExpressionUtil.createContextForMethodCall(callExpression, assignHintType, methodModel.getMethod()); + HaxeCallExpressionEvaluation evaluate = callExpressionContext.evaluate(); + functionType = evaluate.getFunctionType(methodModel); + }else { + functionType = handle(callExpressionRef, context, localResolver).getType(); } + boolean varIsMacroFunction = isCallExpressionToMacroMethod(callExpressionRef); + boolean callIsFromMacroContext = isInMacroFunction(callExpressionRef); + if (varIsMacroFunction && !callIsFromMacroContext) { + ResultHolder holder = resolveMacroTypesForFunction(functionType.createHolder()); + functionType = holder.getFunctionType(); + } }else if (callExpression.getMacroExpressionReification() != null) { functionType = SpecificTypeReference.getUnknown(callExpression.getMacroExpressionReification()); }else { @@ -1537,20 +1550,27 @@ static ResultHolder handleCallExpression( return createUnknown(callExpression); } - private static HaxeClass tryGetMethodDeclaringClass(HaxeCallExpression expression) { + @Nullable + private static HaxeMethodModel tryGetMethodModel(HaxeCallExpression expression) { if (expression.getExpression() instanceof HaxeReference reference) { final PsiElement resolved = reference.resolve(); if (resolved instanceof HaxeMethod method) { - HaxeMethodModel model = method.getModel(); - if(model != null) { - HaxeClassModel classModel = model.getDeclaringClass(); - if(classModel != null) return classModel.haxeClass; - } + return method.getModel(); } } return null; } + @Null + private static HaxeClass tryGetMethodDeclaringClass(HaxeCallExpression expression) { + HaxeMethodModel model = tryGetMethodModel(expression); + if (model != null) { + HaxeClassModel classModel = model.getDeclaringClass(); + if (classModel != null) return classModel.haxeClass; + } + return null; + } + private static boolean isInMacroFunction(HaxeExpression ref) { HaxeMethodDeclaration type = PsiTreeUtil.getParentOfType(ref, HaxeMethodDeclaration.class); if (type != null && type.getModel() != null) { diff --git a/src/main/java/com/intellij/plugins/haxe/model/evaluator/HaxeExpressionUsageUtil.java b/src/main/java/com/intellij/plugins/haxe/model/evaluator/HaxeExpressionUsageUtil.java index 9fffb4ace..014f132c9 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/evaluator/HaxeExpressionUsageUtil.java +++ b/src/main/java/com/intellij/plugins/haxe/model/evaluator/HaxeExpressionUsageUtil.java @@ -103,10 +103,10 @@ public static ResultHolder searchReferencesForTypeParameters(final HaxeComponent // This is a common problem when you got a variable that gets its typeParameters from method calls on that instance, // and our code will try to find callie type var newValues = searchReferencesForTypeParametersRecursionGuard.computePreventingRecursion(componentName, false, () -> { - ResultHolder originalType = resultHolder.duplicate(); - SpecificHaxeClassReference classType = originalType.getClassType(); + ResultHolder updatedType = resultHolder.duplicate(); + SpecificHaxeClassReference classType = updatedType.getClassType(); // TODO mlo: should we add some kind of support for functions here ? - if (classType == null) return originalType; + if (classType == null) return updatedType; HaxeGenericResolver classResolver = classType.getGenericResolver(); PsiSearchHelper searchHelper = PsiSearchHelper.getInstance(componentName.getProject()); @@ -136,14 +136,14 @@ public static ResultHolder searchReferencesForTypeParameters(final HaxeComponent PsiElement parent = expression.getParent(); if (reference instanceof HaxeReferenceExpression referenceExpression) { - ResultHolder result = tryFindTypeWhenUsedAsParameterInCallExpression(originalType, referenceExpression, parent); + ResultHolder result = tryFindTypeWhenUsedAsParameterInCallExpression(updatedType, referenceExpression, parent); if (result == null) return null; - if (!result.isUnknown()) originalType = mapTypeParameterIfAssignable(originalType, result); - if (!originalType.containsUnknownTypes()) return originalType; + if (!result.isUnknown()) updatedType = mapTypeParameterIfAssignable(updatedType, result); + if (!updatedType.containsUnknownTypes()) return updatedType; } if (parent instanceof HaxeAssignExpression assignExpression) { - ResultHolder assignType = tryTypeFromAssignExpression(context, resolver, originalType, assignExpression, componentName); + ResultHolder assignType = tryTypeFromAssignExpression(context, resolver, updatedType, assignExpression, componentName); if (assignType == null) return null; if (!assignType.isUnknown()) { // we want to ignore assign to null value (flag to not change isFirst) @@ -160,50 +160,50 @@ public static ResultHolder searchReferencesForTypeParameters(final HaxeComponent } } if (isRightExpresion) { - ResultHolder instanceType = getInstanceTypeWithDefaultTypeParameters(originalType); + ResultHolder instanceType = getInstanceTypeWithDefaultTypeParameters(updatedType); if (instanceType.canAssign(assignType)) { - originalType = mapTypeParameter(originalType, assignType); + updatedType = mapTypeParameter(updatedType, assignType); } } else { - if (assignType.canAssign(originalType)) { - originalType = mapTypeParameter(originalType, assignType); + if (assignType.canAssign(updatedType)) { + updatedType = mapTypeParameter(updatedType, assignType); } } } } - if (!originalType.containsUnknownTypes()) return originalType; + if (!updatedType.containsUnknownTypes()) return updatedType; } if (parent instanceof HaxeReferenceExpression referenceExpression) { - ResultHolder result = tryFindTypeFromMethodCallOnReference(originalType, referenceExpression); + ResultHolder result = tryFindTypeFromMethodCallOnReference(updatedType, referenceExpression); if (result == null) return null; - if (!result.isUnknown()) originalType = mapTypeParameterIfAssignable(originalType, result); - if (!originalType.containsUnknownTypes()) return originalType; + if (!result.isUnknown()) updatedType = mapTypeParameterIfAssignable(updatedType, result); + if (!updatedType.containsUnknownTypes()) return updatedType; } if (parent instanceof HaxeObjectLiteralElement literalElement) { ResultHolder result = tryTypeFromObjectLiteral(context, resolver, literalElement); if (result == null) return null; - if (!result.isUnknown()) originalType = mapTypeParameterIfAssignable(originalType, result); - if (!originalType.containsUnknownTypes()) return originalType; + if (!result.isUnknown()) updatedType = mapTypeParameterIfAssignable(updatedType, result); + if (!updatedType.containsUnknownTypes()) return updatedType; } if (parent instanceof HaxeArrayAccessExpression arrayAccessExpression) { ResultHolder result = tryUpdateTypeParamFromArrayAccess(context, resolver, arrayAccessExpression, classType, classResolver, classType); if (result == null) return null; - if (!result.isUnknown()) originalType = mapTypeParameterIfAssignable(originalType, result); - if (!originalType.containsUnknownTypes()) return originalType; + if (!result.isUnknown()) updatedType = mapTypeParameterIfAssignable(updatedType, result); + if (!updatedType.containsUnknownTypes()) return updatedType; } if (parent instanceof HaxeObjectLiteralElement literalElement) { ResultHolder result = tryUpdateTypeParamFromObjectLiteral(context, resolver, literalElement, classType); if (result == null) return null; - if (!result.isUnknown()) originalType = mapTypeParameterIfAssignable(originalType, result); - if (!originalType.containsUnknownTypes()) return originalType; + if (!result.isUnknown()) updatedType = mapTypeParameterIfAssignable(updatedType, result); + if (!updatedType.containsUnknownTypes()) return updatedType; } } } - return originalType; + return updatedType; }); return newValues != null ? newValues.noCache() : resultHolder.noCache(); } diff --git a/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionContext.java b/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionContext.java index 939a08b7a..4904a22df 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionContext.java +++ b/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionContext.java @@ -39,6 +39,7 @@ public class HaxeCallExpressionContext { @Setter @Nullable SpecificHaxeClassReference callie; + SpecificTypeReference assignHint; @Nullable private PsiElement sourceExpression; @@ -125,6 +126,8 @@ private HaxeCallExpressionEvaluation evaluate(boolean trackErrors, PsiElement so parameterResolver.addAll(methodResolver); parameterResolver.addAll(callExpressionScopeResolver); + applyAssignHint(argumentResolver, parameterResolver); + // we use a third resolver that combines callie and method parameter values to correctly resolve parameter type // we do this because we want the parameter resolver to only keep track the values used in the callExpression // and not be affected by callie values, this is important to correctly resolve returnType. @@ -243,11 +246,27 @@ private HaxeCallExpressionEvaluation evaluate(boolean trackErrors, PsiElement so } // update callExpressionResolver with any new resolve values from argument-parameter types - evaluation.callExpressionResolver.addAll(parameterResolver); + evaluation.callExpressionResolver.addAll(combinedResolver); evaluation.setCompleted(true); return evaluation; } + private void applyAssignHint(HaxeGenericResolver argumentResolver, HaxeGenericResolver parameterResolver) { + if (assignHint != null && returnType != null) { + SpecificTypeReference _assignHint = assignHint; + if(_assignHint instanceof SpecificHaxeClassReference classReference) { + _assignHint = classReference.fullyResolveTypeDefAndUnwrapNullTypeReference(); + } + + SpecificTypeReference _returnType = returnType.getType(); + if(_returnType instanceof SpecificHaxeClassReference classReference) { + _returnType = classReference.fullyResolveTypeDefAndUnwrapNullTypeReference(); + } + + updateResolverIfNecessary(_assignHint, argumentResolver,_returnType, parameterResolver); + } + } + private static @NotNull HaxeGenericResolver getCallieResolver(SpecificTypeReference resolvedCallie) { return Optional.ofNullable(resolvedCallie) .filter(s -> s instanceof SpecificHaxeClassReference) @@ -268,7 +287,9 @@ private void updateResolverIfNecessary(SpecificTypeReference argumentType, HaxeG if(parameterResolver.containsConstraint(typeParameter)) { ResultHolder resolve = parameterResolver.resolve(typeParameter); if (resolve == null || resolve.isUnknown() || resolve.isTypeParameter()) { - parameterResolver.add(typeParameter, argumentType.createHolder()); + if (parameterClassReference.canAssign(argumentType)) { + parameterResolver.add(typeParameter, argumentType.createHolder()); + } } } } @@ -280,9 +301,9 @@ else if (parameterClassReference.createHolder().containsUnknownTypeParameters()) @NotNull ResultHolder[] argumentSpecifics = downCastedType.getSpecifics(); int argumentsToCheck = Math.min(argumentSpecifics.length, parameterSpecifics.length); for (int i = 0; i < argumentsToCheck; i++) { - ResultHolder parameterSpecific = parameterSpecifics[i]; - ResultHolder argumentSpecific = argumentResolver.resolve(argumentSpecifics[i]); - if (argumentSpecific != null) { + ResultHolder parameterSpecific = tryUnwrapNull(parameterSpecifics[i]); + ResultHolder argumentSpecific = tryUnwrapNull(argumentResolver.resolve(argumentSpecifics[i])); + if (argumentSpecific != null && parameterSpecific.canAssign(argumentSpecific)) { updateResolverIfNecessary( argumentSpecific.getType(), argumentResolver, parameterSpecific.getType(), parameterResolver); @@ -334,6 +355,14 @@ else if (parameterClassReference.createHolder().containsUnknownTypeParameters()) } } + private ResultHolder tryUnwrapNull(@Nullable ResultHolder holder) { + if(holder == null) return null; + if(holder.isNullWrappedType()) { + return holder.getClassType().unwrapNullType().createHolder(); + } + return holder; + } + private static @NotNull SpecificTypeReference tryResolve(HaxeGenericResolver callExpressionResolver, SpecificTypeReference type) { ResultHolder resolvedParameterType = callExpressionResolver.resolve(type); if (resolvedParameterType != null && !resolvedParameterType.isUnknown()) { diff --git a/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionEvaluation.java b/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionEvaluation.java index 67f733478..986903ae5 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionEvaluation.java +++ b/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionEvaluation.java @@ -1,19 +1,16 @@ package com.intellij.plugins.haxe.model.evaluator.callexpression; import com.intellij.openapi.util.TextRange; -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.plugins.haxe.model.HaxeMethodModel; +import com.intellij.plugins.haxe.model.HaxeParameterModel; +import com.intellij.plugins.haxe.model.type.*; import com.intellij.psi.PsiElement; import lombok.Getter; import lombok.Setter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class HaxeCallExpressionEvaluation { @@ -101,7 +98,8 @@ public int getParameterForArgument(int argumentIndex) { public ResultHolder getReturnType() { - return callExpressionResolver.resolve(returnType); + ResultHolder resolve = callExpressionResolver.resolve(returnType); + return resolve != null ? resolve : returnType; } public ResultHolder getCallie() { return callExpressionResolver.resolve(callie); @@ -112,6 +110,13 @@ public HaxeCallExpressionEvaluation validationFailed() { return this; } - - + public SpecificFunctionReference getFunctionType(HaxeMethodModel haxeMethod) { + LinkedList args = new LinkedList<>(); + List parameters = haxeMethod.getParameters(); + for (int i = 0; i < parameters.size(); i++) { + HaxeParameterModel param = parameters.get(i); + args.add(new HaxeArgument(param.getParameterPsi(), i, param.isOptional(), param.isRest(), param.getType(callExpressionResolver), param.getName())); + } + return new SpecificFunctionReference(args, getReturnType(), haxeMethod, haxeMethod.getMethod()); + } } diff --git a/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionUtil.java b/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionUtil.java index 4bf3cd5ab..001feba18 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionUtil.java +++ b/src/main/java/com/intellij/plugins/haxe/model/evaluator/callexpression/HaxeCallExpressionUtil.java @@ -69,6 +69,13 @@ public static HaxeCallExpressionContext createContextForMethodCall(@NotNull List @NotNull public static HaxeCallExpressionContext createContextForMethodCall(@NotNull HaxeCallExpression callExpression, @NotNull HaxeMethod method) { + return createContextForMethodCall(callExpression, null, method); + } + @NotNull + public static HaxeCallExpressionContext createContextForMethodCall(@NotNull HaxeCallExpression callExpression, + @Nullable SpecificTypeReference assignHint, + @NotNull HaxeMethod method + ) { HaxeMethodModel methodModel = method.getModel(); HaxeGenericResolver genericResolver = new HaxeGenericResolver(); @@ -98,6 +105,7 @@ public static HaxeCallExpressionContext createContextForMethodCall(@NotNull Haxe HaxeCallExpressionContext evaluation = new HaxeCallExpressionContext(argumentList, parameterList, returnType, parentResolver, methodTranslatedResolver); evaluation.isStaticExtension = isStaticExtension; evaluation.isMacroFunction = isMacroFunction; + evaluation.assignHint = assignHint; evaluation.callie = callie; diff --git a/src/main/java/com/intellij/plugins/haxe/model/type/HaxeAssignContext.java b/src/main/java/com/intellij/plugins/haxe/model/type/HaxeAssignContext.java deleted file mode 100644 index 1d6ec00f6..000000000 --- a/src/main/java/com/intellij/plugins/haxe/model/type/HaxeAssignContext.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.intellij.plugins.haxe.model.type; - -import com.intellij.openapi.util.text.Strings; -import com.intellij.psi.PsiElement; -import lombok.Data; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Data -public class HaxeAssignContext { - - @Nullable PsiElement toOrigin; - @Nullable PsiElement fromOrigin; - - //NOTE: bit of a hack maybe. - // helper flag to tell canAssign check that target is a constraint. this to allow enumTypeParameters to be assigned to EnumValue - private boolean constraintCheck = false; - - public HaxeAssignContext(@Nullable PsiElement toOrigin, @Nullable PsiElement fromOrigin) { - this.toOrigin = toOrigin; - this.fromOrigin = fromOrigin; - } - - final List missingMembers = new ArrayList<>(); - final Map wrongTypeMembers = new HashMap<>(); - final Map wrongTypePsi = new HashMap<>(); - - public void addMissingMember(String name) { - missingMembers.add(name); - } - public void addWrongTypeMember(String have, String wants, PsiElement psi) { - wrongTypeMembers.put(have, wants); - wrongTypePsi.put(psi, "have '" + have + "' wants '" + wants + "'"); - } - - public boolean hasMissingMembers() { - return !missingMembers.isEmpty(); - } - public boolean hasWrongTypeMembers() { - return !wrongTypeMembers.isEmpty(); - } - - // TODO use map to generate errors - public Map getWrongTypeMap(){ - return wrongTypePsi; - } - - public String getMissingMembersString() { - return Strings.join(getMissingMembers(), ", "); - } - public String geWrongTypeMembersString() { - return getWrongTypeMembers().entrySet().stream() - //TODO bundle - .map(entry -> "Incompatible type: have '" + entry.getKey() + "' wants '" + entry.getValue() + "'") - .collect(Collectors.joining(", ")); - } - - public void setConstraintCheck(boolean constraintCheck) { - this.constraintCheck = constraintCheck; - } - public boolean isConstraintCheck() { - return constraintCheck; - } - - public void clearErrors() { - missingMembers.clear(); - wrongTypeMembers.clear(); - } -} diff --git a/src/main/java/com/intellij/plugins/haxe/model/type/HaxeGenericResolver.java b/src/main/java/com/intellij/plugins/haxe/model/type/HaxeGenericResolver.java index 1cf113fe2..e745c25b1 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/type/HaxeGenericResolver.java +++ b/src/main/java/com/intellij/plugins/haxe/model/type/HaxeGenericResolver.java @@ -96,7 +96,10 @@ public void addArgument(@NotNull ResolverEntry entry) { public HaxeGenericResolver addAll(@Nullable HaxeGenericResolver parentResolver) { if (null != parentResolver && parentResolver != this) { - assignHint = parentResolver.assignHint; + ResultHolder parentHint = parentResolver.assignHint; + if (parentHint != null && !parentHint.isUnknown()) { + assignHint = parentHint; + } // not using "collection.addAll" because there is extra logic in add() that we need to execute for (ResolverEntry resolver : parentResolver.resolvers) { this.add(resolver.typeParameter(), resolver.type()); diff --git a/src/main/java/com/intellij/plugins/haxe/model/type/SpecificHaxeClassReference.java b/src/main/java/com/intellij/plugins/haxe/model/type/SpecificHaxeClassReference.java index b42fada16..02ffacfa1 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/type/SpecificHaxeClassReference.java +++ b/src/main/java/com/intellij/plugins/haxe/model/type/SpecificHaxeClassReference.java @@ -255,6 +255,13 @@ public HaxeGenericResolver getGenericResolver() { HaxeGenericResolver resolver = new HaxeGenericResolver(); HaxeClassModel model = getHaxeClassModel(); if (model != null) { + if(model instanceof HaxeGenericParamModel genericParamModel) { + ResultHolder constraint = genericParamModel.getConstraint(null); + if (constraint != null && constraint.getClassType() != null) { + // TODO might need a recursion guard ? + return constraint.getClassType().getGenericResolver(); + } + } if (model instanceof HaxeConstraintTypeListModel constraintModel) { //TODO mlo: move into this stream/logic to method in HaxeConstraintTypeListModel List list = diff --git a/src/main/java/com/intellij/plugins/haxe/model/type/resolver/HaxeGenericResolverCastUtil.java b/src/main/java/com/intellij/plugins/haxe/model/type/resolver/HaxeGenericResolverCastUtil.java index 3d95b4c75..003d47914 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/type/resolver/HaxeGenericResolverCastUtil.java +++ b/src/main/java/com/intellij/plugins/haxe/model/type/resolver/HaxeGenericResolverCastUtil.java @@ -340,7 +340,16 @@ public static HaxeGenericResolver createExtendingClassResolver(@NotNull HaxeGene if (replaced instanceof HaxeTypeParameterDeclaration replacedTypeParameter) { ResultHolder resolve = mappedResolver.resolve(replacedTypeParameter); if (resolve != null) { - nextResolver.add(typeParameter, resolve); + if (resolve.getClassType() != null && resolve.getClassType().getHaxeClass() == replaced) { + // if the typeParameter we are updating is pointing to itself we update the type to new typeParameter as well + // we dont want to update just typeParameter and keep type as that would cause issues down the line. + + // TODO need some deeper research, might check if class is same ? + // also do we need to do something regarding constraints now being lost? + nextResolver.add(typeParameter, typeParameter.getModel().getInstanceType()); + }else { + nextResolver.add(typeParameter, resolve); + } } } } diff --git a/src/test/java/com/intellij/plugins/haxe/ide/HaxeSemanticAnnotatorTest.java b/src/test/java/com/intellij/plugins/haxe/ide/HaxeSemanticAnnotatorTest.java index f6f4749fb..ebee6de90 100644 --- a/src/test/java/com/intellij/plugins/haxe/ide/HaxeSemanticAnnotatorTest.java +++ b/src/test/java/com/intellij/plugins/haxe/ide/HaxeSemanticAnnotatorTest.java @@ -937,4 +937,9 @@ public void testIteratorTypeResolve() throws Throwable { public void testUnificationRules() throws Exception { doTestNoFixWithWarnings(); } + + @Test + public void testAssignFromTypeParameterConstraints() throws Exception { + doTestNoFixWithWarnings(); + } } diff --git a/src/test/resources/testData/annotation.semantic/AssignFromTypeParameterConstraints.hx b/src/test/resources/testData/annotation.semantic/AssignFromTypeParameterConstraints.hx new file mode 100644 index 000000000..704f26269 --- /dev/null +++ b/src/test/resources/testData/annotation.semantic/AssignFromTypeParameterConstraints.hx @@ -0,0 +1,34 @@ +package ; + +class NullTypeReturnsTest { + public function new() { + // should work A extends B that implements D + var a:A = nullWrappedReturn(); + var a:A = normalReturn(); + var a:Array = nullWrappedTypeParameterReturn(); + + // should work B implements D + var b:B = nullWrappedReturn(); + var b:B = normalReturn(); + var a:Array = nullWrappedTypeParameterReturn(); + + //TODO errors should probably just show constraint + + // should fail, C does not implement interface D + var c:C = nullWrappedReturn(); + var c:C = normalReturn(); + var a:Array = nullWrappedTypeParameterReturn() + + // wrong does not match constraints + var c:String = normalReturn(); + } + public function nullWrappedReturn():Null {return null;} + public function normalReturn():T {return null;} + public function nullWrappedTypeParameterReturn():Array> {return null;} + public function nullWrappedTypeParameterReturn2():Array> {return null;} +} + +class A extends B {function new() { super(); } } +class B extends C implements D {function new() {super();}} +class C {function new() {} } +interface D {}