Community
Participate
Working Groups
The functionaljava project compiles successfully using gradle on the command-line, but the eclipse compiler reports many errors. It appears that type inference in Eclipse is not as good as the Oracle java compiler. To reproduce: 1. Make sure you are using a Java 8 JDK with compatibility set to Java 1.8 2. Clone the github repo at https://github.com/functionaljava/functionaljava 3. Run "gradlew eclipse" in the functionaljava folder to compile the project and generate eclipse .project files 4. Create a new eclipse workspace 5. "File > Import > Existing Projects", select the functionaljava folder. 6. Import all projects Observe that type inference fails in many places. I have tested this in Eclipse Luna and in a milestone build of Eclipse Mars and both fail to compile FunctionalJava. These are the errors I am seeing: <pre><code> Description Resource Path Location Type The method of(List.Buffer::new, List.Buffer::snoc, (<no type> acc1, <no type> acc2) -> {}, (<no type> buf) -> {}) is undefined for the type Collector Collectors.java /java8/src/main/java/fj/data line 20 Java Problem The method of(List.Buffer::new, List.Buffer::snoc, (<no type> acc1, <no type> acc2) -> {}, (<no type> buf) -> {}) is undefined for the type Collector Collectors.java /java8/src/main/java/fj/data line 29 Java Problem The method of(List.Buffer::new, List.Buffer::snoc, (<no type> acc1, <no type> acc2) -> {}, List.Buffer::toList) is undefined for the type Collector Collectors.java /java8/src/main/java/fj/data line 11 Java Problem The method parMap(Stream<Object>, F<Stream<A>,Stream<B>>) is undefined for the type ParModule ParModule.java /core/src/main/java/fj/control/parallel line 503 Java Problem The parameterized method <A>list(A...) of type List is not applicable for the arguments (Object[]) Java.java /core/src/main/java/fj/data line 1484 Java Problem Type mismatch: cannot convert from Either<L,Object> to Either<L,Option<B>> Option.java /core/src/main/java/fj/data line 420 Java Problem Type mismatch: cannot convert from IO<Object> to IO<Option<B>> Option.java /core/src/main/java/fj/data line 424 Java Problem Type mismatch: cannot convert from IO<Object> to IO<Validation<E,C>> Validation.java /core/src/main/java/fj/data line 839 Java Problem Type mismatch: cannot convert from List<Object> to List<Option<B>> Option.java /core/src/main/java/fj/data line 428 Java Problem Type mismatch: cannot convert from List<Object> to List<Validation<E,C>> Validation.java /core/src/main/java/fj/data line 821 Java Problem Type mismatch: cannot convert from Object to int ReaderTest.java /core/src/test/java/fj/data line 34 Java Problem Type mismatch: cannot convert from Object[] to A Java.java /core/src/main/java/fj/data line 1484 Java Problem Type mismatch: cannot convert from Option<Object> to Option<Validation<E,C>> Validation.java /core/src/main/java/fj/data line 833 Java Problem Type mismatch: cannot convert from P1<Object> to P1<Option<B>> Option.java /core/src/main/java/fj/data line 440 Java Problem Type mismatch: cannot convert from P1<Object> to P1<Validation<E,C>> Validation.java /core/src/main/java/fj/data line 845 Java Problem Type mismatch: cannot convert from Seq<Object> to Seq<Option<B>> Option.java /core/src/main/java/fj/data line 444 Java Problem Type mismatch: cannot convert from Stream<Object> to Stream<Option<B>> Option.java /core/src/main/java/fj/data line 436 Java Problem Type mismatch: cannot convert from Stream<Object> to Stream<Validation<E,C>> Validation.java /core/src/main/java/fj/data line 827 Java Problem Type mismatch: cannot convert from Validation<E,Object> to Validation<E,Option<B>> Option.java /core/src/main/java/fj/data line 457 Java Problem </pre></code>
When compiling, the compiler also throws this exception: java.lang.ArrayIndexOutOfBoundsException: 2 at org.eclipse.jdt.internal.compiler.lookup.InferenceContext18.checkExpression(InferenceContext18.java:751) at org.eclipse.jdt.internal.compiler.lookup.InferenceContext18.moreSpecificMain(InferenceContext18.java:706) at org.eclipse.jdt.internal.compiler.lookup.InferenceContext18.isMoreSpecificThan(InferenceContext18.java:654) at org.eclipse.jdt.internal.compiler.lookup.Scope.mostSpecificMethodBinding(Scope.java:4303) at org.eclipse.jdt.internal.compiler.lookup.Scope.findMethod0(Scope.java:1784) at org.eclipse.jdt.internal.compiler.lookup.Scope.findMethod(Scope.java:1525) at org.eclipse.jdt.internal.compiler.lookup.Scope.getImplicitMethod(Scope.java:2609) at org.eclipse.jdt.internal.compiler.ast.MessageSend.findMethodBinding(MessageSend.java:889) at org.eclipse.jdt.internal.compiler.ast.MessageSend.resolveType(MessageSend.java:704) at org.eclipse.jdt.internal.compiler.ast.MessageSend.resolveType(MessageSend.java:659) at org.eclipse.jdt.internal.compiler.ast.ReturnStatement.resolve(ReturnStatement.java:338) at org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.resolveStatements(AbstractMethodDeclaration.java:638) at org.eclipse.jdt.internal.compiler.ast.MethodDeclaration.resolveStatements(MethodDeclaration.java:307) at org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.resolve(AbstractMethodDeclaration.java:548) at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1188) at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1301) at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.resolve(CompilationUnitDeclaration.java:590) at org.eclipse.jdt.internal.compiler.Compiler.process(Compiler.java:803) at org.eclipse.jdt.internal.compiler.ProcessTaskManager.run(ProcessTaskManager.java:141) at java.lang.Thread.run(Thread.java:745)
I reproduced this in 4.4.2 on Linux with pure Java: * the InterfaceContext18.java line number changed to 792 but is otherwise the same stack as the original posting * Key phrases: InferenceContext18.checkExpression, java 8, generics, overloading, different argument counts * in this case, BiPredicate takes 2 params while Function takes one (I'm guessing that as the root cause, since the only under-guarded array access in checkExpression is comparing types 'u[i]' and 'v[i]') Sample: class Ice { static <T> BiPredicate<T, T> create(BiPredicate<? super T, ? super T> fn) { return null; } static <T> BiPredicate<T, T> create(Function<? super T, ? extends K> map) { return null; } void method() { BiPredicate<String, String> eq = String::equalsIgnoreCase; // these all compile: BiPredicate<String, String> ok1 = create( eq ); BiPredicate<String, String> ok2 = create( (a, b) -> true ); BiPredicate<String, String> ok3 = create( String::valueOf ); // this causes an internal compiler error, ArrayIndexOutOfBoundsException: 1 BiPredicate<String, String> fail = create( String::equalsIgnoreCase ); } }
Modified example that does produce the exception on master class Ice { static <T> BiPredicate<T, T> create(BiPredicate<? super T, ? super T> fn) { return null; } static <T> BiPredicate<T, T> create(Function<? super T, ? extends K> map) { return null; } void someMethod(BiPredicate<String, String> b) { // do nothing } void method() { // this causes an internal compiler error, ArrayIndexOutOfBoundsException: 1 someMethod(create( String::equalsIgnoreCase )); } }
The exception occurs because of String::equalsIgnoreCase being an exact method reference. Finding that both the create methods are potentially applicable, we try to find the most specific among them. ReferenceExpression.resolveExpressionExpecting() return 'this' as the resolved expression for exact method references based on arity, but in this case, the reference expression is not really compatible with create(Function<? super T, ? extends K> map) because the compile time declaration has arity n and is not static. Adding appropriate checks for static vs non-static methods fixes the exception. @Stephan, this is what I tried and it works. Does this look correct? diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java index eee548a..912e1bc 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java @@ -844,6 +844,10 @@ return null; int n = functionType.parameters.length; int k = this.exactMethodBinding.parameters.length; + + if (!this.haveReceiver && this.isMethodReference()) { + return (this.exactMethodBinding.isStatic() ? ((n == k) ? this : null) : ((n == k+1) ? this : null)); + } return (n == k || n == k + 1) ? this : null; } // descriptors parameters should be free of inference variables.
Created attachment 255445 [details] Proposed change Proposed change as per comment 4. @stephan, please take a look. If you agree with the analysis, I will prepare a gerrit change. All java8 tests pass with this.
The direction looks good, yet a few questions back: - Is it relevant for the example from comment 3 that 'K' is unresolved? - IMHO, the nested ternary is a bit hard to read (at least I'd like some line breaks / indentation, or: an outer if with inner ternaries ...) - with the suggested addition there's some overlap with the original ternary, do we still need to check the k+1 case? What do you think of this simpler addition instead: if (!this.haveReceiver && !this.exactMethodBinding.isStatic() && isMethodReference()) k++; This is meant to directly encode: when a non-static method reference has no receiver, we must have one more parameters. I haven't run all tests, but it seems more precise to me. BTW: I did some history browsing for this little paragraph, the closest I got to an explanation was bug 437444 comment 150, which doesn't motivate why both options are equally and unconditionally checked.
(In reply to comment #6) > The direction looks good, yet a few questions back: > > - Is it relevant for the example from comment 3 that 'K' is unresolved? > oops, mistake copying and modifying the example. The one I used declared K as a type variable of the method but I missed it here. > - IMHO, the nested ternary is a bit hard to read (at least I'd like some line > breaks / indentation, or: an outer if with inner ternaries ...) > sorry, I will remember that for next time > - with the suggested addition there's some overlap with the original ternary, do > we still need to check the k+1 case? > > What do you think of this simpler addition instead: > > if (!this.haveReceiver && !this.exactMethodBinding.isStatic() && > isMethodReference()) > k++; > I tried to encode the potential compatibility condition as is, but yes this is much simpler and more to the point. I will take it. With this, we shouldn't check the n=k+1 case because that is allowed only for a non-static method > All java8 tests pass. Gerrit change which includes test to follow. Thanks Stephan
New Gerrit change created: https://git.eclipse.org/r/52924
Gerrit change https://git.eclipse.org/r/52924 was merged to [master]. Commit: http://git.eclipse.org/c/jdt/eclipse.jdt.core.git/commit/?id=94200ed39d00137579089415688a6cedf88ddec1
resolved.
Verified for 4.6 M1 with build I20150804-2000.
*** Bug 480649 has been marked as a duplicate of this bug. ***
Duplicates start coming in, fix looks straight-forward: candidate for 4.5.2?
*** Bug 469587 has been marked as a duplicate of this bug. ***
(In reply to Stephan Herrmann from comment #13) > Duplicates start coming in, fix looks straight-forward: candidate for 4.5.2? Sure, Stephan. Let's put this in 4.5.2.
New Gerrit change created: https://git.eclipse.org/r/63606
Released to 4.5.2, resolving
Verified for 4.5.2 with build M20160113-1000.
*** Bug 500883 has been marked as a duplicate of this bug. ***