/*
* 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.translate;
import com.google.devtools.j2objc.GenerationTest;
import com.google.devtools.j2objc.ast.Statement;
import java.io.IOException;
import java.util.List;
/**
* Unit tests for {@link CastResolver}.
*
* @author Keith Stanger
*/
public class CastResolverTest extends GenerationTest {
public void testFieldOfGenericParameter() throws IOException {
String translation = translateSourceFile(
"class Test { int foo; static class Other<T extends Test> {"
+ " int test(T t) { return t.foo + t.foo; } } }", "Test", "Test.m");
assertTranslation(translation, "return ((Test *) nil_chk(t))->foo_ + t->foo_;");
}
public void testIntCastInStringConcatenation() throws IOException {
String translation = translateSourceFile(
"public class Test { void test() { "
+ " String a = \"abc\"; "
+ " String b = \"foo\" + a.hashCode() + \"bar\" + a.hashCode() + \"baz\"; } }",
"Test", "Test.m");
assertTranslation(translation,
"JreStrcat(\"$I$I$\", @\"foo\", ((jint) [a hash]), @\"bar\", ((jint) [a hash]),"
+ " @\"baz\")");
}
public void testCastInConstructorChain() throws IOException {
String source = "int i = new Throwable().hashCode();";
List<Statement> stmts = translateStatements(source);
assertEquals(1, stmts.size());
String result = generateStatement(stmts.get(0));
assertEquals("jint i = ((jint) [create_NSException_init() hash]);", result);
}
// b/5872710: generic return type needs to be cast if chaining invocations.
public void testTypeVariableCast() throws IOException {
String translation = translateSourceFile(
"import java.util.ArrayList; public class Test {"
+ " int length; static ArrayList<String> strings = new ArrayList<String>();"
+ " public static void main(String[] args) { int n = strings.get(1).hashCode(); }}",
"Test", "Test.m");
assertTranslation(translation, "((jint) [((NSString *) "
+ "nil_chk([((JavaUtilArrayList *) nil_chk(Test_strings)) getWithInt:1])) hash]);");
}
// Verify that Object.hashCode() return value is cast when used.
public void testStringLengthCompare() throws IOException {
String translation = translateSourceFile(
"public class Test { boolean test(String s) { return -2 < \"1\".length(); }"
+ " void test2(Object o) { o.hashCode(); }"
+ " int test3() { return super.hashCode(); } }",
"Test", "Test.m");
// Verify referenced return value is cast.
assertTranslation(translation, "return -2 < [@\"1\" java_length];");
// Verify unused return value isn't.
assertTranslation(translation, "[nil_chk(o) hash];");
// Verify that super call to hashCode() is cast.
assertTranslation(translation, "return ((jint) [super hash]);");
}
public void testDerivedTypeVariableInvocation() throws IOException {
String translation = translateSourceFile(
"public class Test {"
+ " static class Base <T extends BaseFoo> {"
+ " protected T foo;"
+ " public Base(T foo) {"
+ " this.foo = foo;"
+ " }"
+ " }"
+ " static class BaseFoo {"
+ " void baseMethod() {}"
+ " }"
+ " static class Derived extends Base<DerivedFoo> {"
+ " public Derived(DerivedFoo foo) {"
+ " super(foo);"
+ " }"
+ " int test() {"
+ " foo.baseMethod();"
+ " foo.derivedMethod();"
+ " return foo.myInt;"
+ " }"
+ " }"
+ " static class DerivedFoo extends BaseFoo {"
+ " int myInt;"
+ " void derivedMethod() {}"
+ " }"
+ "}", "Test", "Test.m");
// Verify foo.derivedMethod() has cast of appropriate type variable.
assertTranslation(translation, "[((Test_DerivedFoo *) nil_chk(foo_)) derivedMethod];");
// Verify that a cast can be added to a QualifiedName node.
assertTranslation(translation, "return ((Test_DerivedFoo *) nil_chk(foo_))->myInt_;");
}
public void testCapturedType() throws IOException {
String translation = translateSourceFile(
"class Test {"
+ " interface Bar { void bar(); }"
+ " static class Foo<T extends Bar> { T get() { return null; } }"
+ " void test(Foo<?> foo) { foo.get().bar(); } }", "Test", "Test.m");
assertTranslation(translation,
"[((id<Test_Bar>) nil_chk([((Test_Foo *) nil_chk(foo)) get])) bar];");
}
public void testChainedFieldLookup() throws IOException {
String translation = translateSourceFile(
"class Test {"
+ " static class Foo { Bar bar; }"
+ " static class Bar { int baz; }"
+ " static class GenericImpl<T> { T foo; }"
+ " static class Impl extends GenericImpl<Foo> {"
+ " int test() {"
// Need to call "foo.bar.baz" twice so that the second expression is
// free of nil_chk's.
+ " int i = foo.bar.baz;"
+ " return foo.bar.baz; } } }", "Test", "Test.m");
// This is actually a regression for a NPE in the translator, but we may as
// well check the output.
assertTranslation(translation, "return ((Test_Foo *) foo_)->bar_->baz_;");
}
public void testCastOfParenthesizedExpression() throws IOException {
String translation = translateSourceFile(
"class Test { static class Node { int key; } static Node next;"
+ " Node getNext() { return null; }"
+ " int test() { return (next = getNext()).key; } }", "Test", "Test.m");
assertTranslation(translation,
"return ((Test_Node *) (JreStrongAssign(&Test_next, [self getNext])))->key_;");
}
public void testCastOfInferredWildcardType() throws IOException {
String translation = translateSourceFile(
"class Test { <T> T genericMethod(T a, T b) { return null; }"
+ " interface I { void foo(); } static class C {}"
+ " static class Bar extends C implements I { public void foo() {} }"
+ " static class Baz extends C implements I { public void foo() {} }"
+ " I test() { genericMethod(new Bar(), new Baz()).foo();"
+ " return genericMethod(new Bar(), new Baz()); } }", "Test", "Test.m");
assertTranslatedLines(translation,
"- (id<Test_I>)test {",
// Type cast must contain both "Test_C" and "Test_I".
" [((Test_C<Test_I> *) nil_chk([self genericMethodWithId:create_Test_Bar_init()"
+ " withId:create_Test_Baz_init()])) foo];",
// No need for a cast because genericMethodWithId:withId: is declared to return "id".
" return [self genericMethodWithId:create_Test_Bar_init() withId:create_Test_Baz_init()];",
"}");
}
public void testAccessOfFieldFromSubclassWithMoreSpecificTypeVariable() throws IOException {
String translation = translateSourceFile(
"class Test<T extends I2> {"
// This method won't be functionized.
+ "void test1(T i) {}"
// The private method will be functionized.
+ "private void test2(T i) {}"
+ "void test3(B<T> b) { test1(b.foo); test2(b.foo); } }"
+ "interface I1 {}"
+ "interface I2 extends I1 {}"
+ "class A<T extends I1> { T foo; }"
+ "class B<T extends I2> extends A<T> {}", "Test", "Test.m");
// Test that access of "foo" from subclass B is cast to id<I2>.
if (options.isJDT()) {
// JDT resolves the "b" in "b.foo" to type A while Javac resolves it to type B. Both are
// correct.
assertTranslation(translation, "[self test1WithI2:((id<I2>) ((A *) nil_chk(b))->foo_)];");
} else {
assertTranslation(translation, "[self test1WithI2:((id<I2>) ((B *) nil_chk(b))->foo_)];");
}
assertTranslation(translation, "Test_test2WithI2_(self, ((id<I2>) b->foo_));");
}
/**
* Clang reports an incompatible pointer comparison when comparing two
* objects with different interface types. That's potentially wrong both
* in Java and Objective-C, since a single class can implement both interfaces.
*/
public void testInterfaceComparisons() throws IOException {
String translation = translateSourceFile(
"class Test { "
+ " interface Foo {}"
+ " interface Bar {}"
+ " static class Mumble implements Foo, Bar {}"
+ " static boolean test(Foo f, Bar b) {"
+ " return f == b;"
+ " }"
+ " static boolean test2(Bar b, Foo f) {"
+ " return b != f;"
+ " }"
+ " static boolean test3(byte[] buffer, int offset) {"
+ " return buffer[offset] != 'Z';" // Verify primitive types don't get (id) casts.
+ " }"
+ " public static void main(String... args) {"
+ " Mumble m = new Mumble();"
+ " test(m, m);"
+ " test2(m, m);"
+ " }"
+ "}", "Test", "Test.m");
// Wrong: clang will report a compare-distinct-pointer-types warning.
assertNotInTranslation(translation, "return f == b;");
assertNotInTranslation(translation, "return b != f;");
// Right: weaker right-hand type, since Java compiler already type-checked.
assertTranslation(translation, "return f == (id) b;");
assertTranslation(translation, "return b != (id) f;");
assertTranslation(translation, "IOSByteArray_Get(nil_chk(buffer), offset) != 'Z';");
}
public void testGenericArrayCast() throws IOException {
String translation = translateSourceFile(
"class Test<E> { E[] test(Object[] o) { E[] e = (E[]) new Object[0]; return (E[])o; } }",
"Test", "Test.m");
// No need to check either cast because the erasure of E[] is Object[].
assertTranslation(translation,
"IOSObjectArray *e = [IOSObjectArray arrayWithLength:0 type:NSObject_class_()];");
assertTranslation(translation, "return o;");
translation = translateSourceFile(
"class Test<E extends String> { E[] test(Object[] o) { return (E[])o; } }",
"Test", "Test.m");
// Need to check the cast because the erasure of E[] is String[].
assertTranslation(translation,
"return (IOSObjectArray *) cast_check(o, IOSClass_arrayType(NSString_class_(), 1));");
}
public void testAssignmentCast() throws IOException {
String translation = translateSourceFile(
"class Test implements java.io.Serializable {"
+ " static class A<T extends java.io.Serializable> { T foo; }"
+ " void test (A<Test> a, Test t) { if (a != null) { t = a.foo; } } }", "Test", "Test.m");
assertTranslation(translation, "t = ((Test *) a->foo_);");
}
public void testCastInConditionalExpression() throws IOException {
String translation = translateSourceFile(
"class Test implements java.io.Serializable {"
+ " static class A<T extends java.io.Serializable> { T foo; }"
+ " Test test (A<Test> a1, A<Test> a2, boolean b) {"
+ " if (a1 == null || a2 == null) return null; return b ? a1.foo : a2.foo; } }",
"Test", "Test.m");
assertTranslation(translation, "return b ? ((Test *) a1->foo_) : ((Test *) a2->foo_);");
}
public void testCastLocallyParameterizedType() throws IOException {
addSourceFile("interface Foo<A> { A foo(); }", "Foo.java");
addSourceFile("interface Bar<B extends Number> extends Foo<B> {}", "Bar.java");
String translation = translateSourceFile(
"class Test { Integer test(Bar<Integer> bar) { return bar.foo(); } }", "Test", "Test.m");
// Needs the JavaLangInteger cast.
assertTranslation(translation, "return ((JavaLangInteger *) [((id<Bar>) nil_chk(bar)) foo]);");
}
public void testCastInSuperFieldAccess() throws IOException {
addSourceFile("class A <T> { T foo; }", "A.java");
String translation = translateSourceFile("class Test extends A<String> {"
+ " int fooLength() { return super.foo.length(); } }", "Test", "Test.m");
assertTranslation(translation, "(NSString *) nil_chk(foo_)");
}
}