Bug 468276 - [1.8][compiler] JDT compiler reporting ambiguous method, but code compiles fine with Oracle JDK
Summary: [1.8][compiler] JDT compiler reporting ambiguous method, but code compiles fi...
Status: NEW
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 4.4.2   Edit
Hardware: PC Mac OS X
: P3 enhancement with 8 votes (vote)
Target Milestone: ---   Edit
Assignee: JDT-Core-Inbox CLA
QA Contact:
URL:
Whiteboard: stalebug
Keywords:
: 491114 (view as bug list)
Depends on:
Blocks:
 
Reported: 2015-05-26 05:21 EDT by Mirco Dotta CLA
Modified: 2022-08-16 08:50 EDT (History)
15 users (show)

See Also:


Attachments
Sample project to reproduce the issue. (3.38 KB, application/zip)
2015-05-26 15:48 EDT, Eugen Paraschiv CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Mirco Dotta CLA 2015-05-26 05:21:03 EDT
Hi,

I'm hitting what seem to be a JDT compiler bug. Note that I can reproduce the below described issue with a fresh download of Eclipse 4.4 Standard (https://eclipse.org/downloads/packages/eclipse-standard-44/lunar).

Here are the sources needed to reproduce the problem:

A.java
package p;

import scala.collection.immutable.Map;

public class A {
    public static Map<String, String> foo() {
        return null;
    }
}

B.java
package p;

import scala.collection.immutable.Map;

public class B {
    public String bar(String name) {
        return A.foo().apply(name);
    }
}

To resolve the import to `scala.collection.immutable.Map` you will need the scala-library available in the classpath. You can grab it here: http://mvnrepository.com/artifact/org.scala-lang/scala-library/2.11.6.

The error reported in Eclipse is: "The method apply(String) is ambiguous for the type 
 Map<String,String>".

