haxe icon indicating copy to clipboard operation
haxe copied to clipboard

[jvm] Functional Interfaces not extending proper interface when interface has type parameters

Open EliteMasterEric opened this issue 1 year ago • 16 comments

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.

EliteMasterEric avatar Nov 19 '23 02:11 EliteMasterEric

I think you forgot to update the Main.hx, I'm still getting the old output from that repo.

Simn avatar Nov 19 '23 21:11 Simn

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.

EliteMasterEric avatar Dec 03 '23 21:12 EliteMasterEric

Did you get around to testing out this example more? It should reliably produce the error.

EliteMasterEric avatar Dec 16 '23 05:12 EliteMasterEric

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.

Simn avatar Dec 16 '23 08:12 Simn

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!

Simn avatar Jan 01 '24 16:01 Simn

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.

EliteMasterEric avatar Jan 07 '24 19:01 EliteMasterEric

@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.

EliteMasterEric avatar Jan 07 '24 20:01 EliteMasterEric

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.

Simn avatar Jan 07 '24 22:01 Simn

There was a cascade of problems here:

  1. The T return type of manufacture is constrained to Robot, which means that it actually ends up becoming Robot itself. With that being a raw type, we're looking at Robot<Object>, which is not assignable from Robot<GreetRobot> because of variance. I've allowed the assignment in the unification, hopefully this doesn't lead to false positives.
  2. The unification was checked in the wrong direction anyway, so it tried to to assign Object to GreetRobot.
  3. The generated manufacture function had the signature of the closure's invoke method, 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...

Simn avatar Jan 08 '24 06:01 Simn

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

EliteMasterEric avatar Jan 08 '24 11:01 EliteMasterEric

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.

EliteMasterEric avatar Jan 08 '24 11:01 EliteMasterEric

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

EliteMasterEric avatar Jan 08 '24 13:01 EliteMasterEric

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.

Simn avatar Jan 08 '24 13:01 Simn

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

Simn avatar Jan 08 '24 14:01 Simn

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.

Simn avatar Jan 08 '24 17:01 Simn

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 --version java 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)

EliteMasterEric avatar Jan 08 '24 18:01 EliteMasterEric