Skip to content
This repository has been archived by the owner on Sep 28, 2022. It is now read-only.

Issues when using closures or adapters for functional interfaces or SAM having default methods or method defined in Object #491

Open
utybo opened this issue Jul 6, 2017 · 6 comments
Labels

Comments

@utybo
Copy link

utybo commented Jul 6, 2017

The Java interface java.util.Comparator has, for some reason, the equals method as an abstract method. Even though it is implemented by java.lang.Object for every object, this seems to confuse Golo which spits out a There is no implementation or override for: public abstract boolean java.util.Comparator.equals(java.lang.Object) when trying to create an adapter. This also makes Golo confused when only using a closure instead of an adapter (i.e using a closure as a functional interface) where a Comparator is expected.

Error stack :

Exception in thread "AWT-EventQueue-0" org.eclipse.golo.runtime.adapters.AdapterDefinitionProblem: There is no implementation or override for: public abstract boolean java.util.Comparator.equals(java.lang.Object)
	at org.eclipse.golo.runtime.adapters.AdapterDefinition.checkMethodsToBeImplemented(AdapterDefinition.java:181)
	at org.eclipse.golo.runtime.adapters.AdapterDefinition.validate(AdapterDefinition.java:91)
	at gololang.AdapterFabric.maker(AdapterFabric.java:158)
	at java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:627)
	at org.eclipse.golo.runtime.MethodInvocationSupport.fallback(MethodInvocationSupport.java:282)
	at gololang.Adapters$gololang$Adapters$types$adapter.maker(adapters.golo:106)
	at java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:627)
	at org.eclipse.golo.runtime.MethodInvocationSupport.fallback(MethodInvocationSupport.java:282)
	at gololang.Adapters$gololang$Adapters$types$adapter.newInstance(adapters.golo:129)
	at java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:627)
	at org.eclipse.golo.runtime.MethodInvocationSupport.fallback(MethodInvocationSupport.java:282)

Code :

Adapter(): interfaces(["java.util.Comparator"])
    : implements("compare", |this,e1,e2| {
      # Implementation here
    }): newInstance()
@yloiseau
Copy link
Contributor

yloiseau commented Jul 6, 2017

require(
  1 == java.util.Collections.min(
    list[5, 1, 2, 3],
    asInterfaceInstance(
      java.util.Comparator.class,
      |a, b| -> Integer.compare(a, b))),
  "err")

works as expected...

@utybo
Copy link
Author

utybo commented Jul 6, 2017

Huh, I forgot about that method... I expected it to work by just putting a closure but it seems Comparator doesn't count as a functional interface. Adapters still yield an error though I wonder if it's relevant since there is this asInterfaceInstance function

Sorry for my ignorance >_<

@yloiseau
Copy link
Contributor

yloiseau commented Jul 6, 2017

It should be automatically converted, but we have a bug in identifying SAM since 1.8, since now interfaces can have default methods. The asInterfaceInstance is a way to force the conversion explicitly.

Although in this particular case, the adapter is not necessary, I think the found behavior should be investigated, since it can be the sign of an underlying bug.

@yloiseau
Copy link
Contributor

yloiseau commented Jul 6, 2017

Moreover, Comparator is a functional interface, so it should work. Indeed,

require(
  1 == java.util.Collections.min(
    list[5, 1, 2, 3],
    asFunctionalInterface(
      java.util.Comparator.class,
      |a, b| -> Integer.compare(a, b))),
  "err")

fails (since it only creates an adapter under the hood), as well as

require(
  1 == java.util.Collections.min(
    list[5, 1, 2, 3],
    |a, b| -> Integer.compare(a, b)),
  "err")

failing to wrap the closure in a FI using a LambdaMetafactory

@yloiseau yloiseau added the bug label Jul 6, 2017
@yloiseau yloiseau changed the title "There is no implementation or override" for methods implemented in java.lang.Object Issues when using closures or adapters for functional interfaces or SAM having default methods or method defined in Object Jul 6, 2017
@yloiseau
Copy link
Contributor

yloiseau commented Jul 6, 2017

