diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index fa22a2ee5..2dd4f5349 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,6 +6,11 @@ on: - 'master' - 'develop' - 'feature/*' + pull_request: + branches: + - 'master' + - 'develop' + - 'feature/*' jobs: Build: diff --git a/grammar/haxe.bnf b/grammar/haxe.bnf index 0cfc34edd..a0bf0fd8e 100644 --- a/grammar/haxe.bnf +++ b/grammar/haxe.bnf @@ -229,6 +229,15 @@ private prefixOperator ::= '-' | '!' | '~' { extends=operator } private questionOperator ::= '?' { extends=operator } private suffixOperator ::= '!' { extends=operator } +// KFROM and KTO are only keywords for abstracts and can be used as identifiers elsewhere in the code +identifier ::= ID | KFROM | KTO +{mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxeIdentifierPsiMixinImpl" implements="com.intellij.plugins.haxe.lang.psi.HaxeIdentifierPsiMixin" name="identifier"} + +thisExpression ::= 'this' +{mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxeReferenceImpl" implements="com.intellij.plugins.haxe.lang.psi.HaxeReference"} + +superExpression ::= 'super' +{mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxeReferenceImpl" implements="com.intellij.plugins.haxe.lang.psi.HaxeReference"} packageStatement ::= 'package' simpleQualifiedReferenceExpression? ';' {pin=1 implements="com.intellij.plugins.haxe.lang.psi.HaxePackageStatementPsiMixin" mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxePackageStatementPsiMixinImpl"} @@ -305,11 +314,13 @@ externInterfaceDeclaration ::= externAndMaybePrivate? 'interface' componentName classDeclaration ::= classModifierList? 'class' componentName genericParam? inheritList? classBody {pin=3 mixin="com.intellij.plugins.haxe.lang.psi.impl.AbstractHaxePsiClass" implements="com.intellij.plugins.haxe.lang.psi.HaxeClass"} -//'from' | 'to' -underlyingType ::= '(' typeWrapper ')' -abstractClassDeclaration ::= privateKeyWord? abstractClassType componentName genericParam? underlyingType? ((identifier) type)* abstractBody + +abstractClassDeclaration ::= privateKeyWord? abstractClassType componentName genericParam? underlyingType? (abstractToType | abstractFromType)* abstractBody {pin=3 mixin="com.intellij.plugins.haxe.lang.psi.impl.AbstractHaxePsiClass" implements="com.intellij.plugins.haxe.lang.psi.HaxeClass"} abstractClassType ::= 'enum'? 'abstract' +underlyingType ::= '(' typeWrapper ')' +abstractToType ::= 'to' ('(' typeWrapper ')' | typeWrapper){pin=1} +abstractFromType ::= 'from' ('(' typeWrapper ')' | typeWrapper){pin=1} abstractBody ::= '{' abstractBodyPart* '}' {extends="classBody"} private abstractBodyPart ::= fieldDeclaration | methodDeclaration | constructorDeclaration {recoverWhile="class_body_part_recover"} @@ -436,7 +447,7 @@ private namedTypeOrAnonymous ::= [optionalMark] [componentName ':'] typeOrAnonym private namedFunctionType ::= [optionalMark] [componentName ':'] functionType private optionalFunctionTypeWithoutName ::= optionalMark '(' functionType ')' {pin=3} -private oldFunctionType ::= oldFunctionTypeArgumentsList functionReturnType {pin=2} +private oldFunctionType ::= oldFunctionTypeArgumentsList ('(' functionReturnType ')' | functionReturnType) {pin=2} private oldFunctionTypeArgumentsList ::= ( oldFunctionTypeArgument '->')+ oldFunctionTypeArgument ::= optionalMark? ('(' functionType ')' | typeOrAnonymous) {elementType="functionArgument" pin(".*")=3} @@ -713,14 +724,6 @@ private simpleQualifiedReferenceExpression ::= referenceExpression qualifiedRefe componentName ::= identifier {mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxeNamedElementImpl" implements="com.intellij.plugins.haxe.lang.psi.HaxeNamedElement"} -identifier ::= ID -{mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxeIdentifierPsiMixinImpl" implements="com.intellij.plugins.haxe.lang.psi.HaxeIdentifierPsiMixin" name="identifier"} - -thisExpression ::= 'this' -{mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxeReferenceImpl" implements="com.intellij.plugins.haxe.lang.psi.HaxeReference"} - -superExpression ::= 'super' -{mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxeReferenceImpl" implements="com.intellij.plugins.haxe.lang.psi.HaxeReference"} // XXX - This could be callExpression, and it makes sense that newExpression would extend/implement callExpression, // but that affects a lot of code and is best not attempted just before a release... diff --git a/grammar/haxe.flex b/grammar/haxe.flex index c8da40d05..a8f6cd3dc 100644 --- a/grammar/haxe.flex +++ b/grammar/haxe.flex @@ -297,8 +297,8 @@ CONDITIONAL_ERROR="#error"[^\r\n]* "cast" { return emitToken( KCAST); } "abstract" { return emitToken( KABSTRACT); } -//"from" { return emitToken( KFROM); } -//"to" { return emitToken( KTO ); } +"from" { return emitToken( KFROM); } +"to" { return emitToken( KTO ); } "class" { return emitToken( KCLASS); } "enum" { return emitToken( KENUM); } diff --git a/src/common/com/intellij/plugins/haxe/haxelib/HaxeLibrary.java b/src/common/com/intellij/plugins/haxe/haxelib/HaxeLibrary.java index d00fed0dc..cf1074c68 100644 --- a/src/common/com/intellij/plugins/haxe/haxelib/HaxeLibrary.java +++ b/src/common/com/intellij/plugins/haxe/haxelib/HaxeLibrary.java @@ -196,6 +196,8 @@ public static HaxeLibrary load(HaxelibLibraryCache owner, String libName, Sdk sd return new HaxeLibrary(libName, libraryRoot, owner); } catch (InvalidParameterException e) { ; // libName must not have been an url + } catch (Exception e) { + LOG.error("Unable to read Haxelib '" + libName +"' reason:" + e.getMessage(), e); } return null; } diff --git a/src/common/com/intellij/plugins/haxe/haxelib/HaxelibLibraryCache.java b/src/common/com/intellij/plugins/haxe/haxelib/HaxelibLibraryCache.java index d81953686..bf22dbef4 100644 --- a/src/common/com/intellij/plugins/haxe/haxelib/HaxelibLibraryCache.java +++ b/src/common/com/intellij/plugins/haxe/haxelib/HaxelibLibraryCache.java @@ -112,10 +112,11 @@ public HaxeClasspath getClasspathForHaxelib(String libraryName) { // It's not in the cache, so go get it and cache the results. HaxeLibrary newlib = HaxeLibrary.load(this, libraryName, mySdk); - myCache.add(newlib); - - timeLog.stamp("Finished loading library: " + libraryName); - return newlib.getClasspathEntries(); + if(null != newlib) { + myCache.add(newlib); + timeLog.stamp("Finished loading library: " + libraryName); + return newlib.getClasspathEntries(); + } } timeLog.stamp("Unknown library !!! " + libraryName + " !!! "); diff --git a/src/common/com/intellij/plugins/haxe/model/HaxeClassModel.java b/src/common/com/intellij/plugins/haxe/model/HaxeClassModel.java index e996fc10d..f00782ced 100644 --- a/src/common/com/intellij/plugins/haxe/model/HaxeClassModel.java +++ b/src/common/com/intellij/plugins/haxe/model/HaxeClassModel.java @@ -195,16 +195,15 @@ public SpecificHaxeClassReference getUnderlyingClassReference(HaxeGenericResolve return null; } - // @TODO: this should be properly parsed in haxe.bnf so searching for to is not required public List getAbstractToList() { - if (!isAbstract()) return Collections.emptyList(); + if (!isAbstract() ) return Collections.emptyList(); + List types = new LinkedList(); - for (HaxeIdentifier id : UsefulPsiTreeUtil.getChildren(haxeClass, HaxeIdentifier.class)) { - if (id.getText().equals("to")) { - PsiElement sibling = UsefulPsiTreeUtil.getNextSiblingSkipWhiteSpacesAndComments(id); - if (sibling instanceof HaxeType) { - types.add((HaxeType)sibling); - } + HaxeAbstractClassDeclaration abstractClass = (HaxeAbstractClassDeclaration)haxeClass; + List list = abstractClass.getAbstractToTypeList(); + for(HaxeAbstractToType toType : list) { + if(toType.getTypeOrAnonymous() != null ) { + types.add(toType.getTypeOrAnonymous().getType()); } } return types; @@ -304,16 +303,14 @@ private SpecificHaxeClassReference getTypeOfFirstParameter(@NotNull HaxeMethodMo - // @TODO: this should be properly parsed in haxe.bnf so searching for from is not required public List getAbstractFromList() { - if (!isAbstract()) return Collections.emptyList(); + if (!isAbstract() ) return Collections.emptyList(); List types = new LinkedList(); - for (HaxeIdentifier id : UsefulPsiTreeUtil.getChildren(haxeClass, HaxeIdentifier.class)) { - if (id.getText().equals("from")) { - PsiElement sibling = UsefulPsiTreeUtil.getNextSiblingSkipWhiteSpacesAndComments(id); - if (sibling instanceof HaxeType) { - types.add((HaxeType)sibling); - } + HaxeAbstractClassDeclaration abstractClass = (HaxeAbstractClassDeclaration)haxeClass; + List list = abstractClass.getAbstractFromTypeList(); + for(HaxeAbstractFromType fromType : list) { + if(fromType.getTypeOrAnonymous() != null ) { + types.add(fromType.getTypeOrAnonymous().getType()); } } return types; diff --git a/src/common/com/intellij/plugins/haxe/model/type/HaxeTypeCompatible.java b/src/common/com/intellij/plugins/haxe/model/type/HaxeTypeCompatible.java index a8bc87278..22695ff2e 100644 --- a/src/common/com/intellij/plugins/haxe/model/type/HaxeTypeCompatible.java +++ b/src/common/com/intellij/plugins/haxe/model/type/HaxeTypeCompatible.java @@ -19,14 +19,12 @@ */ package com.intellij.plugins.haxe.model.type; -import com.intellij.plugins.haxe.lang.psi.HaxeClass; -import com.intellij.plugins.haxe.lang.psi.HaxeSpecificFunction; +import com.intellij.plugins.haxe.lang.psi.*; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Objects; -import java.util.Set; +import java.util.*; import static com.intellij.plugins.haxe.model.type.SpecificTypeReference.getStdClass; @@ -63,6 +61,7 @@ static private boolean isTypeDefFunction(SpecificTypeReference ref ) { return false; } + @NotNull static private SpecificFunctionReference asFunctionReference(SpecificTypeReference ref) { if (ref instanceof SpecificFunctionReference) return (SpecificFunctionReference)ref; @@ -97,6 +96,22 @@ static public boolean canAssignToFrom( if (to == null || from == null) return false; if (to.isDynamic() || from.isDynamic()) return true; + // if abstract of function is being compared to a function we map the abstract to its underlying function + if (hasAbstractFunctionTypeCast(to, true) && isFunctionTypeOrReference(from)) { + List functionTypes = getAbstractFunctionTypes((SpecificHaxeClassReference)to, true); + SpecificFunctionReference fromFunctionType = asFunctionReference(from); + for(SpecificFunctionReference functionType : functionTypes) { + if(canAssignToFromFunction(functionType, fromFunctionType)) return true; + } + } + if (isFunctionTypeOrReference(to) && hasAbstractFunctionTypeCast(from, false)) { + List functionTypes = getAbstractFunctionTypes((SpecificHaxeClassReference)from, false); + SpecificFunctionReference toFunctionType = asFunctionReference(to); + for(SpecificFunctionReference functionType : functionTypes) { + if(canAssignToFromFunction(toFunctionType, functionType)) return true; + } + } + if (isFunctionTypeOrReference(to) && isFunctionTypeOrReference(from)) { SpecificFunctionReference toRef = asFunctionReference(to); SpecificFunctionReference fromRef = asFunctionReference(from); @@ -144,6 +159,46 @@ static private boolean canAssignToFromFunction( return to.returnValue != null && (to.returnValue.isVoid() || to.returnValue.canAssign(from.returnValue)); } + private static boolean hasAbstractFunctionTypeCast(SpecificTypeReference reference, boolean castFrom) { + if (reference instanceof SpecificHaxeClassReference) { + HaxeClass haxeClass = ((SpecificHaxeClassReference)reference).getHaxeClass(); + if (haxeClass instanceof HaxeAbstractClassDeclaration) { + HaxeAbstractClassDeclaration abstractClass = (HaxeAbstractClassDeclaration)haxeClass; + if (castFrom && !abstractClass.getAbstractFromTypeList().isEmpty()) return true; + if (!castFrom && !abstractClass.getAbstractToTypeList().isEmpty()) return true; + } + } + return false; + } + + @NotNull + private static List getAbstractFunctionTypes(SpecificHaxeClassReference classReference, boolean getCastFrom) { + if (!(classReference.getHaxeClass() instanceof HaxeAbstractClassDeclaration)) return Collections.emptyList(); + HaxeAbstractClassDeclaration abstractClass = (HaxeAbstractClassDeclaration)classReference.getHaxeClass(); + List list = new ArrayList<>(); + if (abstractClass != null) { + if (getCastFrom && !abstractClass.getAbstractFromTypeList().isEmpty()) { + for(HaxeAbstractFromType type : abstractClass.getAbstractFromTypeList()) { + if(type.getFunctionType() != null) { + HaxeSpecificFunction specificFunction = + new HaxeSpecificFunction(type.getFunctionType(), classReference.getGenericResolver().getSpecialization(null)); + list.add(SpecificFunctionReference.create(specificFunction)); + } + } + } + if (!getCastFrom && !abstractClass.getAbstractToTypeList().isEmpty()){ + for(HaxeAbstractToType type : abstractClass.getAbstractToTypeList()) { + if (type.getFunctionType() != null) { + HaxeSpecificFunction specificFunction = + new HaxeSpecificFunction(type.getFunctionType(), classReference.getGenericResolver().getSpecialization(null)); + list.add(SpecificFunctionReference.create(specificFunction)); + } + } + } + } + return list; + } + static public SpecificHaxeClassReference getUnderlyingClassIfAbstractNull(SpecificHaxeClassReference ref) { diff --git a/testData/annotation.semantic/AbstractAssignmentFromToFunctions.hx b/testData/annotation.semantic/AbstractAssignmentFromToFunctions.hx new file mode 100644 index 000000000..ef7148302 --- /dev/null +++ b/testData/annotation.semantic/AbstractAssignmentFromToFunctions.hx @@ -0,0 +1,35 @@ +package; + +class AbstractAssignmentFromTo1 { + public static function variableTests():Void { + var val:FunctionFromTo = function(i:Int){return;}; + var val:FunctionFrom = function(i:Int){return;}; + + var val:FunctionFromTo = testMethodIV; + var val:FunctionFrom = testMethodIV; + + // should fail due to wrong parameter types + var val:FunctionFromTo = function(i:String){return 1;}; + var val:FunctionFrom = function(i:String){return 1;}; + var val:FunctionFromTo = testMethodSV; + + //should fail (has no from type or implisit casts) + var val:FunctionTo = function(i:Int){return;}; + var val:FunctionTo = testMethodIV; + var val:FunctionNon = testMethodIV; + + } + + public static function testMethodIV(i:Int) {return;} + public static function testMethodSV(i:String) {return;} +} + + + +abstract FunctionFromTo(Int->Void) from Int->Void to Int->Void {} + +abstract FunctionFrom(Int->Void) from Int->Void {} + +abstract FunctionTo(Int->Void) to Int->Void {} + +abstract FunctionNon(Int->Void) {} diff --git a/testData/parsing/haxe/declarations/import/Empty182.txt b/testData/parsing/haxe/declarations/import/Empty182.txt index d01c79904..02e4bdb80 100644 --- a/testData/parsing/haxe/declarations/import/Empty182.txt +++ b/testData/parsing/haxe/declarations/import/Empty182.txt @@ -1,5 +1,5 @@ Haxe File IMPORT_STATEMENT HaxePsiToken:import('import') - PsiErrorElement:ID expected + PsiErrorElement: expected \ No newline at end of file diff --git a/testData/parsing/haxe/expressions/Haxe3.txt b/testData/parsing/haxe/expressions/Haxe3.txt index 61f7053f1..6861f0f95 100644 --- a/testData/parsing/haxe/expressions/Haxe3.txt +++ b/testData/parsing/haxe/expressions/Haxe3.txt @@ -1141,12 +1141,13 @@ Haxe File COMPONENT_NAME IDENTIFIER HaxePsiToken:ID('Kilometer') - IDENTIFIER - HaxePsiToken:ID('from') - TYPE - REFERENCE_EXPRESSION - IDENTIFIER - HaxePsiToken:ID('Int') + ABSTRACT_FROM_TYPE + HaxePsiToken:KFROM('from') + TYPE_OR_ANONYMOUS + TYPE + REFERENCE_EXPRESSION + IDENTIFIER + HaxePsiToken:ID('Int') ABSTRACT_BODY HaxePsiToken:{('{') METHOD_DECLARATION @@ -1240,12 +1241,13 @@ Haxe File COMPONENT_NAME IDENTIFIER HaxePsiToken:ID('Int') - IDENTIFIER - HaxePsiToken:ID('to') - TYPE - REFERENCE_EXPRESSION - IDENTIFIER - HaxePsiToken:ID('Float') + ABSTRACT_TO_TYPE + HaxePsiToken:KTO('to') + TYPE_OR_ANONYMOUS + TYPE + REFERENCE_EXPRESSION + IDENTIFIER + HaxePsiToken:ID('Float') ABSTRACT_BODY HaxePsiToken:{('{') HaxePsiToken:}('}') @@ -1256,18 +1258,20 @@ Haxe File COMPONENT_NAME IDENTIFIER HaxePsiToken:ID('UInt') - IDENTIFIER - HaxePsiToken:ID('to') - TYPE - REFERENCE_EXPRESSION - IDENTIFIER - HaxePsiToken:ID('Int') - IDENTIFIER - HaxePsiToken:ID('from') - TYPE - REFERENCE_EXPRESSION - IDENTIFIER - HaxePsiToken:ID('Int') + ABSTRACT_TO_TYPE + HaxePsiToken:KTO('to') + TYPE_OR_ANONYMOUS + TYPE + REFERENCE_EXPRESSION + IDENTIFIER + HaxePsiToken:ID('Int') + ABSTRACT_FROM_TYPE + HaxePsiToken:KFROM('from') + TYPE_OR_ANONYMOUS + TYPE + REFERENCE_EXPRESSION + IDENTIFIER + HaxePsiToken:ID('Int') ABSTRACT_BODY HaxePsiToken:{('{') HaxePsiToken:}('}') @@ -1478,18 +1482,20 @@ Haxe File IDENTIFIER HaxePsiToken:ID('Int') HaxePsiToken:)(')') - IDENTIFIER - HaxePsiToken:ID('from') - TYPE - REFERENCE_EXPRESSION - IDENTIFIER - HaxePsiToken:ID('Int') - IDENTIFIER - HaxePsiToken:ID('to') - TYPE - REFERENCE_EXPRESSION - IDENTIFIER - HaxePsiToken:ID('Int') + ABSTRACT_FROM_TYPE + HaxePsiToken:KFROM('from') + TYPE_OR_ANONYMOUS + TYPE + REFERENCE_EXPRESSION + IDENTIFIER + HaxePsiToken:ID('Int') + ABSTRACT_TO_TYPE + HaxePsiToken:KTO('to') + TYPE_OR_ANONYMOUS + TYPE + REFERENCE_EXPRESSION + IDENTIFIER + HaxePsiToken:ID('Int') ABSTRACT_BODY HaxePsiToken:{('{') PsiComment(MSL_COMMENT)('// MyInt + MyInt can be used as is, and returns a MyInt') diff --git a/testSrc/com/intellij/plugins/haxe/ide/HaxeSemanticAnnotatorTest.java b/testSrc/com/intellij/plugins/haxe/ide/HaxeSemanticAnnotatorTest.java index 37ceef4e5..baf11659f 100644 --- a/testSrc/com/intellij/plugins/haxe/ide/HaxeSemanticAnnotatorTest.java +++ b/testSrc/com/intellij/plugins/haxe/ide/HaxeSemanticAnnotatorTest.java @@ -210,6 +210,10 @@ public void testAbstractAssignmentFromTo6() throws Exception { doTestNoFixWithWarnings(); } + public void testAbstractAssignmentFromToFunctions() throws Exception { + doTestNoFixWithWarnings(); + } + public void testNullFunction() throws Exception { doTestNoFixWithWarnings(); }