/*
* 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 java.io.IOException;
/**
* Unit tests for {@link MethodReference}.
*
* @author Seth Kirby
*/
public class MethodReferenceTest extends GenerationTest {
// Test the creation of explicit blocks for lambdas with expression bodies.
public void testCreationReferenceBlockWrapper() throws IOException {
String creationReferenceHeader =
"class I { I() { } I(int x) { } I(int x, I j, String s, Object o) { } }\n"
+ "interface FunInt<T> { T apply(int x); }"
+ "interface FunInt4<T> { T apply(int x, I j, String s, Object o); }"
+ "interface Call<T> { T call(); }";
String noArgumentTranslation = translateSourceFile(
creationReferenceHeader + "class Test { Call<I> iInit = I::new; }",
"Test", "Test.m");
assertTranslatedLines(noArgumentTranslation,
"- (id)call {",
" return create_I_init();",
"}");
String oneArgumentTranslation = translateSourceFile(
creationReferenceHeader + "class Test { FunInt<I> iInit2 = I::new; }", "Test", "Test.m");
assertTranslatedLines(oneArgumentTranslation,
"- (id)applyWithInt:(jint)a {",
" return create_I_initWithInt_(a);",
"}");
String mixedArgumentTranslation = translateSourceFile(
creationReferenceHeader + "class Test { FunInt4<I> iInit3 = I::new; }", "Test", "Test.m");
assertTranslatedLines(mixedArgumentTranslation,
"- (id)applyWithInt:(jint)a",
" withI:(I *)b",
" withNSString:(NSString *)c",
" withId:(id)d {",
" return create_I_initWithInt_withI_withNSString_withId_(a, b, c, d);",
"}");
}
// Test that expression method references resolve correctly for static and non-static methods.
public void testExpressionReferenceStaticResolution() throws IOException {
String expressionReferenceHeader = "class Q { static Object o(Object x) { return x; }\n"
+ " Object o2(Object x) { return x; }}" + "interface F<T, R> { R f(T t); }";
String staticTranslation = translateSourceFile(
expressionReferenceHeader + "class Test { F fun = Q::o; }",
"Test", "Test.m");
// Should be non-capturing.
assertTranslation(staticTranslation,
"JreStrongAssign(&self->fun_, JreLoadStatic(Test_$Lambda$1, instance));");
assertTranslatedLines(staticTranslation,
"- (id)fWithId:(id)a {",
" return Q_oWithId_(a);",
"}");
String instanceTranslation = translateSourceFile(
expressionReferenceHeader + "class Test { F fun = new Q()::o2; }",
"Test", "Test.m");
// Should be capturing.
assertTranslation(instanceTranslation,
"JreStrongAssignAndConsume(&self->fun_, new_Test_$Lambda$1_initWithQ_(create_Q_init()));");
assertTranslatedLines(instanceTranslation,
"- (id)fWithId:(id)a {",
" return [target$_ o2WithId:a];",
"}");
String staticInstanceTranslation = translateSourceFile(
expressionReferenceHeader + "class Test { static F fun = new Q()::o2; }",
"Test", "Test.m");
assertTranslation(staticInstanceTranslation,
"JreStrongAssignAndConsume(&Test_fun, new_Test_$Lambda$1_initWithQ_(create_Q_init()));");
assertTranslatedLines(staticInstanceTranslation,
"- (id)fWithId:(id)a {",
" return [target$_ o2WithId:a];",
"}");
}
public void testTypeReference() throws IOException {
String typeReferenceHeader = "interface H { Object copy(int[] i); }";
String translation = translateSourceFile(
typeReferenceHeader + "class Test { H h = int[]::clone; }", "Test", "Test.m");
assertTranslatedLines(translation,
"- (id)copy__WithIntArray:(IOSIntArray *)a {",
" return [((IOSIntArray *) nil_chk(a)) java_clone];",
"}");
}
public void testReferenceToInstanceMethodOfType() throws IOException {
String source = "import java.util.Comparator;"
+ "class Test { void f() { Comparator<String> s = String::compareTo; } }";
String impl = translateSourceFile(source, "Test", "Test.m");
assertTranslation(impl, "return [((NSString *) nil_chk(a)) compareToWithId:b];");
}
public void testReferenceToInstanceMethodOfGenericType() throws IOException {
String source = "interface BiConsumer<T,U> { void accept(T t, U u); } "
+ "interface Collection<E> { boolean add(E x); } "
+ "class Test {"
+ " <T> void f(Collection<T> c, T o) {"
+ " BiConsumer<Collection<T>, T> bc = Collection<T>::add;"
+ " }"
+ "}";
String impl = translateSourceFile(source, "Test", "Test.m");
assertTranslation(impl, "[((id<Collection>) nil_chk(a)) addWithId:b];");
assertNotInTranslation(impl, "return [((id<Collection>) nil_chk(a)) addWithId:b];");
}
public void testReferenceToInstanceMethodOfGenericTypeWithReturnType() throws IOException {
String source = "interface BiConsumer<T,U> { boolean accept(T t, U u); } "
+ "interface Collection<E> { boolean add(E x); } "
+ "class Test {"
+ " <T> void f(Collection<T> c, T o) {"
+ " BiConsumer<Collection<T>, T> bc = Collection<T>::add;"
+ " }"
+ "}";
String impl = translateSourceFile(source, "Test", "Test.m");
assertTranslation(impl, "return [((id<Collection>) nil_chk(a)) addWithId:b];");
}
public void testVarArgs() throws IOException {
String varArgsHeader = "interface I { void foo(int a1, String a2, String a3); }"
+ "interface I2 { void foo(int a1, String a2, String a3, String a4); }"
+ "class Y { static void m(int a1, String... rest) { } }";
String translation = translateSourceFile(
varArgsHeader + "class Test { I i = Y::m; I2 i2 = Y::m; }",
"Test", "Test.m");
assertTranslatedLines(translation,
"- (void)fooWithInt:(jint)a",
" withNSString:(NSString *)b",
" withNSString:(NSString *)c {",
" Y_mWithInt_withNSStringArray_(a, [IOSObjectArray arrayWithObjects:(id[]){ b, c } "
+ "count:2 type:NSString_class_()]);",
"}");
assertTranslatedLines(translation,
"- (void)fooWithInt:(jint)a",
" withNSString:(NSString *)b",
" withNSString:(NSString *)c",
" withNSString:(NSString *)d {",
" Y_mWithInt_withNSStringArray_(a, [IOSObjectArray arrayWithObjects:(id[]){ b, c, d } "
+ "count:3 type:NSString_class_()]);",
"}");
}
public void testReferenceToInstanceMethodOfTypeWithVarArgs() throws IOException {
String p = "interface P<T> { void f(T t); }";
String q = "interface Q<T> { void f(T t, String a, String b); }";
String r = "interface R<T> { void f(T t, String... rest); }";
String x1 = p + "class X { void g(String... rest) {} void h() { P<X> ff = X::g; } }";
String x2 = q + "class X { void g(String... rest) {} void h() { Q<X> ff = X::g; } }";
String x3 = r + "class X { void g(String... rest) {} void h() { R<X> ff = X::g; } }";
String impl1 = translateSourceFile(x1, "X", "X.m");
String impl2 = translateSourceFile(x2, "X", "X.m");
String impl3 = translateSourceFile(x3, "X", "X.m");
// Pass an empty array to the referenced method.
assertTranslatedLines(impl1,
"- (void)fWithId:(X *)a {",
" [((X *) nil_chk(a)) gWithNSStringArray:"
+ "[IOSObjectArray arrayWithLength:0 type:NSString_class_()]];",
"}");
// Pass an array of the arguments b and c to the referenced method.
assertTranslatedLines(impl2,
"- (void)fWithId:(X *)a",
" withNSString:(NSString *)b",
" withNSString:(NSString *)c {",
" [((X *) nil_chk(a)) gWithNSStringArray:[IOSObjectArray arrayWithObjects:(id[]){ b, c } "
+ "count:2 type:NSString_class_()]];",
"}");
// Pass the varargs array to the referenced method.
assertTranslatedLines(impl3,
"- (void)fWithId:(X *)a",
"withNSStringArray:(IOSObjectArray *)b {",
" [((X *) nil_chk(a)) gWithNSStringArray:b];",
"}");
}
public void testArgumentBoxingAndUnboxing() throws IOException {
String header = "interface IntFun { void apply(int a); }\n"
+ "interface IntegerFun { void apply(Integer a); }";
String translation = translateSourceFile(header
+ "class Test { static void foo(Integer x) {}; static void bar(int x) {};"
+ "IntFun f = Test::foo; IntegerFun f2 = Test::bar; }", "Test", "Test.m");
assertTranslatedLines(translation,
"- (void)applyWithInt:(jint)a {",
" Test_fooWithJavaLangInteger_(JavaLangInteger_valueOfWithInt_(a));",
"}");
assertTranslatedLines(translation,
"- (void)applyWithJavaLangInteger:(JavaLangInteger *)a {",
" Test_barWithInt_([((JavaLangInteger *) nil_chk(a)) intValue]);",
"}");
}
public void testReturnBoxingAndUnboxing() throws IOException {
String header = "interface Fun { Integer a(); } interface Fun2 { int a(); }";
String translation = translateSourceFile(header
+ "class Test { int size() { return 42; } Integer size2() { return 43; }"
+ "Fun f = this::size; Fun2 f2 = this::size2; }",
"Test", "Test.m");
assertTranslatedLines(translation,
"- (JavaLangInteger *)a {",
" return JavaLangInteger_valueOfWithInt_([target$_ size]);",
"}");
assertTranslatedLines(translation,
"- (jint)a {",
" return [((JavaLangInteger *) nil_chk([target$_ size2])) intValue];",
"}");
}
// Creation references can be initialized only for side effects, and have a void return.
public void testCreationReferenceVoidReturn() throws IOException {
String header = "interface V { void f(); }";
String translation = translateSourceFile(header + "class Test { V v = Test::new; }", "Test",
"Test.m");
assertTranslatedLines(translation,
"- (void)f {",
" create_Test_init();",
"}");
}
public void testCreationReferenceNonVoidReturn() throws IOException {
String header = "interface V { Object f(); }";
String translation = translateSourceFile(header + "class Test { V v = Test::new; }", "Test",
"Test.m");
assertTranslatedLines(translation, "- (id)f {", "return create_Test_init();");
}
public void testArrayCreationReference() throws IOException {
String translation = translateSourceFile("import java.util.function.Supplier;"
+ "interface IntFunction<R> {"
+ " R apply(int value);"
+ "}"
+ "class Test {"
+ " IntFunction<int[]> i = int[]::new;"
+ "}", "Test", "Test.m");
assertNotInTranslation(translation, "return create_IntFunction_initWithIntArray_");
assertTranslatedLines(translation,
"- (id)applyWithInt:(jint)a {",
" return [IOSIntArray arrayWithLength:a];",
"}");
}
public void testCreationReferenceOfLocalCapturingType() throws IOException {
String translation = translateSourceFile(
"interface Supplier<T> { T get(); }"
+ "class Test { static Supplier<Runnable> test(Runnable r) {"
+ "class Runner implements Runnable { public void run() { r.run(); } }"
+ "return Runner::new; } }", "Test", "Test.m");
assertTranslatedLines(translation,
"void Test_$Lambda$1_initWithJavaLangRunnable_("
+ "Test_$Lambda$1 *self, id<JavaLangRunnable> capture$0) {",
" JreStrongAssign(&self->val$r_, capture$0);",
" NSObject_init(self);",
"}");
assertTranslatedLines(translation,
"- (id)get {",
" return create_Test_1Runner_initWithJavaLangRunnable_(val$r_);",
"}");
}
public void testQualifiedSuperMethodReference() throws IOException {
String translation = translateSourceFile(
"interface I { void bar(); }"
+ "class Test { void foo() {} static class TestSub extends Test { void foo() {}"
+ "class Inner { I test() { return TestSub.super::foo; } } } }",
"Test", "Test.m");
assertTranslatedSegments(translation,
"static void (*Test_TestSub_super$_foo)(id, SEL);",
"- (void)bar {",
" Test_TestSub_super$_foo(this$0_->this$0_, @selector(foo));",
"}");
}
public void testMultipleMethodReferencesNilChecks() throws IOException {
String translation = translateSourceFile(
"interface Foo { void f(Test t); }"
+ "class Test { void foo() {} void test() {"
+ " Foo f1 = Test::foo; Foo f2 = Test::foo; } }", "Test", "Test.m");
// Both lambdas must perform a nil_chk on their local variable "a".
assertOccurrences(translation, "nil_chk(a)", 2);
}
public void testCapturingExpressionMethodReferences() throws IOException {
String translation = translateSourceFile(
"interface Supplier { int get(); }"
+ "class Holder { private int num; public Holder(int i) {num = i;} int get() {return num;}}"
+ "class Test { public void run() { Holder h = new Holder(1); Supplier s = h::get; } }",
"Test", "Test.m");
// Make sure there is a captured variable.
assertTranslatedLines(translation,
"@interface Test_$Lambda$1 : NSObject < Supplier > {",
" @public",
" Holder *target$_;",
"}");
assertTranslatedLines(translation,
"- (void)run {",
" Holder *h = create_Holder_initWithInt_(1);",
" id<Supplier> s = create_Test_$Lambda$1_initWithHolder_(h);",
"}");
// Make sure the receiver field is initialized.
assertTranslation(translation, "JreStrongAssign(&self->target$_, outer$);");
}
}