{"record":{"id":"ad5577987dd042e2","repo":"oracle/graal","slug":"methods-with-same-signature-but-incompatible-re-ad5577","errorCode":null,"errorMessage":"methods with same signature {} but incompatible return types: {}","messagePattern":"methods with same signature (.+?) but incompatible return types: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/EspressoForeignProxyGenerator.java","lineNumber":788,"sourceCode":"            }\n\n            /*\n             * If we got through the list of existing uncovered return types without an\n             * assignability relationship, then add the new return type to the list of uncovered\n             * ones.\n             */\n            if (!added) {\n                uncoveredReturnTypes.add(newReturnType);\n            }\n        }\n\n        /*\n         * We shouldn't end up with more than one return type that is not assignable from any of the\n         * others.\n         */\n        if (uncoveredReturnTypes.size() > 1) {\n            ProxyMethod pm = methods.get(0);\n            throw new IllegalArgumentException(\n                            \"methods with same signature \" +\n                                            getFriendlyMethodSignature(pm.methodName, pm.parameterTypes) +\n                                            \" but incompatible return types: \" + uncoveredReturnTypes);\n        }\n    }\n\n    /**\n     * Visit a bytecode for a constant.\n     *\n     * @param mv The MethodVisitor\n     * @param cst The constant value\n     */\n    private static void emitIconstInsn(MethodVisitor mv, final int cst) {\n        if (cst >= -1 && cst <= 5) {\n            mv.visitInsn(ICONST_0 + cst);\n        } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) {\n            mv.visitIntInsn(BIPUSH, cst);\n        } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) {","sourceCodeStart":770,"sourceCodeEnd":806,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/EspressoForeignProxyGenerator.java#L770-L806","documentation":"The companion failure in the proxy generator's return-type coverage check: after processing all same-signature methods, if more than one return type remains 'uncovered' (none assignable from another), proxy creation aborts with the friendly signature and the list of uncovered return types. E.g. 'm()' returning both Socket and InputStream from different interfaces with neither a subtype of the other. Same rule as java.lang.reflect.Proxy.","triggerScenarios":"Proxying two interfaces that both declare 'X m()' and 'Y m()' where X and Y are unrelated reference types; the pairwise-assignability loop cannot reduce the uncovered set to one, so the final size>1 check throws.","commonSituations":"Combining generic fluent interfaces from different libraries with same-named methods; cross-version API collisions after an upgrade changed a return type; proxy composition patterns that merge unrelated SPIs.","solutions":["Proxy the conflicting interfaces separately and compose the two proxy objects behind a façade.","Change one method's return type so one is assignable from the other (common supertype or interface).","Rename or avoid one of the colliding methods if you own the interfaces.","Inspect the uncovered list in the message to see exactly which return types conflict."],"exampleFix":"// before\ninterface A { Socket connect(); }\ninterface B { Channel connect(); }\nproxy(A.class, B.class); // throws: Socket and Channel uncovered\n\n// after: separate proxies behind a facade\nclass ConnFacade { final A a; final B b; ... }","handlingStrategy":"validation","validationCode":"// simulate Proxy's coverage rule: reduce same-signature return types to one assignable chain\nList<Klass> uncovered = new ArrayList<>();\nfor (Klass rt : returnTypesOfSameSignature) {\n    boolean covered = uncovered.removeIf(u -> u.isAssignableTo(rt));\n    if (!covered && uncovered.stream().noneMatch(u -> rt.isAssignableTo(u))) uncovered.add(rt);\n}\nif (uncovered.size() > 1) throw new IllegalArgumentException(\"incompatible return types: \" + uncovered);","typeGuard":null,"tryCatchPattern":"try {\n    generator.getProxy(context, loader, interfaces);\n} catch (IllegalArgumentException e) {\n    // uncoveredReturnTypes listed in message: proxy conflicting interfaces separately\n}","preventionTips":["Check that same-signature methods across proxied interfaces form a single subtype chain of returns.","Split proxies when unrelated libraries collide on method signatures.","Add a startup validation of the interface set for dynamically composed proxies."],"tags":["proxy","method-signature","return-type","interoperability","espresso"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}