Skip to content

Commit

Permalink
adding support for combining defaults and constraints in type generics
Browse files Browse the repository at this point in the history
  • Loading branch information
m0rkeulv committed May 19, 2024
1 parent cc2e121 commit 1225c26
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 79 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Bugfix: import would be displayed as unused if last reference in a file was a fully qualified reference.
* Bugfix: Fixing problem with resolving setter method reference from property.
* Bugfix: Imports would not be added automatically when package statement was missing.
* Bugfix: Constraints and defaults could not be used together in type generics
* Improvement: major rework of completion suggestions
- Added completion for constructors
- Added public static members to completion suggestions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.intellij.plugins.haxe.ide.annotator.semantics;

import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypes;
import com.intellij.plugins.haxe.lang.psi.HaxeGenericDefaultType;
import com.intellij.plugins.haxe.lang.psi.HaxeGenericListPart;
import com.intellij.plugins.haxe.lang.psi.HaxeMethodDeclaration;
import com.intellij.plugins.haxe.model.fixer.HaxeFixer;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;

public class HaxeDefaultTypeParameterAnnotator implements Annotator {
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
if (element instanceof HaxeGenericListPart genericListPart) {
check(genericListPart, holder);
}
}

public static void check(HaxeGenericListPart psi, AnnotationHolder holder) {

HaxeGenericDefaultType type = psi.getGenericDefaultType();
PsiElement parent1 = psi.getParent();
PsiElement parent2 = parent1.getParent();

if (type != null && parent2 instanceof HaxeMethodDeclaration) {
PsiElement equalsToken = PsiTreeUtil.findSiblingBackward(type, HaxeTokenTypes.OASSIGN, null);
//TODO extract text to bundle
holder.newAnnotation(HighlightSeverity.ERROR, "Default type parameters are only supported on types")
.range(type)
.withFix(new HaxeFixer("Remove default type") {
@Override
public void run() {
psi.deleteChildRange(equalsToken, type);
}
})
.create();
}
}
}
9 changes: 5 additions & 4 deletions src/main/java/com/intellij/plugins/haxe/lang/parser/haxe.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -527,13 +527,14 @@ haxe4typeList ::= typeListPart ('&' typeListPart)+
//typeGenericParam ::= '<' defaultGenericListPart (',' defaultGenericListPart)* '>'
//methodGenericParam ::= '<' genericListPart (',' genericListPart)* '>'
genericParam ::= '<' genericListPart (',' genericListPart)* '>'
genericListPart ::= defaultGenericListPart | regularGenericListPart | constGenericListPart
genericListPart ::= normalGenericListPart | constGenericListPart
{mixin="com.intellij.plugins.haxe.lang.psi.impl.AbstractHaxeNamedComponent" implements="com.intellij.plugins.haxe.lang.psi.HaxeComponent"}
private defaultGenericListPart ::= componentName '=' typeWrapper
private regularGenericListPart ::= componentName (':' ('(' (haxe4typeList | typeList) ')' | haxe4typeList | typeListPart ))? {pin=2}
genericDefaultType ::= typeWrapper
genericConstraintPart ::= ('(' (haxe4typeList | typeList) ')' | haxe4typeList | typeListPart )
private normalGenericListPart ::= componentName (':' genericConstraintPart)? ('=' genericDefaultType)?
// constGenericListPart is only available when the macroMember is '@:const' (constMeta). It's only useful in macros.
// We can't use constMeta here until the lexer returns a string for macros instead of META_ID.
private constGenericListPart ::= constantExpression (':' ('(' (haxe4typeList | typeList) ')' | haxe4typeList | typeListPart ))? {pin=2}
private constGenericListPart ::= constantExpression (':' genericConstraintPart)? {pin=2}

