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

Method arguments - inconsistent implicit conversion for boxed types #1098

Open
michi42 opened this issue Oct 26, 2022 · 6 comments
Open

Method arguments - inconsistent implicit conversion for boxed types #1098

michi42 opened this issue Oct 26, 2022 · 6 comments

Comments

@michi42
Copy link
Contributor

michi42 commented Oct 26, 2022

It appears the implicit conversion (for method arguments) handles boxed types inconsistently. For primitive types, implicit conversions do what I would expect:

  • Java int from python int
  • Java long from python int
  • Java double from python float or python int

However, for Boxed types this is not the case:

  • Java Integer does not accept python int
  • Java Long does not accept python int
  • Java Double accepts python float but not python int.

Minimal example:

public class Test {
	public void fnPrimitiveInt(int arg) {
		System.out.println("fnPrimitiveInt " + arg);
	} 
	public void fnPrimitiveLong(long arg) {
		System.out.println("fnPrimitiveLong " + arg);
	} 
	public void fnPrimitiveDouble(double arg) {
		System.out.println("fnPrimitiveDouble " + arg);
	} 
	public void fnBoxedInteger(Integer arg) {
		System.out.println("fnBoxedInteger " + arg);
	} 
	public void fnBoxedLong(Long arg) {
		System.out.println("fnBoxedLong " + arg);
	} 
	public void fnBoxedDouble(Double arg) {
		System.out.println("fnBoxedDouble " + arg);
	} 
}
import jpype

jpype.startJVM(classpath='.')

Test = jpype.JClass('Test')
test = Test()

In [16]: test.fnPrimitiveInt(42)
fnPrimitiveInt 42

In [17]: test.fnPrimitiveLong(42)
fnPrimitiveLong 42

In [18]: test.fnPrimitiveDouble(42.0)
fnPrimitiveDouble 42.0

In [19]: test.fnPrimitiveDouble(42)
fnPrimitiveDouble 42.0

In [20]: test.fnBoxedInteger(42)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-20-af66380d6bb2> in <module>()
----> 1 test.fnBoxedInteger(42)

TypeError: No matching overloads found for Test.fnBoxedInteger(int), options are:
	public void Test.fnBoxedInteger(java.lang.Integer)



In [21]: test.fnBoxedLong(42)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-21-bde103eb7722> in <module>()
----> 1 test.fnBoxedLong(42)

TypeError: No matching overloads found for Test.fnBoxedLong(int), options are:
	public void Test.fnBoxedLong(java.lang.Long)



In [22]: test.fnBoxedDouble(42.0)
fnBoxedDouble 42.0

In [23]: test.fnBoxedDouble(42)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-23-1f775f23cb93> in <module>()
----> 1 test.fnBoxedDouble(42)

TypeError: No matching overloads found for Test.fnBoxedDouble(int), options are:
	public void Test.fnBoxedDouble(java.lang.Double)


@Thrameos
Copy link
Contributor

The reason for the current situation is that we can't have int and float automatically go to a Java object as they don't have a defined size. A Python int is equal to short, integer, and long depending on the context while a Python float is both a float and double. For this reason, we need a cast so that it knows which conversion rules to apply.

import jpype
jpype.startJVM(classpath=".")
Test= jpype.JClass("Test")()
Test.fnBoxedInteger(jpype.JInt(1))
Test.fnBoxedLong(jpype.JLong(2))
Test.fnBoxedDouble(jpype.JDouble(3))

The reason for this stems from the fact that the casting rules only have 4 levels of matching (exact, implicit, explicit, and none). If we place int and float on the boxed conversion list as implicit, it allows free matching including places such as Object conversions.

I can look at it more closely, though last time I looked over boxed I determined that the side effects in terms of broken code would be too great to support automatic conversion from Python types to boxed.

@Thrameos
Copy link
Contributor

Thrameos commented Oct 26, 2022

For reference in Java you can't call Test.fnBoxedDouble(3.0f) nor Test.fnBoxedDouble(1). Thus only exactly Java double is allowed to be boxed automatically.

@michi42
Copy link
Contributor Author

michi42 commented Oct 26, 2022

Yes, asking for int to be implicitly converted to java.lang.Double is probably too much.
However an implicit conversion form int to java.lang.Integer (and Short and Long) would be nice. After all, a python float can also be equivalent to java.lang.Float or java.lang.Double, yet the implicit conversion works...

@Thrameos
Copy link
Contributor

I can give it a look after I get Python 3.11 version released, but if I remember the issue if I added that conversion then it allowed unfavorable conversions because JInt, JShort, and JLong are all derived from Python int. Thus adding an implicit conversion to a boxed types, opened all conversion rather than just the ones that were reasonable without an upgrade to the resolution rules. Perhaps I can add a check that only if it is exactly the Python type (not derived from) then it will convert implicitly. Though that would be rather odd from the OO as having something allowed for the parent and not the child is counter intuitive.

@michi42
Copy link
Contributor Author

michi42 commented Oct 26, 2022

Thanks!

Would it be possible to do the same as what is currently done for primitive types: up-casting short -> int -> long is a allowed, but not vice versa:

In [3]: test.fnPrimitiveInt(jpype.JInt(42))
fnPrimitiveInt 42

In [4]: test.fnPrimitiveInt(jpype.JLong(42))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-b28c03d2c960> in <module>()
----> 1 test.fnPrimitiveInt(jpype.JLong(42))

TypeError: No matching overloads found for Test.fnPrimitiveInt(JLong), options are:
	public void Test.fnPrimitiveInt(int)



In [5]: test.fnPrimitiveInt(jpype.JShort(42))
fnPrimitiveInt 42

In [6]: test.fnPrimitiveLong(jpype.JInt(42))
fnPrimitiveLong 42

In [7]: test.fnPrimitiveLong(jpype.JLong(42))
fnPrimitiveLong 42

In [8]: test.fnPrimitiveLong(jpype.JShort(42))
fnPrimitiveLong 42

@Thrameos
Copy link
Contributor

I suspect that the second request is easier than the first. Looking at the source code each Primitive has its own wrapper type with its own type conversion table. Thus adding a new promotion as implicit to the primitive tables is just one line for each conversion.

On the other hand boxed types just use the primitive table to figure out the conversions. I think part of the difficulty is coming from this structure. If I add JShort->JInt->JLong then you won't be able to use casting to select between java.lang.Short, java.lang.Integer, and java.lang.Long using the cast operators because the best match will be implicit and the cast up will also be implicit and thus it will start generating errors. We can break the boxed type wrapper up and add explicit tables for conversions which would allow the primitive types to promote properly which would add enough flexibility to make some of these conversions work without breaking the type tables. Though as it stands those tables are very linked.

There is one alternative. I see that Boxed types have a hints conversion so we can add conversions using Python. Though that would be slower and thus not preferred.

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