From ae3b4364e1ae57c4fca0734551835d1e3d237ddd Mon Sep 17 00:00:00 2001 From: J2ObjC Team Date: Thu, 23 Jul 2026 14:07:01 -0700 Subject: [PATCH] Preserve generics on static member variables translated to class properties. J2ObjC was not preserving generic type information when translating static member variables to Objective-C class properties. This change updates PropertyGenerator to use the generic type declaration if generics are enabled. Also updates HeaderImportCollector to collect generic parameters for fields, ensuring they are forward declared or imported in the generated headers when generics are enabled. Generic parameters that are JRE types (e.g. java.lang.Boolean) are explicitly imported (#include) in the header instead of forward declared (@class) to avoid Swift compiler errors (incomplete interface) when importing properties using these generic parameters. To prevent circular dependency cycles when compiling the JRE emulation library (jre_emul) itself, HeaderImportCollector checks if the current compilation unit belongs to a core JRE package (java.*, and selected javax.*, android.*, org.* packages that are part of jre_emul). If so, it bypasses the JRE force-import logic and falls back to default forward declarations for all generic parameters. This core JRE check specifically excludes packages like javax.inject (jsr330) which are not part of jre_emul, preventing dependency violations in modular builds where headers would otherwise try to directly import javax/inject/Provider.h without the target declaring a direct dependency on the jsr330 module. To resolve Objective-C compilation errors (incompatible pointer types) when passing generic arguments to generic methods (which are erased to bounds in ObjC, e.g. DXLTextProtocolNestedNestedModelType> * expected but DXLTextProtocolNestedNestedModelType * provided), this change makes all Objective-C lightweight generics covariant by prepending `__covariant` to generic type parameters in generated class declarations and extensions (TypeDeclarationGenerator and TypePrivateDeclarationGenerator). This matches Java's type safety checks which were already performed by javac, and allows implicit conversions (e.g., from Generic * to Generic> * or Generic *) in ObjC that would otherwise be rejected by strict invariant pointer checks. To resolve similar pointer mismatch errors when assigning erased generic method returns (e.g. SettingKey returned by generic factory method) to specific generic static fields (e.g. SettingKey), this change updates CastResolver to perform a stricter assignability check that considers ObjC generic type parameters when options.asObjCGenericDecl() is enabled. If the generic parameters do not match (taking covariance into account), CastResolver will now correctly insert a cast in the ObjC translation (which is generated as a raw class pointer cast, e.g. `(SettingKey *)`, sufficient to satisfy the ObjC compiler). Note on C function signatures: J2ObjC generated C functions (representing static methods and constructors) must remain erased (no ObjC generics) in their signatures. This is because C functions are global and do not have access to class-level or method-level generic type variables, and trying to use them (e.g. `JavaLangEnum *`) would trigger ObjC compiler errors. Furthermore, enabling generics for C functions causes Swift compilation errors (generic argument mismatch) when passing these typed J2ObjC generics to native ObjC APIs that expect raw types (imported as `` in Swift). Thus, C function signatures are kept raw, and tests are added to verify this erasure behavior. Also updates TypeGenerator, TypeDeclarationGenerator, and TypeImplementationGenerator to support generic types in C-style accessors and global variable declarations for static fields (the default translation when class properties are disabled). Generics support in TypeGenerator.getDeclarationType is restricted to static fields only, to avoid generating generic types for instance fields which can trigger ObjC compiler errors (incompatible pointer types) on generic method return assignments due to J2ObjC missing casts for ObjC generics. To support generic types with multiple parameters (which contain commas) in C preprocessor macros (like J2OBJC_STATIC_FIELD_OBJ), TypeDeclarationGenerator now generates a typedef for these types in the header and uses the typedef name in the macro call and accessor declarations, avoiding macro argument parsing errors. Adds test cases for: - Automatic class properties (setClassProperties=true). - Automatic class properties with @GenerateObjectiveCGenerics. - Explicit class properties via @Property. - Default static field translation (both options false) to verify C-style accessors and global variables are used, and they correctly preserve generics when enabled. - Default static field translation with multiple generic parameters (containing commas) to verify the typedef workaround. - HeaderImportCollector JRE generic parameters import behavior. - CastResolver generic method assignment cast behavior. - C function signatures for static methods being erased (no generics). - C function signatures for constructors being erased (no generics). Updates existing GenerateObjectiveCGenericsTest cases to expect `__covariant` in generated class declarations and extensions. PiperOrigin-RevId: 952947498 --- .../j2objc/gen/PropertyGenerator.java | 11 +- .../j2objc/gen/TypeDeclarationGenerator.java | 24 +++- .../devtools/j2objc/gen/TypeGenerator.java | 8 +- .../gen/TypeImplementationGenerator.java | 12 +- .../j2objc/translate/CastResolver.java | 25 ++++ .../j2objc/types/HeaderImportCollector.java | 67 +++++++++- .../gen/GenerateObjectiveCGenericsTest.java | 117 ++++++++++++++++-- .../j2objc/translate/CastResolverTest.java | 28 +++++ .../types/HeaderImportCollectorTest.java | 30 +++++ 9 files changed, 299 insertions(+), 23 deletions(-) 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;"); + } }