Merge "Fix coverage filters" into ub-jack
diff --git a/jack-tests/prebuilts/core-stubs-mini.jack b/jack-tests/prebuilts/core-stubs-mini.jack
index 07a520a..d1d6faa 100644
--- a/jack-tests/prebuilts/core-stubs-mini.jack
+++ b/jack-tests/prebuilts/core-stubs-mini.jack
Binary files differ
diff --git a/jack-tests/prebuilts/core-stubs-mini.jar b/jack-tests/prebuilts/core-stubs-mini.jar
index c18c4d0..91452bb 100644
--- a/jack-tests/prebuilts/core-stubs-mini.jar
+++ b/jack-tests/prebuilts/core-stubs-mini.jar
Binary files differ
diff --git a/jack-tests/prebuilts/junit4-lib.jack b/jack-tests/prebuilts/junit4-lib.jack
index a381784..4af150f 100644
--- a/jack-tests/prebuilts/junit4-lib.jack
+++ b/jack-tests/prebuilts/junit4-lib.jack
Binary files differ
diff --git a/jack-tests/tests/com/android/jack/java8/EcjLambdaTest.java b/jack-tests/tests/com/android/jack/java8/EcjLambdaTest.java
index 3641835..963c665 100644
--- a/jack-tests/tests/com/android/jack/java8/EcjLambdaTest.java
+++ b/jack-tests/tests/com/android/jack/java8/EcjLambdaTest.java
@@ -60,8 +60,7 @@
           @Override
           public boolean shouldRun(Description description) {
             if (testWithApiUsage.contains(description.getMethodName())
-                || testWithOtherErrorMsg.contains(description.getMethodName())
-                || knownBugs.contains(description.getMethodName())) {
+                || testWithOtherErrorMsg.contains(description.getMethodName())) {
               return false;
             }
             return true;
@@ -78,9 +77,6 @@
     }
   }
 
-  private static final List<String> knownBugs =
-      Arrays.asList("test430035c", "test430035d", "test430035e", "test428552", "test027");
-
   @Nonnull
   private static final List<String> testWithOtherErrorMsg =
       Arrays.asList("test015", "test045", "test056", "test425512", "test430766", "test430766a",
diff --git a/jack-tests/tests/com/android/jack/java8/LambdaTest.java b/jack-tests/tests/com/android/jack/java8/LambdaTest.java
index 1c03f14..2700d71 100644
--- a/jack-tests/tests/com/android/jack/java8/LambdaTest.java
+++ b/jack-tests/tests/com/android/jack/java8/LambdaTest.java
@@ -18,7 +18,6 @@
 
 import com.android.jack.test.helper.FileChecker;
 import com.android.jack.test.helper.RuntimeTestHelper;
-import com.android.jack.test.junit.KnownIssue;
 import com.android.jack.test.runtime.RuntimeTestInfo;
 import com.android.jack.test.toolchain.AbstractTestTools;
 import com.android.jack.test.toolchain.JackApiV01;
@@ -180,6 +179,19 @@
       AbstractTestTools.getTestRootDir("com.android.jack.java8.lambda.test032"),
       "com.android.jack.java8.lambda.test032.jack.Tests");
 
+  private RuntimeTestInfo LAMBDA033 = new RuntimeTestInfo(
+      AbstractTestTools.getTestRootDir("com.android.jack.java8.lambda.test033"),
+      "com.android.jack.java8.lambda.test033.jack.Tests")
+          .addProguardFlagsFileName("proguard.flags");
+
+  private RuntimeTestInfo LAMBDA034 = new RuntimeTestInfo(
+      AbstractTestTools.getTestRootDir("com.android.jack.java8.lambda.test034"),
+      "com.android.jack.java8.lambda.test034.jack.Tests");
+
+  private RuntimeTestInfo LAMBDA035 = new RuntimeTestInfo(
+      AbstractTestTools.getTestRootDir("com.android.jack.java8.lambda.test035"),
+      "com.android.jack.java8.lambda.test035.jack.Tests");
+
   @Test
   public void testLamba001() throws Exception {
     run(LAMBDA001);
@@ -374,11 +386,25 @@
   }
 
   @Test
-  @KnownIssue
   public void testLamba032() throws Exception {
     run(LAMBDA032);
   }
 
