From d05d92de40542e85f9f26712d976e710be82914e Mon Sep 17 00:00:00 2001 From: kirbs Date: Wed, 22 Jul 2015 10:32:47 -0700 Subject: [PATCH] Refactored lambda runtime class generators. Added creation references. Change on 2015/07/22 by kirbs ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=98846613 --- jre_emul/Classes/J2ObjC_source.h | 4 +- jre_emul/Classes/JreEmulation.m | 103 +++++--------- .../j2objc/java8/CreationReferenceTest.java | 100 +++++++++++++ .../com/google/j2objc/java8/SmallTests.java | 1 + jre_emul/tests.mk | 1 + .../devtools/j2objc/ast/MethodReference.java | 8 ++ .../j2objc/gen/StatementGenerator.java | 131 ++++++++++++++++-- .../google/devtools/j2objc/SmallTests.java | 4 +- .../LambdaExpressionTest.java | 6 +- .../j2objc/ast/MethodReferenceTest.java | 67 +++++++++ 10 files changed, 339 insertions(+), 86 deletions(-) create mode 100644 jre_emul/Tests/com/google/j2objc/java8/CreationReferenceTest.java rename translator/src/test/java/com/google/devtools/j2objc/{translate => ast}/LambdaExpressionTest.java (97%) create mode 100644 translator/src/test/java/com/google/devtools/j2objc/ast/MethodReferenceTest.java diff --git a/jre_emul/Classes/J2ObjC_source.h b/jre_emul/Classes/J2ObjC_source.h index 048413a90a..51ffe0970c 100644 --- a/jre_emul/Classes/J2ObjC_source.h +++ b/jre_emul/Classes/J2ObjC_source.h @@ -36,8 +36,8 @@ FOUNDATION_EXPORT void JreRelease(id obj); // Defined in JreEmulation.m FOUNDATION_EXPORT id GetNonCapturingLambda(Class baseClass, Protocol *protocol, NSString *blockClassName, SEL methodSelector, id block); -FOUNDATION_EXPORT id GetCapturingLambda(int argumentCount, Class baseClass, Protocol *protocol, - NSString *blockClassName, SEL methodSelector, id block); +FOUNDATION_EXPORT id GetCapturingLambda(Class baseClass, Protocol *protocol, + NSString *blockClassName, SEL methodSelector, id wrapperBlock, id block); /*! * Returns correct result when casting a double to an integral type. In C, a diff --git a/jre_emul/Classes/JreEmulation.m b/jre_emul/Classes/JreEmulation.m index d0a133cfda..b213957b3c 100644 --- a/jre_emul/Classes/JreEmulation.m +++ b/jre_emul/Classes/JreEmulation.m @@ -91,6 +91,32 @@ id JreRetainVolatile(volatile_id *pVar) { return [value retain]; } +// Block flag position for copy dispose, (1 << 25). +#define COPY_DISPOSE_FLAG 0x02000000 + +// Modified from clang block implementation http://clang.llvm.org/docs/Block-ABI-Apple.html +typedef struct Block_literal_1 { + void *isa; + int flags; + int reserved; + void (*invoke)(void *, ...); + struct Block_descriptor_1 { + unsigned long int reserved; + unsigned long int size; + // There will be 2 function pointers at the beginning of the signature if the copy_dispose flag + // is set. + void *signature[1]; + } *descriptor; +} Block_literal; + +// Returns a type string from a block. +const char *blockTypeSignature(id block) { + Block_literal *blockLiteral = (__bridge void *) block; + // Offset for optional function pointers. + int i = (blockLiteral->flags & COPY_DISPOSE_FLAG) ? 2 : 0; + return (const char *) blockLiteral->descriptor->signature[i]; +} + typedef struct { void *id; } LambdaHolder; @@ -119,10 +145,9 @@ id GetNonCapturingLambda(Class baseClass, Protocol *protocol, NSString *blockCla @throw AUTORELEASE([[JavaLangAssertionError alloc] initWithNSString:@"Unable to add protocol to non-capturing lambda class."]); } - Method method = class_getInstanceMethod(baseClass, methodSelector); - const char *types = method_getTypeEncoding(method); IMP block_implementation = imp_implementationWithBlock(block); - if (!class_addMethod(blockClass, methodSelector, block_implementation, types)) { + if (!class_addMethod(blockClass, methodSelector, block_implementation, + blockTypeSignature(block))) { @throw AUTORELEASE([[JavaLangAssertionError alloc] initWithNSString:@"Unable to add method to non-capturing lambda class."]); } @@ -133,82 +158,22 @@ id GetNonCapturingLambda(Class baseClass, Protocol *protocol, NSString *blockCla return (__bridge id) lambdaHolder->id; } -// Having this hardcoded is definitely not ideal, and I would love a dynamic solution to generated -// capturingLambdaBlockCallers that doesn't rely on packing and unpacking arrays for each lambda. -id capturingLambdaBlockCallers[10]; -char *capturingLambdaBlockTypes[10]; - // Method to handle dynamic creation of class wrappers surrounding blocks from lambdas requiring // a capture. -id GetCapturingLambda(int argumentCount, Class baseClass, Protocol *protocol, - NSString *blockClassName, SEL methodSelector, id block) { - static dispatch_once_t once; - dispatch_once(&once, ^{ - char typeHolder[10]; - for(int i = 0; i < 10; i++) { - if (i) { - typeHolder[i - 1] = '@'; - } - typeHolder[i] = 0; - // strcpy returns the address of the destination string, so we can use one call for writing - // and assignment. - capturingLambdaBlockTypes[i] = strcpy(malloc(i + 1), typeHolder); - } - - capturingLambdaBlockCallers[0] = ^id(id _self){ - id (^block)() = objc_getAssociatedObject(_self, (void *) 'b'); - return block(_self); - }; - capturingLambdaBlockCallers[1] = ^id(id _self, id a){ - id (^block)() = objc_getAssociatedObject(_self, (void *) 'b'); - return block(_self, a); - }; - capturingLambdaBlockCallers[2] = ^id(id _self, id a, id b){ - id (^block)() = objc_getAssociatedObject(_self, (void *) 'b'); - return block(_self, a, b); - }; - capturingLambdaBlockCallers[3] = ^id(id _self, id a, id b, id c){ - id (^block)() = objc_getAssociatedObject(_self, (void *) 'b'); - return block(_self, a, b, c); - }; - capturingLambdaBlockCallers[4] = ^id(id _self, id a, id b, id c, id d){ - id (^block)() = objc_getAssociatedObject(_self, (void *) 'b'); - return block(_self, a, b, c, d); - }; - capturingLambdaBlockCallers[5] = ^id(id _self, id a, id b, id c, id d, id e){ - id (^block)() = objc_getAssociatedObject(_self, (void *) 'b'); - return block(_self, a, b, c, d, e); - }; - capturingLambdaBlockCallers[6] = ^id(id _self, id a, id b, id c, id d, id e, id f){ - id (^block)() = objc_getAssociatedObject(_self, (void *) 'b'); - return block(_self, a, b, c, d, e, f); - }; - capturingLambdaBlockCallers[7] = ^id(id _self, id a, id b, id c, id d, id e, id f, id g){ - id (^block)() = objc_getAssociatedObject(_self, (void *) 'b'); - return block(_self, a, b, c, d, e, f, g); - }; - capturingLambdaBlockCallers[8] = ^id(id _self, id a, id b, id c, id d, id e, id f, id g, id h, id i){ - id (^block)() = objc_getAssociatedObject(_self, (void *) 'b'); - return block(_self, a, b, c, d, e, f, g, h); - }; - capturingLambdaBlockCallers[9] = ^id(id _self, id a, id b, id c, id d, id e, id f, id g, id h, id i){ - id (^block)() = objc_getAssociatedObject(_self, (void *) 'b'); - return block(_self, a, b, c, d, e, f, g, h, i); - }; - }); - +id GetCapturingLambda(Class baseClass, Protocol *protocol, NSString *blockClassName, + SEL methodSelector, id blockWrapper, id block) { LambdaHolder *lambdaHolder = FastPointerLookup(&lambdaLookup, (__bridge void*) blockClassName); @synchronized(baseClass) { if (lambdaHolder->id == nil) { - Class lambdaClass = objc_allocateClassPair(baseClass, [blockClassName UTF8String], sizeof(id)); + Class lambdaClass = objc_allocateClassPair(baseClass, [blockClassName UTF8String], 0); // Fail quickly if we can't create the runtime class. if (!class_addProtocol(lambdaClass, protocol)) { @throw AUTORELEASE([[JavaLangAssertionError alloc] initWithNSString:@"Unable to add protocol to capturing lambda class."]); } - IMP block_implementation = imp_implementationWithBlock(capturingLambdaBlockCallers[argumentCount]); + IMP block_implementation = imp_implementationWithBlock(blockWrapper); if (!class_addMethod([lambdaClass class], methodSelector, block_implementation, - capturingLambdaBlockTypes[argumentCount])) { + blockTypeSignature(blockWrapper))) { @throw AUTORELEASE([[JavaLangAssertionError alloc] initWithNSString:@"Unable to add method to capturing lambda class."]); } @@ -217,7 +182,7 @@ id GetCapturingLambda(int argumentCount, Class baseClass, Protocol *protocol, } } id instance = [[(id) lambdaHolder->id alloc] init]; - objc_setAssociatedObject(instance, (void*) 'b', [block copy], OBJC_ASSOCIATION_ASSIGN); + objc_setAssociatedObject(instance, (void*) 0, block, OBJC_ASSOCIATION_COPY_NONATOMIC); return instance; } diff --git a/jre_emul/Tests/com/google/j2objc/java8/CreationReferenceTest.java b/jre_emul/Tests/com/google/j2objc/java8/CreationReferenceTest.java new file mode 100644 index 0000000000..9e9036cabe --- /dev/null +++ b/jre_emul/Tests/com/google/j2objc/java8/CreationReferenceTest.java @@ -0,0 +1,100 @@ +/* + * 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 junit.framework.TestCase; + +interface FunInt { + T apply(int x); +} + +interface FunInt4 { + T apply(int x, I j, String s, Object o); +} + +interface Call { + T call(); +} + +class I { + String s = "..."; + I() { } + I(int x) { + s = "" + x; + } + I(int x, I j, String s, Object o) { + this.s = s; + } + static String hello() { + return "Hello"; + } + String world() { + return "World"; + } + String getS() { + return s; + } +} + +final class J { + int x = 41; + J() { } + J(int x) { + this.x = x; + } + J(int x, I j, String s, Object o) { + this.x = x; + } + static String hello() { + return "Hello"; + } + String world() { + return "World"; + } + int getX() { + return x; + } +} + +/** + * Command-line tests for creation references. + * + * @author Seth Kirby + */ +public class CreationReferenceTest extends TestCase { + public CreationReferenceTest() {} + + public void testBasicReferences() throws Exception { + Call iInit = I::new; + FunInt iInit2 = I::new; + FunInt4 iInit3 = I::new; + I myI = iInit.call(); + I myI2 = iInit2.apply(42); + I myI3 = iInit3.apply(0, myI, "43", ""); + assertEquals("World", myI.world()); + assertEquals("...", myI.getS()); + assertEquals("42", myI2.getS()); + assertEquals("43", myI3.getS()); + Call jInit = J::new; + FunInt jInit2 = J::new; + FunInt4 jInit3 = J::new; + J myJ = jInit.call(); + J myJ2 = jInit2.apply(42); + J myJ3 = jInit3.apply(43, myI, "", ""); + assertEquals("World", myJ.world()); + assertEquals(41, myJ.getX()); + assertEquals(42, myJ2.getX()); + assertEquals(43, myJ3.getX()); + } +} diff --git a/jre_emul/Tests/com/google/j2objc/java8/SmallTests.java b/jre_emul/Tests/com/google/j2objc/java8/SmallTests.java index c5bd134ddb..bbc78fafb9 100644 --- a/jre_emul/Tests/com/google/j2objc/java8/SmallTests.java +++ b/jre_emul/Tests/com/google/j2objc/java8/SmallTests.java @@ -21,6 +21,7 @@ */ public class SmallTests { private static final Class[] smallTestClasses = new Class[] { + CreationReferenceTest.class, LambdaTest.class }; diff --git a/jre_emul/tests.mk b/jre_emul/tests.mk index abbf827827..8a31de072a 100644 --- a/jre_emul/tests.mk +++ b/jre_emul/tests.mk @@ -517,6 +517,7 @@ SUITE_SOURCES = \ org/json/SmallTests.java \ JAVA8_TEST_SOURCES := \ + com/google/j2objc/java8/CreationReferenceTest.java \ com/google/j2objc/java8/LambdaTest.java \ JAVA8_SUITE_SOURCES = \ diff --git a/translator/src/main/java/com/google/devtools/j2objc/ast/MethodReference.java b/translator/src/main/java/com/google/devtools/j2objc/ast/MethodReference.java index 31d3acd043..1a23f3b5f5 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/ast/MethodReference.java +++ b/translator/src/main/java/com/google/devtools/j2objc/ast/MethodReference.java @@ -13,6 +13,7 @@ */ package com.google.devtools.j2objc.ast; +import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import java.util.List; @@ -32,11 +33,13 @@ public abstract class MethodReference extends Expression { protected ITypeBinding typeBinding; + protected IMethodBinding methodBinding; protected ChildList typeArguments = ChildList.create(Type.class, this); public MethodReference(org.eclipse.jdt.core.dom.MethodReference jdtNode) { super(jdtNode); typeBinding = jdtNode.resolveTypeBinding(); + methodBinding = jdtNode.resolveMethodBinding(); for (Object x : jdtNode.typeArguments()) { typeArguments.add((Type) TreeConverter.convert(x)); } @@ -45,6 +48,7 @@ public MethodReference(org.eclipse.jdt.core.dom.MethodReference jdtNode) { public MethodReference(MethodReference other) { super(other); typeBinding = other.getTypeBinding(); + methodBinding = other.getMethodBinding(); typeArguments.copyFrom(other.typeArguments()); } @@ -53,6 +57,10 @@ public ITypeBinding getTypeBinding() { return typeBinding; } + public IMethodBinding getMethodBinding() { + return methodBinding; + } + public List typeArguments() { return typeArguments; } diff --git a/translator/src/main/java/com/google/devtools/j2objc/gen/StatementGenerator.java b/translator/src/main/java/com/google/devtools/j2objc/gen/StatementGenerator.java index 9ad1de39e2..44f3e32ba3 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/gen/StatementGenerator.java +++ b/translator/src/main/java/com/google/devtools/j2objc/gen/StatementGenerator.java @@ -441,7 +441,53 @@ public boolean visit(ContinueStatement node) { @Override public boolean visit(CreationReference node) { // TODO(kirbs): Implement correct conversion of Java 8 features to Objective-C. - return assertIncompleteJava8Support(node); + assert Options + .isJava8Translator() : "CreationReference in translator with -source less than 8."; + ITypeBinding functionalTypeBinding = node.getTypeBinding(); + ITypeBinding returnType = node.getType().getTypeBinding(); + printCreationReferenceCall(returnType, functionalTypeBinding, node.getMethodBinding()); + return false; + } + + /** + * Generates a creation reference using a block wrapper surrounding a new_Type_init call. This + * block is used to create a new function class. Currently a seperate class will be created for + * each unique FullFunctionName of the creation reference method bindings. We could reduce the + * number of class types by using the capturing lambda construct and generating new class names + * based on return type and selector. + */ + public void printCreationReferenceCall(ITypeBinding returnType, + ITypeBinding functionalTypeBinding, IMethodBinding methodBinding) { + String functionalClassName = nameTable.getFullName(functionalTypeBinding); + String newClassName = nameTable.getFullFunctionName(methodBinding); + printLambdaCallWithoutBlocks(functionalTypeBinding.getFunctionalInterfaceMethod(), + functionalClassName, newClassName, methodBinding, false); + buffer.append('^'); + buffer.append(nameTable.getSpecificObjCType(returnType)); + // Required argument for imp_implementationWithBlock. + buffer.append("(id _self"); + char var = 'a'; + for (ITypeBinding t : methodBinding.getParameterTypes()) { + buffer.append(", "); + buffer.append(nameTable.getSpecificObjCType(t)); + buffer.append(' '); + buffer.append(var++); + } + buffer.append(") {\n return "); + buffer.append(nameTable.getAllocatingConstructorName(methodBinding)); + buffer.append("("); + var = 'a'; + boolean delimiterFlag = false; + for (int i = 0; i < methodBinding.getParameterTypes().length; i++) { + if (delimiterFlag) { + buffer.append(", "); + } else { + delimiterFlag = true; + } + buffer.append(var++); + } + buffer.append(");\n}"); + buffer.append(")"); } @Override @@ -635,11 +681,66 @@ public boolean visit(LambdaExpression node) { assert Options.isJava8Translator() : "Lambda expression in translator with -source less than 8."; IMethodBinding functionalInterface = node.getFunctionalInterfaceMethod(); - String functionalClassName = nameTable.getFullName(node.functionalTypeBinding()); - if (node.isCapturing()) { - buffer.append("GetCapturingLambda("); - buffer.append(node.getParameters().size()); + printLambdaCall(functionalInterface, node.functionalTypeBinding(), node.getMethodBinding(), + node.getParameters(), node.isCapturing()); + node.getBody().accept(this); + buffer.append(")"); + return false; + } + + /** + * Creates a block that is swizzled in as a class method in created capturing lambdas at runtime. + * This outer block calls the underlying block for each lambda instance. Each captured lambda has + * an outer block that matches the function signature of the functional interface, which calls an + * underlying block specific to the instance. + */ + public void printBlockCallWrapper(IMethodBinding binding) { + buffer.append('^'); + buffer.append(nameTable.getSpecificObjCType(binding.getReturnType())); + // Required argument for imp_implementationWithBlock. + buffer.append("(id _self"); + char var = 'a'; + for (ITypeBinding t : binding.getParameterTypes()) { buffer.append(", "); + buffer.append(nameTable.getSpecificObjCType(t)); + buffer.append(' '); + buffer.append(var++); + } + buffer.append(") {\n id (^block)() = objc_getAssociatedObject(_self, (void *) 0);\n"); + if (!BindingUtil.isVoid(binding.getReturnType())) { + buffer.append("return "); + } + buffer.append("block(_self"); + var = 'a'; + for (int i = 0; i < binding.getParameterTypes().length; i++) { + buffer.append(", "); + buffer.append(var++); + } + buffer.append(");\n},\n"); + } + + /** + * Creates a lambda call by combining the call without blocks and the call blocks, so that the + * calling portion sans blocks can be reused by method references. + */ + public void printLambdaCall(IMethodBinding functionalInterface, + ITypeBinding functionalTypeBinding, IMethodBinding methodBinding, + List parameters, boolean isCapturing) { + String functionalClassName = nameTable.getFullName(functionalTypeBinding); + String createdClassName = nameTable.getFullLambdaName(methodBinding); + printLambdaCallWithoutBlocks(functionalInterface, functionalClassName, createdClassName, + methodBinding, isCapturing); + printLambdaCallBlocks(functionalInterface, parameters, isCapturing); + } + + /** + * The lambda call sans wrapper and method blocks. + */ + public void printLambdaCallWithoutBlocks(IMethodBinding functionalInterface, + String functionalClassName, String newClassName, IMethodBinding methodBinding, + boolean isCapturing) { + if (isCapturing) { + buffer.append("GetCapturingLambda("); } else { buffer.append("GetNonCapturingLambda("); } @@ -648,14 +749,25 @@ public boolean visit(LambdaExpression node) { buffer.append(" class], @protocol("); buffer.append(functionalClassName); buffer.append("), @\""); - buffer.append(nameTable.getFullLambdaName(node.getMethodBinding())); + buffer.append(newClassName); buffer.append("\", @selector("); buffer.append(nameTable.getMethodSelector(functionalInterface)); - buffer.append("), ^"); + buffer.append("),\n"); + } + + /** + * The lambda wrapper and method blocks. + */ + public void printLambdaCallBlocks(IMethodBinding functionalInterface, + List parameters, boolean isCapturing) { + if (isCapturing) { + printBlockCallWrapper(functionalInterface); + } + buffer.append('^'); buffer.append(nameTable.getSpecificObjCType(functionalInterface.getReturnType())); // Required argument for imp_implementationWithBlock. buffer.append("(id _self"); - for (VariableDeclaration x : node.getParameters()) { + for (VariableDeclaration x : parameters) { IVariableBinding variableBinding = x.getVariableBinding(); buffer.append(", "); buffer.append(nameTable.getSpecificObjCType(x.getVariableBinding().getType())); @@ -663,9 +775,6 @@ public boolean visit(LambdaExpression node) { buffer.append(nameTable.getVariableQualifiedName(variableBinding.getVariableDeclaration())); } buffer.append(")"); - node.getBody().accept(this); - buffer.append(")"); - return false; } @Override diff --git a/translator/src/test/java/com/google/devtools/j2objc/SmallTests.java b/translator/src/test/java/com/google/devtools/j2objc/SmallTests.java index 4543f51424..cf756c3079 100644 --- a/translator/src/test/java/com/google/devtools/j2objc/SmallTests.java +++ b/translator/src/test/java/com/google/devtools/j2objc/SmallTests.java @@ -16,6 +16,8 @@ package com.google.devtools.j2objc; +import com.google.devtools.j2objc.ast.LambdaExpressionTest; +import com.google.devtools.j2objc.ast.MethodReferenceTest; import com.google.devtools.j2objc.ast.TreeConvertTest; import com.google.devtools.j2objc.ast.TreeUtilTest; import com.google.devtools.j2objc.gen.ArrayAccessTest; @@ -50,7 +52,6 @@ import com.google.devtools.j2objc.translate.InnerClassExtractorTest; import com.google.devtools.j2objc.translate.JavaCloneWriterTest; import com.google.devtools.j2objc.translate.JavaToIOSMethodTranslatorTest; -import com.google.devtools.j2objc.translate.LambdaExpressionTest; import com.google.devtools.j2objc.translate.NilCheckResolverTest; import com.google.devtools.j2objc.translate.OcniExtractorTest; import com.google.devtools.j2objc.translate.OperatorRewriterTest; @@ -149,6 +150,7 @@ public static Test suite() { Class.forName("java.lang.invoke.LambdaMetafactory"); // Running with Java 8 JRE, add test classes that depend on it. + testSuite.addTestSuite(MethodReferenceTest.class); testSuite.addTestSuite(LambdaExpressionTest.class); } catch (ClassNotFoundException e) { // Running on pre-Java 8 JRE. diff --git a/translator/src/test/java/com/google/devtools/j2objc/translate/LambdaExpressionTest.java b/translator/src/test/java/com/google/devtools/j2objc/ast/LambdaExpressionTest.java similarity index 97% rename from translator/src/test/java/com/google/devtools/j2objc/translate/LambdaExpressionTest.java rename to translator/src/test/java/com/google/devtools/j2objc/ast/LambdaExpressionTest.java index 21edeae5aa..60a161d273 100644 --- a/translator/src/test/java/com/google/devtools/j2objc/translate/LambdaExpressionTest.java +++ b/translator/src/test/java/com/google/devtools/j2objc/ast/LambdaExpressionTest.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.devtools.j2objc.translate; +package com.google.devtools.j2objc.ast; import com.google.devtools.j2objc.GenerationTest; import com.google.devtools.j2objc.Options; @@ -71,8 +71,8 @@ public void testObjectSelfAddition() throws IOException { public void testTypeInference() throws IOException { String quadObjectTranslation = translateSourceFile( fourToOneHeader + "class Test { FourToOne f = (a, b, c, d) -> 1;}", "Test", "Test.m"); - assertTranslation(quadObjectTranslation, - "@selector(applyWithId:withId:withId:withId:), ^id(id _self, id a, id b, id c, id d)"); + assertTranslatedSegments(quadObjectTranslation, "@selector(applyWithId:withId:withId:withId:)", + "^id(id _self, id a, id b, id c, id d)"); String mixedObjectTranslation = translateSourceFile(fourToOneHeader + "class Test { FourToOne f = " + "(a, b, c, d) -> \"1\";}", "Test", "Test.m"); diff --git a/translator/src/test/java/com/google/devtools/j2objc/ast/MethodReferenceTest.java b/translator/src/test/java/com/google/devtools/j2objc/ast/MethodReferenceTest.java new file mode 100644 index 0000000000..2d121c105f --- /dev/null +++ b/translator/src/test/java/com/google/devtools/j2objc/ast/MethodReferenceTest.java @@ -0,0 +1,67 @@ +/* + * 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.devtools.j2objc.ast; + +import com.google.devtools.j2objc.GenerationTest; +import com.google.devtools.j2objc.Options; +import com.google.devtools.j2objc.util.FileUtil; + +import java.io.IOException; + +/** + * Unit tests for {@link MethodReference}. + * + * @author Seth Kirby + */ +public class MethodReferenceTest extends GenerationTest { + @Override + protected void setUp() throws IOException { + tempDir = FileUtil.createTempDir("testout"); + Options.load(new String[] { "-d", tempDir.getAbsolutePath(), "-sourcepath", + tempDir.getAbsolutePath(), "-q", // Suppress console output. + "-encoding", "UTF-8", // Translate strings correctly when encodings are nonstandard. + "-source", "8", // Treat as Java 8 source. + "-Xforce-incomplete-java8" // Internal flag to force Java 8 support. + }); + parser = GenerationTest.initializeParser(tempDir); + } + + private String classIHeader = + "class I { I() { } I(int x) { } I(int x, I j, String s, Object o) { } }\n" + + "interface FunInt { T apply(int x); }" + + "interface FunInt4 { T apply(int x, I j, String s, Object o); }" + + "interface Call { T call(); }"; + + // Test the creation of explicit blocks for lambdas with expression bodies. + public void testCreationReferenceBlockWrapper() throws IOException { + String noArgumentTranslation = translateSourceFile( + classIHeader + "class Test { Call iInit = I::new; }", + "Test", "Test.m"); + assertTranslatedSegments(noArgumentTranslation, + "GetNonCapturingLambda([Call class], @protocol(Call)", + "@\"I_init\"", "^I *(id _self) {", "return new_I_init();"); + String oneArgumentTranslation = translateSourceFile( + classIHeader + "class Test { FunInt iInit2 = I::new; }", "Test", "Test.m"); + assertTranslatedSegments(oneArgumentTranslation, + "GetNonCapturingLambda([FunInt class], @protocol(FunInt)", "@\"I_initWithInt_\"", + "^I *(id _self, jint a) {", "return new_I_initWithInt_(a);"); + String mixedArgumentTranslation = translateSourceFile( + classIHeader + "class Test { FunInt4 iInit3 = I::new; }", "Test", "Test.m"); + assertTranslatedSegments(mixedArgumentTranslation, + "GetNonCapturingLambda([FunInt4 class], @protocol(FunInt4)", + "@\"I_initWithInt_withI_withNSString_withId_\"", + "^I *(id _self, jint a, I * b, NSString * c, id d) {", + "return new_I_initWithInt_withI_withNSString_withId_(a, b, c, d);"); + } +}