While, the above works fine if compiled from the command-line (I'm using JDK8u45, but I suppose any JDK6+ will do it).

I'd argue that the described issue is common enough for users mixing Scala and Java.
Comment 1 Manoj N Palat CLA 2015-05-26 05:31:48 EDT
Could you please try a latest build of 4.5 (Mars)? This error is not seen with the latest 4.5.
Comment 2 Mirco Dotta CLA 2015-05-26 05:44:27 EDT
I can confirm that the error is still seen with S-4.5RC2a-201505222000*

Let me know if there is a more recent build I should use (and where I can download it).

* downloaded from http://www.eclipse.org/downloads/download.php?file=/eclipse/downloads/drops4/S-4.5RC2a-201505222000/eclipse-SDK-4.5RC2a-macosx-cocoa-x86_64.tar.gz
Comment 3 Manoj N Palat CLA 2015-05-26 06:12:45 EDT
(In reply to Mirco Dotta from comment #2)
> I can confirm that the error is still seen with S-4.5RC2a-201505222000*
> 
> Let me know if there is a more recent build I should use (and where I can
> download it).
> 
> * downloaded from
> http://www.eclipse.org/downloads/download.php?file=/eclipse/downloads/drops4/
> S-4.5RC2a-201505222000/eclipse-SDK-4.5RC2a-macosx-cocoa-x86_64.tar.gz

Strange. I don't see it with the RC2a build while with Luna this is reproduced. [this is on windows though there should not be an impact based on the machine/os]
Comment 4 Mirco Dotta CLA 2015-05-26 06:21:29 EDT
Thanks a lot for trying it out, and taking the time.

Your comment made me wondering what was the difference, and noticed that the error is reported only if I'm using JRE8 (the exact version is 1.8.0_45). Indeed, if I use a JRE6 or JRE7, the code typechecks.

Could you check if you can reproduce it using the JRE8?
Comment 5 Stephan Herrmann CLA 2015-05-26 07:18:07 EDT
For those who don't have that library on their hard disk: what are the declared signatures of the method(s) "apply"?
Comment 6 Mirco Dotta CLA 2015-05-26 07:38:21 EDT
@Stephan The signature of `scala.collection.immutable.Map` is quite involved, as it inherits from several interfaces:

$ javap -p -classpath scala-library.jar scala/collection/immutable/Map
Compiled from "Map.scala"
public interface scala.collection.immutable.Map<A, B> extends scala.collection.immutable.Iterable<scala.Tuple2<A, B>>, scala.collection.Map<A, B>, scala.collection.immutable.MapLike<A, B, scala.collection.immutable.Map<A, B>> {
...
}

`scala.collection.immutable.Map` doesn't declare the `apply` method, but a few of its super types do. The signature of the method is always the same:

public abstract B apply(A);

Where B and A are class' type parameters. For instance, `scala.collection.MapLike` is one of the interfaces `scala.collection.immutable.Map` inherits from:

$ javap -p -classpath scala-library.jar scala/collection/MapLike
Compiled from "MapLike.scala"
public interface scala.collection.MapLike<A, B, This extends scala.collection.MapLike<A, B, This> & scala.collection.Map<A, B>> extends scala.PartialFunction<A, B>, scala.collection.IterableLike<scala.Tuple2<A, B>, This>, scala.collection.GenMapLike<A, B, This>, scala.collection.generic.Subtractable<A, This>, scala.collection.Parallelizable<scala.Tuple2<A, B>, scala.collection.parallel.ParMap<A, B>> {
...
public abstract B apply(A);
...
}

AFAICT, there are no overloads of this method.

Not sure if the above is actually helpful, so let me know if there is more I can do.
Comment 7 Stephan Herrmann CLA 2015-05-26 09:35:43 EDT
Thanks, Mirco.

From what you write I'd guess that ecj might see different versions of apply with different parameterizations inherited from different super types.

Would you be able to reproduce with pure Java?
Comment 8 Mirco Dotta CLA 2015-05-26 09:56:05 EDT
I tried to minimise it so that it would be only Java code, but I didn't succeed so far. At this point, I'm not even sure this can be reproduced with only Java code.
Comment 9 Mirco Dotta CLA 2015-05-26 10:14:20 EDT
Following up on my previous comment, I was trying to emulate the complex hierarchy of `scala.collection.immutable.Map` in Java, as I believe it may be playing a role. Unfortunately, I got stuck pretty quickly. The following doesn't compile:

interface MapLike<A,B, This extends MapLike<A, B, This> & AMap<A,B>> {
	B apply(A key);
}

interface AMap<A,B> extends MapLike<A,B, AMap<A,B>> {

}

The reported error is: The interface MapLike cannot be implemented more than once with different arguments: MapLike<A,B,AMap<A,B>> and MapLike<A,B,This>

Is there a way to correct the above declaration so that it produces a signature similar to `scala.collection.MapLike`? (see the result of javap in my comment https://bugs.eclipse.org/bugs/show_bug.cgi?id=468276#c6)

If it's not possible to obtain that signature from pure Java code, then it is possible that this issue cannot be reproduced with Java code alone.
Comment 10 Stephan Herrmann CLA 2015-05-26 10:40:03 EDT
Thanks for trying.

From staring at the snippet for some minutes, I have no idea how to morph this into legal Java.

If this is the final word on emulating, then I see little chance we can improve the situation.
The compiler should certainly not throw any exceptions when seeing unexpected library types (it doesn't), but otherwise its behavior would just be unspecified.
Comment 11 Eugen Paraschiv CLA 2015-05-26 15:47:42 EDT
I'm also seeing a very similar (if not identical) problem when using the iterator method of a scala.collection.Seq: 

Seq<Broker> brokers = ...
Iterator<Broker> it = brokers.iterator();

The exact error message in Eclipse is: 
The method iterator() is ambiguous for the type Seq<Broker>

I'm using Eclipse 4.4.2 and Java 8 (1.8.0_45)
I can also replicate it 4.5 RC1 (Build id: 20150521-1252)
I'm attaching a sample maven project to easily replicate the problem. 

A final note is that the problem only occurs when the I convert the project to Faceted Form - before that, it's perfectly fine. 
Thanks, 
Eugen.
Comment 12 Eugen Paraschiv CLA 2015-05-26 15:48:25 EDT
Created attachment 253806 [details]
Sample project to reproduce the issue.
Comment 13 Stephan Herrmann CLA 2015-05-26 16:53:39 EDT
(In reply to Eugen Paraschiv from comment #11)
> I'm also seeing a very similar (if not identical) problem when using the
> iterator method of a scala.collection.Seq: 

I wasn't looking for more Scala, but for a way to reproduce the problem in pure Java :)
Comment 14 Eugen Paraschiv CLA 2015-05-26 17:56:33 EDT
:) Yeah, that makes sense. The reason I added it here is - I thought it might be the same problem. If it's not, I'll definitely open up a new bug.
Comment 15 Mirco Dotta CLA 2015-05-27 03:31:44 EDT
@Eugen It's very likely that it is the same problem. In fact, I've seen similar issues with different collection classes in Scala, when using it with Java8 in Eclipse. That said, thanks for chiming in, as this adds to my feeling that many Eclipse users mixing Scala and Java8 will experience the problem, as it's reproducible with widely used type such as Map, Seq, Buffer, and others.

@Stephan I'll do further investigation, and will also try to involve a few colleagues working on the Scala compiler. However, let's suppose that the issue is not reproducible with only Java code, this doesn't imply that there isn't a problem in the Eclipse Java8 compiler. In fact, it's interesting that the offending code works fine in Eclipse with Java7, and it can also be compiled from the command-line with the Oracle JDK8 (I've used 1.8.0_45). So, if the issue is not reproducible with only Java code, I guess the question would boil down to: "Is the bytecode produced by Scala valid JVM8 bytecode?" If it is, then the Eclipse Java8 implementation is indeed faulty, and it would be really fantastic if you would consider accepting this ticket, as it has the potential of badly affecting both the Scala and Java8 Eclipse community.
Comment 16 Stephan Herrmann CLA 2015-05-27 11:17:04 EDT
If the situation indeed requires scala for reproduction, then the compiler behavior is *unspecified*. This implies that some compilers may by chance accept the program, while others don't - still both are valid compilers for Java.

As for the difference ecj-1.7 vs. ecj-1.8: please consider that type inference for Java 8 is a complete rewrite (both in JLS and implementation), so differences in unspecified behavior are to be expected.

I don't see JVMS having an impact here. A compiler for Java cannot be required to understand all possible byte codes. Being valid byte code only guarantees that the JVM will accept it, not that it is callable from all other JVM languages. JLS has only very few references to JVMS, none of which seems to indicate that JVMS is binding for Java compilers in the way you suggest.

If we work on this issue based on the above understanding, priority will definitely be: not to break any pure Java use cases.

All this is said just to adjust expectations. If the issue can be reproduced in pure Java, we will definitely work on a solution, if it can't, we'll still give it a try - but with open ending.
Comment 17 Jason Zaugg CLA 2015-06-12 13:02:16 EDT
I've converted the involved interfaces to Java sources by way of javap.

This program gives the same error messages at the call site: "method is ambiguous". 

class B {
	public String foo(s_c_i_Map map) {
        return map.apply("");
    }
}

interface s_c_i_MapLike<This extends s_c_i_MapLike<This> & s_c_i_Map> extends s_c_MapLike<This> {
}

interface s_c_MapLike<This extends s_c_MapLike<This> & s_c_Map> {
	public String apply(String a);
}

interface s_c_i_Map extends s_c_Map, s_c_i_MapLike<s_c_i_Map> {	
}

interface s_c_Map extends s_c_MapLike<s_c_Map> {
}


However, the definitions of these interfaces are rejected by both JDT and Oracle JDK.

The Oracle JDK errors:

error: s_c_MapLike cannot be inherited with different arguments: <A,B,org.test.util.s_c_Map<A,B>> and <A,B,org.test.util.s_c_i_Map<A,B>>
interface s_c_i_Map<A, B> extends s_c_Map<A, B>, s_c_i_MapLike<A, B, s_c_i_Map<A, B>> {
^
error: s_c_MapLike cannot be inherited with different arguments: <A,B,This> and <A,B,org.test.util.s_c_Map<A,B>>
interface s_c_MapLike<A, B, This extends s_c_MapLike<A, B, This> & s_c_Map<A, B>> {
                            ^
error: s_c_MapLike cannot be inherited with different arguments: <A,B,This> and <A,B,org.test.util.s_c_Map<A,B>>
interface s_c_i_MapLike<A, B, This extends s_c_i_MapLike<A, B, This> & s_c_i_Map<A, B>> extends s_c_MapLike<A, B, This> {
                              ^

I believe that this is allowed in Scala because the type parameter `This` is marked as covariant.

I had to upgrade from JDT 4.3 to 4.4 in order to reproduce this error. 

This gist contains the test file and transcripts of compilation with with JDT 1.7/1.8 and Oracle javac:

   https://gist.github.com/retronym/f5a54ddd30bced7e0285

Note that Oracle compiler does not report an error at the call to `apply`. It would be enormously helpful if we could find a way to restrict JDT to issuing this error at the definition site, rather than at use site.
Comment 18 Stephan Herrmann CLA 2015-06-12 15:39:52 EDT
(In reply to Jason Zaugg from comment #17)
> I've converted the involved interfaces to Java sources by way of javap.

Thanks, Jason, for mapping this into pure-Java space.

As it stands, we are outside the spec'd territory, as we are trying to compile against interfaces that are not legal according to Java rules. Current behavior of Java compilers in this situation is thus owed to private, arbitrary implementation choices.

I'm not against filling this unspec'd space with an implementation that helps the Scala crowd. The problem is: how can we discern this particular situation so we can invent a change to the compiler that will not affect any legal Java situation.

I'm tentatively putting this on my plate.

My current solution idea being: detect in .class files the situation that, if it occurred in Java source, would raise the error "The interface X cannot be implemented more than once with different arguments". 
Tag the type X and when about to report ambiguity against methods from type X, perform extra work to figure out, whether the ambiguity can be traced back to the first error.
The last part of the analysis may be complex, but being triggered only in extreme corner cases, this should be tolerable.
I'm more concerned about the analysis of *all* interface .class-files, which currently are *assumed* to be type-correct wrt JLS. To avoid such performance penalty for all interfaces, we may have to do this part on-demand, intertwined with the analysis of ambiguity. Not easy.
Comment 19 Timo Kinnunen CLA 2015-06-20 10:09:01 EDT
(In reply to comment #17)
> This gist contains the test file and transcripts of compilation with with JDT
> 1.7/1.8 and Oracle javac:
> 
> https://gist.github.com/retronym/f5a54ddd30bced7e0285

I can fix this so that it compiles by introducing a self-type type parameter to both s_c_i_Map and s_c_Map and then rewriting their definitions in terms of those. I don't know if it's by design that immutable and mutable Map extend supertypes which take in a self-type parameter and not provide them an accurate one, but this seems problematic to me... Anyway, I don't know Scala well enough to say if this is where the problem might be, but here's the code that compiles, at least:

package org.test.util;

public class Test {
	public static <ImmutableMap extends s_c_i_Map<String, String, ImmutableMap>> String foo(ImmutableMap map) {
		return map.apply("");
	}
}
interface s_c_i_MapLike<A, B, This extends s_c_i_MapLike<A, B, This> & s_c_i_Map<A, B, This>> extends
	s_c_MapLike<A, B, This> {}
interface s_c_MapLike<A, B, This extends s_c_MapLike<A, B, This> & s_c_Map<A, B, This>> {
	public B apply(A a);
}
interface s_c_i_Map<A, B, This extends s_c_i_Map<A, B, This>> extends s_c_Map<A, B, This>, s_c_i_MapLike<A, B, This> {}
interface s_c_Map<A, B, This extends s_c_Map<A, B, This>> extends s_c_MapLike<A, B, This> {}
Comment 20 Mirco Dotta CLA 2015-10-09 11:02:47 EDT
@StephanHerrmann Just wondering if you made any progress, and if there is a chance this may be fixed in time for the next Eclipse release. Thanks again for putting your time into it!
Comment 21 Stephan Herrmann CLA 2016-01-24 14:50:14 EST
(In reply to Mirco Dotta from comment #20)
> @StephanHerrmann Just wondering if you made any progress, and if there is a
> chance this may be fixed in time for the next Eclipse release. Thanks again
> for putting your time into it!

Other than the above thinking aloud: nothing done, yet. Sorry.
I'm particularly worried about penalizing all Java compilations for this corner case.

Any simpler strategies for detecting the exact situation for which the compiler behavior should be changed would increase the chances of addressing this...
Comment 22 Stephan Herrmann CLA 2016-03-25 10:29:37 EDT
Too much on my plate for 4.6. Bulk deferral to 4.7
Comment 23 Stephan Herrmann CLA 2016-04-17 15:11:40 EDT
*** Bug 491114 has been marked as a duplicate of this bug. ***
Comment 24 Brandy Kinlaw CLA 2017-01-20 12:39:22 EST
I had to switch to IntelliJ because of this bug. The bug causes code deployed by JRebel to the remote server to throw this error. My server started to automatically throw this error and crash when JRebel(running in eclipse) hot swapped the code to it. I know that this is an eclipse problem because the code deploys fine outside of eclipse, and JRebel works properly in IntelliJ. This bug needs to be fixed ASAP, mixing Scala and Java is a fairly common practice.

Error in Eclipse:

The method iterator() is ambiguous for the type Seq<Object>...
Comment 25 Stephan Herrmann CLA 2017-01-20 14:02:03 EST
(In reply to Brandy Kinlaw from comment #24)
> I had to switch to IntelliJ because of this bug. The bug causes code
> deployed by JRebel to the remote server to throw this error. My server
> started to automatically throw this error and crash when JRebel(running in
> eclipse) hot swapped the code to it. I know that this is an eclipse problem
> because the code deploys fine outside of eclipse, and JRebel works properly
> in IntelliJ. This bug needs to be fixed ASAP, mixing Scala and Java is a
> fairly common practice.

BTW: has anyone contacted the Scala guys to check if they could change their compiler (or library) to produce bytecode that's legal in terms of JLS?


(In reply to Timo Kinnunen from comment #19)
> (In reply to comment #17)
> > This gist contains the test file and transcripts of compilation with with JDT
> > 1.7/1.8 and Oracle javac:
> > 
> > https://gist.github.com/retronym/f5a54ddd30bced7e0285
> 
> I can fix this so that it compiles by introducing a self-type type parameter
> to both s_c_i_Map and s_c_Map and then rewriting their definitions in terms
> of those. I don't know if it's by design that immutable and mutable Map
> extend supertypes which take in a self-type parameter and not provide them
> an accurate one, but this seems problematic to me... Anyway, I don't know
> Scala well enough to say if this is where the problem might be, but here's
> the code that compiles, at least:
> 
> package org.test.util;
> 
> public class Test {
> 	public static <ImmutableMap extends s_c_i_Map<String, String,
> ImmutableMap>> String foo(ImmutableMap map) {
> 		return map.apply("");
> 	}
> }
> interface s_c_i_MapLike<A, B, This extends s_c_i_MapLike<A, B, This> &
> s_c_i_Map<A, B, This>> extends
> 	s_c_MapLike<A, B, This> {}
> interface s_c_MapLike<A, B, This extends s_c_MapLike<A, B, This> &
> s_c_Map<A, B, This>> {
> 	public B apply(A a);
> }
> interface s_c_i_Map<A, B, This extends s_c_i_Map<A, B, This>> extends
> s_c_Map<A, B, This>, s_c_i_MapLike<A, B, This> {}
> interface s_c_Map<A, B, This extends s_c_Map<A, B, This>> extends
> s_c_MapLike<A, B, This> {}

Are you saying, with this the problem is reproducible in pure Java?
Comment 26 Stephan Herrmann CLA 2017-05-16 12:05:09 EDT
Ran out of time for 4.7. Bulk move to 4.8.
Comment 27 Stephen CLA 2017-10-18 21:26:37 EDT
Here's some heavily simplified code of what I'm hitting (i.e. it doesn't achieve anything sensible).

It'd be nice to get confirmation that this example is for this bug. I think it is


import java.util.Optional;
import java.util.function.Function;

interface DifferentFunction<T, R> {
	R method(T o);
}

public interface ValidOverrides<T> {

	<R> T doFun(Function<Optional<T>, R> function);

	<R> T doFun(DifferentFunction<T, R> function);
}

class ValidCaller<T> {
	public final T t;

	public ValidCaller(final ValidOverrides<T> valid) {
		this.t = valid.doFun(Optional::get);
	}
}


Errors with "The method doFun(Function<Optional<T>,T>) is ambiguous for the type ValidOverrides<T>" (Oxygen 4.7.1a)


However extracting the local variable works:

	public ValidCaller(final ValidOverrides<T> valid) {
		Function<Optional<T>, T> function = Optional::get;
		this.t = valid.doFun(function);
	}

As you can see, the type of the local is the exact type that is in the error.
Comment 28 Stephan Herrmann CLA 2017-10-19 06:07:35 EDT
(In reply to Stephen from comment #27)
> Here's some heavily simplified code of what I'm hitting (i.e. it doesn't
> achieve anything sensible).

This example was accepted by ecj 4.5 but newer versions (4.6 and greater) report ambiguity.

Seeing that javac versions 8 and 9 both report the very same ambiguity I assume that this was a bug in ecj that was fixed for 4.6 (don't have the time to lookup the bug number right now).

=> No further action required regarding this example.
Comment 29 Stephen CLA 2017-10-19 20:16:00 EDT
(In reply to Stephan Herrmann from comment #28)
> (In reply to Stephen from comment #27)

> Seeing that javac versions 8 and 9 both report the very same ambiguity

I concur - sorry about that; I thought I had apparent clean build success from javac, but there must have been an incremental compile from IDE or build tool somewhere.
Comment 30 Stephan Herrmann CLA 2018-03-10 17:55:07 EST
Till today I haven't found a compelling strategy to accommodate the Scala type system in a Java compiler. Perhaps people with inside knowledge of Scala type checking can propose an algorithm to detect when method ambiguity is not "real" because of extra safety provided by Scala typing rules.

As for Java we have more pressing development on our plate moving forward.

I'm putting this bug report back into the general bucket.
Comment 31 Duch Bechlegor CLA 2018-05-25 09:28:16 EDT
I have posted a question to Scala Contributors list, but no one answers :(

https://contributors.scala-lang.org/t/eclipse-java-compiler-reports-error-when-compiling-to-java-8-the-method-xxx-is-ambiguous-for-the-type-yyy-help-needed/1899
Comment 32 Joseph Fourny CLA 2018-08-07 16:49:42 EDT
This is a general issue where generic type inference gets confused. I have come across several cases where Eclipse JDT, Oracle javac, and IBM javac all disagree with what is, or is not, ambiguous. The solution is to give hints to the compiler's type inference to avoid the ambiguity.

Here is a simple example of something that does not compile in Eclipse JDT:

scala.collection.mutable.Buffer<String> buf;
scala.collection.Seq<String> seq = buf.toSeq(); //<== Reports as ambiguous.

Since we know the toSeq() method is defined in scala.collection.SeqLike, I can cast to remove the ambiguity.

scala.collection.mutable.Buffer<String> buf;
scala.collection.Seq<String> seq = ((scala.collection.SeqLike<String, ?>) buf).toSeq(); //<== Compiles!

The trick is to cast your target to ONE of the conflicting interfaces, so exactly one signature is found. I know, technically speaking, these all have the same signature after type erasure, but compilers have to distinguish them, nonetheless.
Comment 33 Eclipse Genie CLA 2022-07-14 12:55:54 EDT
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet.

If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant.

--
The automated Eclipse Genie.
Comment 34 Damir Kero CLA 2022-07-22 06:54:28 EDT
This bug is still present.

I am running this environment:

Eclipse IDE for Enterprise Java and Web Developers (includes Incubating components)

Version: 2022-06 (4.24.0)
Build id: 20220609-1112

with java-openjdk-8u242-b08 JDK
Comment 35 Stephan Herrmann CLA 2022-08-16 08:50:08 EDT
(In reply to Damir Kero from comment #34)
> This bug is still present.

Thanks for re-checking.

For clarification I changed this report to "enhancement", because we haven't found any violation of JLS in the situation discussed here.

For implementing this as an enhancement specifically for Scala-interop we could use some more information from Scala-compiler devs, which we haven't gotten so far, have we?