diff --git a/translator/src/main/java/com/google/devtools/j2objc/gen/PropertyGenerator.java b/translator/src/main/java/com/google/devtools/j2objc/gen/PropertyGenerator.java index 6c5bc300b1..178bac2ee8 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/gen/PropertyGenerator.java +++ b/translator/src/main/java/com/google/devtools/j2objc/gen/PropertyGenerator.java @@ -213,13 +213,20 @@ private String getStringRepresentation(Set attributes) { buffer.append('(').append(PropertyAnnotation.toAttributeString(attributes)).append(") "); } - String objcType = nameTable.getObjCType(varType); + TypeElement declaringClass = ElementUtil.getDeclaringClass(varElement); + boolean allowGenerics = !typeUtil.isProtoClass(varType); + boolean enableGenerics = + allowGenerics + && (options.asObjCGenericDecl() + || TypeUtil.hasGenerateObjectiveCGenerics(varType) + || (declaringClass != null + && TypeUtil.hasGenerateObjectiveCGenerics(declaringClass))); + String objcType = nameTable.getObjCTypeDeclaration(varType, enableGenerics, declaringClass); buffer.append(objcType); if (!objcType.endsWith("*")) { buffer.append(' '); } buffer.append(propertyName); - TypeElement declaringClass = ElementUtil.getDeclaringClass(varElement); boolean inSwiftNameContext = declaringClass != null && (nameTable.packageHasSwiftNameAnnotation(declaringClass) diff --git a/translator/src/main/java/com/google/devtools/j2objc/gen/TypeDeclarationGenerator.java b/translator/src/main/java/com/google/devtools/j2objc/gen/TypeDeclarationGenerator.java index 79179710a3..c40f32b2ac 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/gen/TypeDeclarationGenerator.java +++ b/translator/src/main/java/com/google/devtools/j2objc/gen/TypeDeclarationGenerator.java @@ -290,7 +290,11 @@ protected void printInterfaceGenerics() { if (generateObjectiveCGenerics(typeElement.asType())) { List genericNames = nameTable.getClassObjCGenericTypeNames(typeElement.asType()); if (!genericNames.isEmpty()) { - printf("<%s>", String.join(", ", genericNames)); + List covariantNames = new java.util.ArrayList<>(); + for (String name : genericNames) { + covariantNames.add("__covariant " + name); + } + printf("<%s>", String.join(", ", covariantNames)); } } } @@ -579,10 +583,22 @@ protected void printStaticFieldDeclaration( private void printStaticFieldFullDeclaration(VariableDeclarationFragment fragment) { VariableElement var = fragment.getVariableElement(); boolean isVolatile = ElementUtil.isVolatile(var); - String objcType = nameTable.getObjCTypeDeclaration(var.asType()); - String objcTypePadded = paddedType(objcType, var); - String declType = paddedType(getDeclarationType(var), var); + boolean allowGenerics = !typeUtil.isProtoClass(var.asType()); + boolean enableGenerics = + allowGenerics + && (generateObjectiveCGenerics(var.asType()) + || generateObjectiveCGenerics(typeElement.asType())); + String objcType = nameTable.getObjCTypeDeclaration(var.asType(), enableGenerics, typeElement); + String declType = getDeclarationType(var); String name = nameTable.getVariableShortName(var); + if (objcType.contains(",")) { + String typedefName = typeName + "_" + name + "_typedef"; + printf("typedef %s %s;\n", objcType, typedefName); + objcType = typedefName; + declType = typedefName; + } + String objcTypePadded = paddedType(objcType, var); + declType = paddedType(declType, var); boolean isFinal = ElementUtil.isFinal(var); boolean isPrimitive = var.asType().getKind().isPrimitive(); boolean isConstant = ElementUtil.isPrimitiveConstant(var); diff --git a/translator/src/main/java/com/google/devtools/j2objc/gen/TypeGenerator.java b/translator/src/main/java/com/google/devtools/j2objc/gen/TypeGenerator.java index e8278c8022..618042af56 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/gen/TypeGenerator.java +++ b/translator/src/main/java/com/google/devtools/j2objc/gen/TypeGenerator.java @@ -310,7 +310,13 @@ protected String getDeclarationType(VariableElement var) { if (ElementUtil.isVolatile(var)) { return "volatile_" + NameTable.getPrimitiveObjCType(type); } else { - return nameTable.getObjCType(type); + boolean allowGenerics = !typeUtil.isProtoClass(type); + boolean enableGenerics = + allowGenerics + && ElementUtil.isStatic(var) + && (generateObjectiveCGenerics(type) + || generateObjectiveCGenerics(typeElement.asType())); + return nameTable.getObjCTypeDeclaration(type, enableGenerics, typeElement); } } diff --git a/translator/src/main/java/com/google/devtools/j2objc/gen/TypeImplementationGenerator.java b/translator/src/main/java/com/google/devtools/j2objc/gen/TypeImplementationGenerator.java index 92d8debeb2..ebad8f3360 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/gen/TypeImplementationGenerator.java +++ b/translator/src/main/java/com/google/devtools/j2objc/gen/TypeImplementationGenerator.java @@ -297,15 +297,21 @@ private void printStaticFieldExterns() { } for (VariableDeclarationFragment fragment : getStaticFields()) { VariableElement var = fragment.getVariableElement(); - String objcTypePadded = paddedType(nameTable.getObjCTypeDeclaration(var.asType()), var); + boolean allowGenerics = !typeUtil.isProtoClass(var.asType()); + boolean enableGenerics = + allowGenerics + && (generateObjectiveCGenerics(var.asType()) + || generateObjectiveCGenerics(typeElement.asType())); + String objcType = nameTable.getObjCTypeDeclaration(var.asType(), enableGenerics, typeElement); + String objcTypePadded = paddedType(objcType, var); String name = nameTable.getVariableShortName(var); newline(); printf("extern %s%s_get_%s(void);\n", objcTypePadded, typeName, name); if (!ElementUtil.isFinal(var)) { printf("extern %s%s_set_%s(%svalue);\n", objcTypePadded, typeName, name, objcTypePadded); if (var.asType().getKind().isPrimitive() && !ElementUtil.isVolatile(var)) { - String objcType = nameTable.getObjCTypeDeclaration(var.asType()); - printf("extern %s *%s_getRef_%s(void);\n", objcType, typeName, name); + String primitiveType = nameTable.getObjCTypeDeclaration(var.asType()); + printf("extern %s *%s_getRef_%s(void);\n", primitiveType, typeName, name); } } } diff --git a/translator/src/main/java/com/google/devtools/j2objc/translate/CastResolver.java b/translator/src/main/java/com/google/devtools/j2objc/translate/CastResolver.java index d743858ad3..aacf21d10c 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/translate/CastResolver.java +++ b/translator/src/main/java/com/google/devtools/j2objc/translate/CastResolver.java @@ -186,6 +186,25 @@ private void maybeAddCast(Expression expr, TypeMirror expectedType, boolean shou } } + private boolean isObjcGenericAssignable(TypeMirror t1, TypeMirror t2) { + if (t1 instanceof DeclaredType && t2 instanceof DeclaredType) { + DeclaredType dt1 = (DeclaredType) t1; + DeclaredType dt2 = (DeclaredType) t2; + List args1 = dt1.getTypeArguments(); + List args2 = dt2.getTypeArguments(); + if (args1.size() == args2.size() && !args1.isEmpty()) { + for (int i = 0; i < args1.size(); i++) { + TypeMirror arg1 = args1.get(i); + TypeMirror arg2 = args2.get(i); + if (!typeUtil.isObjcAssignable(arg1, arg2) || !isObjcGenericAssignable(arg1, arg2)) { + return false; + } + } + } + } + return true; + } + private boolean needsCast(Expression expr, TypeMirror expectedType, boolean shouldCastFromId) { TypeMirror declaredType = getDeclaredType(expr); if (declaredType == null) { @@ -203,6 +222,12 @@ private boolean needsCast(Expression expr, TypeMirror expectedType, boolean shou // expected type, then the compiler already has sufficient type info. || typeUtil.isObjcAssignable(declaredType, exprType) || (expectedType != null && typeUtil.isObjcAssignable(declaredType, expectedType))) { + if (options.asObjCGenericDecl()) { + if (!isObjcGenericAssignable(declaredType, exprType) + || (expectedType != null && !isObjcGenericAssignable(declaredType, expectedType))) { + return true; + } + } return false; } return true; diff --git a/translator/src/main/java/com/google/devtools/j2objc/types/HeaderImportCollector.java b/translator/src/main/java/com/google/devtools/j2objc/types/HeaderImportCollector.java index 78072b1e19..7e48bff1ed 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/types/HeaderImportCollector.java +++ b/translator/src/main/java/com/google/devtools/j2objc/types/HeaderImportCollector.java @@ -26,6 +26,7 @@ import com.google.devtools.j2objc.ast.FieldDeclaration; import com.google.devtools.j2objc.ast.FunctionDeclaration; import com.google.devtools.j2objc.ast.MethodDeclaration; +import com.google.devtools.j2objc.ast.PackageDeclaration; import com.google.devtools.j2objc.ast.RecordDeclaration; import com.google.devtools.j2objc.ast.SingleVariableDeclaration; import com.google.devtools.j2objc.ast.Type; @@ -123,6 +124,61 @@ private void addForwardDecl(TypeMirror type) { forwardDecls.addAll(Sets.difference(Import.getImports(type, unit.getEnv()), declaredTypes)); } + private boolean isCoreJrePackage(String name) { + if (name.startsWith("java.")) { + return true; + } + if (name.startsWith("javax.")) { + return name.startsWith("javax.crypto.") + || name.equals("javax.crypto") + || name.startsWith("javax.net.") + || name.equals("javax.net") + || name.startsWith("javax.security.") + || name.equals("javax.security") + || name.startsWith("javax.xml.") + || name.equals("javax.xml"); + } + return name.startsWith("android.") + || name.equals("android") + || name.startsWith("org.xml.") + || name.equals("org.xml") + || name.startsWith("org.w3c.") + || name.equals("org.w3c") + || name.startsWith("org.json.") + || name.equals("org.json"); + } + + private boolean isJreType(Import imp) { + String qName = imp.getJavaQualifiedName(); + if (qName == null) { + return false; + } + return isCoreJrePackage(qName); + } + + private boolean isCurrentUnitJre() { + PackageDeclaration pkg = unit.getPackage(); + if (pkg == null || pkg.isDefaultPackage()) { + return false; + } + String pkgName = pkg.getName().getFullyQualifiedName(); + return isCoreJrePackage(pkgName); + } + + private void addGenericParameter(TypeMirror type) { + if (isCurrentUnitJre()) { + addForwardDecl(type); + return; + } + for (Import imp : Import.getImports(type, unit.getEnv())) { + if (isJreType(imp)) { + superTypes.add(imp); + } else { + forwardDecls.add(imp); + } + } + } + private void addSuperType(TypeElement type) { if (type != null) { Import.addImports(type.asType(), superTypes, unit.getEnv()); @@ -145,6 +201,9 @@ public boolean visit(AnnotationTypeMemberDeclaration node) { public boolean visit(FieldDeclaration node) { if (filter.include(node) && includeInnerTypes) { addForwardDecl(node.getTypeMirror()); + for (TypeMirror fieldGeneric : objCForwardDeclaredGenericParameters(node.getTypeMirror())) { + addGenericParameter(fieldGeneric); + } } return false; } @@ -174,13 +233,13 @@ public boolean visit(FunctionDeclaration node) { addForwardDecl(node.getReturnType()); for (TypeMirror returnGeneric : objCForwardDeclaredGenericParameters(node.getReturnType().getTypeMirror())) { - addForwardDecl(returnGeneric); + addGenericParameter(returnGeneric); } for (SingleVariableDeclaration param : node.getParameters()) { addForwardDecl(param.getVariableElement().asType()); for (TypeMirror paramGeneric : objCForwardDeclaredGenericParameters(param.getVariableElement().asType())) { - addForwardDecl(paramGeneric); + addGenericParameter(paramGeneric); } } } @@ -193,12 +252,12 @@ public boolean visit(MethodDeclaration node) { addForwardDecl(node.getReturnTypeMirror()); for (TypeMirror returnGeneric : objCForwardDeclaredGenericParameters(node.getReturnTypeMirror())) { - addForwardDecl(returnGeneric); + addGenericParameter(returnGeneric); } for (VariableElement param : node.getExecutableElement().getParameters()) { addForwardDecl(param.asType()); for (TypeMirror paramGeneric : objCForwardDeclaredGenericParameters(param.asType())) { - addForwardDecl(paramGeneric); + addGenericParameter(paramGeneric); } } } diff --git a/translator/src/test/java/com/google/devtools/j2objc/gen/GenerateObjectiveCGenericsTest.java b/translator/src/test/java/com/google/devtools/j2objc/gen/GenerateObjectiveCGenericsTest.java index c08641f380..c71e4aa5b1 100644 --- a/translator/src/test/java/com/google/devtools/j2objc/gen/GenerateObjectiveCGenericsTest.java +++ b/translator/src/test/java/com/google/devtools/j2objc/gen/GenerateObjectiveCGenericsTest.java @@ -37,7 +37,7 @@ public void testGenericsOptionClass() throws IOException { String testHeader = translateSourceFile("Test", "Test.h"); String testSource = translateSourceFile("Test", "Test.m"); - assertInTranslation(testHeader, "@interface Test : NSObject"); + assertInTranslation(testHeader, "@interface Test<__covariant V> : NSObject"); assertInTranslation(testHeader, "- (V)getWithId:(V)input;"); assertInTranslation(testSource, "- (id)getWithId:(id)input"); } @@ -56,7 +56,7 @@ public void testGenericsAnnotatedClass() throws IOException { String testHeader = translateSourceFile("Test", "Test.h"); String testSource = translateSourceFile("Test", "Test.m"); - assertInTranslation(testHeader, "@interface Test : NSObject"); + assertInTranslation(testHeader, "@interface Test<__covariant V> : NSObject"); assertInTranslation(testHeader, "- (V)getWithId:(V)input;"); assertInTranslation(testSource, "- (id)getWithId:(id)input"); } @@ -74,7 +74,7 @@ public void testPublicDeclaration() throws IOException { String testHeader = translateSourceFile("Test", "Test.h"); String testSource = translateSourceFile("Test", "Test.m"); - assertInTranslation(testHeader, "@interface Test : NSObject"); + assertInTranslation(testHeader, "@interface Test<__covariant V> : NSObject"); assertInTranslation(testHeader, "- (V)getWithId:(V)input;"); assertInTranslation(testSource, "@implementation Test\n"); assertInTranslation(testSource, "- (id)getWithId:(id)input"); @@ -93,9 +93,9 @@ public void testPrivateDeclaration() throws IOException { String testHeader = translateSourceFile("Test", "Test.h"); String testSource = translateSourceFile("Test", "Test.m"); - assertInTranslation(testHeader, "@interface Test : NSObject"); + assertInTranslation(testHeader, "@interface Test<__covariant V> : NSObject"); assertNotInTranslation(testHeader, "- (V)getWithId:(V)input;"); - assertInTranslation(testSource, "@interface Test ()"); + assertInTranslation(testSource, "@interface Test<__covariant V> ()"); assertInTranslation(testSource, "- (V)getWithId:(V)input;"); assertInTranslation(testSource, "@implementation Test\n"); assertInTranslation(testSource, "- (id)getWithId:(id)input"); @@ -262,7 +262,7 @@ public void testMultipleGenericTypes() throws IOException { String testHeader = translateSourceFile("Test", "Test.h"); - assertInTranslation(testHeader, "@interface Test : NSObject"); + assertInTranslation(testHeader, "@interface Test<__covariant U, __covariant V> : NSObject"); assertInTranslation(testHeader, "- (U)getFirst;"); assertInTranslation(testHeader, "- (V)getSecond;"); } @@ -360,9 +360,9 @@ public void testInnerClassGenerics() throws IOException { String testHeader = translateSourceFile("A", "A.h"); - assertInTranslation(testHeader, "@interface A : NSObject"); + assertInTranslation(testHeader, "@interface A<__covariant X> : NSObject"); assertInTranslation(testHeader, "- (X)getAXWithId:(X)input;"); - assertInTranslation(testHeader, "@interface A_B : NSObject"); + assertInTranslation(testHeader, "@interface A_B<__covariant Y> : NSObject"); // Inner classes using generics from outer class presently unsupported. assertInTranslation(testHeader, "- (id)getBXWithId:(id)input;"); assertInTranslation(testHeader, "- (Y)getBYWithId:(Y)input;"); @@ -384,7 +384,7 @@ public void testInnerInterfaceGenerics() throws IOException { String testHeader = translateSourceFile("A", "A.h"); - assertInTranslation(testHeader, "@interface A : NSObject"); + assertInTranslation(testHeader, "@interface A<__covariant X> : NSObject"); assertInTranslation(testHeader, "- (X)getAXWithId:(X)input;"); assertInTranslation(testHeader, "@protocol A_B < JavaObject >"); assertInTranslation(testHeader, "- (id)getBYWithId:(id)input;"); @@ -422,4 +422,103 @@ public void testProtoDeclaration() throws IOException { assertInTranslation(testHeader, "@interface ComGoogleProtobufTest : NSObject"); assertInTranslation(testHeader, "- (id)getWithId:(id)input;"); } + + public void testStaticPropertyGenerics() throws IOException { + options.setAsObjCGenericDecl(true); + options.setClassProperties(true); + options.setStaticAccessorMethods(true); + addSourceFile( + "public class Test { " + " public static final Test FOO = null; " + "}", + "Test.java"); + + String testHeader = translateSourceFile("Test", "Test.h"); + assertInTranslation( + testHeader, + "@property (readonly, class, strong) Test *FOO NS_SWIFT_NAME(FOO);"); + } + + public void testStaticPropertyGenericsWithAnnotation() throws IOException { + options.setClassProperties(true); + options.setStaticAccessorMethods(true); + addSourceFile( + "import com.google.j2objc.annotations.GenerateObjectiveCGenerics; " + + "@GenerateObjectiveCGenerics " + + "public class Test { " + + " public static final Test FOO = null; " + + "}", + "Test.java"); + + String testHeader = translateSourceFile("Test", "Test.h"); + assertInTranslation( + testHeader, + "@property (readonly, class, strong) Test *FOO NS_SWIFT_NAME(FOO);"); + } + + public void testExplicitStaticPropertyGenerics() throws IOException { + options.setAsObjCGenericDecl(true); + options.setStaticAccessorMethods(true); + addSourceFile( + "import com.google.j2objc.annotations.Property; " + + "public class Test { " + + " @Property public static final Test FOO = null; " + + "}", + "Test.java"); + + String testHeader = translateSourceFile("Test", "Test.h"); + assertInTranslation(testHeader, "@property (readonly, class, strong) Test *FOO;"); + } + + public void testDefaultStaticFieldTranslation() throws IOException { + options.setAsObjCGenericDecl(true); + options.setClassProperties(false); + options.setStaticAccessorMethods(false); + addSourceFile( + "public class Test { " + " public static final Test FOO = null; " + "}", + "Test.java"); + + String testHeader = translateSourceFile("Test", "Test.h"); + assertNotInTranslation(testHeader, "@property"); + assertInTranslation(testHeader, "inline Test *Test_get_FOO(void);"); + assertInTranslation(testHeader, "FOUNDATION_EXPORT Test *Test_FOO;"); + } + + public void testStaticPropertyGenericsWithCommas() throws IOException { + options.setAsObjCGenericDecl(true); + options.setClassProperties(false); + options.setStaticAccessorMethods(false); + addSourceFile("class Map {}", "Map.java"); + addSourceFile( + "public class Test { " + " public static final Map FOO = null; " + "}", + "Test.java"); + + String testHeader = translateSourceFile("Test", "Test.h"); + assertInTranslation(testHeader, "typedef Map * Test_FOO_typedef;"); + assertInTranslation(testHeader, "inline Test_FOO_typedef Test_get_FOO(void);"); + assertInTranslation(testHeader, "FOUNDATION_EXPORT Test_FOO_typedef Test_FOO;"); + assertInTranslation(testHeader, "J2OBJC_STATIC_FIELD_OBJ_FINAL(Test, FOO, Test_FOO_typedef)"); + } + + public void testStaticMethodCFunctionGenericsErased() throws IOException { + options.setAsObjCGenericDecl(true); + addSourceFile("class List {}", "List.java"); + addSourceFile( + "public class Test { " + + " public static List foo(List list) { return null; }" + + "}", + "Test.java"); + + String testHeader = translateSourceFile("Test", "Test.h"); + assertInTranslation(testHeader, "FOUNDATION_EXPORT List *Test_fooWithList_(List *list);"); + } + + public void testConstructorCFunctionGenericsErased() throws IOException { + options.setAsObjCGenericDecl(true); + addSourceFile( + "public class Test { " + " public Test(Test other) {} " + "}", "Test.java"); + + String testHeader = translateSourceFile("Test", "Test.h"); + assertInTranslation( + testHeader, "FOUNDATION_EXPORT void Test_initWithTest_(Test *self, Test *other);"); + } } + diff --git a/translator/src/test/java/com/google/devtools/j2objc/translate/CastResolverTest.java b/translator/src/test/java/com/google/devtools/j2objc/translate/CastResolverTest.java index 2d91495570..59502e76f8 100644 --- a/translator/src/test/java/com/google/devtools/j2objc/translate/CastResolverTest.java +++ b/translator/src/test/java/com/google/devtools/j2objc/translate/CastResolverTest.java @@ -318,4 +318,32 @@ public void testIfStatementCastChkNoOpt() throws IOException { "return (JavaLangNullPointerException *) " + "cast_chk(o, [JavaLangNullPointerException class]);"); } + + public void testGenericMethodAssignmentCast() throws IOException { + options.setAsObjCGenericDecl(true); + addSourceFile("interface MessageLite {}", "MessageLite.java"); + addSourceFile( + "class ActiveExperimentIds implements MessageLite {}", "ActiveExperimentIds.java"); + addSourceFile( + "class SettingKey {" + + " static class Factory {" + + " SettingKey messageKeyWithDefault() { return null; }" + + " }" + + " static final Factory factory = new Factory();" + + "}", + "SettingKey.java"); + String translation = + translateSourceFile( + "class Test {" + + " static final SettingKey ACTIVE_EXPERIMENT_IDS =" + + " SettingKey.factory.messageKeyWithDefault();" + + "}", + "Test", + "Test.m"); + assertInTranslation( + translation, + "JreStrongAssign(&Test_ACTIVE_EXPERIMENT_IDS, ((SettingKey *) " + + "[((SettingKey_Factory *) nil_chk(JreLoadStatic(SettingKey, factory))) " + + "messageKeyWithDefault]));"); + } } diff --git a/translator/src/test/java/com/google/devtools/j2objc/types/HeaderImportCollectorTest.java b/translator/src/test/java/com/google/devtools/j2objc/types/HeaderImportCollectorTest.java index e28a037e73..b6d713bf7d 100644 --- a/translator/src/test/java/com/google/devtools/j2objc/types/HeaderImportCollectorTest.java +++ b/translator/src/test/java/com/google/devtools/j2objc/types/HeaderImportCollectorTest.java @@ -56,4 +56,34 @@ public void testNoForwardDeclarationForPrivateDeclaration() throws IOException { // because the method is private. assertNotInTranslation(translation, "Runnable"); } + + public void testFieldGenerics() throws IOException { + options.setAsObjCGenericDecl(true); + addSourceFile("class Foo {}", "Foo.java"); + addSourceFile("class Bar {}", "Bar.java"); + addSourceFile( + "import com.google.j2objc.annotations.Property; " + + "class Test { " + + " @Property public Foo field; " + + "}", + "Test.java"); + String translation = translateSourceFile("Test", "Test.h"); + assertInTranslation(translation, "@class Foo;"); + assertInTranslation(translation, "@class Bar;"); + } + + public void testFieldGenericsWithJreTypes() throws IOException { + options.setAsObjCGenericDecl(true); + addSourceFile("class Foo {}", "Foo.java"); + addSourceFile( + "import com.google.j2objc.annotations.Property; " + + "class Test { " + + " @Property public Foo field; " + + "}", + "Test.java"); + String translation = translateSourceFile("Test", "Test.h"); + assertInTranslation(translation, "@class Foo;"); + assertInTranslation(translation, "#include \"java/lang/Boolean.h\""); + assertNotInTranslation(translation, "@class JavaLangBoolean;"); + } }