Bug 461004 - Multiple spurious errors compiling FunctionalJava project
Summary: Multiple spurious errors compiling FunctionalJava project
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 4.4.1   Edit
Hardware: PC Windows 8
: P3 normal with 2 votes (vote)
Target Milestone: 4.5.2   Edit
Assignee: Sasikanth Bharadwaj CLA
QA Contact:
URL:
Whiteboard:
Keywords:
: 469587 480649 500883 (view as bug list)
Depends on:
Blocks:
 
Reported: 2015-02-26 18:03 EST by Dobes Vandermeer CLA
Modified: 2016-09-05 16:54 EDT (History)
9 users (show)

See Also:
stephan.herrmann: review+


Attachments
Proposed change (864 bytes, patch)
2015-07-27 04:05 EDT, Sasikanth Bharadwaj CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Dobes Vandermeer CLA 2015-02-26 18:03:30 EST
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>
Comment 1 Jay Arthanareeswaran CLA 2015-02-27 07:18:45 EST
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)
Comment 2 Tim W CLA 2015-03-04 17:57:01 EST
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 );
  }
}
Comment 3 Sasikanth Bharadwaj CLA 2015-07-22 04:04:32 EDT
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 ));
  }
}
Comment 4 Sasikanth Bharadwaj CLA 2015-07-22 04:13:47 EDT
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.
Comment 5 Sasikanth Bharadwaj CLA 2015-07-27 04:05:44 EDT
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.
Comment 6 Stephan Herrmann CLA 2015-07-28 14:02:37 EDT
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.
Comment 7 Sasikanth Bharadwaj CLA 2015-07-29 05:57:34 EDT
(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
Comment 8 Eclipse Genie CLA 2015-07-31 01:50:26 EDT
New Gerrit change created: https://git.eclipse.org/r/52924
Comment 10 Sasikanth Bharadwaj CLA 2015-07-31 05:38:29 EDT
resolved.
Comment 11 Jay Arthanareeswaran CLA 2015-08-05 01:47:14 EDT
Verified for 4.6 M1 with build I20150804-2000.
Comment 12 Stephan Herrmann CLA 2015-10-26 17:54:14 EDT
*** Bug 480649 has been marked as a duplicate of this bug. ***
Comment 13 Stephan Herrmann CLA 2015-10-26 17:55:32 EDT
Duplicates start coming in, fix looks straight-forward: candidate for 4.5.2?
Comment 14 Stephan Herrmann CLA 2015-10-26 17:59:50 EDT
*** Bug 469587 has been marked as a duplicate of this bug. ***
Comment 15 Jay Arthanareeswaran CLA 2015-10-27 00:48:45 EDT
(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.
Comment 16 Eclipse Genie CLA 2016-01-06 01:09:08 EST
New Gerrit change created: https://git.eclipse.org/r/63606
Comment 17 Sasikanth Bharadwaj CLA 2016-01-06 03:38:26 EST
Released to 4.5.2, resolving
Comment 18 Jay Arthanareeswaran CLA 2016-01-18 21:18:21 EST
Verified for 4.5.2 with build M20160113-1000.
Comment 19 Stephan Herrmann CLA 2016-09-05 16:54:23 EDT
*** Bug 500883 has been marked as a duplicate of this bug. ***