Issue Details (XML | Word | Printable)

Key: JANINO-79
Type: Bug Bug
Status: Resolved Resolved
Resolution: Fixed
Priority: Major Major
Assignee: Arno Unkrig
Reporter: Adam Heath
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
Janino

Overridden method resolution thru the inheritance chain is not correct

Created: 06/Apr/07 04:26 PM   Updated: 12/Jun/07 02:01 PM
Component/s: None
Affects Version/s: None
Fix Version/s: None

Time Tracking:
Not Specified

File Attachments: 1. Java Source File JaninoTest.java (0.6 kB)


Testcase included: yes


 Description  « Hide
Please see attached files. Janino 2.5.6 can't compile the class, but javac can.

 All   Comments   Work Log   Change History      Sort Order: Ascending order - Click to sort in descending order
Arno Unkrig added a comment - 22/Apr/07 02:54 PM
I will try to look into this one in the next days...

Arno Unkrig added a comment - 08/May/07 02:33 PM
I get
$ java -cp janino.jar org.codehaus.janino.Compiler JaninoTest.java
File JaninoTest.java, Line 17, Column 13:
Invocation of constructor/method with actual parameter type(s)
"java.lang.String, java.lang.String, java.lang.String" is ambiguous:
"void JaninoTest.doIt(java.lang.String, java.lang.Object, java.lang.Object)" vs.
"void Base.doIt(java.lang.String, java.lang.String, java.lang.String)"
org.codehaus.janino.CompileException: 1 errors while compiling unit "JaninoTest.java"
$

Problem reproduced.


Arno Unkrig added a comment - 08/May/07 02:34 PM
The example can be even be simplified to reproduce the same error:
class Base {
    public static void doIt(String msg, String left, String right) {
        System.err.println("Base.a:doIt(" + msg + ", " + left + ", " + right + ")");
    }

//    public static void doIt(String msg, Object left, Object right) {
//        System.err.println("Base.b:doIt(" + msg + ", " + left + ", " + right + ")");
//    }
}
public class JaninoTest extends Base {
    public static void doIt(String msg, Object left, Object right) {
        System.err.println("JaninoTest:doIt(" + msg + ", " + left + ", " + right + ")");
//        Base.doIt(msg, left, right);
    }

    public static void main(String[] args) {
        doIt("a", "b", "c");
    }
}

Arno Unkrig added a comment - 08/May/07 03:03 PM
Hm... to me it seems like javac is wrong here.

JLS 2 says:

JLS2: 15.12.2.2 Choose the Most Specific Method

Let m be a name and suppose that there are two declarations of methods named m, each having n parameters. Suppose that one declaration appears within a class or interface T and that the types of the parameters are T1, . . . , Tn; suppose moreover that the other declaration appears within a class or interface U and that the types of the parameters are U1, . . . , Un. Then the method m declared in T is more specific than the method m declared in U if and only if both of the following are true:

  • T can be converted to U by method invocation conversion.
  • Tj can be converted to Uj by method invocation conversion, for all j from 1 to n.

A method is said to be maximally specific for a method invocation if it is applicable and accessible and there is no other applicable and accessible method that is more specific.

If there is exactly one maximally specific method, then it is in fact the most specific method; it is necessarily more specific than any other method that is applicable and accessible. It is then subjected to some further compile-time checks as described in §15.12.3.

It is possible that no method is the most specific, because there are two or more maximally specific methods. In this case:

  • If all the maximally specific methods have the same signature, then:
    o If one of the maximally specific methods is not declared abstract, it is the most specific method.
    o Otherwise, all the maximally specific methods are necessarily declared abstract. The most specific method is chosen arbitrarily among the maximally specific methods. However, the most specific method is considered to throw a checked exception if and only if that exception is declared in the throws clauses of each of the maximally specific methods.
  • Otherwise, we say that the method invocation is ambiguous, and a compile-time error occurs.

In your example we have

T=JaninoTest ==method-convertable to==> U=Base
T1=String <==method-convertable to==> U1=String
T2=Object <==method-convertable to== U2=String
T3=Object <==method-convertable to== U3=String

So neither of the two methods is more specific than the other. Considering the rest of the JLS2 section, JANINO is right to issue a compile-time error.

Amazingly, all of JAVAC 1.6.0, 1.5.0 and 1.4.1 compile the example without errors and warnings (and call "Base.doIt(String, String String)").

Only JAVAC 1.2.2 says

$ jdk-1.2.2/bin/javac JaninoTest.java
JaninoTest.java:17: Reference to doIt is ambiguous. It is defined in void doIt(java.lang.String, java.lang.Object, java.lang.Object) and void doIt(jav
a.lang.String, java.lang.String, java.lang.String).
        doIt("a", "b", "c");
            ^
1 error
$

– it seems to share the opinion of JANINO and JLS2!

JLS3 is quite difficult to understand:

