Skip to content

Commit

Permalink
Support for lambda expressions in an interface's constant initializer…
Browse files Browse the repository at this point in the history
…; the lambda impl method will be moved to a companion class

Fixes #42
  • Loading branch information
luontola committed Jan 6, 2015
1 parent 3413871 commit e3c8647
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 22 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ Version History
([Issue #31](https://github.com/orfjackal/retrolambda/issues/31))
- *Experimental! Can be enabled with the enviroment variable
DEFAULT_METHODS=2*
- Backports lambda expressions in an interface's constant initializer
([Issue #42](https://github.com/orfjackal/retrolambda/issues/42))

### Retrolambda 1.8.0 (2014-11-16)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
// Copyright © 2013-2015 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

Expand Down Expand Up @@ -27,6 +27,9 @@ public byte[] backportLambdaClass(ClassReader reader) {
} else if (FeatureToggles.DEFAULT_METHODS == 2) {
next = new UpdateRelocatedMethodInvocations(next, methodRelocations);
next = new AddMethodDefaultImplementations(next, methodRelocations);
} else {
// needed for lambdas in an interface's constant initializer
next = new UpdateRelocatedMethodInvocations(next, methodRelocations);
}
next = new BackportLambdaClass(next);
return next;
Expand Down Expand Up @@ -56,24 +59,25 @@ public byte[] backportInterface(ClassReader reader) {
next = new RemoveStaticMethods(next);
next = new RemoveDefaultMethodBodies(next);
next = new UpdateRelocatedMethodInvocations(next, methodRelocations);
} else {
// needed for lambdas in an interface's constant initializer
next = new RemoveStaticMethods(next);
}
next = new BackportLambdaInvocations(next);
return next;
});
}

public byte[] extractInterfaceCompanion(ClassReader reader) {
String companion;
if (FeatureToggles.DEFAULT_METHODS == 2
&& (companion = methodRelocations.getCompanionClass(reader.getClassName())) != null) {
return transform(reader, (next) -> {
next = new UpdateRelocatedMethodInvocations(next, methodRelocations);
next = new ExtractInterfaceCompanionClass(next, companion);
return next;
});
} else {
String companion = methodRelocations.getCompanionClass(reader.getClassName());
if (companion == null) {
return null;
}
return transform(reader, (next) -> {
next = new UpdateRelocatedMethodInvocations(next, methodRelocations);
next = new ExtractInterfaceCompanionClass(next, companion);
return next;
});
}

private byte[] transform(ClassReader reader, ClassVisitorChain chain) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
// Copyright © 2013-2015 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

Expand Down Expand Up @@ -34,6 +34,19 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
// do not copy abstract methods to the companion class
return null;
}
if (Flags.isClassInitializer(name, desc, access)) {
// we won't copy constant fields from the interface, so a class initializer won't be needed
return null;
}
if (Flags.hasFlag(access, ACC_STATIC)
&& Flags.hasFlag(access, ACC_PRIVATE)) {
// XXX: Possibly a lambda impl method, which is private. It is the easiest for us to make it visible,
// which should be quite safe because static methods are not inherited (and anyways nothing inherits
// the companion class). The clean solution would be to generate an access method for it, but due to
// the location in code which generates those access methods, it would require complex code changes to
// pass around the information from one transformation to another.
access &= ~ACC_PRIVATE;
}
if (!Flags.hasFlag(access, ACC_STATIC)) {
// default method; make static and take 'this' as the first argument
access |= ACC_STATIC;
Expand All @@ -42,4 +55,10 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
}
return super.visitMethod(access, name, desc, signature, exceptions);
}

@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
// an interface can only contain constant fields; they don't need to be copied
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
// Copyright © 2013-2015 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

Expand All @@ -17,7 +17,7 @@ public RemoveStaticMethods(ClassVisitor next) {

@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (isStaticMethod(access)) {
if (isStaticMethod(access) && !Flags.isClassInitializer(name, desc, access)) {
return null;
} else {
return super.visitMethod(access, name, desc, signature, exceptions);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
// Copyright © 2013-2015 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

Expand Down Expand Up @@ -57,7 +57,7 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
}
if (FeatureToggles.DEFAULT_METHODS == 0
&& isNonAbstractMethodOnInterface(access)
&& !isClassInitializerMethod(name, desc, access)) {
&& !Flags.isClassInitializer(name, desc, access)) {
// In case we have missed a case of Java 8 producing non-abstract methods
// on interfaces, we have this warning here to get a bug report sooner.
// Not allowed by Java 7:
Expand Down Expand Up @@ -85,16 +85,14 @@ private boolean isNonAbstractMethodOnInterface(int methodAccess) {
!Flags.hasFlag(methodAccess, ACC_ABSTRACT);
}

private static boolean isClassInitializerMethod(String name, String desc, int methodAccess) {
return name.equals("<clinit>") &&
desc.equals("()V") &&
Flags.hasFlag(methodAccess, ACC_STATIC);
}

Handle getLambdaAccessMethod(Handle implMethod) {
if (!implMethod.getOwner().equals(className)) {
return implMethod;
}
if (Flags.hasFlag(classAccess, ACC_INTERFACE)) {
// the method will be relocated to a companion class
return implMethod;
}
// TODO: do not generate an access method if the impl method is not private (probably not implementable with a single pass)
String name = "access$lambda$" + lambdaAccessToImplMethods.size();
String desc = implMethod.getTag() == H_INVOKESTATIC
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
// Copyright © 2013-2015 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

package net.orfjackal.retrolambda.util;

import static org.objectweb.asm.Opcodes.ACC_STATIC;

public class Flags {

public static boolean hasFlag(int subject, int flag) {
return (subject & flag) == flag;
}

public static boolean isClassInitializer(String name, String desc, int methodAccess) {
return name.equals("<clinit>") &&
desc.equals("()V") &&
hasFlag(methodAccess, ACC_STATIC);
}
}

0 comments on commit e3c8647

Please sign in to comment.