I actually have 2 old branches try to fix this aspect (https://github.com/yloiseau/golo-lang/tree/improvement/java-function-integration and https://github.com/yloiseau/golo-lang/tree/wip/java-function-integration) if someone wants to have a look 😄

@yloiseau
Copy link
Contributor

yloiseau commented Jul 6, 2017

Here are some minimal tests:

@FunctionalInterface
public interface FI {
  int get();
}
@FunctionalInterface
public interface FiWithDefault {
  int get();

  default int apply() {
    return get() + 1;
  }
}

@FunctionalInterface
public interface FiWithEquals {
  int get();
  boolean equals(Object other);
}

public interface SAM {
  int get();
}
public interface SamWithDefault {
  int get();

  default int apply() {
    return get() + 1;
  }
}
public interface SamWithEquals {
  int get();
  boolean equals(Object other);
}

public final class Utils {

  public static int useSam(SAM f) {
    return f.get();
  }

  public static int useFi(FI f) {
    return f.get();
  }

  public static int useSamWithDefault(SamWithDefault f) {
    return f.get();
  }

  public static int useFiWithDefault(FiWithDefault f) {
    return f.get();
  }

  public static int useSamWithDefaultUseDefault(SamWithDefault f) {
    return f.apply();
  }

  public static int useFiWithDefaultUseDefault(FiWithDefault f) {
    return f.apply();
  }

  public static int useSamWithEquals(SamWithEquals f) {
    return f.get();
  }

  public static int useFiWithEquals(FiWithEquals f) {
    return f.get();
  }
}

test.golo:

module Test

import gololang.AnsiCodes
import gololang.Adapters

function title = |t| {
  println("")
  bold()
  fg_magenta()
  println(t)
  reset()
}

function test = |d, e, f| {
  print("")
  fg_blue()
  print(d)
  reset()
  print(": [")
  try {
    let r = f()
    require(r == e, "expected %s, got %s": format(e, r))
    fg_green()
    print("OK")
    reset()
    println("]")
  } catch (e) {
    if (e oftype AssertionError.class) {
      fg_yellow()
      print("FAILED")
      reset()
      println("]")
      fg_cyan()
      println("  " + e: message())
      reset()
    } else {
      fg_red()
      print("ERROR")
      reset()
      println("]")
      fg_cyan()
      println("  " + e)
      reset()
    }
  }
}

function main = |args| {
  title("Pure SAM and FI")
  test("sam with closure", 42,
    -> Utils.useSam(-> 42))
  test("fi with closure", 42,
    -> Utils.useFi(-> 42))
  test("sam asInterfaceInstance", 42,
    -> Utils.useSam(asInterfaceInstance(SAM.class, -> 42)))
  test("fi asFunctionalInterface", 42,
    -> Utils.useFi(asFunctionalInterface(FI.class, -> 42)))

  title("SAM and FI with default method (use abstract)")
  # fails since SamWithDefault is not identified as a SAM due to the default
  # method (see o.e.g.runtime.TypeMatching.isSam)
  test("sam with closure", 42,
    -> Utils.useSamWithDefault(-> 42))
  test("fi with closure", 42,
    -> Utils.useFiWithDefault(-> 42))
  test("sam asInterfaceInstance", 42,
    -> Utils.useSamWithDefault(asInterfaceInstance(SamWithDefault.class, -> 42)))
  test("fi asFunctionalInterface", 42,
    -> Utils.useFiWithDefault(asFunctionalInterface(FiWithDefault.class, -> 42)))

  title("SAM and FI with default method (use default)")
  # fails since SamWithDefault is not identified as a SAM due to the default
  # method (see o.e.g.runtime.TypeMatching.isSam)
  test("sam with closure", 42,
    -> Utils.useSamWithDefaultUseDefault(-> 41))
  test("fi with closure", 42,
    -> Utils.useFiWithDefaultUseDefault(-> 41))

  # fails: the MethodHandleProxies does not define nor use the default interface method.
  test("sam asInterfaceInstance", 42,
    -> Utils.useSamWithDefaultUseDefault(asInterfaceInstance(SamWithDefault.class, -> 41)))

  # fails: the adapter does not define nor use the default interface method.
  test("fi asFunctionalInterface", 42,
    -> Utils.useFiWithDefaultUseDefault(asFunctionalInterface(FiWithDefault.class, -> 41)))
  test("sam with adapter (manual delegate)", 42,
    -> Utils.useSamWithDefault(Adapter(): interfaces(["SamWithDefault"])
        : implements("get", |this| -> 42)
        : implements("apply", |this| -> ^SamWithDefault::apply(this))
      : newInstance()))
  test("fi with adapter (manual delegate)", 42,
    -> Utils.useFiWithDefault(Adapter(): interfaces(["FiWithDefault"])
        : implements("get", |this| -> 42)
        : implements("apply", |this| -> ^FiWithDefault::apply(this))
      : newInstance()))


  title("SAM and FI with equals")
  # fails since SamWithEquals is not identified as a SAM due to the abstract
  # equals method, even if inherited from Object (see o.e.g.runtime.TypeMatching.isSam)
  test("sam with closure", 42,
    -> Utils.useSamWithEquals(-> 42))
  test("fi with closure", 42,
    -> Utils.useFiWithEquals(-> 42))
  test("sam asInterfaceInstance", 42,
    -> Utils.useSamWithEquals(asInterfaceInstance(SamWithEquals.class, -> 42)))

  # fails: the adapter does not define nor use the inherited equals method.
  test("fi asFunctionalInterface", 42,
    -> Utils.useFiWithEquals(asFunctionalInterface(FiWithEquals.class, -> 42)))

  # fails: the adapter does not accept the equals overriding. Why ?
  # probably a conflict with the one inherited from Object
  test("sam with adapter (override)", 42,
    -> Utils.useSamWithEquals(Adapter(): interfaces(["SamWithEquals"])
        : implements("get", |this| -> 42)
        : overrides("equals", |super, this, other| -> this == other)
      : newInstance()))

  # fails: the adapter does not accept the equals implementation. Why ?
  # probably a conflict with the one inherited from Object
  test("sam with adapter (implements)", 42,
    -> Utils.useSamWithEquals(Adapter(): interfaces(["SamWithEquals"])
        : implements("get", |this| -> 42)
        : implements("equals", |this, other| -> this == other)
      : newInstance()))
}

Makefile:

CLASSES := $(addsuffix .class, $(basename $(wildcard *.java)))

.PHONY: test
test: $(CLASSES)
	@golo golo --classpath . --files test.golo

%.class: %.java
	@javac -d . $<

clean: 
	@rm -f *.class

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants