m-m-m / util

Mature Modular Meta-Framework
http://m-m-m.sourceforge.net
Apache License 2.0
10 stars 5 forks source link

Collection of bugs with Oracle compiler and Eclipse compiler where one of each compiler fails #166

Closed hohwille closed 8 years ago

hohwille commented 8 years ago

With util-property we hit a bug in Oracle java compiler:

We have the interface BooleanProperty that is combining a generic method setValue(VALUE) with VALUE bound to Boolean with a typed method setValue(Boolean). Also BooleanProperty has a default method that is calling setValue with a Boolean argument. This is causing the following error in oracle java compiler (invoked via maven):

[ERROR] ...\mmm-util-property\src\main\java\net\sf\mmm\util\property\api\BooleanProperty.java:[34,4] error: reference to setValue is ambiguous

My environment:

> mvn -v
Apache Maven 3.2.5 (12a6b3acb947671f09b81f49094c53f426d8cea1; 2014-12-14T18:29:23+01:00)
Maven home: C:\Projekte\mmm\software\maven
Java version: 1.8.0_60, vendor: Oracle Corporation
Java home: C:\Projekte\mmm\software\java\jre
Default locale: en_US, platform encoding: Cp1252
OS name: "windows 7", version: "6.1", arch: "amd64", family: "dos"

The error does not occur with Eclipse java compiler and I am quite sure that it is actually a bug in Oracle java compiler.

hohwille commented 8 years ago

OK. I was correct. Here is the minimal and simple isolated code to reproduce the bug in oracle compiler:

public interface GenericInterface<V> {
  void setValue(V value);
}
public interface TypedInterface {
  void setValue(Boolean value);
}
public interface CombinedInterface extends GenericInterface<Boolean>, TypedInterface {
  default void set(boolean value) {
    setValue(Boolean.valueOf(value));
  }
}

Works in Eclipse but fails in Oracle java compiler:

CombinedInterface.java:[14,4] error: reference to setValue is ambiguous
hohwille commented 8 years ago

I filed a bug at http://bugreport.java.com. Also I added workaround and travis is now green. I will keep this open until I have feedback from oracle (e.g. after I added the link to a public visible bug entry).

hohwille commented 8 years ago
From: <Bug-Report-Daemon_WW@oracle.com>
Date: Mon, Jan 4, 2016 at 1:08 PM
Subject: Your Report (Review ID: JI-9028245) - reference is ambiguous with default method calling generic+typed method
To:*****

Dear Java Developer,

Thank you for reporting this issue.

We are evaluating this report and have assigned it a Review ID: JI-9028245. In the event this report is determined to be a defect or enhancement request, it will be referenced with a new Bug ID and will be listed on Bugs.java.com. For other related issues, please visit our Bug Database at http://bugs.java.com.

We try to process all newly posted bugs in a timely manner, but make no promises about the amount of time in which a bug might be fixed. If the issue just reported could have a major impact on your project, consider using one of the technical support offerings available at Oracle Support.

Regards,
Java Community Developer Support
hohwille commented 8 years ago

