Skip to content

Commit

Permalink
Make default methods work in lambdas (#714)
Browse files Browse the repository at this point in the history
	Change on 2016/03/07 by lukhnos <lukhnos@google.com>
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=116586412
  • Loading branch information
lukhnos authored and tomball committed Mar 8, 2016
1 parent a594d60 commit 42b8448
Show file tree
Hide file tree
Showing 16 changed files with 308 additions and 47 deletions.
14 changes: 8 additions & 6 deletions jre_emul/Classes/J2ObjC_common.m
Expand Up @@ -222,17 +222,18 @@ void JreCloneVolatileStrong(volatile_id *pVar, volatile_id *pOther) {

// Method to handle dynamic creation of class wrappers surrounding blocks which come from lambdas
// not requiring a capture.
id GetNonCapturingLambda(Protocol *protocol, NSString *blockClassName,
id GetNonCapturingLambda(Class clazz, Protocol *protocol, NSString *blockClassName,
SEL methodSelector, id block) {
// Relies on lambda names being constant strings with matching pointers for matching names.
// This should happen as a clang optimization, as all string constants are kept on the stack for
// the program duration.
LambdaHolder *lambdaHolder = FastPointerLookup(&lambdaLookup, (__bridge void*) blockClassName);
@synchronized(blockClassName) {
if (lambdaHolder->id == nil) {
Class blockClass = objc_allocateClassPair([NSObject class], [blockClassName UTF8String], 0);
Class blockClass = objc_allocateClassPair(clazz ? : [NSObject class],
[blockClassName UTF8String], 0);
// Fail quickly if we can't create the runtime class.
if (!class_addProtocol(blockClass, protocol)) {
if (protocol && !class_addProtocol(blockClass, protocol)) {
@throw AUTORELEASE([[JavaLangAssertionError alloc]
initWithId:@"Unable to add protocol to non-capturing lambda class."]);
}
Expand All @@ -251,14 +252,15 @@ id GetNonCapturingLambda(Protocol *protocol, NSString *blockClassName,

// Method to handle dynamic creation of class wrappers surrounding blocks from lambdas requiring
// a capture.
id GetCapturingLambda(Protocol *protocol, NSString *blockClassName,
id GetCapturingLambda(Class clazz, Protocol *protocol, NSString *blockClassName,
SEL methodSelector, id blockWrapper, id block) {
LambdaHolder *lambdaHolder = FastPointerLookup(&lambdaLookup, (__bridge void*) blockClassName);
@synchronized(blockClassName) {
if (lambdaHolder->id == nil) {
Class lambdaClass = objc_allocateClassPair([NSObject class], [blockClassName UTF8String], 0);
Class lambdaClass = objc_allocateClassPair(clazz ? : [NSObject class],
[blockClassName UTF8String], 0);
// Fail quickly if we can't create the runtime class.
if (!class_addProtocol(lambdaClass, protocol)) {
if (protocol && !class_addProtocol(lambdaClass, protocol)) {
@throw AUTORELEASE([[JavaLangAssertionError alloc]
initWithId:@"Unable to add protocol to capturing lambda class."]);
}
Expand Down
6 changes: 3 additions & 3 deletions jre_emul/Classes/J2ObjC_source.h
Expand Up @@ -138,10 +138,10 @@ __attribute__((always_inline)) inline void JreCheckFinalize(id self, Class cls)
return self;
#endif

// Defined in JreEmulation.m
FOUNDATION_EXPORT id GetNonCapturingLambda(Protocol *protocol,
// Defined in J2ObjC_common.m
FOUNDATION_EXPORT id GetNonCapturingLambda(Class clazz, Protocol *protocol,
NSString *blockClassName, SEL methodSelector, id block);
FOUNDATION_EXPORT id GetCapturingLambda(Protocol *protocol,
FOUNDATION_EXPORT id GetCapturingLambda(Class clazz, Protocol *protocol,
NSString *blockClassName, SEL methodSelector, id wrapperBlock, id block);

#define J2OBJC_IGNORE_DESIGNATED_BEGIN \
Expand Down
128 changes: 128 additions & 0 deletions jre_emul/Tests/com/google/j2objc/java8/DefaultMethodsTest.java
@@ -0,0 +1,128 @@
/*
* 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.google.j2objc.java8;

import static junit.framework.Assert.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;

import junit.framework.TestCase;

/**
* Tests for default method support.
*
* @author Lukhnos Liu
*/
public class DefaultMethodsTest extends TestCase {
interface A {
default boolean f() {
return true;
}

default boolean notF() {
return !f();
}

default String getPrefix() {
return "";
}

default String getTag(String name) {
return getPrefix() + name;
}

default String getDefaultTag() {
return getTag("default");
}
}

interface B extends A {
boolean f();

// TODO: This should return a capturing lambda to capture the implicit this/self.
default B not() {
return () -> !f();
}
}

interface C extends A {
default boolean f() {
return !A.super.f();
}
}

interface D extends A {
static String getDPrefix() {
return "static-";
}

String getTag(String name);
}

static class AP implements A {
}

static class AQ implements A {
@Override
public boolean f() {
return false;
}

@Override
public String getPrefix() {
return "prefix-";
}

@Override
public String getDefaultTag() {
return getTag("DEFAULT");
}
}

public void testBasicInstantiation() {
AP a = new AP();
assertTrue(a.f());
assertFalse(a.notF());
assertEquals("default", a.getDefaultTag());
}

public void testSuperInvocation() {
A c = new C() {};
assertFalse(c.f());
assertTrue(c.notF());
}

public void testOverriding() {
AQ a = new AQ();
assertFalse(a.f());
assertTrue(a.notF());
assertEquals("prefix-DEFAULT", a.getDefaultTag());
}

public void testLambdaWithDefaultMethods() {
B b1 = () -> true;
assertTrue(b1.f());
assertFalse(b1.notF());
// TODO: Enable this assertion once the non-capturing lambda bug is fixed.
// assertFalse(b1.not().f());

B b2 = () -> false;
assertFalse(b2.f());
assertTrue(b2.notF());
// assertTrue(b2.not().f());

D d = (name) -> D.getDPrefix() + name;
assertEquals("static-default", d.getDefaultTag());
}
}
1 change: 1 addition & 0 deletions jre_emul/Tests/com/google/j2objc/java8/SmallTests.java
Expand Up @@ -22,6 +22,7 @@
public class SmallTests {
private static final Class<?>[] smallTestClasses = new Class[] {
CreationReferenceTest.class,
DefaultMethodsTest.class,
ExpressionMethodReferenceTest.class,
LambdaTest.class,
SuperMethodReferenceTest.class,
Expand Down
1 change: 1 addition & 0 deletions jre_emul/tests.mk
Expand Up @@ -612,6 +612,7 @@ COPIED_ARC_TEST_SOURCES = \

JAVA8_TEST_SOURCES := \
com/google/j2objc/java8/CreationReferenceTest.java \
com/google/j2objc/java8/DefaultMethodsTest.java \
com/google/j2objc/java8/ExpressionMethodReferenceTest.java \
com/google/j2objc/java8/LambdaTest.java \
com/google/j2objc/java8/SuperMethodReferenceTest.java \
Expand Down
Expand Up @@ -785,14 +785,28 @@ private void printCallWithoutBlocksInner(ITypeBinding functionalTypeBinding, Str
boolean isCapturing) {
String functionalClassName = nameTable.getFullName(functionalTypeBinding);
IMethodBinding functionalInterface = functionalTypeBinding.getFunctionalInterfaceMethod();
boolean hasDefaultMethods = BindingUtil.hasDefaultMethodsInFamily(functionalTypeBinding);
if (isCapturing) {
buffer.append("GetCapturingLambda(");
} else {
buffer.append("GetNonCapturingLambda(");
}
buffer.append("@protocol(");
buffer.append(functionalClassName);
buffer.append("), @\"");
if (hasDefaultMethods) {
buffer.append("[");
buffer.append(functionalClassName);
buffer.append(" class]");
} else {
buffer.append("NULL");
}
buffer.append(", ");
if (hasDefaultMethods) {
buffer.append("NULL");
} else {
buffer.append("@protocol(");
buffer.append(functionalClassName);
buffer.append(")");
}
buffer.append(", @\"");
buffer.append(newClassName);
buffer.append("\", @selector(");
buffer.append(nameTable.getMethodSelector(functionalInterface));
Expand Down
Expand Up @@ -350,6 +350,8 @@ protected void printCompanionClassDeclaration() {
} else {
newline();
}
} else if (BindingUtil.hasDefaultMethodsInFamily(typeBinding)) {
printf(" < %s >", typeName);
} else {
newline();
}
Expand Down
Expand Up @@ -232,9 +232,10 @@ private boolean hasStaticAccessorMethods() {
}

protected boolean needsPublicCompanionClass() {
return !typeNode.hasPrivateDeclaration()
&& (hasInitializeMethod() || BindingUtil.isRuntimeAnnotation(typeBinding)
|| hasStaticAccessorMethods());
return BindingUtil.hasDefaultMethodsInFamily(typeBinding)
|| (!typeNode.hasPrivateDeclaration()
&& (hasInitializeMethod() || BindingUtil.isRuntimeAnnotation(typeBinding)
|| hasStaticAccessorMethods()));
}

protected boolean needsCompanionClass() {
Expand Down
Expand Up @@ -248,9 +248,10 @@ private boolean isDesignatedInitializer(IMethodBinding method) {

@Override
protected void printMethodDeclaration(MethodDeclaration m) {
if (typeBinding.isInterface() || Modifier.isAbstract(m.getModifiers())) {
if (Modifier.isAbstract(m.getModifiers())) {
return;
}

newline();
boolean isDesignatedInitializer = isDesignatedInitializer(m.getMethodBinding());
if (isDesignatedInitializer) {
Expand Down
Expand Up @@ -37,8 +37,9 @@
import java.util.Set;

/**
* Checks for missing methods that would cause an ObjC compilation error.
* Adds stubs for existing abstract methods.
* Checks for missing methods that would cause an ObjC compilation error. Adds stubs for existing
* abstract methods. Adds the ABSTRACT bit to a MethodDeclaration node if the method is a
* non-default one from an interface.
*
* @author Tom Ball, Keith Stanger
*/
Expand All @@ -52,12 +53,28 @@ public AbstractMethodRewriter(CompilationUnit unit) {

@Override
public void endVisit(MethodDeclaration node) {
if (Modifier.isAbstract(node.getModifiers())) {
if (!TranslationUtil.needsReflection(node.getMethodBinding().getDeclaringClass())) {
IMethodBinding methodBinding = node.getMethodBinding();

if (BindingUtil.isAbstract(methodBinding)) {
// JDT only adds the abstract bit to a MethodDeclaration node's modifiers if the abstract
// method is from a class. Since we want our code generator to go over an interface's
// method nodes for default method support and skip abstract methods, we add the bit if the
// method is from an interface.
ITypeBinding declaringClass = methodBinding.getDeclaringClass();
boolean isInterface = declaringClass.isInterface();
if (isInterface) {
node.addModifiers(Modifier.ABSTRACT);
}

// There's no need to stub out an abstract method for an interface's companion class.
// Similarly, if this is an abstract method in a class and there's no need for reflection,
// we skip the stubbing out.
if (isInterface || !TranslationUtil.needsReflection(declaringClass)) {
unit.setHasIncompleteProtocol();
unit.setHasIncompleteImplementation();
return;
}

Block body = new Block();
// Generate a body which throws a NSInvalidArgumentException.
String bodyCode = "// can't call an abstract method\n"
Expand Down
Expand Up @@ -75,17 +75,21 @@ public void endVisit(TypeDeclaration node) {
* that implements I2 can only be an abstract class. If C is not abstract, it results in a
* compiler error. Of course, if C is an abstract class because of the abstract M, we should never
* generate a shim for M.
*
* Note that the node parameter here can be a class or an interface. If node is an interface,
* the shim methods added here will go into its companion class. We need the shims so that lambdas
* based on the interface will also carry the default methods.
*/
private void addDefaultMethodShims(AbstractTypeDeclaration node) {
ITypeBinding type = node.getTypeBinding();
if (type.isInterface() || type.isAnnotation()) {
if (type.isAnnotation()) {
return;
}

// First, collect all interfaces that are implemented by this type.
Set<ITypeBinding> interfaces = BindingUtil.getAllInterfaces(type);

// Now, collect those implemented by the super.
// Now, collect those implemented by the super. This gets an empty set if type is an interface.
Set<ITypeBinding> implementedBySuper = BindingUtil.getAllInterfaces(type.getSuperclass());

// Remove those already implemented by super. These are the interfaces we care about. This
Expand Down Expand Up @@ -131,6 +135,10 @@ private void addDefaultMethodShims(AbstractTypeDeclaration node) {

// Create the method binding and declaration.
GeneratedMethodBinding binding = new GeneratedMethodBinding(method);

// Don't carry over the default method flag from the original binding.
binding.setModifiers(binding.getModifiers() & ~BindingUtil.ACC_DEFAULT);

binding.setDeclaringClass(type);
MethodDeclaration methodDecl = new MethodDeclaration(binding);

Expand Down
Expand Up @@ -248,8 +248,7 @@ public void endVisit(ClassInstanceCreation node) {
public void endVisit(MethodDeclaration node) {
IMethodBinding binding = node.getMethodBinding();
boolean isInstanceMethod = !BindingUtil.isStatic(binding) && !binding.isConstructor();
boolean isInterface = binding.getDeclaringClass().isInterface();
boolean isDefaultMethod = BindingUtil.isDefault(binding) && isInterface;
boolean isDefaultMethod = BindingUtil.isDefault(binding);
FunctionDeclaration function = null;
List<BodyDeclaration> declarationList = TreeUtil.asDeclarationSublist(node);
List<String> extraSelectors = nameTable.getExtraSelectors(binding);
Expand Down

0 comments on commit 42b8448

Please sign in to comment.