+  @Test
+  public void testLamba033() throws Exception {
+    run(LAMBDA033);
+  }
+
+  @Test
+  public void testLamba034() throws Exception {
+    run(LAMBDA034);
+  }
+
+  @Test
+  public void testLamba035() throws Exception {
+    run(LAMBDA035);
+  }
+
   private void run(@Nonnull RuntimeTestInfo rti) throws Exception {
     new RuntimeTestHelper(rti)
         .setSourceLevel(SourceLevel.JAVA_8)
diff --git a/jack-tests/tests/com/android/jack/java8/lambda/test033/jack/I.java b/jack-tests/tests/com/android/jack/java8/lambda/test033/jack/I.java
new file mode 100644
index 0000000..b37924d
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/java8/lambda/test033/jack/I.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.jack.java8.lambda.test033.jack;
+
+interface C<T> {
+  boolean check(T t);
+}
\ No newline at end of file
diff --git a/jack-tests/tests/com/android/jack/java8/lambda/test033/jack/Lambda.java b/jack-tests/tests/com/android/jack/java8/lambda/test033/jack/Lambda.java
new file mode 100644
index 0000000..74d413b
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/java8/lambda/test033/jack/Lambda.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.jack.java8.lambda.test033.jack;
+
+/**
+ * Lambda with obfuscation.
+ */
+public class Lambda {
+
+
+  public boolean testGet10(String s) {
+    C<String> c = (t) -> t.isEmpty();
+    return c.check(s);
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/java8/lambda/test033/jack/Tests.java b/jack-tests/tests/com/android/jack/java8/lambda/test033/jack/Tests.java
new file mode 100644
index 0000000..2f8ea55
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/java8/lambda/test033/jack/Tests.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.jack.java8.lambda.test033.jack;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Lambda with obfuscation to check that MethodId is well managed
+ */
+public class Tests {
+
+  @Test
+  public void test001() {
+    Assert.assertEquals(true, new Lambda().testGet10(""));
+    Assert.assertEquals(false, new Lambda().testGet10("notEmpty"));
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/java8/lambda/test033/mapping.flags b/jack-tests/tests/com/android/jack/java8/lambda/test033/mapping.flags
new file mode 100644
index 0000000..10efa52
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/java8/lambda/test033/mapping.flags
@@ -0,0 +1,2 @@
+com.android.jack.java8.lambda.test033.jack.C -> com.android.jack.java8.lambda.test033.jack.Z:
+boolean check(java.lang.Object) -> renamedGetValue
\ No newline at end of file
diff --git a/jack-tests/tests/com/android/jack/java8/lambda/test033/proguard.flags b/jack-tests/tests/com/android/jack/java8/lambda/test033/proguard.flags
new file mode 100644
index 0000000..1a40ce3
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/java8/lambda/test033/proguard.flags
@@ -0,0 +1,6 @@
+-applymapping mapping.flags
+
+-keepattributes
+-keep class * {
+  *;
+}
\ No newline at end of file
diff --git a/jack-tests/tests/com/android/jack/java8/lambda/test034/jack/Tests.java b/jack-tests/tests/com/android/jack/java8/lambda/test034/jack/Tests.java
new file mode 100644
index 0000000..9c8360c
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/java8/lambda/test034/jack/Tests.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.jack.java8.lambda.test034.jack;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
+
+/**
+ * Lambda with reflect API
+ */
+public class Tests {
+
+  @Test
+  public void test001() throws Exception {
+    Callable<String> c = () -> "Hello";
+    Assert.assertEquals(1, c.getClass().getDeclaredMethods().length);
+    Assert.assertEquals(Object.class, c.getClass().getMethod("call").getReturnType());
+  }
+
+  interface Condition<T> {
+    boolean check(T arg);
+  }
+
+  @Test
+  public void test002() throws Exception {
+    Condition<String> c = String::isEmpty;
+    Assert.assertEquals(1, c.getClass().getDeclaredMethods().length);
+    Method m = c.getClass().getMethod("check", Object.class);
+    Assert.assertEquals(boolean.class, m.getReturnType());
+    Assert.assertFalse(m.isSynthetic());
+    Assert.assertFalse(m.isBridge());
+  }
+
+  @Test
+  public void test003() throws Exception {
+    Runnable r = () -> System.out.println("Hello");
+    Method m = r.getClass().getMethod("run");
+    Assert.assertSame(r.getClass(), m.getDeclaringClass());
+    Assert.assertFalse(m.isSynthetic());
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/java8/lambda/test035/jack/Tests.java b/jack-tests/tests/com/android/jack/java8/lambda/test035/jack/Tests.java
new file mode 100644
index 0000000..a33452d
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/java8/lambda/test035/jack/Tests.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.jack.java8.lambda.test035.jack;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * Lambda with obfuscation to check that MethodId is well managed
+ */
+public class Tests {
+
+  private static final String MSG = "Hello World";
+
+  private static String staticMethod() {
+    return MSG;
+  }
+
+  private interface MarkerInterface {
+  }
+
+  interface Condition<T> {
+    boolean check(T arg);
+  }
+
+  @Test
+  public void nonCapturingLambda() throws Exception {
+    Callable<String> r1 = () -> MSG;
+    Assert.assertNotNull(r1.getClass().getName());
+    assertGeneralLambdaClassCharacteristics(r1);
+    assertLambdaInterfaces(r1, Callable.class);
+    assertLambdaMethodCharacteristics(r1, Callable.class);
+    assertNonSerializableLambdaCharacteristics(r1);
+
+    assertCallableBehavior(r1, MSG);
+
+    Callable r2 = () -> MSG;
+    Assert.assertNotNull(r2.getClass().getName());
+    assertMultipleInstanceCharacteristics(r1, r2);
+  }
+
+  @Test
+  public void testInstanceMethodReferenceLambda() throws Exception {
+    Condition<String> c = String::isEmpty;
+    Class<?> lambdaClass = c.getClass();
+    Assert.assertNotNull(lambdaClass.getName());
+    assertGeneralLambdaClassCharacteristics(c);
+    assertLambdaInterfaces(c, Condition.class);
+    assertLambdaMethodCharacteristics(c, Condition.class);
+    assertNonSerializableLambdaCharacteristics(c);
+
+    Assert.assertTrue(c.check(""));
+    Assert.assertFalse(c.check("notEmpty"));
+
+    Method implCallMethod =
+        lambdaClass.getMethod("check", Object.class /* type erasure => not String.class */);
+    Assert.assertTrue((Boolean) implCallMethod.invoke(c, ""));
+    Assert.assertFalse((Boolean) implCallMethod.invoke(c, "notEmpty"));
+
+    Method interfaceCallMethod = Condition.class.getDeclaredMethod("check",
+        Object.class /* type erasure => not String.class */);
+    Assert.assertTrue((Boolean) interfaceCallMethod.invoke(c, ""));
+    Assert.assertFalse((Boolean) interfaceCallMethod.invoke(c, "notEmpty"));
+  }
+
+  @Test
+  public void testStaticMethodReferenceLambda() throws Exception {
+    Callable<String> r1 = Tests::staticMethod;
+    Assert.assertNotNull(r1.getClass().getName());
+    assertGeneralLambdaClassCharacteristics(r1);
+    assertLambdaInterfaces(r1, Callable.class);
+    assertLambdaMethodCharacteristics(r1, Callable.class);
+    assertNonSerializableLambdaCharacteristics(r1);
+
+    assertCallableBehavior(r1, MSG);
+
+    Callable<String> r2 = Tests::staticMethod;
+    Assert.assertNotNull(r2.getClass().getName());
+    assertMultipleInstanceCharacteristics(r1, r2);
+  }
+
+  @Test
+  public void testObjectMethodReferenceLambda() throws Exception {
+    StringBuilder o = new StringBuilder(MSG);
+    Callable<String> r1 = o::toString;
+    Assert.assertNotNull(r1.getClass().getName());
+    assertGeneralLambdaClassCharacteristics(r1);
+    assertLambdaInterfaces(r1, Callable.class);
+    assertLambdaMethodCharacteristics(r1, Callable.class);
+    assertNonSerializableLambdaCharacteristics(r1);
+
+    assertCallableBehavior(r1, MSG);
+
+    Callable<String> r2 = o::toString;
+    Assert.assertNotNull(r2.getClass().getName());
+    assertMultipleInstanceCharacteristics(r1, r2);
+  }
+
+  @Test
+  public void testArgumentCapturingLambda() throws Exception {
+    String msg = MSG;
+    Callable<String> r1 = () -> msg;
+    Assert.assertNotNull(r1.getClass().getName());
+    assertGeneralLambdaClassCharacteristics(r1);
+    assertLambdaInterfaces(r1, Callable.class);
+    assertLambdaMethodCharacteristics(r1, Callable.class);
+    assertNonSerializableLambdaCharacteristics(r1);
+
+    assertCallableBehavior(r1, MSG);
+
+    Callable<String> r2 = () -> msg;
+    Assert.assertNotNull(r2.getClass().getName());
+    assertMultipleInstanceCharacteristics(r1, r2);
+  }
+
+  @Test
+  public void testMultipleInterfaceLambda() throws Exception {
+    Callable<String> r1 = (Callable<String> & MarkerInterface) () -> MSG;
+    Assert.assertTrue(r1 instanceof MarkerInterface);
+    Assert.assertNotNull(r1.getClass().getName());
+    assertGeneralLambdaClassCharacteristics(r1);
+    assertLambdaMethodCharacteristics(r1, Callable.class);
+    assertLambdaInterfaces(r1, Callable.class, MarkerInterface.class);
+    assertNonSerializableLambdaCharacteristics(r1);
+
+    assertCallableBehavior(r1, MSG);
+  }
+
+
+
+  private static void assertNonSerializableLambdaCharacteristics(Object r1) throws Exception {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    try (ObjectOutputStream os = new ObjectOutputStream(baos)) {
+      os.writeObject(r1);
+      os.flush();
+      Assert.fail();
+    } catch (ObjectStreamException expected) {
+    }
+  }
+
+  private static void assertMultipleInstanceCharacteristics(Object r1, Object r2) throws Exception {
+    Assert.assertNotSame(r1, r2);
+    Assert.assertTrue(!r1.equals(r2));
+
+    Class<?> lambda1Class = r1.getClass();
+    Class<?> lambda2Class = r2.getClass();
+    Assert.assertNotSame(lambda1Class, lambda2Class);
+  }
+
+  private static <T> void assertCallableBehavior(Callable<T> r1, T expectedResult)
+      throws Exception {
+    Assert.assertEquals(expectedResult, r1.call());
+    Method implCallMethod = r1.getClass().getDeclaredMethod("call");
+    Assert.assertEquals(expectedResult, implCallMethod.invoke(r1));
+
+    Method interfaceCallMethod = Callable.class.getDeclaredMethod("call");
+    Assert.assertEquals(expectedResult, interfaceCallMethod.invoke(r1));
+  }
+
+  private static void assertGeneralLambdaClassCharacteristics(Object r1) throws Exception {
+    Class<?> lambdaClass = r1.getClass();
+
+    Assert.assertFalse(lambdaClass.isAnnotation());
+    Assert.assertFalse(lambdaClass.isAnonymousClass());
+    Assert.assertFalse(lambdaClass.isInterface());
+    Assert.assertFalse(lambdaClass.isLocalClass());
+    Assert.assertNull(lambdaClass.getEnclosingMethod());
+    Assert.assertNull(lambdaClass.getEnclosingConstructor());
+    Assert.assertNotNull(lambdaClass.getDeclaringClass());
+    Assert.assertNotNull(lambdaClass.getEnclosingClass());
+    Assert.assertTrue(lambdaClass.isMemberClass());
+    Assert.assertEquals(1, lambdaClass.getConstructors().length);
+
+    Assert.assertNotNull(lambdaClass.getSimpleName());
+    Assert.assertNotNull(lambdaClass.getCanonicalName());
+    Assert.assertEquals(0, lambdaClass.getClasses().length);
+    Assert.assertFalse(lambdaClass.isArray());
+    Assert.assertFalse(lambdaClass.isEnum());
+    Assert.assertFalse(lambdaClass.isPrimitive());
+    Assert.assertTrue(lambdaClass.isSynthetic());
+    Assert.assertNull(lambdaClass.getComponentType());
+    Assert.assertTrue((Modifier.isFinal(lambdaClass.getModifiers())));
+    // Unexpected modifiers
+    int unexpectedModifiers = Modifier.PRIVATE | Modifier.PUBLIC | Modifier.PROTECTED
+        | Modifier.STATIC | Modifier.SYNCHRONIZED | Modifier.VOLATILE | Modifier.TRANSIENT
+        | Modifier.NATIVE | Modifier.INTERFACE | Modifier.ABSTRACT | Modifier.STRICT;
+    Assert.assertTrue((unexpectedModifiers & lambdaClass.getModifiers()) == 0);
+    Assert.assertNull(lambdaClass.getSigners());
+
+    Assert.assertSame(Tests.class.getClassLoader(), lambdaClass.getClassLoader());
+    Assert.assertEquals(0, lambdaClass.getTypeParameters().length);
+
+    Assert.assertSame(Object.class, lambdaClass.getSuperclass());
+    Assert.assertSame(Object.class, lambdaClass.getGenericSuperclass());
+    Assert.assertEquals(Tests.class.getPackage(), lambdaClass.getPackage());
+
+    Assert.assertNotNull(r1.toString());
+    Assert.assertTrue(r1.equals(r1));
+    Assert.assertEquals(System.identityHashCode(r1), r1.hashCode());
+
+  }
+
+  private static <T> void assertLambdaMethodCharacteristics(T r1, Class<?> samInterfaceClass)
+      throws Exception {
+    Method singleAbstractMethod = null;
+    for (Method method : samInterfaceClass.getDeclaredMethods()) {
+      if (Modifier.isAbstract(method.getModifiers())) {
+        singleAbstractMethod = method;
+        break;
+      }
+    }
+    Assert.assertNotNull(singleAbstractMethod);
+
+    Method implementationMethod = r1.getClass().getMethod(singleAbstractMethod.getName(),
+        singleAbstractMethod.getParameterTypes());
+    Assert.assertSame(singleAbstractMethod.getReturnType(), implementationMethod.getReturnType());
+    Assert.assertSame(r1.getClass(), implementationMethod.getDeclaringClass());
+    Assert.assertFalse(implementationMethod.isSynthetic());
+    Assert.assertFalse(implementationMethod.isBridge());
+  }
+
+  private static <T> void assertLambdaInterfaces(T r1, Class<?>... interfaceClasses)
+      throws Exception {
+    Class<?> lambdaClass = r1.getClass();
+    Assert.assertEquals(interfaceClasses.length, lambdaClass.getInterfaces().length);
+    Set<Class<?>> actual = new HashSet<>(Arrays.asList(lambdaClass.getInterfaces()));
+    Set<Class<?>> expected = new HashSet<>(Arrays.asList(interfaceClasses));
+    Assert.assertEquals(expected, actual);
+
+    Set<Method> declaredMethods = new HashSet<>();
+    addNonStaticPublicMethods(declaredMethods, lambdaClass);
+    Set<Method> expectedMethods = new HashSet<>();
+    for (Class<?> interfaceClass : interfaceClasses) {
+      while (interfaceClass != null) {
+        addNonStaticPublicMethods(expectedMethods, interfaceClass);
+        interfaceClass = interfaceClass.getSuperclass();
+      }
+    }
+    Assert.assertEquals(expectedMethods.size(), declaredMethods.size());
+    for (Method expectedMethod : expectedMethods) {
+      Method actualMethod =
+          lambdaClass.getMethod(expectedMethod.getName(), expectedMethod.getParameterTypes());
+      Assert.assertEquals(expectedMethod.getReturnType(), actualMethod.getReturnType());
+    }
+  }
+
+  private static void addNonStaticPublicMethods(Set<Method> methodSet, Class<?> clazz) {
+    for (Method interfaceMethod : clazz.getDeclaredMethods()) {
+      int modifiers = interfaceMethod.getModifiers();
+      if ((!Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
+        methodSet.add(interfaceMethod);
+      }
+    }
+  }
+}
diff --git a/jack/prebuilts/core-stubs-mini.jack b/jack/prebuilts/core-stubs-mini.jack
index 07a520a..d1d6faa 100644
--- a/jack/prebuilts/core-stubs-mini.jack
+++ b/jack/prebuilts/core-stubs-mini.jack
Binary files differ
diff --git a/jack/prebuilts/core-stubs-mini.jar b/jack/prebuilts/core-stubs-mini.jar
index c18c4d0..91452bb 100644
--- a/jack/prebuilts/core-stubs-mini.jar
+++ b/jack/prebuilts/core-stubs-mini.jar
Binary files differ
diff --git a/jack/src/com/android/jack/frontend/MethodIdDuplicateRemover.java b/jack/src/com/android/jack/frontend/MethodIdDuplicateRemover.java
index 83767af..65cc08f 100644
--- a/jack/src/com/android/jack/frontend/MethodIdDuplicateRemover.java
+++ b/jack/src/com/android/jack/frontend/MethodIdDuplicateRemover.java
@@ -18,11 +18,16 @@
 
 import com.android.jack.ir.ast.JAnnotation;
 import com.android.jack.ir.ast.JClassOrInterface;
+import com.android.jack.ir.ast.JInterface;
+import com.android.jack.ir.ast.JLambda;
 import com.android.jack.ir.ast.JMethod;
 import com.android.jack.ir.ast.JMethodCall;
 import com.android.jack.ir.ast.JMethodId;
+import com.android.jack.ir.ast.JMethodIdWithReturnType;
 import com.android.jack.ir.ast.JNameValuePair;
 import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.ast.MethodKind;
+import com.android.jack.lookup.JMethodWithReturnLookupException;
 
 import java.util.Collection;
 
@@ -50,6 +55,20 @@
   }
 
   @Override
+  public boolean visit(@Nonnull JLambda lambda) {
+    JInterface lambdaType = lambda.getType();
+    try {
+      JMethodIdWithReturnType mthIdWithReturnType = lambda.getMethodIdToImplement();
+      JMethodId newMthId = lambdaType.getMethodId(mthIdWithReturnType.getName(),
+          mthIdWithReturnType.getParameterTypes(), MethodKind.INSTANCE_VIRTUAL);
+      mthIdWithReturnType.setMethodId(newMthId);
+    } catch (JMethodWithReturnLookupException e) {
+      throw new AssertionError();
+    }
+    return super.visit(lambda);
+  }
+
+  @Override
   public boolean visit(@Nonnull JMethodCall call) {
     JMethodId id = getResolvedMethodId(call.getReceiverType(), call.getMethodId());
     call.resolveMethodId(id);
diff --git a/jack/src/com/android/jack/frontend/MethodIdMerger.java b/jack/src/com/android/jack/frontend/MethodIdMerger.java
index d5eb410..622a607 100644
--- a/jack/src/com/android/jack/frontend/MethodIdMerger.java
+++ b/jack/src/com/android/jack/frontend/MethodIdMerger.java
@@ -23,7 +23,6 @@
 import com.android.jack.ir.ast.JDefinedClassOrInterface;
 import com.android.jack.ir.ast.JDefinedInterface;
 import com.android.jack.ir.ast.JInterface;
-import com.android.jack.ir.ast.JLambda;
 import com.android.jack.ir.ast.JMethod;
 import com.android.jack.ir.ast.JMethodId;
 import com.android.jack.ir.ast.JNode;
@@ -74,18 +73,6 @@
     return super.visit(node);
   }
 
-  @Override
-  public boolean visit(@Nonnull JLambda lambda) {
-    // Ensure that interface implemented by a lambda is already visited
-    JDefinedInterface type = lambda.getType();
-    accept(type);
-    VirtualMethodsMarker vmm = type.getMarker(VirtualMethodsMarker.class);
-    assert vmm != null;
-    addId(vmm, lambda.getMethod().getMethodId());
-    accept(lambda.getBody());
-    return super.visit(lambda);
-  }
-
   private void ensureHierarchyVisited(@Nonnull JClassOrInterface node) {
     JClass zuper = getSuper(node);
     if (zuper != null) {
diff --git a/jack/src/com/android/jack/ir/ast/JLambda.java b/jack/src/com/android/jack/ir/ast/JLambda.java
index d57d998..7a3c857 100644
--- a/jack/src/com/android/jack/ir/ast/JLambda.java
+++ b/jack/src/com/android/jack/ir/ast/JLambda.java
@@ -33,38 +33,58 @@
 @Description("Lambda expression")
 public class JLambda extends JExpression {
 
+  @Nonnull
+  private final JMethodIdWithReturnType mthIdToImplement;
+
+  @Nonnull
+  private final JInterface type;
+
+  @Nonnull
+  private final List<JInterface> interfaceBounds;
+
   private boolean captureInstance;
 
   @Nonnull
   private final List<JVariableRef> capturedVariablesRef = new ArrayList<JVariableRef>(0);
 
+  // TODO(jack-team): JMethod must be replace by a JCallable
   @Nonnull
   private final JMethod method;
 
   @Nonnull
-  private final JDefinedInterface type;
+  private final List<JMethodIdWithReturnType> bridges = new ArrayList<JMethodIdWithReturnType>();
 
-  @Nonnull
-  private final JMethodBody body;
-
-  @Nonnull
-  private final List<JInterface> interfaceBounds;
-
-  public JLambda(@Nonnull SourceInfo info, @Nonnull JMethod method, @Nonnull JDefinedInterface type,
-      boolean captureInstance, @Nonnull List<JInterface> interfaceBounds) {
+  public JLambda(@Nonnull SourceInfo info, @Nonnull JMethodIdWithReturnType mthToImplement,
+      @Nonnull JMethod method, @Nonnull JInterface type, boolean captureInstance,
+      @Nonnull List<JInterface> interfaceBounds) {
     super(info);
     assert method != null;
     assert type != null;
-    assert type.isSingleAbstractMethodType();
+    this.mthIdToImplement = mthToImplement;
     this.type = type;
     this.method = method;
     this.captureInstance = captureInstance;
-    JMethodBody localBody = (JMethodBody) method.getBody();
-    assert localBody != null;
-    body = localBody;
     this.interfaceBounds = interfaceBounds;
   }
 
+  @Nonnull
+  public JMethodIdWithReturnType getMethodIdToImplement() {
+    return mthIdToImplement;
+  }
+
+  @Nonnull
+  public List<JMethodIdWithReturnType> getBridgeMethodIds() {
+    return bridges;
+  }
+
+  public void addBridgeMethodId(@Nonnull JMethodIdWithReturnType bridgeMethodId) {
+    this.bridges.add(bridgeMethodId);
+  }
+
+  public void addBridgeMethodIds(@Nonnull List<JMethodIdWithReturnType> bridgeMethodIds) {
+    this.bridges.addAll(bridgeMethodIds);
+  }
+
   public void addCapturedVariable(@Nonnull JVariableRef capturedVariableRef) {
     capturedVariablesRef.add(capturedVariableRef);
   }
@@ -80,6 +100,8 @@
   @Override
   public void traverse(@Nonnull ScheduleInstance<? super Component> schedule) throws Exception {
     schedule.process(this);
+    JMethodBody body = (JMethodBody) method.getBody();
+    assert body != null;
     body.traverse(schedule);
   }
 
@@ -90,13 +112,15 @@
 
   @Override
   @Nonnull
-  public JDefinedInterface getType() {
+  public JInterface getType() {
     return type;
   }
 
   @Nonnull
-  public JBlock getBody() {
-    return body.getBlock();
+  public JMethodBody getBody() {
+    JMethodBody body = (JMethodBody) method.getBody();
+    assert body != null;
+    return body;
   }
 
   @Nonnull
@@ -104,6 +128,7 @@
     return method.getParams();
   }
 
+  @Deprecated
   @Nonnull
   public JMethod getMethod() {
     return method;
diff --git a/jack/src/com/android/jack/ir/ast/JMethodIdWithReturnType.java b/jack/src/com/android/jack/ir/ast/JMethodIdWithReturnType.java
new file mode 100644
index 0000000..a1f006d
--- /dev/null
+++ b/jack/src/com/android/jack/ir/ast/JMethodIdWithReturnType.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.jack.ir.ast;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+/**
+ * {@link JMethodIdWithReturnType} describes a method by using a {@link JMethodId} and its return
+ * type.
+ */
+public class JMethodIdWithReturnType {
+
+  @Nonnull
+  private JMethodId methodId;
+
+  @Nonnull
+  private final JType returnType;
+
+  public JMethodIdWithReturnType(@Nonnull JMethodId methodId, @Nonnull JType returnType) {
+    this.methodId = methodId;
+    this.returnType = returnType;
+  }
+
+  @Nonnull
+  public JMethodId getMethodId() {
+    return methodId;
+  }
+
+  public void setMethodId(@Nonnull JMethodId newMthId) {
+    methodId = newMthId;
+  }
+
+  @Nonnull
+  public JType getReturnType() {
+    return returnType;
+  }
+
+  @Nonnull
+  public List<JType> getParameterTypes() {
+    return methodId.getParamTypes();
+  }
+
+  @Nonnull
+  public String getName() {
+    return methodId.getName();
+  }
+}
\ No newline at end of file
diff --git a/jack/src/com/android/jack/ir/impl/JackIrBuilder.java b/jack/src/com/android/jack/ir/impl/JackIrBuilder.java
index 0557d0b..966b172 100644
--- a/jack/src/com/android/jack/ir/impl/JackIrBuilder.java
+++ b/jack/src/com/android/jack/ir/impl/JackIrBuilder.java
@@ -80,6 +80,7 @@
 import com.android.jack.ir.ast.JMethodBody;
 import com.android.jack.ir.ast.JMethodCall;
 import com.android.jack.ir.ast.JMethodId;
+import com.android.jack.ir.ast.JMethodIdWithReturnType;
 import com.android.jack.ir.ast.JModifier;
 import com.android.jack.ir.ast.JMultiExpression;
 import com.android.jack.ir.ast.JNameValuePair;
@@ -1394,6 +1395,8 @@
       MethodInfo newMethodInfo =
           createMethodInfoForLambda(referenceExpression.descriptor, /* arguments = */ null,
               /* (MethodScope) referenceExpression.enclosingScope */ curMethod.scope);
+      JMethodIdWithReturnType methodIdToImplement =
+          getJMethodIdWithReturnType(referenceExpression.descriptor.original());
       newMethodInfo.method.setThis(null);
       newMethodInfo.method.setModifier(newMethodInfo.method.getModifier() & ~JModifier.STATIC);
       JType returnTypeOfLambdaMethod = newMethodInfo.method.getType();
@@ -1407,10 +1410,10 @@
             || referenceExpression.lhs instanceof QualifiedThisReference;
         boolean shouldCaptureInstance = isSuperRef || isThisRef;
 
-        exprRepresentingLambda =
-            new JLambda(sourceInfo, newMethodInfo.method,
-                (JDefinedInterface) getTypeMap().get(referenceExpression.resolvedType),
-                shouldCaptureInstance, getInterfaceBounds(referenceExpression, blockScope));
+        exprRepresentingLambda = new JLambda(sourceInfo, methodIdToImplement, newMethodInfo.method,
+            (JDefinedInterface) getTypeMap().get(referenceExpression.resolvedType),
+            shouldCaptureInstance, getInterfaceBounds(referenceExpression, blockScope));
+        ((JLambda) exprRepresentingLambda).addBridgeMethodIds(getBridges(referenceExpression));
 
         if (!(referenceExpression.lhs instanceof TypeReference)) {
           if (lhsExpr != null && !shouldCaptureInstance) {
@@ -1472,9 +1475,12 @@
       } else if (referenceExpression.isArrayConstructorReference()) {
         assert args.size() == 1;
 
-        exprRepresentingLambda = new JLambda(sourceInfo, newMethodInfo.method,
-            (JDefinedInterface) getTypeMap().get(referenceExpression.resolvedType),
-            /* captureInstance= */ false, getInterfaceBounds(referenceExpression, blockScope));
+        exprRepresentingLambda =
+            new JLambda(sourceInfo, methodIdToImplement, newMethodInfo.method,
+                (JDefinedInterface) getTypeMap().get(referenceExpression.resolvedType),
+                /* captureInstance= */ false, getInterfaceBounds(referenceExpression, blockScope));
+        ((JLambda) exprRepresentingLambda)
+            .addBridgeMethodIds(getBridges(referenceExpression));
 
         Expression lhs = referenceExpression.lhs;
         JArrayType arrayType = (JArrayType) getTypeMap().get(lhs.resolvedType);
@@ -1530,9 +1536,12 @@
         }
       }
 
-      JLambda lambda = new JLambda(sourceInfo, newMethodInfo.method,
+      JLambda lambda = new JLambda(sourceInfo,
+          getJMethodIdWithReturnType(referenceExpression.descriptor.original()),
+          newMethodInfo.method,
           (JDefinedInterface) getTypeMap().get(referenceExpression.resolvedType),
           shouldCaptureInstance, getInterfaceBounds(referenceExpression, blockScope));
+      lambda.addBridgeMethodIds(getBridges(referenceExpression));
 
       addArgToMethodCall(referenceExpression, argsOfLambdaMth, constructor,
           newInstance, paramCountCons, 0);
@@ -1663,6 +1672,32 @@
       return true;
     }
 
+    @Nonnull
+    private JMethodIdWithReturnType getJMethodIdWithReturnType(@Nonnull MethodBinding mb) {
+      JMethodId methodId =
+          new JMethodId(ReferenceMapper.intern(mb.selector), MethodKind.INSTANCE_VIRTUAL);
+
+      for (TypeBinding parameterType : mb.parameters) {
+        methodId.addParam(getTypeMap().get(parameterType));
+      }
+
+      return new JMethodIdWithReturnType(methodId, getTypeMap().get(mb.returnType));
+    }
+
+    @Nonnull
+    private List<JMethodIdWithReturnType> getBridges(@Nonnull FunctionalExpression fe) {
+      MethodBinding[] bridges = fe.getRequiredBridges();
+      List<JMethodIdWithReturnType> mds = new ArrayList<JMethodIdWithReturnType>();
+
+      if (bridges != null) {
+        for (MethodBinding bridge : bridges) {
+          mds.add(getJMethodIdWithReturnType(bridge));
+        }
+      }
+
+      return mds;
+    }
+
     @Override
     public void endVisit(LambdaExpression lambdaExpression, BlockScope blockScope) {
       MethodInfo lambdaMethodInfo = curMethod;
@@ -1695,10 +1730,12 @@
       popMethodInfo();
 
       SourceInfo sourceInfo = makeSourceInfo(lambdaExpression);
-      JLambda lambda = new JLambda(sourceInfo, lambdaMethodInfo.method,
+      JLambda lambda = new JLambda(sourceInfo,
+          getJMethodIdWithReturnType(lambdaExpression.descriptor.original()),
+          lambdaMethodInfo.method,
           (JDefinedInterface) getTypeMap().get(lambdaExpression.resolvedType),
-          lambdaExpression.shouldCaptureInstance,
-          getInterfaceBounds(lambdaExpression, blockScope));
+          lambdaExpression.shouldCaptureInstance, getInterfaceBounds(lambdaExpression, blockScope));
+      lambda.addBridgeMethodIds(getBridges(lambdaExpression));
 
       // Capture all local variable that are not already moved into fields
       for (SyntheticArgumentBinding synthArg : lambdaExpression.outerLocalVariables) {
diff --git a/jack/src/com/android/jack/jayce/v0003/NodeFactory.java b/jack/src/com/android/jack/jayce/v0003/NodeFactory.java
index 7343d9c..6bb0cd2 100644
--- a/jack/src/com/android/jack/jayce/v0003/NodeFactory.java
+++ b/jack/src/com/android/jack/jayce/v0003/NodeFactory.java
@@ -93,6 +93,7 @@
 import com.android.jack.ir.ast.JMethod;
 import com.android.jack.ir.ast.JMethodBody;
 import com.android.jack.ir.ast.JMethodCall;
+import com.android.jack.ir.ast.JMethodIdWithReturnType;
 import com.android.jack.ir.ast.JMethodLiteral;
 import com.android.jack.ir.ast.JModOperation;
 import com.android.jack.ir.ast.JMulOperation;
@@ -209,6 +210,7 @@
 import com.android.jack.jayce.v0003.nodes.NMethod;
 import com.android.jack.jayce.v0003.nodes.NMethodBody;
 import com.android.jack.jayce.v0003.nodes.NMethodCall;
+import com.android.jack.jayce.v0003.nodes.NMethodIdWithReturnType;
 import com.android.jack.jayce.v0003.nodes.NMethodLiteral;
 import com.android.jack.jayce.v0003.nodes.NModOperation;
 import com.android.jack.jayce.v0003.nodes.NMulOperation;
@@ -616,6 +618,7 @@
       return false;
     }
 
+
     @Override
     public boolean visit(@Nonnull JMethodLiteral x) {
       newNode = new NMethodLiteral();
@@ -777,6 +780,8 @@
       }
     } else if (from instanceof Marker) {
       return createMarkerNode((Marker) from);
+    } else if (from instanceof JMethodIdWithReturnType) {
+      return new NMethodIdWithReturnType();
     }
     throw new AssertionError("Not yet implemented (" + from.getClass().getCanonicalName() + ")");
   }
diff --git a/jack/src/com/android/jack/jayce/v0003/Version.java b/jack/src/com/android/jack/jayce/v0003/Version.java
index 305ec4c..e1b2b0b 100644
--- a/jack/src/com/android/jack/jayce/v0003/Version.java
+++ b/jack/src/com/android/jack/jayce/v0003/Version.java
@@ -21,7 +21,7 @@
  */
 public class Version {
 
-  public static final int MINOR_MIN = 2;
+  public static final int MINOR_MIN = 3;
 
-  public static final int CURRENT_MINOR = 2;
+  public static final int CURRENT_MINOR = 3;
 }
diff --git a/jack/src/com/android/jack/jayce/v0003/io/Token.java b/jack/src/com/android/jack/jayce/v0003/io/Token.java
index 2cceb52..edd07b4 100644
--- a/jack/src/com/android/jack/jayce/v0003/io/Token.java
+++ b/jack/src/com/android/jack/jayce/v0003/io/Token.java
@@ -93,6 +93,7 @@
 import com.android.jack.jayce.v0003.nodes.NMethod;
 import com.android.jack.jayce.v0003.nodes.NMethodBody;
 import com.android.jack.jayce.v0003.nodes.NMethodCall;
+import com.android.jack.jayce.v0003.nodes.NMethodIdWithReturnType;
 import com.android.jack.jayce.v0003.nodes.NMethodLiteral;
 import com.android.jack.jayce.v0003.nodes.NModOperation;
 import com.android.jack.jayce.v0003.nodes.NMulOperation;
@@ -676,6 +677,13 @@
       return new NMethodCall();
     }
   },
+  METHODID_WITH_RETURN_TYPE("method-id-with-return-type", NodeLevel.STRUCTURE) {
+    @Nonnull
+    @Override
+    public NNode newNode() {
+      return new NMethodIdWithReturnType();
+    }
+  },
   METHOD_LITERAL("method-literal", NodeLevel.STRUCTURE) {
     @Nonnull
     @Override
diff --git a/jack/src/com/android/jack/jayce/v0003/nodes/NLambda.java b/jack/src/com/android/jack/jayce/v0003/nodes/NLambda.java
index 1edd24e..780440c 100644
--- a/jack/src/com/android/jack/jayce/v0003/nodes/NLambda.java
+++ b/jack/src/com/android/jack/jayce/v0003/nodes/NLambda.java
@@ -16,18 +16,15 @@
 
 package com.android.jack.jayce.v0003.nodes;
 
-import com.android.jack.ir.ast.JDefinedInterface;
 import com.android.jack.ir.ast.JExpression;
 import com.android.jack.ir.ast.JInterface;
 import com.android.jack.ir.ast.JLambda;
 import com.android.jack.ir.ast.JMethod;
-import com.android.jack.ir.ast.JPhantomInterface;
+import com.android.jack.ir.ast.JMethodIdWithReturnType;
 import com.android.jack.ir.ast.JTypeLookupException;
 import com.android.jack.ir.ast.JVariableRef;
-import com.android.jack.ir.ast.MissingJTypeLookupException;
 import com.android.jack.jayce.JayceClassOrInterfaceLoader;
 import com.android.jack.jayce.NodeLevel;
-import com.android.jack.jayce.linker.VariableRefLinker;
 import com.android.jack.jayce.v0003.io.ExportSession;
 import com.android.jack.jayce.v0003.io.ImportHelper;
 import com.android.jack.jayce.v0003.io.JayceInternalReaderImpl;
@@ -37,6 +34,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import javax.annotation.CheckForNull;
@@ -65,7 +63,13 @@
   public NSourceInfo sourceInfo;
 
   @Nonnull
-  private List<String> boundsIds = new ArrayList<String>();
+  private List<String> boundsIds = Collections.emptyList();
+
+  @CheckForNull
+  private NMethodIdWithReturnType mthIdToImplement;
+
+  @Nonnull
+  private List<NMethodIdWithReturnType> bridges = Collections.emptyList();
 
   @Override
   public void importFromJast(@Nonnull ImportHelper loader, @Nonnull Object node) {
@@ -78,6 +82,8 @@
     typeSig = ImportHelper.getSignatureName(lambda.getType());
     sourceInfo = loader.load(lambda.getSourceInfo());
     boundsIds = ImportHelper.getSignatureNameList(lambda.getInterfaceBounds());
+    mthIdToImplement = (NMethodIdWithReturnType) loader.load(lambda.getMethodIdToImplement());
+    bridges = loader.load(NMethodIdWithReturnType.class, lambda.getBridgeMethodIds());
   }
 
   @Override
@@ -88,6 +94,7 @@
     assert capturedVariableIds != null;
     assert method != null;
     assert typeSig != null;
+    assert mthIdToImplement != null;
 
     ExportSession exportSessionForLambdaMethStructure = new ExportSession(exportSession.getLookup(),
         exportSession.getSession(), NodeLevel.STRUCTURE);
@@ -115,17 +122,14 @@
       jBounds.add(exportSession.getLookup().getInterface(bound));
     }
 
-    JInterface implementedInterface = exportSession.getLookup().getInterface(typeSig);
-    if (implementedInterface instanceof JPhantomInterface) {
-      throw new MissingJTypeLookupException((JPhantomInterface) implementedInterface);
-    }
-    JLambda lambda = new JLambda(sourceInfo.exportAsJast(exportSession), lambdaMethod,
-        (JDefinedInterface) implementedInterface, captureInstance,
-        jBounds);
+    JMethodIdWithReturnType mthIdToImplements =
+        (JMethodIdWithReturnType) mthIdToImplement.exportAsJast(exportSession);
 
-    for (String capturedVariableId : capturedVariableIds) {
-      exportSession.getVariableResolver().addLink(capturedVariableId,
-          new VariableRefLinker(lambda));
+    JLambda lambda = new JLambda(sourceInfo.exportAsJast(exportSession), mthIdToImplements,
+        lambdaMethod, exportSession.getLookup().getInterface(typeSig), captureInstance, jBounds);
+
+    for (NMethodIdWithReturnType bridge : bridges) {
+      lambda.addBridgeMethodId((JMethodIdWithReturnType) bridge.exportAsJast(exportSession));
     }
 
     return lambda;
@@ -138,6 +142,8 @@
     out.writeNode(method);
     out.writeId(typeSig);
     out.writeIds(boundsIds);
+    out.writeNode(mthIdToImplement);
+    out.writeNodes(bridges);
   }
 
   @Override
@@ -147,6 +153,8 @@
     method = in.readNode(NMethod.class);
     typeSig = in.readId();
     boundsIds = in.readIds();
+    mthIdToImplement = in.readNode(NMethodIdWithReturnType.class);
+    bridges = in.readNodes(NMethodIdWithReturnType.class);
   }
 
   @Override
diff --git a/jack/src/com/android/jack/jayce/v0003/nodes/NMethodIdWithReturnType.java b/jack/src/com/android/jack/jayce/v0003/nodes/NMethodIdWithReturnType.java
new file mode 100644
index 0000000..d38c5f9
--- /dev/null
+++ b/jack/src/com/android/jack/jayce/v0003/nodes/NMethodIdWithReturnType.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.jack.jayce.v0003.nodes;
+
+import com.android.jack.ir.ast.JMethodId;
+import com.android.jack.ir.ast.JMethodIdWithReturnType;
+import com.android.jack.ir.ast.JType;
+import com.android.jack.ir.ast.JTypeLookupException;
+import com.android.jack.ir.ast.MethodKind;
+import com.android.jack.jayce.v0003.NNode;
+import com.android.jack.jayce.v0003.io.ExportSession;
+import com.android.jack.jayce.v0003.io.ImportHelper;
+import com.android.jack.jayce.v0003.io.JayceInternalReaderImpl;
+import com.android.jack.jayce.v0003.io.JayceInternalWriterImpl;
+import com.android.jack.jayce.v0003.io.Token;
+import com.android.jack.lookup.JMethodLookupException;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Node representing a method id with a return type.
+ */
+public class NMethodIdWithReturnType extends NNode {
+  @Nonnull
+  public static final Token TOKEN = Token.METHODID_WITH_RETURN_TYPE;
+
+  @CheckForNull String name;
+
+  @CheckForNull String returnTypeSig;
+
+  @Nonnull
+  private List<String> paramTypeSigs = Collections.emptyList();
+
+  @CheckForNull
+  private MethodKind methodKind;
+
+  @Override
+  public void importFromJast(@Nonnull ImportHelper loader, @Nonnull Object node) {
+    JMethodIdWithReturnType mthIdToImplement = (JMethodIdWithReturnType) node;
+    name = mthIdToImplement.getName();
+    returnTypeSig = ImportHelper.getSignatureName(mthIdToImplement.getReturnType());
+    paramTypeSigs = ImportHelper.getSignatureNameList(mthIdToImplement.getParameterTypes());
+    methodKind = mthIdToImplement.getMethodId().getKind();
+  }
+
+  @Override
+  @Nonnull
+  public Object exportAsJast(@Nonnull ExportSession exportSession)
+      throws JTypeLookupException, JMethodLookupException {
+    assert name != null;
+    assert returnTypeSig != null;
+    assert methodKind != null;
+    JType returnType = exportSession.getLookup().getType(returnTypeSig);
+    JMethodId mthId = new JMethodId(name, methodKind);
+    for (String paramSig : paramTypeSigs) {
+      mthId.addParam(exportSession.getLookup().getType(paramSig));
+    }
+    return new JMethodIdWithReturnType(mthId, returnType);
+  }
+
+  @Override
+  public void writeContent(@Nonnull JayceInternalWriterImpl out) throws IOException {
+    assert methodKind != null;
+    out.writeId(name);
+    out.writeMethodKindEnum(methodKind);
+    out.writeId(returnTypeSig);
+    out.writeIds(paramTypeSigs);
+  }
+
+  @Override
+  public void readContent(@Nonnull JayceInternalReaderImpl in) throws IOException {
+    name = in.readId();
+    methodKind = in.readMethodKindEnum();
+    returnTypeSig = in.readId();
+    paramTypeSigs = in.readIds();
+  }
+
+  @Override
+  @Nonnull
+  public Token getToken() {
+    return TOKEN;
+  }
+}
\ No newline at end of file
diff --git a/jack/src/com/android/jack/library/v0002/Version.java b/jack/src/com/android/jack/library/v0002/Version.java
index 0fbaa27..ab41e3f 100644
--- a/jack/src/com/android/jack/library/v0002/Version.java
+++ b/jack/src/com/android/jack/library/v0002/Version.java
@@ -21,9 +21,9 @@
  */
 public class Version {
 
-  public static final int MINOR_MIN = 1;
+  public static final int MINOR_MIN = 2;
 
-  public static final int MINOR = 1;
+  public static final int MINOR = 2;
 
   public static final int MAJOR = 2;
 }
diff --git a/jack/src/com/android/jack/transformations/lambda/LambdaConverter.java b/jack/src/com/android/jack/transformations/lambda/LambdaConverter.java
index 88b450e..14f5280 100644
--- a/jack/src/com/android/jack/transformations/lambda/LambdaConverter.java
+++ b/jack/src/com/android/jack/transformations/lambda/LambdaConverter.java
@@ -18,6 +18,7 @@
 
 import com.android.jack.Jack;
 import com.android.jack.ir.ast.JAsgOperation;
+import com.android.jack.ir.ast.JBinaryOperator;
 import com.android.jack.ir.ast.JBlock;
 import com.android.jack.ir.ast.JClass;
 import com.android.jack.ir.ast.JConstructor;
@@ -36,12 +37,14 @@
 import com.android.jack.ir.ast.JMethodBody;
 import com.android.jack.ir.ast.JMethodCall;
 import com.android.jack.ir.ast.JMethodId;
+import com.android.jack.ir.ast.JMethodIdWithReturnType;
 import com.android.jack.ir.ast.JModifier;
 import com.android.jack.ir.ast.JNewInstance;
 import com.android.jack.ir.ast.JParameter;
 import com.android.jack.ir.ast.JParameterRef;
 import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
 import com.android.jack.ir.ast.JReturnStatement;
+import com.android.jack.ir.ast.JStatement;
 import com.android.jack.ir.ast.JThis;
 import com.android.jack.ir.ast.JThisRef;
 import com.android.jack.ir.ast.JType;
@@ -59,6 +62,7 @@
 import com.android.jack.scheduling.feature.SourceVersion8;
 import com.android.jack.transformations.request.AppendField;
 import com.android.jack.transformations.request.AppendMethod;
+import com.android.jack.transformations.request.PrependStatement;
 import com.android.jack.transformations.request.Replace;
 import com.android.jack.transformations.request.TransformationRequest;
 import com.android.jack.util.NamingTools;
@@ -71,7 +75,6 @@
 
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
@@ -98,11 +101,16 @@
 public class LambdaConverter implements RunnableSchedulable<JMethod> {
 
   /**
-   * LambdaCtx save the relation between captured variables and theirs corresponding fields.
-   * It also keep JThis of the method implementing the lambda.
+   * LambdaCtx keeps the following mappings:
+   * - mappings between captured variables and theirs corresponding fields
+   * - mappings between lambda parameters and theirs corresponding locals
+   * and it also keeps JThis of the method implementing the lambda.
    */
   private static class LambdaCtx {
     @Nonnull
+    private final Map<JParameter, JLocal> lambdaParam2Local = new HashMap<JParameter, JLocal>();
+
+    @Nonnull
     private final Map<JVariable, JField> capturedVar2Field = new HashMap<JVariable, JField>();
 
     @Nonnull
@@ -112,6 +120,21 @@
       this.thisOfLambdaImpl = thisOfLambdaImpl;
     }
 
+    public void addLambdaParam2LocalMapping(@Nonnull JParameter oldParam,
+        @Nonnull JLocal newLocal) {
+      assert !lambdaParam2Local.containsKey(oldParam);
+      lambdaParam2Local.put(oldParam, newLocal);
+    }
+
+    @CheckForNull
+    public JLocalRef getLambdaParameter(@Nonnull JParameter oldParam) {
+      JLocal local = lambdaParam2Local.get(oldParam);
+      if (local != null) {
+        return local.makeRef(SourceInfo.UNKNOWN);
+      }
+      return null;
+    }
+
     public void addVar2FieldMapping(@Nonnull JVariable capturedVar, @Nonnull JField field) {
       assert !capturedVar2Field.containsKey(capturedVar);
       capturedVar2Field.put(capturedVar, field);
@@ -177,9 +200,10 @@
 
     @Override
     public boolean visit(@Nonnull JLambda lambdaExpr) {
-      JMethod lambdaMethod = lambdaExpr.getMethod();
       LambdaCtx lambdaCtx = null;
+      JMethod lambdaMethod = lambdaExpr.getMethod();
       JConstructor lambdaImplCons = lambdaToLambaImplConst.get(lambdaMethod);
+      JMethodBody lambdaBody = lambdaExpr.getBody();
 
       JThis capturedInstance = null;
 
@@ -190,24 +214,43 @@
       if (lambdaImplCons == null) {
         JDefinedClass lambdaImplClass = createLambdaImplClass(lambdaExpr);
 
-        JMethod samMethod = lambdaExpr.getType().getSingleAbstractMethod();
-        assert samMethod != null;
-        if (needBridgeMethod(samMethod, lambdaExpr)) {
-          synthesizeBridge(lambdaImplClass, samMethod, lambdaMethod);
+        JMethodIdWithReturnType mthIdToImplement = lambdaExpr.getMethodIdToImplement();
+        JMethod mthToImplement =
+            createMethod(lambdaImplClass, mthIdToImplement, /* isBridge= */ false);
+        JThis thisOfLambda = mthToImplement.getThis();
+        assert thisOfLambda != null;
+        lambdaCtx = new LambdaCtx(thisOfLambda);
+
+        // The body of the method to implement is the body of the lambda expression
+        mthToImplement.setBody(lambdaBody);
+        // Body is already transform, remove it
+        lambdaMethod.setBody(null);
+        JBlock blockOfBodytoImplement = lambdaBody.getBlock();
+
+        // Move parameters of lambda body as local variables of mthToImplement
+        assert mthToImplement.getParams().size() == mthToImplement.getParams().size();
+        int pIdx = 0;
+        for (JParameter param : lambdaMethod.getParams()) {
+          JLocal local = new JLocal(param.getSourceInfo(), param.getName(), param.getType(),
+              param.getModifier(), lambdaBody);
+          lambdaBody.addLocal(local);
+          lambdaCtx.addLambdaParam2LocalMapping(param, local);
+
+          JStatement stmt = JAsgOperation
+          .create(SourceInfo.UNKNOWN, JBinaryOperator.ASG, local.makeRef(SourceInfo.UNKNOWN),
+              new JDynamicCastOperation(SourceInfo.UNKNOWN,
+                  mthToImplement.getParams().get(pIdx++).makeRef(SourceInfo.UNKNOWN),
+                  local.getType()))
+          .makeStatement();
+
+          tr.append(new PrependStatement(blockOfBodytoImplement, stmt));
         }
 
-        // Move method representing lambda body into the class implementing lambda.
-        // It is possible to do this, since lambda expression will be removed from
-        // IR and thus the corresponding JMethod will not be longer attach to the IR and can be
-        // directly reused.
-        tr.append(new AppendMethod(lambdaImplClass, lambdaMethod));
-        lambdaMethod.setModifier(JModifier.FINAL | JModifier.SYNTHETIC | JModifier.PUBLIC);
-        lambdaMethod.setEnclosingType(lambdaImplClass);
-        JThis thisOfLambdaImpl = new JThis(lambdaMethod);
-        assert lambdaMethod.getThis() == null;
-        lambdaMethod.setThis(thisOfLambdaImpl);
-        lambdaCtx = new LambdaCtx(thisOfLambdaImpl);
-
+        for (JMethodIdWithReturnType bridgeMthIdWithReturnType : lambdaExpr.getBridgeMethodIds()) {
+          JMethod bridge =
+              createMethod(lambdaImplClass, bridgeMthIdWithReturnType, /* isBridge= */ true);
+          delegateImplementation(bridge, mthToImplement);
+        }
 
         // Build <init> method of class implementing lambda and fields for all captured variables.
         // Generated code looks like
@@ -259,7 +302,14 @@
         JVariable capturedVar = capturedVarRef.getTarget();
         JExpression arg = getCapturedVar(capturedVar);
         if (arg == null) {
-          arg = capturedVar.makeRef(SourceInfo.UNKNOWN);
+          if (capturedVarRef instanceof JParameterRef) {
+            // The parameter reference was not captured but it could be a parameter that was move to
+            // local
+            arg = getLambdaParameter((JParameter) capturedVarRef.getTarget());
+          }
+          if (arg == null) {
+            arg = capturedVar.makeRef(SourceInfo.UNKNOWN);
+          }
         }
         newAnnonymous.addArg(arg);
       }
@@ -277,7 +327,9 @@
 
       lambdaCtxStack.push(lambdaCtx);
 
-      accept(lambdaExpr.getBody());
+      if (lambdaBody != null) {
+        accept(lambdaBody);
+      }
 
       return false;
     }
@@ -291,6 +343,15 @@
       return fieldRef;
     }
 
+    @CheckForNull
+    private JLocalRef getLambdaParameter(@Nonnull JParameter parameter) {
+      JLocalRef localRef = null;
+      if (!lambdaCtxStack.isEmpty()) {
+        localRef = lambdaCtxStack.peek().getLambdaParameter(parameter);
+      }
+      return localRef;
+    }
+
     @Nonnull
     private void createFieldAndAssignment(@Nonnull LambdaCtx lambdaCtx,
         @Nonnull JConstructor constructor, @Nonnull JVariable capturedVar) {
@@ -324,51 +385,76 @@
       super.endVisit(lambdaExpr);
     }
 
-    private void synthesizeBridge(@Nonnull JDefinedClass jClass, @Nonnull JMethod method,
-        @Nonnull JMethod lamdbaMethodImpl) {
+    @Nonnull
+    private JMethod createMethod(@Nonnull JDefinedClass jClass,
+        @Nonnull JMethodIdWithReturnType methIdWithReturnType, boolean isBridge) {
       SourceInfo sourceInfo = SourceInfo.UNKNOWN;
-      JMethodId methodId = method.getMethodId();
 
-      int bridgeModifier = method.getModifier();
-      bridgeModifier &= ~(JModifier.ABSTRACT);
-      bridgeModifier |= JModifier.SYNTHETIC | JModifier.BRIDGE;
+      int mthModifier =
+          JModifier.PUBLIC | (isBridge ? (JModifier.SYNTHETIC | JModifier.BRIDGE) : 0);
 
-      JMethod bridge = new JMethod(sourceInfo, methodId, jClass, method.getType(), bridgeModifier);
+      JMethod mth = new JMethod(sourceInfo, methIdWithReturnType.getMethodId(), jClass,
+          methIdWithReturnType.getReturnType(), mthModifier);
 
-      for (JParameter param : method.getParams()) {
-        bridge.addParam(new JParameter(sourceInfo, param.getName(), param.getType(),
-            param.getModifier(), bridge));
+      int pIdx = 0;
+      for (JType parameterType : methIdWithReturnType.getParameterTypes()) {
+        mth.addParam(
+            new JParameter(sourceInfo, "arg" + pIdx++, parameterType, JModifier.DEFAULT, mth));
       }
 
+      tr.append(new AppendMethod(jClass, mth));
+
+      return mth;
+    }
+
+    private void delegateImplementation(@Nonnull JMethod mth, @Nonnull JMethod mthToCall) {
+      SourceInfo sourceInfo = SourceInfo.UNKNOWN;
+
       JBlock bodyBlock = new JBlock(sourceInfo);
       JMethodBody body = new JMethodBody(sourceInfo, bodyBlock);
-      JThis jThis = bridge.getThis();
+      JThis jThis = mth.getThis();
       assert jThis != null;
 
-      JMethodCall callToSuper = new JMethodCall(sourceInfo, jThis.makeRef(sourceInfo), jClass,
-          lamdbaMethodImpl.getMethodId(), lamdbaMethodImpl.getType(), true /* isVirtualDispatch */);
+      JMethodCall call =
+          new JMethodCall(sourceInfo, jThis.makeRef(sourceInfo), mth.getEnclosingType(),
+              mthToCall.getMethodId(), mthToCall.getType(), true /* isVirtualDispatch */);
 
-      List<JType> paramType = lamdbaMethodImpl.getMethodId().getParamTypes();
-      int pIndex =  0;
-      for (JParameter param : bridge.getParams()) {
-        callToSuper.addArg(new JDynamicCastOperation(sourceInfo,
-            param.makeRef(sourceInfo), paramType.get(pIndex++)));
+      List<JType> paramType = mthToCall.getMethodId().getParamTypes();
+      int pIndex = 0;
+      for (JParameter param : mth.getParams()) {
+        call.addArg(new JDynamicCastOperation(sourceInfo, param.makeRef(sourceInfo),
+            paramType.get(pIndex++)));
       }
 
-      if (method.getType() != JPrimitiveTypeEnum.VOID.getType()) {
-        bodyBlock.addStmt(new JReturnStatement(sourceInfo, callToSuper));
+      if (mth.getType() != JPrimitiveTypeEnum.VOID.getType()) {
+        bodyBlock.addStmt(new JReturnStatement(sourceInfo, call));
       } else {
-        bodyBlock.addStmt(new JExpressionStatement(sourceInfo, callToSuper));
+        bodyBlock.addStmt(new JExpressionStatement(sourceInfo, call));
         bodyBlock.addStmt(new JReturnStatement(sourceInfo, null));
       }
 
-      bridge.setBody(body);
+      mth.setBody(body);
+    }
 
-      tr.append(new AppendMethod(jClass, bridge));
+    @Override
+    public boolean visit(@Nonnull JParameterRef varRef) {
+      // Check if parameter reference targets a lambda parameter that must be rewrite in local
+      // reference
+
+      JExpression exprToUse = getLambdaParameter((JParameter) varRef.getTarget());
+      if (exprToUse != null) {
+        tr.append(new Replace(varRef, exprToUse));
+        return false;
+      }
+
+      // Need to visit super to check if this parameter reference targets a captured variable
+      return super.visit(varRef);
     }
 
     @Override
     public boolean visit(@Nonnull JVariableRef varRef) {
+      // Check if a captured variable must be rewrite into a field access containing the value of
+      // the captured variable
       JExpression exprToUse = getCapturedVar(varRef.getTarget());
       if (exprToUse != null) {
         tr.append(new Replace(varRef, exprToUse));
@@ -379,11 +465,10 @@
     @Nonnull
     private JDefinedClass createLambdaImplClass(@Nonnull JLambda lambdaExpr) {
       String simpleName = lambdaClassNamePrefix + anonymousCountByMeth;
-      JDefinedClass lambdaImpl =
-          new JDefinedClass(new SourceInfoFactory().create(currentClass.getSourceInfo()
-              .getFileName()), currentClass.getName() + "$" + simpleName,
-              JModifier.PUBLIC | JModifier.SYNTHETIC, currentClass.getEnclosingPackage(),
-              NopClassOrInterfaceLoader.INSTANCE);
+      JDefinedClass lambdaImpl = new JDefinedClass(
+          new SourceInfoFactory().create(currentClass.getSourceInfo().getFileName()),
+          currentClass.getName() + "$" + simpleName, JModifier.FINAL | JModifier.SYNTHETIC,
+          currentClass.getEnclosingPackage(), NopClassOrInterfaceLoader.INSTANCE);
       anonymousCountByMeth++;
 
       currentClass.addMemberType(lambdaImpl);
@@ -402,23 +487,6 @@
 
       return lambdaImpl;
     }
-
-    private boolean needBridgeMethod(@Nonnull JMethod samMethod, @Nonnull JLambda lambdaExpr) {
-      assert samMethod.getParams().size() == lambdaExpr.getParameters().size();
-      JMethod lambdaMethod = lambdaExpr.getMethod();
-
-      if (!lambdaMethod.getType().equals(samMethod.getType())) {
-        return true;
-      }
-
-      Iterator<JParameter> lambdaParameter = lambdaMethod.getParams().iterator();
-      for (JParameter samParameter : samMethod.getParams()) {
-        if (!samParameter.getType().isSameType(lambdaParameter.next().getType())) {
-           return true;
-           }
-      }
-      return false;
-    }
   }
 
   @Override
diff --git a/jack/src/com/android/jack/util/CloneExpressionVisitor.java b/jack/src/com/android/jack/util/CloneExpressionVisitor.java
index 8ad82d8..4ae4058 100644
--- a/jack/src/com/android/jack/util/CloneExpressionVisitor.java
+++ b/jack/src/com/android/jack/util/CloneExpressionVisitor.java
@@ -344,8 +344,8 @@
 
   @Override
   public boolean visit(@Nonnull JLambda x) {
-    expression = new JLambda(x.getSourceInfo(), x.getMethod(), x.getType(),
-        x.needToCaptureInstance(), x.getInterfaceBounds());
+    expression = new JLambda(x.getSourceInfo(), x.getMethodIdToImplement(), x.getMethod(),
+        x.getType(), x.needToCaptureInstance(), x.getInterfaceBounds());
     return false;
   }
 }
\ No newline at end of file
diff --git a/jack/src/com/android/jack/util/CloneStatementVisitor.java b/jack/src/com/android/jack/util/CloneStatementVisitor.java
index dab1cf8..9f02e41 100644
--- a/jack/src/com/android/jack/util/CloneStatementVisitor.java
+++ b/jack/src/com/android/jack/util/CloneStatementVisitor.java
@@ -490,8 +490,9 @@
 
   @Override
   public boolean visit(@Nonnull JLambda lambda) {
-    JLambda clonedLambda = new JLambda(lambda.getSourceInfo(), lambda.getMethod(), lambda.getType(),
-        lambda.needToCaptureInstance(), lambda.getInterfaceBounds());
+    JLambda clonedLambda =
+        new JLambda(lambda.getSourceInfo(), lambda.getMethodIdToImplement(), lambda.getMethod(),
+            lambda.getType(), lambda.needToCaptureInstance(), lambda.getInterfaceBounds());
 
     for (JVariableRef capturedVarRef : lambda.getCapturedVariables()) {
       JVariable capturedVar = capturedVarRef.getTarget();
diff --git a/jill/src/com/android/jill/backend/jayce/Token.java b/jill/src/com/android/jill/backend/jayce/Token.java
index 8d18083..6a2005f 100644
--- a/jill/src/com/android/jill/backend/jayce/Token.java
+++ b/jill/src/com/android/jill/backend/jayce/Token.java
@@ -115,6 +115,7 @@
   METHOD("method"),
   METHOD_BODY("body"),
   METHOD_CALL("call"),
+  METHODID_WITH_RETURN_TYPE("mthid-rt"),
   METHOD_LITERAL("method-literal"),
   MOD_OPERATION("%"),
   MUL_OPERATION("*"),
diff --git a/jill/src/com/android/jill/frontend/java/JavaTransformer.java b/jill/src/com/android/jill/frontend/java/JavaTransformer.java
index 0b90108..13b2897 100644
--- a/jill/src/com/android/jill/frontend/java/JavaTransformer.java
+++ b/jill/src/com/android/jill/frontend/java/JavaTransformer.java
@@ -52,13 +52,13 @@
   private static final String LIB_MAJOR_VERSION = "2";
 
   @Nonnull
-  private static final String LIB_MINOR_VERSION = "1";
+  private static final String LIB_MINOR_VERSION = "2";
 
   @Nonnull
   private static final String JAYCE_MAJOR_VERSION = "3";
 
   @Nonnull
-  private static final String JAYCE_MINOR_VERSION = "2";
+  private static final String JAYCE_MINOR_VERSION = "3";
 
   @Nonnull
   private static final String KEY_LIB_MAJOR_VERSION = "lib.version.major";