Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS Backend : Bug when a method is overloaded on an abstract class that implements JSObject #997

Open
cmorin28 opened this issue Jan 19, 2025 · 8 comments

Comments

@cmorin28
Copy link

Hello,

I have an unexpected bug that I can't understand. I'm working with the JS backend and I've this type of relationship between my classes :

public class MainClass {

	public static abstract class Parent implements JSObject {

		public Parent() {
			this.test();
		}

		protected void test() {
			System.out.println("test parent");
		}
	}

	public static class Child extends Parent {
		
		@Override
		protected void test() {
			super.test();
			System.out.println("test child");
		}
	}

	public static void main(String[] args) {
		var test = new Child();
	}
}

In this case, Parent class is absent :
Image

but, if I remove implements JSObject on the parent class, it's working fine :
Image

@cmorin28 cmorin28 changed the title JS Backend : Bug when a method is overloaded on a abstract class which is implement JSObject JS Backend : Bug when a method is overloaded on an abstract class that implements JSObject Jan 19, 2025
@konsoletyper
Copy link
Owner

I believe it's close to the expected behaviour. By declaring public static abstract class Parent implements JSObject, you declare that there's JS class named Parent, and by inheriting Child from it, you try to extend this JS class, not Java class. And since there's no Parent class declared in JS, you get this error.

@cmorin28
Copy link
Author

cmorin28 commented Jan 19, 2025

OK, now I understand. So JSObject only act as a compatibility layer for existing JavaScript APIs.

I've had this problem because I'm trying to export the Child class in a module but I can't... I've tried adding the class to @JSExportClasses or annotating it with @JSClass but it never gets exported. I don't understand why and that's why I added JSObject as the parent interface.

@konsoletyper
Copy link
Owner

@cmorin28 how did you figure out that these classes aren't exported? Did you try to instantiate them and got error? What was this error like? Did you annotate constructor with @JSExport? Did you try just to take these classes by reference and try to print them?

@cmorin28
Copy link
Author

cmorin28 commented Jan 19, 2025

The code for module export :

@JSExportClasses({ example.MainClass.Child.class })
public class MainClass {

	public static class Parent {

		@JSExport
		public Parent() {
			this.test();
		}

		protected void test() {
			System.out.println("test parent");
		}
	}

	public static class Child extends Parent {

		@Override
		protected void test() {
			super.test();
			System.out.println("test child");
		}
	}
}

When I try to import, I get this error:
Image

And when I check the file "example.js", nothing is exported:
Image

Finally, my second string ("test child") doesn't appear in the string pool:
Image

Maybe I'm using TeaVM wrong, but I don't understand where...

EDIT:
My HTML code, if it can help:

<!DOCTYPE html>
<html>
  <head>
    <title>TeaVM example</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script type="module" charset="utf-8">
      import { Child } from './example.js';
      
      var test = new Child();
    </script>
  </head>
  <body></body>
</html>

My build.gradle (I'm using Java 21) :

dependencies {
    implementation teavm.libs.jsoApis
}

teavm {
    all {
        mainClass = "example.MainClass"
    }
    js {
        targetFileName = "example.js"
        obfuscated = false
        moduleType = JSModuleType.ES2015
        debugInformation = true
        optimization = OptimizationLevel.NONE
    }
}

@konsoletyper
Copy link
Owner

Perhaps, you need to specify exported names explicitly with @JSClass, since nested classes have names like MainClass$Parent and MainClass$Child, not Parent or Child. Another option is to bring these classes in top level. As for missing "test child" string, you should export Child's constructor explicitly.

@cmorin28
Copy link
Author

cmorin28 commented Jan 20, 2025

I have tried to move each class into separate files and I have added @JSExport annotation to an explicit Child constructor. Nothing changes...

@cmorin28
Copy link
Author

@konsoletyper I've tried lots of things, but nothing works. Is there any way of debugging the compiler to help understand this behaviour?

@konsoletyper
Copy link
Owner

Looks like due to a bug, constructor is not exported when there aren't any methods exported. So this works for me:

@JSExportClasses({ MainClass.Child.class })
public class MainClass {

    public static class Parent {

        public Parent() {
            this.test();
        }

        protected void test() {
            System.out.println("test parent");
        }
    }

    public static class Child extends Parent {
        @JSExport
        public Child() {
        }

        @Override
        protected void test() {
            super.test();
            System.out.println("test child");
        }

        @JSExport
        public static void ttt() {
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants