Commit dce7adf
authored
[jvm] auto-detect Java SAM interfaces; honor SAM in overload resolution (#12901)
* [jvm] auto-detect Java SAM interfaces; honor SAM in overload resolution
Two related changes that together make Haxe match javac's lambda-vs-SAM
conversion rules on the JVM target.
1. Structural SAM detection in javaModern.ml
Previously, only interfaces carrying @FunctionalInterface were tagged
CFunctionalInterface and so eligible for implicit conversion from a
function value. The annotation is documentation in javac too — what
makes a type lambda-convertible is having exactly one abstract instance
method, per JLS §9.8. The Android SDK rarely annotates its listeners,
which forced every consumer of the JVM target to hand-roll adapter
classes for View.OnClickListener, DialogInterface.OnClickListener,
MediaPlayer.OnCompletionListener and so on.
This patch auto-tags any .jar-imported interface as @:functionalInterface
when it has exactly one abstract instance method, excluding:
- static / default / private / synthetic methods,
- constructors,
- Object members re-declared as abstract (equals/hashCode/toString,
matched by name+arity since their signatures on j.l.Object are
unambiguous — JLS §9.8 excludes these from the SAM count).
The explicit @FunctionalInterface annotation path is preserved.
2. SAM-aware overload candidate filtering
overloadResolution.ml's unify_cf used strict Type.unify to filter
candidates before the cast layer could fire. A function value passed to
an overload whose parameter is a SAM class would be discarded at the
filter step. Now, when strict unify fails, the filter retries via the
SAM conversion path — unifying the function's TFun against the SAM
method's signature, mirroring what AbstractCast does at cast time. Only
candidates whose SAM signature actually matches the function shape
survive, so this isn't a blind accept.
Two supporting fixes round this out:
- overloads.ml rate_conv: added a TInst(SAM), TFun case so specificity
ranking works when two SAM-parametered overloads both accept the
same function. The rate descends through the SAM method's signature
rather than raising Not_found, which previously discarded the
candidate during reduce_compatible.
- tOther.ml get_singular_interface_field and the parallel walker in
genjvm.ml's check_functional_interface now exclude equals/hashCode/
toString by name+arity, matching the new exclusion in javaModern so
interfaces like Comparator (abstract equals re-declared) — and the
Android SDK's various 'override equals' interfaces — are still
detected as SAMs at both typer and codegen stages.
Tests:
- tests/unit/.../IssueFunctionalInterfaceOverload.hx exercises the
overload disambiguation path with two @:functionalInterface
interfaces of disjoint shape (Int,Int -> Int vs String -> String).
- tests/misc/jvm/projects/StructuralSam/ ships a .java file with six
interface shapes — plain SAM, abstract-equals re-declaration,
default/static method skipping, multi-abstract non-SAM, generic-arg
SAM, and overloaded call sites — none annotated @FunctionalInterface.
Runs the generated jar end-to-end to verify both type-checking and
runtime closure-adapter dispatch.
Limitations matching javac:
- Abstract-class SAMs (e.g. android.content.BroadcastReceiver) remain
unsupported; javac doesn't allow lambda conversion to those either.
- Two SAM-parametered overloads where both signatures would accept
the same function value remain ambiguous and require explicit
disambiguation.
* [jvm] skip JDK-internal packages in structural SAM detection
Auto-tagging every structurally-SAM interface as @:functionalInterface
swept in sun.reflect.ConstructorAccessor, sun.nio.ch.Interruptible,
sun.reflect.generics.tree.TypeTree and similar JDK-internal classes.
The existing JFI matcher then made every signature-matching closure
declare `implements sun.reflect.ConstructorAccessor`, which compiles
fine against the extern jar but fails to defineClass at runtime on
JDK 9+ — sun.* was moved to jdk.internal.* and is inaccessible to
application code.
This broke unit tests at startup: Issue5556 was the first class to
trigger loading the affected closure adapter, raising
NoClassDefFoundError before any test ran.
User code can't legitimately target these internal packages anyway,
so the fix is to exclude sun.*, com.sun.*, and jdk.internal.* from
structural detection. Classes that actually carry @FunctionalInterface
are still honored (none of the affected internal classes do, so this
is safe). Issue5556 serves as the regression test — its closure
adapter previously implemented sun.reflect.ConstructorAccessor and
now no longer does.
* [jvm] only implement SAM interfaces the program actually uses
Structural SAM detection tags every single-abstract-method interface on
the --java-lib classpath as @:functionalInterface, and a closure then
implements every one of them whose signature structurally matches. That
includes interfaces transitively loaded from the SDK jar but never named
in user code — e.g. compiling against android-34 drags in
android.os.OutcomeReceiver (API 33) and SplashScreen.OnExitAnimationListener
(API 31). A closure implementing those hard-fails class linking
(NoClassDefFoundError) the moment it loads on an older runtime, crashing
the app at activity instantiation.
Restrict the promiscuous binding to interfaces the program genuinely
converts a function to:
- new com.functional_interfaces_used set (shared across cloned contexts,
exposed on Gctx.t for the generator);
- AbstractCast records each interface as its SAM-conversion branch fires,
keyed by the @:native path since Native.apply_native_paths rewrites
cl_path between typing and generation;
- genjvm's Preprocessor only registers an interface in
gctx.functional_interfaces when it is in that set, resolved by physical
class identity in the modules loop (before check_path can rewrite the
path again).
StructuralSam gains an Unused interface — structurally identical to
OnClick but never used as a conversion target — and asserts a closure
implements OnClick but not Unused.
* [jvm] derive used SAM interfaces from the AST, not just AbstractCast
The functional_interfaces_used set populated by AbstractCast only covers
interfaces reached through an implicit SAM conversion typed from source.
That misses two cases: an explicit `cast` of a closure to a functional
interface bypasses AbstractCast entirely, and an `--hxb-lib` build never
re-runs typing so the set is empty — closures then fail to implement the
interface and hard-cast at runtime (Issue9576, Issue11236,
IssueFunctionalInterfaceOverload under the hxb-lib CI configs).
Additionally scan the AST of non-extern types in genjvm's Preprocessor:
field/static/constructor signatures and their cf_overloads, plus the
etype of every sub-expression (which catches the TFun type of a called
extern method whose parameter is a SAM interface). This is independent
of how the module was loaded. Restricting the scan to non-extern types
keeps incidental SAM interfaces from a --java-lib classpath out of the
set unless user code actually references them.
* [tests] move test to TestJava
* [jvm] route check_functional_interface through get_singular_interface_field
The JLS §9.8 SAM rule (exclude default methods and Object members) was
duplicated between tOther and genjvm; the latter now just calls the
shared helper.
* [jvm] drop functional_interfaces_used; AST scan is the sole source
AbstractCast was writing into a Hashtbl on the common context to track
which SAM interfaces a program converts a function to, with @:native
re-keying to bridge the typing↔generation path rewrite. The genjvm
preprocessor already walks the AST and collects every SAM-typed TInst
it sees, which strictly subsumes the AbstractCast set (it also catches
explicit casts and --hxb-lib builds). Remove the redundant bookkeeping
and the field on common.context / Gctx.t.
* [jvm] swap fi_used_classes List.memq for path-keyed Hashtbl
Avoid an O(n*m) lookup over the SAM-used list during the interface
preprocessing loop. The list is finalized into a Hashtbl after loop 1
completes (and therefore after check_path's cl_path rewrites), so
keying by cl_path is stable.
* [jvm] drop functional_interface_lut; compute SAM field on demand
The LUT memoized get_singular_interface_field per class, populated by
typeloadFields.check_functional_interface and re-checked lazily by
AbstractCast (#11549, for display-mode flows where init_class hadn't
run yet by the time a SAM conversion was queried). It was a global
mutable hashtbl on common.context whose only reader was AbstractCast.
The cached computation is a walk over a SAM interface's field list —
typically under 10 entries, and short-circuits on the second non-default
method, so the perf delta from recomputing is well below noise. Doing
that directly in AbstractCast collapses the two load paths into one and
removes the field from common.context.
Also drops the now-no-op typeloadFields.check_functional_interface
(its only effect was populating the LUT) and the bridge call that
re-invoked it from init_class when the flag was set via @:functionalInterface
meta in typeloadModule.
* [jvm] extract collect_used_functional_interfaces
Move the SAM-usage AST scan out of preprocess and into its own
top-level function in the Preprocessor module that returns the
path-keyed hashtbl. The scan now runs after the check_path loop so
cl_paths are stable when we record them.1 parent 6c739be commit dce7adf
14 files changed
Lines changed: 369 additions & 39 deletions
File tree
- src
- codegen
- context
- core
- generators
- typing
- tests
- misc/jvm/projects/StructuralSam
- project/test
- unit/src/unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
985 | 985 | | |
986 | 986 | | |
987 | 987 | | |
| 988 | + | |
988 | 989 | | |
989 | 990 | | |
990 | 991 | | |
991 | 992 | | |
992 | | - | |
| 993 | + | |
993 | 994 | | |
994 | 995 | | |
995 | 996 | | |
996 | 997 | | |
997 | 998 | | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
| 1024 | + | |
| 1025 | + | |
| 1026 | + | |
| 1027 | + | |
| 1028 | + | |
| 1029 | + | |
| 1030 | + | |
| 1031 | + | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
998 | 1050 | | |
999 | 1051 | | |
1000 | 1052 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
192 | 192 | | |
193 | 193 | | |
194 | 194 | | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
195 | 206 | | |
196 | 207 | | |
197 | 208 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
99 | 94 | | |
100 | 95 | | |
101 | 96 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
345 | 345 | | |
346 | 346 | | |
347 | 347 | | |
348 | | - | |
349 | 348 | | |
350 | 349 | | |
351 | 350 | | |
| |||
810 | 809 | | |
811 | 810 | | |
812 | 811 | | |
813 | | - | |
814 | 812 | | |
815 | 813 | | |
816 | 814 | | |
| |||
935 | 933 | | |
936 | 934 | | |
937 | 935 | | |
938 | | - | |
939 | 936 | | |
940 | 937 | | |
941 | 938 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
386 | 386 | | |
387 | 387 | | |
388 | 388 | | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
389 | 398 | | |
390 | | - | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
391 | 402 | | |
392 | 403 | | |
393 | 404 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3201 | 3201 | | |
3202 | 3202 | | |
3203 | 3203 | | |
3204 | | - | |
3205 | | - | |
3206 | | - | |
3207 | | - | |
3208 | | - | |
3209 | | - | |
3210 | | - | |
3211 | | - | |
3212 | | - | |
3213 | | - | |
3214 | | - | |
3215 | | - | |
3216 | | - | |
| 3204 | + | |
3217 | 3205 | | |
3218 | 3206 | | |
3219 | 3207 | | |
| |||
3224 | 3212 | | |
3225 | 3213 | | |
3226 | 3214 | | |
| 3215 | + | |
| 3216 | + | |
| 3217 | + | |
| 3218 | + | |
| 3219 | + | |
| 3220 | + | |
| 3221 | + | |
| 3222 | + | |
| 3223 | + | |
| 3224 | + | |
| 3225 | + | |
| 3226 | + | |
| 3227 | + | |
| 3228 | + | |
| 3229 | + | |
| 3230 | + | |
| 3231 | + | |
| 3232 | + | |
| 3233 | + | |
| 3234 | + | |
| 3235 | + | |
| 3236 | + | |
| 3237 | + | |
| 3238 | + | |
| 3239 | + | |
| 3240 | + | |
| 3241 | + | |
| 3242 | + | |
| 3243 | + | |
| 3244 | + | |
| 3245 | + | |
| 3246 | + | |
| 3247 | + | |
| 3248 | + | |
| 3249 | + | |
| 3250 | + | |
| 3251 | + | |
| 3252 | + | |
| 3253 | + | |
| 3254 | + | |
| 3255 | + | |
| 3256 | + | |
| 3257 | + | |
| 3258 | + | |
| 3259 | + | |
| 3260 | + | |
| 3261 | + | |
| 3262 | + | |
| 3263 | + | |
| 3264 | + | |
| 3265 | + | |
3227 | 3266 | | |
3228 | 3267 | | |
3229 | 3268 | | |
| |||
3233 | 3272 | | |
3234 | 3273 | | |
3235 | 3274 | | |
3236 | | - | |
3237 | 3275 | | |
3238 | 3276 | | |
3239 | 3277 | | |
| |||
3251 | 3289 | | |
3252 | 3290 | | |
3253 | 3291 | | |
| 3292 | + | |
| 3293 | + | |
| 3294 | + | |
3254 | 3295 | | |
3255 | 3296 | | |
3256 | 3297 | | |
| |||
3267 | 3308 | | |
3268 | 3309 | | |
3269 | 3310 | | |
3270 | | - | |
3271 | | - | |
| 3311 | + | |
| 3312 | + | |
3272 | 3313 | | |
3273 | 3314 | | |
3274 | 3315 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
6 | 27 | | |
7 | 28 | | |
8 | 29 | | |
9 | 30 | | |
10 | 31 | | |
11 | 32 | | |
12 | 33 | | |
13 | | - | |
| 34 | + | |
| 35 | + | |
14 | 36 | | |
15 | 37 | | |
16 | 38 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1644 | 1644 | | |
1645 | 1645 | | |
1646 | 1646 | | |
1647 | | - | |
1648 | | - | |
1649 | | - | |
1650 | | - | |
1651 | | - | |
1652 | | - | |
1653 | | - | |
1654 | | - | |
1655 | 1647 | | |
1656 | 1648 | | |
1657 | 1649 | | |
| |||
1793 | 1785 | | |
1794 | 1786 | | |
1795 | 1787 | | |
1796 | | - | |
| 1788 | + | |
1797 | 1789 | | |
1798 | 1790 | | |
1799 | 1791 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
0 commit comments