From e18a65cf6b8c9d48ea59b63c78f3434059f3eaf1 Mon Sep 17 00:00:00 2001 From: m0rkeulv Date: Tue, 10 Sep 2024 22:49:13 +0200 Subject: [PATCH] resolve members from typeParameter constrains if present --- .../semantics/HaxeCallExpressionUtil.java | 4 +- .../plugins/haxe/lang/psi/HaxeClass.java | 1 + .../plugins/haxe/lang/psi/HaxeResolver.java | 18 +++++ .../lang/psi/impl/AbstractHaxePsiClass.java | 3 +- .../lang/psi/impl/AnonymousHaxeTypeImpl.java | 17 +++++ .../haxe/lang/psi/impl/HaxeReferenceImpl.java | 15 +++-- .../haxe/model/HaxeAnonymousTypeModel.java | 12 ++++ .../type/HaxeExpressionEvaluatorHandlers.java | 19 ++++-- .../haxe/model/type/HaxeGenericResolver.java | 16 +++-- .../model/type/HaxeGenericResolverUtil.java | 31 ++++++--- .../type/SpecificHaxeClassReference.java | 6 +- .../haxe/ide/HaxeSemanticAnnotatorTest.java | 6 ++ .../completion/ReferenceCompletionTest.java | 5 ++ .../TypeFromConstraints.hx | 66 +++++++++++++++++++ .../references/TypeParameterConstraints.hx | 12 ++++ .../references/TypeParameterConstraints.txt | 5 ++ 16 files changed, 206 insertions(+), 30 deletions(-) create mode 100644 src/test/resources/testData/annotation.semantic/TypeFromConstraints.hx create mode 100644 src/test/resources/testData/completion/references/TypeParameterConstraints.hx create mode 100644 src/test/resources/testData/completion/references/TypeParameterConstraints.txt diff --git a/src/main/java/com/intellij/plugins/haxe/ide/annotator/semantics/HaxeCallExpressionUtil.java b/src/main/java/com/intellij/plugins/haxe/ide/annotator/semantics/HaxeCallExpressionUtil.java index e8a5cf891..671e30b5a 100644 --- a/src/main/java/com/intellij/plugins/haxe/ide/annotator/semantics/HaxeCallExpressionUtil.java +++ b/src/main/java/com/intellij/plugins/haxe/ide/annotator/semantics/HaxeCallExpressionUtil.java @@ -91,7 +91,9 @@ public static CallExpressionValidation checkMethodCall(@NotNull HaxeCallExpressi // map type Parameters to method declaring class resolver if necessary if (methodClass != null && methodClass.haxeClass != null) { HaxeClass callieClass = callieType.getClassType().getHaxeClass(); - classTypeResolver = HaxeGenericResolverUtil.createInheritedClassResolver(methodClass.haxeClass, callieClass, classTypeResolver); + if(callieClass != null) { + classTypeResolver = HaxeGenericResolverUtil.createInheritedClassResolver(methodClass.haxeClass, callieClass, classTypeResolver); + } } } } diff --git a/src/main/java/com/intellij/plugins/haxe/lang/psi/HaxeClass.java b/src/main/java/com/intellij/plugins/haxe/lang/psi/HaxeClass.java index 0a84cd860..47c9bca56 100644 --- a/src/main/java/com/intellij/plugins/haxe/lang/psi/HaxeClass.java +++ b/src/main/java/com/intellij/plugins/haxe/lang/psi/HaxeClass.java @@ -64,6 +64,7 @@ public String getName() { @NonNls String getQualifiedName(); + @NotNull HaxeClassModel getModel(); @NotNull 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 738e425c8..8d478669d 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 @@ -26,6 +26,7 @@ import com.intellij.openapi.util.RecursionManager; import com.intellij.plugins.haxe.ide.annotator.semantics.HaxeCallExpressionUtil; import com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypes; +import com.intellij.plugins.haxe.lang.psi.impl.HaxeClassWrapperForTypeParameter; import com.intellij.plugins.haxe.lang.psi.impl.HaxeReferenceExpressionImpl; import com.intellij.plugins.haxe.metadata.psi.HaxeMeta; import com.intellij.plugins.haxe.metadata.psi.HaxeMetadataCompileTimeMeta; @@ -1481,6 +1482,8 @@ private List resolveChain(HaxeReference lefthandExpression // this is important as recursion guards might prevent us from getting the type and returning a different result depending on // whether or not we got the type is bad and causes issues. if (haxeClass != null && !result.isUnknown()) { + // if type parameter, try to find constraints and use those ? + haxeClass = useConstraintsIfTypeParameter(reference, haxeClass); HaxeClassModel classModel = haxeClass.getModel(); HaxeBaseMemberModel member = classModel.getMember(identifier, classType.getGenericResolver()); if (member != null) { @@ -1567,6 +1570,21 @@ private List resolveChain(HaxeReference lefthandExpression } + private static HaxeClass useConstraintsIfTypeParameter(HaxeReference reference, HaxeClass haxeClass) { + if(haxeClass instanceof HaxeClassWrapperForTypeParameter typeParameter) { + HaxeGenericResolver referenceResolver = HaxeGenericResolverUtil.generateResolverFromScopeParents(reference); + ResultHolder resolved = referenceResolver.resolve(typeParameter); + if (resolved != null && resolved.isClassType()) { + SpecificHaxeClassReference classReference = resolved.getClassType(); + if (classReference != null) { + HaxeClass newClassValue = classReference.getHaxeClass(); + if(newClassValue != null) haxeClass = newClassValue; + } + } + } + return haxeClass; + } + private PsiElement resolveQualifiedReference(HaxeReference reference) { String qualifiedName = reference.getText(); diff --git a/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/AbstractHaxePsiClass.java b/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/AbstractHaxePsiClass.java index 7b6411622..02487df75 100644 --- a/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/AbstractHaxePsiClass.java +++ b/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/AbstractHaxePsiClass.java @@ -111,6 +111,7 @@ public String getQualifiedName() { private HaxeClassModel _model = null; + @NotNull public HaxeClassModel getModel() { if (_model == null) { if (this instanceof HaxeTypeParameterMultiType multiType) { @@ -261,7 +262,7 @@ public HaxeNamedComponent findHaxeFieldByName(@NotNull final String name, @Nulla } private static CachedValueProvider.Result> getHaxeFieldAllCached(@NotNull AbstractHaxePsiClass haxePsiClass) { - List all = haxePsiClass.getHaxeFieldAll(HaxeComponentType.CLASS, HaxeComponentType.ENUM, HaxeComponentType.ABSTRACT); + List all = haxePsiClass.getHaxeFieldAll(HaxeComponentType.CLASS, HaxeComponentType.ENUM, HaxeComponentType.ABSTRACT, HaxeComponentType.TYPEDEF); List dependencies = collectCacheDependencies(haxePsiClass); return CachedValueProvider.Result.create(all, dependencies); diff --git a/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/AnonymousHaxeTypeImpl.java b/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/AnonymousHaxeTypeImpl.java index 661615413..dd7803450 100644 --- a/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/AnonymousHaxeTypeImpl.java +++ b/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/AnonymousHaxeTypeImpl.java @@ -22,12 +22,17 @@ import com.intellij.openapi.project.Project; import com.intellij.plugins.haxe.lang.psi.*; import com.intellij.plugins.haxe.model.HaxeAnonymousTypeModel; +import com.intellij.plugins.haxe.model.type.ResultHolder; +import com.intellij.plugins.haxe.model.type.SpecificHaxeClassReference; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiIdentifier; +import com.intellij.psi.impl.PsiClassImplUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * @author: Fedor.Korotkov @@ -44,6 +49,18 @@ public List getHaxeExtendsList() { return model.getExtensionTypesPsi(); } + @Override + @NotNull + public PsiClass[] getSupers() { + HaxeAnonymousTypeModel model = (HaxeAnonymousTypeModel) getModel(); + return model.getExtendsTypes().stream() + .map(ResultHolder::getClassType) + .filter(Objects::nonNull) + .map(SpecificHaxeClassReference::getHaxeClass) + .filter(Objects::nonNull) + .toArray(PsiClass[]::new); + } + @Override public HaxeComponentName getComponentName() { return null; diff --git a/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/HaxeReferenceImpl.java b/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/HaxeReferenceImpl.java index fc2f4c6f5..77ca0fbe3 100644 --- a/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/HaxeReferenceImpl.java +++ b/src/main/java/com/intellij/plugins/haxe/lang/psi/impl/HaxeReferenceImpl.java @@ -1236,9 +1236,10 @@ public Object[] getVariants() { // if not first in chain // foo.bar.baz HaxeResolveResult result = null; + HaxeGenericResolver resolver = null; final HaxeReference leftReference = HaxeResolveUtil.getLeftReference(this); if (leftReference != null) { - HaxeGenericResolver resolver = HaxeGenericResolverUtil.generateResolverFromScopeParents(leftReference); + resolver = HaxeGenericResolverUtil.generateResolverFromScopeParents(leftReference); ResultHolder leftResult = HaxeTypeResolver.getPsiElementType(leftReference, resolver); if (leftResult.getClassType() != null) { SpecificTypeReference reference = leftResult.getClassType().fullyResolveTypeDefAndUnwrapNullTypeReference(); @@ -1250,14 +1251,14 @@ public Object[] getVariants() { HaxeClass haxeClass = null; String name = null; - HaxeGenericResolver resolver = null; + if (result != null) { if (result != HaxeResolveResult.EMPTY) { haxeClass = result.getHaxeClass(); if (haxeClass != null) { name = haxeClass.getName(); } - resolver = result.getSpecialization().toGenericResolver(haxeClass); + resolver.addAll(result.getSpecialization().toGenericResolver(haxeClass)); } if (leftReference.resolve() instanceof HaxeImportAlias alias) { name = alias.getIdentifier().getText(); @@ -1493,7 +1494,13 @@ private void addClassNonStaticMembersVariants(Set suggestedVa } } } - + // if type parameter, try to find constraints and use those ? + if(haxeClass instanceof HaxeClassWrapperForTypeParameter typeParameter) { + ResultHolder resolved = resolver.resolve(typeParameter); + if (resolved != null && resolved.isClassType()) { + haxeClass = resolved.getClassType().getHaxeClass(); + } + } for (HaxeNamedComponent namedComponent : HaxeResolveUtil.findNamedSubComponents(resolver, haxeClass)) { final boolean needFilter = filterByAccess && !namedComponent.isPublic(); if (isAbstractEnum && HaxeAbstractEnumUtil.couldBeAbstractEnumField(namedComponent)) { diff --git a/src/main/java/com/intellij/plugins/haxe/model/HaxeAnonymousTypeModel.java b/src/main/java/com/intellij/plugins/haxe/model/HaxeAnonymousTypeModel.java index 7bc3b848f..45a6d461a 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/HaxeAnonymousTypeModel.java +++ b/src/main/java/com/intellij/plugins/haxe/model/HaxeAnonymousTypeModel.java @@ -19,16 +19,28 @@ public HaxeAnonymousTypeModel(@NotNull HaxeAnonymousType anonymousType) { this.anonymousType = anonymousType; } + //TODO consolidate composite types (`{> type, ...}`) and extension types (`typeX & typeY`) public List getCompositeTypes() { List typeList = getCompositeTypesPsi(); return typeList.stream().map(HaxeTypeResolver::getTypeFromType).toList(); } + public List getExtendsTypes() { + List typeList = getExtendsList(); + return typeList.stream().map(HaxeTypeResolver::getTypeFromType).toList(); + } + public List getCompositeTypesPsi() { return CachedValuesManager.getProjectPsiDependentCache(anonymousType, HaxeAnonymousTypeModel::_getCompositeTypes); } public List getExtensionTypesPsi() { return CachedValuesManager.getProjectPsiDependentCache(anonymousType, HaxeAnonymousTypeModel::_getExtendsTypes); } + @NotNull + public List getExtendsList() { + List extendList = new ArrayList<>(getCompositeTypesPsi()); + extendList.addAll(getExtensionTypesPsi()); + return extendList; + } private static List _getCompositeTypes(HaxeAnonymousType anonymousType) { val items = new ArrayList(); diff --git a/src/main/java/com/intellij/plugins/haxe/model/type/HaxeExpressionEvaluatorHandlers.java b/src/main/java/com/intellij/plugins/haxe/model/type/HaxeExpressionEvaluatorHandlers.java index 23a932106..6ac6d77d1 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/type/HaxeExpressionEvaluatorHandlers.java +++ b/src/main/java/com/intellij/plugins/haxe/model/type/HaxeExpressionEvaluatorHandlers.java @@ -185,10 +185,8 @@ static ResultHolder handleReferenceExpression( HaxeExpressionEvaluatorContext co if (typeReference.isUnknown()) continue; - // unwrap Null - //TODO make util for unwrap/get underlying type of Null? (or fix resolver ?) if (typeReference.isNullType()) { - typeHolder = typeHolder.getClassType().getSpecifics()[0]; + typeHolder = typeHolder.tryUnwrapNullType(); } // TODO: Yo! Eric!! This needs to get fixed. The resolver is coming back as Dynamic, when it should be String @@ -201,6 +199,7 @@ static ResultHolder handleReferenceExpression( HaxeExpressionEvaluatorContext co SpecificHaxeClassReference classType = typeHolder.getClassType(); if (null != classType) { + localResolver = localResolver.withoutClassTypeParameters(); localResolver.addAll(classType.getGenericResolver()); } String accessName = child.getText(); @@ -281,6 +280,11 @@ else if (subelement instanceof HaxeFieldDeclaration fieldDeclaration) { HaxeGenericResolver resolverForContainingClass = inheritedClassResolver.getSpecialization(null).toGenericResolver(containingClass); ResultHolder resolve = resolverForContainingClass.resolve(typeHolder); if (resolve != null && !resolve.isUnknown()) typeHolder = resolve; + }else if (typeHolder.isTypeParameter()) { + ResultHolder resolve = resolver.resolve(typeHolder); + if(resolve != null && !resolve.isUnknown()) { + typeHolder = resolve; + } } } @@ -907,8 +911,13 @@ static ResultHolder handleArrayAccessExpression( HaxeArrayAccessExpression arrayAccessExpression) { final List list = arrayAccessExpression.getExpressionList(); if (list.size() >= 2) { - final SpecificTypeReference left = handle(list.get(0), context, resolver).getType(); - final SpecificTypeReference right = handle(list.get(1), context, resolver).getType(); + SpecificTypeReference left = handle(list.get(0), context, resolver).getType(); + SpecificTypeReference right = handle(list.get(1), context, resolver).getType(); + // if left is typeParameter try to use typeParameter constraints and see if it have array accessor + if(left.isTypeParameter()) { + ResultHolder resolve = resolver.resolve(left.createHolder()); + if(resolve != null && !resolve.isUnknown())left = resolve.getType(); + } if (left.isArray()) { Object constant = null; if (left.isConstant()) { 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 d3b291260..2f54da85d 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 @@ -676,15 +676,19 @@ public HaxeGenericResolver removeClassScopeIfMethodIsPresent() { } public HaxeGenericResolver withoutMethodTypeParameters() { - HaxeGenericResolver copy = copy(); - copy.resolvers.removeIf(entry -> entry.resolveSource() == METHOD_TYPE_PARAMETER); - copy.constaints.removeIf(entry -> entry.resolveSource() == METHOD_TYPE_PARAMETER); - return copy; + return without(METHOD_TYPE_PARAMETER); } public HaxeGenericResolver withoutArgumentType() { + return without(ARGUMENT_TYPE); + } + + public HaxeGenericResolver withoutClassTypeParameters() { + return without(CLASS_TYPE_PARAMETER); + } + public HaxeGenericResolver without(ResolveSource source) { HaxeGenericResolver copy = copy(); - copy.resolvers.removeIf(entry -> entry.resolveSource() == ARGUMENT_TYPE); - copy.constaints.removeIf(entry -> entry.resolveSource() == ARGUMENT_TYPE); + copy.resolvers.removeIf(entry -> entry.resolveSource() == source); + copy.constaints.removeIf(entry -> entry.resolveSource() == source); return copy; } } diff --git a/src/main/java/com/intellij/plugins/haxe/model/type/HaxeGenericResolverUtil.java b/src/main/java/com/intellij/plugins/haxe/model/type/HaxeGenericResolverUtil.java index 6be2539a0..fb0cd12ac 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/type/HaxeGenericResolverUtil.java +++ b/src/main/java/com/intellij/plugins/haxe/model/type/HaxeGenericResolverUtil.java @@ -257,17 +257,21 @@ private static void mapTypeParametersFunction(Map map, Spe } } - public static HaxeGenericResolver createInheritedClassResolver(HaxeClass targetClass, HaxeClass currentClass, - HaxeGenericResolver localResolver) { + public static HaxeGenericResolver createInheritedClassResolver(@NotNull HaxeClass targetClass, @NotNull HaxeClass currentClass, + @Nullable HaxeGenericResolver localResolver) { List path = new ArrayList<>(); findClassHierarchy(currentClass, targetClass, path); Collections.reverse(path); + HaxeGenericResolver resolver = currentClass.getMemberResolver(localResolver); + if(resolver == null) resolver = new HaxeGenericResolver(); for (SpecificHaxeClassReference reference : path) { ResultHolder resolved = resolver.resolve(reference.createHolder()); - resolver = resolved.getClassType().getGenericResolver(); + if(resolved.isClassType()) { + resolver = resolved.getClassType().getGenericResolver(); + } } return resolver; } @@ -280,12 +284,23 @@ private static boolean findClassHierarchy(HaxeClass from, HaxeClass to, List> { + var testMember:T; + public function new() { + // member + testMember.indexOf(""); + testMember[0].charAt(1); + + // variable + var testVariable:T; + testVariable.indexOf(""); + testVariable[0].charAt(1); + + // variable (no type tag) + var testNoTypeTag = testMember; + testNoTypeTag.indexOf(""); + testNoTypeTag[0].charAt(1); + } +} + +typedef MyDef = {normalVar:String, typeParamVar:T} + +class ResolveFromConstraints & MyDef>> { + var testMember:T; + + public function testClassMember() { + + //CORRECT + var iterator:Iterator = testMember.iterator(); + var str:String = testMember.normalVar.charAt(1); + var index:Int = testMember.typeParamVar.indexOf(""); + var char:String = testMember.typeParamVar[0].charAt(1); + + var typeParamVar:Array = testMember.typeParamVar; + var iteratorValue:Int = testMember.iterator().next(); + + //WRONG (verifing that expressions returns expected types and dont use type hints when resolving) + var iterator:EnumValue = testMember.iterator(); + var str:EnumValue = testMember.normalVar.charAt(1); + var index:EnumValue = testMember.typeParamVar.indexOf(""); + var char:EnumValue = testMember.typeParamVar[0].charAt(1); + + var typeParamVar:EnumValue = testMember.typeParamVar; + var iteratorValue:EnumValue = testMember.iterator().next(); + + } + public function testLocalVar() { + + var testVariable:T; + var iterator:Iterator = testVariable.iterator(); + var str:String = testVariable.normalVar.charAt(1); + var index:Int = testVariable.typeParamVar.indexOf(""); + var char:String = testVariable.typeParamVar[0].charAt(1); + + var typeParamVar:Array = testVariable.typeParamVar; + var iteratorValue:Int = testVariable.iterator().next(); + + //WRONG (verifing that expressions returns expected types and dont use type hints when resolving) + var iterator:EnumValue = testVariable.iterator(); + var str:EnumValue = testVariable.normalVar.charAt(1); + var index:EnumValue = testVariable.typeParamVar.indexOf(""); + var char:EnumValue = testVariable.typeParamVar[0].charAt(1); + + var typeParamVar:EnumValue = testVariable.typeParamVar; + var iteratorValue:EnumValue = testVariable.iterator().next(); + } +} diff --git a/src/test/resources/testData/completion/references/TypeParameterConstraints.hx b/src/test/resources/testData/completion/references/TypeParameterConstraints.hx new file mode 100644 index 000000000..08fcb6f0a --- /dev/null +++ b/src/test/resources/testData/completion/references/TypeParameterConstraints.hx @@ -0,0 +1,12 @@ +package ; + +typedef MyDef = {normalVar:String, typeParamVar:T} + +class ResolveFromConstraints & MyDef>> { + var testMember:T; + + public function testClassMember() { + testMember. + } +} + diff --git a/src/test/resources/testData/completion/references/TypeParameterConstraints.txt b/src/test/resources/testData/completion/references/TypeParameterConstraints.txt new file mode 100644 index 000000000..9c154ff2c --- /dev/null +++ b/src/test/resources/testData/completion/references/TypeParameterConstraints.txt @@ -0,0 +1,5 @@ +:INCLUDE +normalVar +typeParamVar +iterator +iterator