typeListPart ::= typeWrapper | literalExpression
{mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxeTypeListPartPsiMixinImpl" implements="com.intellij.plugins.haxe.lang.psi.HaxeTypeListPartPsiMixin"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ private static void loadResultWithConstraints(HaxeResolveResult resolveResult,

@Nullable
private static HaxeType getTypeOfGenericListPart(HaxeGenericListPart genericListPart) {
final HaxeTypeListPart typeListPart = genericListPart.getTypeListPart();
HaxeGenericConstraintPart constraintPart = genericListPart.getGenericConstraintPart();
final HaxeTypeListPart typeListPart = constraintPart == null ? null : constraintPart.getTypeListPart();
final HaxeTypeOrAnonymous typeOrAnonymous = ((typeListPart != null) ? typeListPart.getTypeOrAnonymous() : null);
return ((typeOrAnonymous != null) ? typeOrAnonymous.getType() : null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -880,8 +880,9 @@ else if (parameterList.getParent() instanceof HaxeMethodDeclaration method) {
}

if (listPart != null) {
HaxeTypeList list = listPart.getTypeList();
HaxeTypeListPart typeListPart = listPart.getTypeListPart();
HaxeGenericConstraintPart constraintPart = listPart.getGenericConstraintPart();
HaxeTypeList list = constraintPart == null ? null : constraintPart.getTypeList();
HaxeTypeListPart typeListPart =constraintPart == null ? null : constraintPart.getTypeListPart();
ASTNode node = listPart.getContext().getNode();
if (list != null) {
List<HaxeType> classReferences = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@ public class HaxeGenericParamModel {
public HaxeGenericParamModel(@NotNull HaxeGenericListPart part, int index) {
this.index = index;
this.part = part;
this.defaultType = part.getTypeOrAnonymous();
this.defaultFunction = part.getFunctionType();
this.name = part.getComponentName().getText();

HaxeGenericDefaultType defaultPart = part.getGenericDefaultType();
this.defaultType = defaultPart == null ? null : defaultPart.getTypeOrAnonymous();
this.defaultFunction = defaultPart == null ? null : defaultPart.getFunctionType();
}



public int getIndex() {
return index;
}
Expand All @@ -66,53 +70,58 @@ public ResultHolder getConstraint(@Nullable HaxeGenericResolver resolver) {
resolver = new HaxeGenericResolver();
}

HaxeTypeListPart constraint = part.getTypeListPart();
if (constraint == null) {
HaxeTypeList list = part.getTypeList();
if (list != null) {
// no need to use multiType if only one value
if (list.getTypeListPartList().size() == 1) {
HaxeTypeListPart typeListPart = list.getTypeListPartList().get(0);
HaxeFunctionType functionType = typeListPart.getFunctionType();
if (functionType != null) {
return SpecificFunctionReference.create(new HaxeSpecificFunction(functionType, HaxeGenericSpecialization.EMPTY)).createHolder();
}
HaxeTypeOrAnonymous anonymous = typeListPart.getTypeOrAnonymous();
if(anonymous != null) {
return HaxeTypeResolver.getTypeFromTypeOrAnonymous(anonymous);
HaxeGenericConstraintPart constraintPart = part.getGenericConstraintPart();
if (constraintPart != null) {
HaxeTypeListPart constraint = constraintPart.getTypeListPart();
if (constraint == null) {
HaxeTypeList list = constraintPart.getTypeList();
if (list != null) {
// no need to use multiType if only one value
if (list.getTypeListPartList().size() == 1) {
HaxeTypeListPart typeListPart = list.getTypeListPartList().get(0);
HaxeFunctionType functionType = typeListPart.getFunctionType();
if (functionType != null) {
return SpecificFunctionReference.create(new HaxeSpecificFunction(functionType, HaxeGenericSpecialization.EMPTY))
.createHolder();
}
HaxeTypeOrAnonymous anonymous = typeListPart.getTypeOrAnonymous();
if (anonymous != null) {
return HaxeTypeResolver.getTypeFromTypeOrAnonymous(anonymous);
}
}

HaxeTypeParameterMultiType type = convertToMultiTypeParameter(list, resolver);
return new ResultHolder(SpecificHaxeClassReference.withoutGenerics(new HaxeClassReference(type.getModel(), part)));
}
HaxeTypeParameterMultiType type = convertToMultiTypeParameter(list, resolver);
return new ResultHolder(SpecificHaxeClassReference.withoutGenerics(new HaxeClassReference(type.getModel(), part)));
}
}
if (constraint != null) {
HaxeTypeOrAnonymous toa = constraint.getTypeOrAnonymous();
if (toa != null) {
if (null != toa.getType()) {
HaxeReferenceExpression reference = toa.getType().getReferenceExpression();
ResultHolder result = HaxeExpressionEvaluator.evaluate(reference, new HaxeExpressionEvaluatorContext(part), resolver).result;
if (!result.isUnknown()) {
return result;
} else {
if (HaxeTypeResolver.isTypeParameter(reference)) {
// we dont want to resolve typeParameter constraints as the definition of this type parameter might need to inherit the type
return new ResultHolder(SpecificHaxeClassReference.withoutGenerics(new HaxeClassReference(toa.getType().getText(), toa.getType(), true)));
//return HaxeTypeResolver.getTypeFromTypeOrAnonymous(toa);
if (constraint != null) {
HaxeTypeOrAnonymous toa = constraint.getTypeOrAnonymous();
if (toa != null) {
if (null != toa.getType()) {
HaxeReferenceExpression reference = toa.getType().getReferenceExpression();
ResultHolder result = HaxeExpressionEvaluator.evaluate(reference, new HaxeExpressionEvaluatorContext(part), resolver).result;
if (!result.isUnknown()) {
return result;
}
else {
if (HaxeTypeResolver.isTypeParameter(reference)) {
// we dont want to resolve typeParameter constraints as the definition of this type parameter might need to inherit the type
return new ResultHolder(
SpecificHaxeClassReference.withoutGenerics(new HaxeClassReference(toa.getType().getText(), toa.getType(), true)));
//return HaxeTypeResolver.getTypeFromTypeOrAnonymous(toa);
}
}
}
}

else {
// Anonymous struct for a constraint.
// TODO: Turn the anonymous structure into a ResolveResult.
return HaxeTypeResolver.getTypeFromTypeOrAnonymous(toa, resolver); //temp solution
else {
// Anonymous struct for a constraint.
// TODO: Turn the anonymous structure into a ResolveResult.
return HaxeTypeResolver.getTypeFromTypeOrAnonymous(toa, resolver); //temp solution
}
}
HaxeFunctionType functionType = constraint.getFunctionType();
if (functionType != null) {
return HaxeTypeResolver.getTypeFromFunctionType(functionType);
}
}
HaxeFunctionType functionType = constraint.getFunctionType();
if (functionType != null) {
return HaxeTypeResolver.getTypeFromFunctionType(functionType);
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -955,8 +955,10 @@ public static HaxeResolveResult tryResolveType(HaxeType type,
if (haxeClass == null && type != null) {
PsiElement resolve = type.getReferenceExpression().resolve();
if (resolve instanceof HaxeGenericListPart listPart) {
if (listPart.getTypeListPart() != null) {
HaxeTypeOrAnonymous typeOrAnonymous = listPart.getTypeListPart().getTypeOrAnonymous();

HaxeGenericConstraintPart constraintPart = listPart.getGenericConstraintPart();
if (constraintPart != null && constraintPart.getTypeListPart() != null) {
HaxeTypeOrAnonymous typeOrAnonymous = constraintPart.getTypeListPart().getTypeOrAnonymous();
if (typeOrAnonymous != null) {
HaxeType haxeType = typeOrAnonymous.getType();
HaxeAnonymousType anonymousType = typeOrAnonymous.getAnonymousType();
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@
<annotator language="Haxe" implementationClass="com.intellij.plugins.haxe.ide.annotator.semantics.HaxePackageAnnotator"/>
<annotator language="Haxe" implementationClass="com.intellij.plugins.haxe.ide.annotator.semantics.HaxeTypeCheckExprAnnotator"/>
<annotator language="Haxe" implementationClass="com.intellij.plugins.haxe.ide.annotator.semantics.HaxeBinaryExpressionAnnotator"/>
<annotator language="Haxe" implementationClass="com.intellij.plugins.haxe.ide.annotator.semantics.HaxeDefaultTypeParameterAnnotator"/>

<annotator language="Haxe" id="body" implementationClass="com.intellij.plugins.haxe.ide.annotator.semantics.HaxeMethodBodyAnnotator" order="last"/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@ Haxe File
IDENTIFIER
HaxePsiToken:ID('T')
HaxePsiToken::(':')
HaxePsiToken:(('(')
TYPE_LIST
TYPE_LIST_PART
TYPE_OR_ANONYMOUS
TYPE
REFERENCE_EXPRESSION
IDENTIFIER
HaxePsiToken:ID('Event')
HaxePsiToken:,(',')
TYPE_LIST_PART
TYPE_OR_ANONYMOUS
TYPE
REFERENCE_EXPRESSION
IDENTIFIER
HaxePsiToken:ID('EventDispatcher')
HaxePsiToken:)(')')
GENERIC_CONSTRAINT_PART
HaxePsiToken:(('(')
TYPE_LIST
TYPE_LIST_PART
TYPE_OR_ANONYMOUS
TYPE
REFERENCE_EXPRESSION
IDENTIFIER
HaxePsiToken:ID('Event')
HaxePsiToken:,(',')
TYPE_LIST_PART
TYPE_OR_ANONYMOUS
TYPE
REFERENCE_EXPRESSION
IDENTIFIER
HaxePsiToken:ID('EventDispatcher')
HaxePsiToken:)(')')
HaxePsiToken:>('>')
CLASS_BODY
HaxePsiToken:{('{')
Expand Down Expand Up @@ -58,12 +59,13 @@ Haxe File
IDENTIFIER
HaxePsiToken:ID('T')
HaxePsiToken::(':')
TYPE_LIST_PART
TYPE_OR_ANONYMOUS
TYPE
REFERENCE_EXPRESSION
IDENTIFIER
HaxePsiToken:ID('EventDispatcher')
GENERIC_CONSTRAINT_PART
TYPE_LIST_PART
TYPE_OR_ANONYMOUS
TYPE
REFERENCE_EXPRESSION
IDENTIFIER
HaxePsiToken:ID('EventDispatcher')
HaxePsiToken:>('>')
CLASS_BODY
HaxePsiToken:{('{')
Expand Down
13 changes: 7 additions & 6 deletions src/test/resources/testData/parsing/haxe/expressions/Haxe3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1295,12 +1295,13 @@ Haxe File
IDENTIFIER
HaxePsiToken:ID('T')
HaxePsiToken::(':')
TYPE_LIST_PART
TYPE_OR_ANONYMOUS
TYPE
REFERENCE_EXPRESSION
IDENTIFIER
HaxePsiToken:ID('EnumValue')
GENERIC_CONSTRAINT_PART
TYPE_LIST_PART
TYPE_OR_ANONYMOUS
TYPE
REFERENCE_EXPRESSION
IDENTIFIER
HaxePsiToken:ID('EnumValue')
HaxePsiToken:>('>')
UNDERLYING_TYPE
HaxePsiToken:(('(')
Expand Down

0 comments on commit 1225c26

Please sign in to comment.