JLS3: 15.12.2.5 Choosing the Most Specific Method

If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time type error.
One fixed-arity member method named m is more specific than another member method of the same name and arity if all of the following conditions hold:

  • The declared types of the parameters of the first member method are T1, . . . , Tn.
  • The declared types of the parameters of the other method are U1, . . . , Un.
  • If the second method is generic then let R1 ... Rp p1, be its formal type parameters, let Bl be the declared bound of Rl, 1lp, let A1 ... Ap be the actual type arguments inferred (§15.12.2.7) for this invocation under the initial constraints Ti << Ui, 1in and let Si = Ui[R1 = A1, ..., Rp = Ap] 1in; otherwise let Si = Ui 1in.
  • For all j from 1 to n, Tj <: Sj.
  • If the second method is a generic method as described above then Al <: Bl[R1 = A1, ..., Rp = Ap], 1lp.

In addition, one variable arity member method named m is more specific than another variable arity member method of the same name if either:

  • One member method has n parameters and the other has k parameters, where nk. The types of the parameters of the first member method are T1, . . . , Tn-1 , Tn[], the types of the parameters of the other method are U1, . . . , Uk-1, Uk[]. If the second method is generic then let R1 ... Rp p1, be its formal type parameters, let Bl be the declared bound of Rl, 1lp, let A1 ... Ap be the actual type arguments inferred (§15.12.2.7) for this invocation under the initial constraints Ti << Ui,1ik-1, Ti << Uk, kin and let Si = Ui[R1 = A1, ..., Rp = Ap] 1ik; otherwise let Si = Ui, 1ik. Then:
    o for all j from 1 to k-1, Tj <: Sj, and,
    o for all j from k to n, Tj <: Sk, and,
    o If the second method is a generic method as described above then Al <: Bl[R1 = A1, ..., Rp = Ap], 1lp.
  • One member method has k parameters and the other has n parameters, where nk. The types of the parameters of the first method are U1, . . . , Uk-1, Uk[], the types of the parameters of the other method are T1, . . ., Tn-1, Tn[]. If the second method is generic then let R1 ... Rp p1, be its formal type parameters, let Bl be the declared bound of Rl, 1lp, let A1 ... Ap be the actual type arguments inferred (§15.12.2.7) for this invocation under the initial constraints Ui << Ti, 1ik-1, Uk << Ti, kin and let Si = Ti[R1 = A1, ..., Rp = Ap] 1in; otherwise let Si = Ti, 1in. Then:
    o for all j from 1 to k-1 , Uj <: Sj, and,
    o for all j from k to n , Uk <: Sj, and,
    o If the second method is a generic method as described above then Al <: Bl[R1 = A1, ..., Rp = Ap], 1lp.

The above conditions are the only circumstances under which one method may be more specific than another.

A method m1 is strictly more specific than another method m2 if and only if m1 is more specific than m2 and m2 is not more specific than m1.

A method is said to be maximally specific for a method invocation if it is accessible and applicable and there is no other method that is applicable and accessible that is strictly more specific.

If there is exactly one maximally specific method, then that method is in fact the most specific method; it is necessarily more specific than any other accessible method that is applicable. It is then subjected to some further compile-time checks as described in §15.12.3.

It is possible that no method is the most specific, because there are two or more methods that are maximally specific. In this case:

  • If all the maximally specific methods have override-equivalent (§8.4.2) signatures, then:
    o If exactly one of the maximally specific methods is not declared abstract, it is the most specific method.
    o Otherwise, if all the maximally specific methods are declared abstract, and the signatures of all of the maximally specific methods have the same erasure (§4.6), then the most specific method is chosen arbitrarily among the subset of the maximally specific methods that have the most specific return type. However, the most specific method is considered to throw a checked exception if and only if that exception or its erasure is declared in the throws clauses of each of the maximally specific methods.
  • Otherwise, we say that the method invocation is ambiguous, and a compile-time error occurs.

This sounds to me like the type where the two methods are defined (JaninoTest and Base) no longer matter, and thus the invocation is not ambiguous. But I'm not sure.

Notice that JLS3 maps to JDK 1.5, so JDK 1.4.1 should implement the behavior defined by JLS2!?


Arno Unkrig added a comment - 08/May/07 03:06 PM
Adam, what is your understanding of the theory and practice?

Adam Heath added a comment - 20/May/07 04:02 PM
Well, it doesn't say where method T and method U come from, in the inheritance chain. Only that they are defined in a class or a method. So, for instance, T is not more specific than U, but if you swap T and U, then it is.

All parameters of U can be converted to parameters of T, which, by definition, means it's more specific.


Arno Unkrig added a comment - 12/Jun/07 02:01 PM
Well, since this JLS2 incompatibility is about one compile error LESS, I changed JANINO to mimic the behavior of JAVAC 1.4.1, 1.5.0 and 1.6.0.
Will be released as 2.5.8.