Skip to content

Commit 040542b

Browse files
committed
Java frontend: support lambda autoboxing against generic types
It's possible to use a primitive-typed method-reference to implement an appropriately specialised generic functional interface (e.g. something that takes an integer can satisfy Consumer<Byte> by direct method reference without needing to introduce a stub method to do the conversion). Here we implement that by unboxing using the java.lang.Number interface where necessary, thereby catching all valid widening numeric casts at the same time (Short.intValue for example will do the widening and deliver the required primitive type). When boxing we always box to the primitive type's corresponding boxed type, as this is all the compiler seems to allow (i.e. it won't allow satisfying Producer<Long> using a function that returns an int, even though that is type-safe). Note this solution uses virtual dispatch a little more often than is needed: in some simple cases the class file contains enough information to know that the Object we're unboxing must in practice be an Integer (for example) and so no dispatch is necessary. That doesn't account for all cases where a generic interface is implemented however, so I did not pursue this possibility.
1 parent 70f9729 commit 040542b

File tree

11 files changed

+197
-46
lines changed

11 files changed

+197
-46
lines changed
189 Bytes
Binary file not shown.
191 Bytes
Binary file not shown.
299 Bytes
Binary file not shown.
174 Bytes
Binary file not shown.
266 Bytes
Binary file not shown.
2.84 KB
Binary file not shown.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
public class Test {
2+
3+
interface TakesT<T> {
4+
public Object take(T t);
5+
}
6+
7+
interface YieldsT<T> {
8+
public T yield();
9+
}
10+
11+
interface TakesInt {
12+
public Object take(int i);
13+
}
14+
15+
interface TakesLong {
16+
public Object take(long l);
17+
}
18+
19+
interface YieldsInt {
20+
public int yield();
21+
}
22+
23+
public static Object takesLong(long l) { return l; }
24+
25+
public static short yieldsShort() { return (short)1; }
26+
27+
public static void test() {
28+
29+
// Implement a generic interface using a primitive-typed function
30+
// that requires us to unbox the Short then widen it:
31+
TakesT<Short> takesShort = Test::takesLong;
32+
assert takesShort.take((short)1).equals(1L);
33+
34+
// Surprisingly, the following case doesn't compile despite
35+
// being type-safe and symmetrical to the one above:
36+
// YieldsT<Long> yieldsLong = Test::yieldsShort;
37+
38+
// So instead, here's a simpler case, that just requires us
39+
// to box the return value, not widen it then box:
40+
YieldsT<Short> yieldsShort = Test::yieldsShort;
41+
assert yieldsShort.yield() == (short)1;
42+
43+
// Now test the reverse: using an instantiated generic to
44+
// implement a primitive-typed interface:
45+
46+
TakesT<Long> takesLong = x -> x + 1;
47+
// Again this doesn't compile:
48+
// TakesInt takesInt = takesLong::take;
49+
// So here's a simpler example that only boxes to implement the
50+
// primitive interface, rather than boxes then widens:
51+
TakesLong takesPrimitiveLong = takesLong::take;
52+
assert takesPrimitiveLong.take(1L).equals(2L);
53+
54+
// And finally, using an instantiated generic to satisfy a primitive
55+
// return value. This one does work with the conversion requiring both
56+
// to unbox the value and to widen it.
57+
YieldsT<Short> yieldsShortGeneric = () -> (short)1;
58+
YieldsInt yieldsInt = yieldsShortGeneric::yield;
59+
assert yieldsInt.yield() == 1;
60+
61+
// Check we got to the end:
62+
assert false;
63+
64+
}
65+
66+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
Test
3+
--function Test.test --cp `../../../../scripts/format_classpath.sh . ../../../lib/java-models-library/target/core-models.jar`
4+
\[java::Test\.test:\(\)V\.assertion\.1\] line 32 assertion at file Test\.java line 32 function java::Test\.test:\(\)V bytecode-index 15: SUCCESS
5+
\[java::Test\.test:\(\)V\.assertion\.2\] line 41 assertion at file Test\.java line 41 function java::Test\.test:\(\)V bytecode-index 29: SUCCESS
6+
\[java::Test\.test:\(\)V\.assertion\.3\] line 52 assertion at file Test\.java line 52 function java::Test\.test:\(\)V bytecode-index 50: SUCCESS
7+
\[java::Test\.test:\(\)V\.assertion\.4\] line 59 assertion at file Test\.java line 59 function java::Test\.test:\(\)V bytecode-index 68: SUCCESS
8+
\[java::Test\.test:\(\)V\.assertion\.5\] line 62 assertion at file Test\.java line 62 function java::Test\.test:\(\)V bytecode-index 74: FAILURE
9+
^EXIT=10$
10+
^SIGNAL=0$

jbmc/src/java_bytecode/java_utils.cpp

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,33 +72,41 @@ get_java_primitive_type_info(const typet &maybe_primitive_type)
7272
type_info_by_primitive_type = {
7373
{java_boolean_type(),
7474
{"java::java.lang.Boolean",
75-
"java::java.lang.Boolean.valueOf:(Z)Ljava/lang/Boolean;"}},
75+
"java::java.lang.Boolean.valueOf:(Z)Ljava/lang/Boolean;",
76+
"java::java.lang.Boolean.booleanValue:()Z"}},
7677
{java_byte_type(),
7778
{"java::java.lang.Byte",
78-
"java::java.lang.Byte.valueOf:(B)Ljava/lang/Byte;"}},
79+
"java::java.lang.Byte.valueOf:(B)Ljava/lang/Byte;",
80+
"java::java.lang.Number.byteValue:()B"}},
7981
{java_char_type(),
8082
{"java::java.lang.Character",
8183
"java::java.lang.Character.valueOf:(C)"
82-
"Ljava/lang/Character;"}},
84+
"Ljava/lang/Character;",
85+
"java::java.lang.Character.charValue:()C"}},
8386
{java_double_type(),
8487
{"java::java.lang.Double",
8588
"java::java.lang.Double.valueOf:(D)"
86-
"Ljava/lang/Double;"}},
89+
"Ljava/lang/Double;",
90+
"java::java.lang.Number.doubleValue:()D"}},
8791
{java_float_type(),
8892
{"java::java.lang.Float",
8993
"java::java.lang.Float.valueOf:(F)"
90-
"Ljava/lang/Float;"}},
94+
"Ljava/lang/Float;",
95+
"java::java.lang.Number.floatValue:()F"}},
9196
{java_int_type(),
9297
{"java::java.lang.Integer",
9398
"java::java.lang.Integer.valueOf:(I)"
94-
"Ljava/lang/Integer;"}},
99+
"Ljava/lang/Integer;",
100+
"java::java.lang.Number.intValue:()I"}},
95101
{java_long_type(),
96102
{"java::java.lang.Long",
97-
"java::java.lang.Long.valueOf:(J)Ljava/lang/Long;"}},
103+
"java::java.lang.Long.valueOf:(J)Ljava/lang/Long;",
104+
"java::java.lang.Number.longValue:()J"}},
98105
{java_short_type(),
99106
{"java::java.lang.Short",
100107
"java::java.lang.Short.valueOf:(S)"
101-
"Ljava/lang/Short;"}}};
108+
"Ljava/lang/Short;",
109+
"java::java.lang.Number.shortValue:()S"}}};
102110

103111
auto found = type_info_by_primitive_type.find(maybe_primitive_type);
104112
return found == type_info_by_primitive_type.end() ? nullptr : &found->second;

jbmc/src/java_bytecode/java_utils.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ struct java_primitive_type_infot
4040
/// Full identifier of the boxed type's factory method that takes the
4141
/// corresponding primitive as its sole argument
4242
const irep_idt boxed_type_factory_method;
43+
/// Full identifier of the most general boxed-type method that yields this
44+
/// type. For most primitives that means the corresponding method on
45+
/// java.lang.Number; for Character and Boolean it means the method on their
46+
/// specific boxed type.
47+
const irep_idt unboxing_function_name;
4348
};
4449

4550
/// If \p primitive_type is a Java primitive type, return information about it,

0 commit comments

Comments
 (0)