haxe
haxe copied to clipboard
[jvm] Functional Interfaces not extending proper interface when interface has type parameters
Minimal reproduction
https://github.com/EliteMasterEric/Issue11054/tree/488fbb13733643d5406fbcae42802b07d72652e0
Error Message
No compile error is received, instead Java experiences an error at runtime:
Exception in thread "main" java.lang.ClassCastException: class haxe.root.Main$Closure_main_1 cannot be cast to class test.Robot$GreetRobot (haxe.root.Main$Closure_main_1 and test.Robot$GreetRobot are in unnamed module of loader 'app')
at test.RobotFactory$2.performTask(RobotFactory.java:28)
at haxe.root.Main.main(src/Main.hx:16)
at haxe.root.Main.main(src/Main.hx:1)
Notes
This is a similar case to #11054, which occurs in Haxe 5c05e6d. Since the main test case of #11054 is resolved, this is not a regression, so I am making a new issue for ease of tracking.
I think you forgot to update the Main.hx, I'm still getting the old output from that repo.
Didn't see your reply here, ehe.
I did not update Main.hx for this issue, only the .java files. Rebuild the project and then rebuild the Main jar and then run the Main jar and see that with the newly changed project.jar it breaks.
Did you get around to testing out this example more? It should reliably produce the error.
I can't check right now, but when I initially tried to reproduce this I got exactly the same output as before, which is why I thought you forgot to update something. I'll check it again on Monday.
I checked this out today (I never said which Monday I meant!) and fixed something not strictly related, but I still cannot reproduce the problem if target is properly typed, as mentioned in #11390:
C:\git\Issue11054>git reset origin/master --hard
HEAD is now at 488fbb1 Found another circumstance where the same error occurs.
C:\git\Issue11054>git diff
C:\git\Issue11054>cd project && build.bat && cd ../..
C:\git\Issue11054\project>REM Build all the Java files in the src/ folder into the out/ folder
C:\git\Issue11054\project>javac -d out src/test/TestClass.java src/test/Robot.java src/test/RobotFactory.java
Note: src\test\TestClass.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
C:\git\Issue11054\project>REM Create a JAR file from the compiled classes
C:\git\Issue11054\project>cd out/
C:\git\Issue11054\project\out>jar cf project.jar test/TestClass.class test/Robot.class test/Robot$CleanupTask.class test/Robot$MathOperation.class test/Robot$GreetRobot.class test/Robot$MathOperation2.class test/Robot$GreetRobot2.class test/RobotFactory.class test/RobotFactory$1.class test/RobotFactory$2.class
C:\git\Issue11054>haxe compile.hxml
C:\git\Issue11054>java -jar bin/java/Main.jar
Robot.performTask() called!
Result: 7
Robot.performTask() called!
Result: -1
Exception in thread "main" java.lang.ClassCastException: class haxe.root.Main$Closure_main_1 cannot be cast to class test.Robot$GreetRobot (haxe.root.Main$Closure_main_1 and test.Robot$GreetRobot are in unnamed module of loader 'app')
at test.RobotFactory$2.performTask(RobotFactory.java:28)
at haxe.root.Main.main(src/Main.hx:16)
at haxe.root.Main.main(src/Main.hx:1)
C:\git\Issue11054>sed -i 's/(target)/(target:Robot)/g' src/Main.hx
C:\git\Issue11054>haxe compile.hxml
C:\git\Issue11054>java -jar bin/java/Main.jar
Robot.performTask() called!
Result: 7
Robot.performTask() called!
Result: -1
Robot.performTask() called!
src/Main.hx:17: Hello, Robot!
I will have to attempt a working reproduction for this. I don't reproduce the issue in the minimal repro either but I am experiencing an issue where the proper interface is not extended (but a bunch of other interfaces are) even when the parameter is properly typed.
@Simn I have found a minimal reproduction for the issue.
https://github.com/EliteMasterEric/Issue11390
> java test.TestClass
Robot.performTask() called!
Result: 7
Robot.performTask() called!
Hello, Robot!
Robot.performTask() called!
Hello, Robot!
Cleaning up...
Robot.performTask() called!
Robot.manufactureRobot() called!
Output: Robot
> java -jar bin/java/Main.jar
Result: 7
Robot.performTask() called!
Result: -1
Robot.performTask() called!
src/Main.hx:18: Hello, Robot!
src/Main.hx:20: Cleanup...
Exception in thread "main" java.lang.ClassCastException: class haxe.root.Main$Closure_main_3 cannot be cast to class test.Robot$ManufactureRobot (haxe.root.Main$Closure_main_3 and test.Robot$ManufactureRobot are in unnamed module of loader 'app')
at test.RobotFactory$3.performTask(RobotFactory.java:41)
at haxe.root.Main.main(src/Main.hx:23)
at haxe.root.Main.main(src/Main.hx:1)
The issue presents itself when the functional interface has a type parameter, see ManufactureRobot.
You and your robots...
~-D dump shows me that ManufactureRobot.manufacture has a Void return type: function manufacture(param1:java.lang.String):Void;. I'll have to check if that goes wrong during the initial .jar loading or is somehow broken later.~ Never mind I somehow did... I don't even know.
There was a cascade of problems here:
- The
Treturn type ofmanufactureis constrained toRobot, which means that it actually ends up becomingRobotitself. With that being a raw type, we're looking atRobot<Object>, which is not assignable fromRobot<GreetRobot>because of variance. I've allowed the assignment in the unification, hopefully this doesn't lead to false positives. - The unification was checked in the wrong direction anyway, so it tried to to assign
ObjecttoGreetRobot. - The generated
manufacturefunction had the signature of the closure'sinvokemethod, which made the verifier complain about it not being implemented or something. I've changed it so that it is always generated with the exact signature of the interface method.
The unification problems involved here make me question my approach to this as a whole. However, we can't rely on the assignments detected in the typer because the actual closure in question might not even be seen there. It then seems necessary to compare all closure classes against all interface candidates. Perhaps these checks could be done at Haxe-level to utilize our unification mechanisms, but this could lead to differences with how the JVM expects assignments to work too...
Template project works now, but the use case I was trying to solve still doesn't. Back to the robot factory I guess.
EDIT: For the record, this is the function I'm trying to call but failing at: https://github.com/FabricMC/fabric/blob/65c120eded0fb3d65c10cd2a616e38e22746963d/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/FabricDataGenerator.java#L129
I don't think this should be reopened since an issue was fixed. I'll make a new one once I have a reproduction.
Not easy to make a minimal repro, since I tried to make an example with just the Fabric JAR that has the problem class and Haxe complained that it needs Minecraft as a dependency.
I figured it out. The functional interface took an argument and expected a return result of <T extends DataProvider>, where DataProvider is an INTERFACE. It expects you to define a class which implements that interface and return that.
I was doing that, but if I don't explicitly define the return type of the functional interface as a DataProvider, it fails. With the return type explicitly defined (and as the INTERFACE, NOT as the custom return type), it succeeds and everything seems to execute without failing.
Here's a reproduction. Reopening this rather than making a new issue since it DOES work as long as everything is explicitly defined.
https://github.com/EliteMasterEric/Issue150002
Yeah, I'd like to make this more robust. The problem right now is that the Haxe typer checks for assignment (unification) while the functional interface detector in genjvm checks for (mostly) equality. That's why cases like this are admitted by the typer, but then missed by genjvm. Not an ideal situation...
If type relations have to be taken into account, we're pretty much forced to do these checks at Haxe-level because we don't know anything about relationships at JVM-signature-level.
I can't even javac your example though:
C:\git\Issue150002\project>javac --version
javac 13.0.1
C:\git\Issue150002\project>build.bat
C:\git\Issue150002\project>REM Build all the Java files in the src/ folder into the out/ folder
C:\git\Issue150002\project>javac -d out src/test/TestClass.java src/test/DataProvider.java src/test/MyDataProvider.java src/test/FabricDataOutput.java src/test/FabricDataGenerator.java
src\test\FabricDataGenerator.java:74: error: Illegal static declaration in inner class FabricDataGenerator.Pack
public interface Factory<T extends DataProvider> {
^
modifier 'static' is only allowed in constant variable declarations
src\test\FabricDataGenerator.java:83: error: Illegal static declaration in inner class FabricDataGenerator.Pack
public interface RegistryDependentFactory<T extends DataProvider> {
^
modifier 'static' is only allowed in constant variable declarations
2 errors
Try if the linked commit does anything for you. I'm not very confident in our test suite regarding this, so there's a good chance that it blows up.
I can't even javac your example though:
C:\git\Issue150002\project>javac --version javac 13.0.1
I'm on javac 17.06, 4 major versions is a big difference in syntax when it comes to Java.
java --versionjava 17.0.6 2023-01-17 LTS Java(TM) SE Runtime Environment (build 17.0.6+9-LTS-190) Java HotSpot(TM) 64-Bit Server VM (build 17.0.6+9-LTS-190, mixed mode, sharing)