BTW: As I added validation builder support I am now hitting more bugs in Oracle java compiler related to generic type inference and bounds matching that do not arise with Eclipse java compiler. These are however rather complex to isolate. I am unsure if I will report them as well, but it seems there is some work left to do in Oracle java compiler... Why do I have to be the one to hit those bugs? Arent there millions of other developers using Java with generics or am I the only one doing really complicated stuff with generics and others just tend to give up type safty rather than nesting generics like this:

  protected <PROPERTY extends GenericPropertyImpl<? extends VALUE>, BUILDER extends ObjectValidatorBuilder<?, ? extends PropertyBuilder<? extends PROPERTY>, ?>> BUILDER withValdidator(
      Function<PropertyBuilder<PROPERTY>, BUILDER> factory) {

The error is:

[ERROR] ...\IntegerPropertyImpl.java:[65,25] error: incompatible types: inference variable BUILDER has incompatible upper bounds 
ObjectValidatorBuilder<?,? extends GenericPropertyImpl<Number>.PropertyBuilder<GenericPropertyImpl<? extends Number>>,?>,ValidatorBuilderInteger<GenericPropertyImpl<Number>.PropertyBuilder<IntegerPropertyImpl>>
[ERROR] where BUILDER,PROPERTY,VALUE are type-variables:
[ERROR] BUILDER extends ObjectValidatorBuilder<?,? extends GenericPropertyImpl<Number>.PropertyBuilder<PROPERTY>,?> declared in method <PROPERTY,BUILDER>withValdidator(Function<GenericPropertyImpl<VALUE>.PropertyBuilder<PROPERTY>,BUILDER>)
[ERROR] PROPERTY extends GenericPropertyImpl<? extends VALUE> declared in method <PROPERTY,BUILDER>withValdidator(Function<GenericPropertyImpl<VALUE>.PropertyBuilder<PROPERTY>,BUILDER>)
[ERROR] VALUE extends Object declared in class GenericPropertyImpl

Or

[ERROR] ...l\IntegerPropertyImpl.java:[65,25] error: incompatible types: inference variable PARENT has incompatible bounds
[ERROR] equality constraints: GenericPropertyImpl<Number>.PropertyBuilder<IntegerPropertyImpl>
[ERROR] lower bounds: GenericPropertyImpl<Number>.PropertyBuilder<GenericPropertyImpl<? extends Number>>
[ERROR] where PARENT is a type-variable:
[ERROR] PARENT extends Object declared in class ValidatorBuilderInteger
hohwille commented 8 years ago

I tracked down the second issue and it turned out that here Oracle java compiler is not really wrong:

ObjectValidatorBuilder<?, ? extends PropertyBuilder<? extends PROPERTY>, ?>>

actually needs to be

ObjectValidatorBuilder<?, PropertyBuilder<? extends PROPERTY>, ?>>

After fixing this it works with both compilers. So Oracle java compiler is not wrong in the second case but just more strict than Eclipse java compiler.

hohwille commented 8 years ago

I found further differences in Oracle vs. Eclipse compiler.

<V, PROPERTY extends ReadableProperty<V>> PropertyFactory<V, ? extends PROPERTY> getFactory(Class<PROPERTY> propertyType, Class<V> valueType, boolean polymorphic)

where:

public interface PropertyFactory<V, PROPERTY extends WritableProperty<V>> {

is called here:

protected <V, PROPERTY extends ReadableProperty<V>> AbstractProperty<V> createProperty(String name, GenericType<V> valueType, Bean bean, Class<PROPERTY> propertyClass) {

    Class<V> valueClass = null;
    if (valueType != null) {
      valueClass = valueType.getRetrievalClass();
    }
/*[410]*/    PropertyFactory<V, ?> factory = this.propertyFactoryManager.getFactory(propertyClass, valueClass, false);

This is accepted by Eclipse compiler but not by Oracle compiler which says:

[ERROR] ...\
BeanFactoryImpl.java:[410,74] error: incompatible types: 
inference variable PROPERTY#1 has incompatible bounds
[ERROR] equality constraints: PROPERTY#2
[ERROR] upper bounds: WritableProperty<V#1>,ReadableProperty<V#1>
[ERROR] where PROPERTY#1,V#1,PROPERTY#2,V#2 are type-variables:
[ERROR] PROPERTY#1 extends ReadableProperty<V#1> declared in method <V#1,PROPERTY#1>getFactory(Class<PROPERTY#1>,Class<V#1>,boolean)
[ERROR] V#1 extends Object declared in method <V#1,PROPERTY#1>getFactory(Class<PROPERTY#1>,Class<V#1>,boolean)
[ERROR] PROPERTY#2 extends ReadableProperty<V#2> declared in method <V#2,PROPERTY#2>createProperty(String,GenericType<V#2>,Bean,Class<PROPERTY#2>)
[ERROR] V#2 extends Object declared in method <V#2,PROPERTY#2>createProperty(String,GenericType<V#2>,Bean,Class<PROPERTY#2>)

So besides the initial issue that I reported as bug in Oracle tracker all the other things I found indicate that Oracle compiler is very strict and picky while Eclipse compiler is sometimes a little lax. It seems that Java has become so complicated that people can hardly tell what is actually correct when it comes to advanced type inference. I better do not wait for Eclipse or Oracle to react but simply use workarounds to make my code compile.

hohwille commented 8 years ago

I am closing as there is no issue in our project left. However there are issues in Oracle and Eclipse compilers left....

hohwille commented 8 years ago

And another issue that is obviously a bug in Oracle java compiler that does not occur with Eclipse java compiler: https://travis-ci.org/m-m-m/util/builds/127054037

[ERROR] /home/travis/build/m-m-m/util/mmm-util-query-orientdb/src/main/java/net/sf/mmm/orient/impl/property/SinglePropertyBuilderLinkList.java:[66,37] error: incomparable types: Class<CAP#1> and Class<ObservableList>
hohwille commented 8 years ago

After getting no response for my bug reports from Oracle, I wont even waste my time to create an isolated test-case for this bug (what should be easy to do for this obvious bug).

hohwille commented 6 years ago

Another candidate that works fine with Eclipse compiler but fails with oracle javac:

public class GenericTest<A, B extends A> {

  private final GenericTest<? super A, ? super B> parent;

  public GenericTest(GenericTest<? super A, ? super B> parent) {

    super();
    this.parent = parent;
  }

}

IMHO a wildcard (?) is as the name says a placeholder that is not a concrete type replacement. Therefore I can write GenericTest<?, ?> and whatever concrete types will be filled into these wildcards (?) have to comply that the second extends the first without any notion in this declaration.

For what reason should this be different to ? extends ... or ? super ...?

IMHO this is a bug in oracle javac.

hohwille commented 1 year ago

Another insane bug in Eclipse:

WritableCollectionProperty collectionProperty = (WritableCollectionProperty) property;
WritableProperty valueProperty = property.getValueProperty();

The above code gives me a compiler error: The method getValueProperty() is undefined for the type WritableProperty<capture#15-of ?> However the following cast lets the error go away in Eclipse:

WritableCollectionProperty collectionProperty = (WritableCollectionProperty) property;
WritableProperty valueProperty = ((WritableCollectionProperty) property).getValueProperty();

I finally loose my last trust in Java and Eclipse.

hohwille commented 10 months ago

New bug in javac:

Error was (saving as the github action logs will disappear after some time):

Error:  Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project mmm-property: Compilation failure: Compilation failure: 
Error:  /home/runner/work/property/property/core/src/main/java/io/github/mmm/property/factory/PropertyFactoryManager.java:[88,48] error: incompatible types: inference variable P#1 has incompatible bounds
Error:      equality constraints: P#2
Error:      lower bounds: WritableProperty<V#1>,ReadableProperty<V#1>
Error:    where P#1,V#1,P#2,V#2 are type-variables:
Error:      P#1 extends ReadableProperty<V#1> declared in method <V#1,P#1>getRequiredFactory(Class<P#1>,Class<V#1>)
Error:      V#1 extends Object declared in method <V#1,P#1>getRequiredFactory(Class<P#1>,Class<V#1>)
Error:      P#2 extends ReadableProperty<V#2> declared in method <V#2,P#2>create(Class<P#2>,Class<V#2>,String,PropertyMetadata<V#2>)
Error:      V#2 extends Object declared in method <V#2,P#2>create(Class<P#2>,Class<V#2>,String,PropertyMetadata<V#2>)
Error:  /home/runner/work/property/property/core/src/main/java/io/github/mmm/property/factory/PropertyFactoryManager.java:[118,48] error: incompatible types: inference variable P#1 has incompatible bounds
Error:      equality constraints: P#2
Error:      lower bounds: WritableProperty<V#1>,ReadableProperty<V#1>
Error:    where P#1,V#1,P#2,V#2 are type-variables:
Error:      P#1 extends ReadableProperty<V#1> declared in method <V#1,P#1>getRequiredFactory(Class<P#1>,Class<V#1>)
Error:      V#1 extends Object declared in method <V#1,P#1>getRequiredFactory(Class<P#1>,Class<V#1>)
Error:      P#2 extends ReadableProperty<V#2> declared in method <V#2,P#2>create(Class<P#2>,PropertyTypeInfo<V#2>,String,PropertyMetadata<V#2>)
Error:      V#2 extends Object declared in method <V#2,P#2>create(Class<P#2>,PropertyTypeInfo<V#2>,String,PropertyMetadata<V#2>)
Error:  /home/runner/work/property/property/core/src/main/java/io/github/mmm/property/factory/PropertyFactoryManager.java:[159,41] error: incompatible types: inference variable P#1 has incompatible bounds
Error:      equality constraints: P#2
Error:      lower bounds: WritableProperty<V#1>,ReadableProperty<V#1>
Error:    where P#1,V#1,P#2,V#2 are type-variables:
Error:      P#1 extends ReadableProperty<V#1> declared in method <V#1,P#1>getFactoryForPropertyType(Class<P#1>)
Error:      V#1 extends Object declared in method <V#1,P#1>getFactoryForPropertyType(Class<P#1>)
Error:      P#2 extends ReadableProperty<V#2> declared in method <V#2,P#2>getFactory(Class<P#2>,Class<V#2>)
Error:      V#2 extends Object declared in method <V#2,P#2>getFactory(Class<P#2>,Class<V#2>)
Error:  -> [Help 1]
We will review your report and have assigned it an internal review ID : 9076190. Depending upon the completeness of the report and our ability to reproduce the problem, either a new bug will be posted, or we will contact you for further information. 

~This time I learned from the past experience that bugreport.java.com is a big black hole and printed my bug report to a PDF file before I hit submit (filename JavacGenericBug.pdf just in case I forget this and need to remember in a year or so).~ Bug is visible here: https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8319461

I already responded that most likely they have tested master where the build obviously succeeds and one needs to do git checkout 54a637fbcac3fd06553e586e5c6a597e085cc6da before compiling the code.

hohwille commented 10 months ago

Tonight I have another jackpot. So here is the next javac bug:

Indeed the workaround was to rename my method from getClass(ReadableBean) to getJavaClass(ReadableBean). See the associated commits - this made the build green: https://github.com/m-m-m/entity/actions/runs/6751318771 I was not aware that getClass is a reserved keyword for javac - what a joke. So downgrade for my API but whatever it takes to make it work and proceed with my visions...

p.s. I am too tired to report this again officially via bugreport.java.com. My hope was always that people who really care about Java might find this issue interesting and would pro-actively work on resolving these generic issues but this seems to be a scattered dream...

hohwille commented 7 months ago

For the record - not javac but javadoc also has buggy differences between JDK and Eclipse: https://github.com/m-m-m/ui-api/blob/ec460770fc26eb6c47b4235596f6a4b8802cbf4c/menu/src/main/java/io/github/mmm/ui/api/widget/menu/UiAbstractMenuWithItems.java#L32

JavaDoc produces this warning:

[WARNING] Javadoc Warnings
[WARNING] D:\projects\mmm\workspaces\main\ui-api\menu\src\main\java\io\github\mmm\ui\api\widget\menu\UiAbstractMenuWithItems.java:32: warning: reference not found: #getChildIndex(UiAbstractMenuEntry)
[WARNING] * @param index is the {@link #getChildIndex(UiAbstractMenuEntry) index} where to insert the new {@link UiMenuItem}.
[WARNING] ^

However, Eclipse produces this reference syntax via auto-suggestion. If I manually change it to what JavaDoc seems to expect (#getChildIndex(UiWidget) ), I get this warning in Eclipse:

Javadoc: The method getChildIndex(UiAbstractMenuEntry) in the type UiComposite<UiAbstractMenuEntry> is not applicable for the arguments (UiWidget)

So who is right and who is wrong? For me the behavior of Eclipse makes sense and feels natural. Why should method references in JavaDoc differ from the references in the code? If I write this code inside the method body:

UiWidget widge = null;
getChildIndex(widget);

I get a compile error from javac because the method getChildIndex requires a parameter of type UiAbstractMenuEntry so why should I have to write #getChildIndex(UiWidget)? I would consider this a bug in the JavaDoc linter.

hohwille commented 7 months ago

I have isolated the bug https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8319461 into a single class:

public class JDK8319461 {
  public <V, P extends ReadableProperty<V>> P create(Class<P> propertyType, Class<V> valueClass, String name) {
    Factory factory = getFactory(propertyType, valueClass);
    return null;
  }
  public <V, P extends ReadableProperty<V>> Factory<V, ? extends P> getFactory(Class<P> propertyType, Class<V> valueType) {
    Factory<V, ? extends P> factory = null;
    return factory;
  }
  public interface ReadableProperty<V> {  }
  public interface WritableProperty<V> extends ReadableProperty<V> {  }
  public interface Factory<V, P extends WritableProperty<V>> {  }
}

I resent this to the oracle bug database so lets see if they still keep this bug closed without fixing anything. To me the behavior of javac makes no sense. If I can properly declare the method "getFactory" why should I be disallowed to actually call it then with the according signature?

stephan-herrmann commented 4 months ago

Another insane bug in Eclipse:

WritableCollectionProperty collectionProperty = (WritableCollectionProperty) property;
WritableProperty valueProperty = property.getValueProperty();

The above code gives me a compiler error: The method getValueProperty() is undefined for the type WritableProperty<capture#15-of ?> However the following cast lets the error go away in Eclipse:

WritableCollectionProperty collectionProperty = (WritableCollectionProperty) property;
WritableProperty valueProperty = ((WritableCollectionProperty) property).getValueProperty();

I finally loose my last trust in Java and Eclipse.

Could you isolate this into a standalone snippet?

stephan-herrmann commented 4 months ago

Tonight I have another jackpot. So here is the next javac bug:

  • https://github.com/m-m-m/entity/actions/runs/6751215450/job/18354915136
    Error:  /home/runner/work/entity/entity/bean/src/main/java/io/github/mmm/entity/bean/repository/AbstractEntityRepository.java:[40,35] error: method getClass in class Object cannot be applied to given types;
    required: no arguments
    found:    E
    reason: actual and formal argument lists differ in length
    where E is a type-variable:
    E extends EntityBean declared in class AbstractEntityRepository

From code:

public abstract class AbstractEntityRepository<E extends EntityBean> implements EntityRepository<E> {
...
  public AbstractEntityRepository(E prototype, IdGenerator idGenerator) {

    super();
    this.prototype = prototype;
    this.entityClass = ReadableBean.getClass(this.prototype);
                                            * this is where the error is reported

And the called method has this signature:

  static <B extends ReadableBean> Class<B> getClass(B bean) {

I constructed the following extract from your snippets:

interface ReadableBean {}
interface WritableBean extends ReadableBean {}
interface Entity {}
interface EntityBean extends WritableBean, Entity {}
interface EntityRepository <E> {}
public abstract class AbstractEntityRepository<E extends EntityBean> implements EntityRepository<E> {
  static <B extends ReadableBean> Class<B> getClass(B bean) { return null; }

  public AbstractEntityRepository(E prototype) {
    Class<?> entityClass = ReadableBean.getClass(prototype);
  }
}

To me it looks like ReadableBean.getClass(this.prototype); is a typo. In my reduced version saying getClass(this.prototype) compiles fine in all compilers I tested. Am I missing anything?

hohwille commented 4 months ago

To me it looks like ReadableBean.getClass(this.prototype); is a typo. In my reduced version saying getClass(this.prototype) compiles fine in all compilers I tested. Am I missing anything?

Yes, here you are missing something. This is indeed a bug in javac not in EJC and it was fixed with these commits:

If you want you can still follow the link I left pointing the the build from the github action and just by renaming the method from getClass to getJavaClass fixed the build. Before I got this from javac while EJC worked fine:

Error:  /home/runner/work/entity/entity/bean/src/main/java/io/github/mmm/entity/bean/repository/AbstractEntityRepository.java:[40,35] error: method getClass in class Object cannot be applied to given types;
  required: no arguments
  found:    E
  reason: actual and formal argument lists differ in length
  where E is a type-variable:
    E extends EntityBean declared in class AbstractEntityRepository

So there seems to be a build-in magic in javac for the Object.getClass() method that collides with custom getClass(...) methods. Thanks for investigating this issue deeper. IMHO from Eclipse PoV you are wasting your time if you also investigate problems I noted for javac but if you happen to have good connections to Oracle javac developers and want to make use of that, feel more than welcome to keep going :)

hohwille commented 4 months ago

Could you isolate this into a standalone snippet?

I will try it but will take me some weeks since that will require going back in the git history in multiple projects and investing several hours after I can get the bug somehow reproduced in Eclipse...

stephan-herrmann commented 4 months ago

To me it looks like ReadableBean.getClass(this.prototype); is a typo. In my reduced version saying getClass(this.prototype) compiles fine in all compilers I tested. Am I missing anything?

Yes, here you are missing something.

I was missing several things :smile: - first I missed that the method was declared in ReadableBean, indeed. As I placed it into AbstractEntityRepository (see above) I figured that prefixing the method call with ReadableBean. might be a typo on your side.

Now with that corrected it still compiles with all compilers I tried:

interface ReadableBean {
  static <B extends ReadableBean> Class<B> getClass(B bean) { return null; }
}
interface WritableBean extends ReadableBean {}
interface Entity {}
interface EntityBean extends WritableBean, Entity {}
interface EntityRepository <E> {}
public abstract class AbstractEntityRepository<E extends EntityBean> implements EntityRepository<E> {

  public AbstractEntityRepository(E prototype) {
    Class<?> entityClass = ReadableBean.getClass(prototype);
  }
}

So there most be more to it than you showed in the comment above.

In fact as long as overload resolution is involved, I wouldn't be sure about anything until I observed and analyzed the bug. There are indeed situations where overload resolution is forced to pick one method before even checking if that method is applicable (I recall one such situations where a static import was used, there may be others).

hohwille commented 2 months ago

I found another nice one in EJC: image

According to my understanding of the Java generic type system I can declare a generic type using only wildcards anytime as long as I provide exactly the correct number of wildcards (?) as the actual type has generic types declared. There is no compiler check required in that case (maybe only inference) but the declaration itself can then never be incorrect as otherwise already the type itself would have been declared incorrect. All I am saying with the wildcard (?) is that I do not care about the generic type. I would actually omit the wildcards in such case but for some odd reason the Java spec of generic types is messy and type inference will not happen in such case and I do get Object even for types that do have an upper bound other than Object.

stephan-herrmann commented 2 months ago

I found another nice one in EJC:

If you can provide a minimal reproducer feel free to report this against jdt.

hohwille commented 2 months ago

I found another nice one in EJC:

If you can provide a minimal reproducer feel free to report this against jdt.

I tried but due to magic the error also disappeared in its original place and I cannot reproduce it. So maybe some in-determinism or temporary issue but as it seems to real EJC bug I found...

stephan-herrmann commented 2 months ago

I tried but due to magic the error also disappeared in its original place and I cannot reproduce it.

The bug got scared of the prospect of meeting me :smiling_imp: