Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,18 @@ public static Optional<String> generate(
NameTable nameTable,
TypeUtil typeUtil,
boolean parametersNonnullByDefault) {
return new PropertyGenerator(fragment, options, nameTable, typeUtil, parametersNonnullByDefault)
return generate(fragment, options, nameTable, typeUtil, parametersNonnullByDefault, false);
}

public static Optional<String> generate(
VariableDeclarationFragment fragment,
Options options,
NameTable nameTable,
TypeUtil typeUtil,
boolean parametersNonnullByDefault,
boolean staticToInstance) {
return new PropertyGenerator(
fragment, options, nameTable, typeUtil, parametersNonnullByDefault, staticToInstance)
.build();
}

Expand All @@ -58,18 +69,21 @@ public static Optional<String> generate(
private final TypeMirror varType;
private final String propertyName;
private final FieldDeclaration declaration;
private final boolean staticToInstance;

private PropertyGenerator(
VariableDeclarationFragment fragment,
Options options,
NameTable nameTable,
TypeUtil typeUtil,
boolean parametersNonnullByDefault) {
boolean parametersNonnullByDefault,
boolean staticToInstance) {
this.fragment = fragment;
this.options = options;
this.nameTable = nameTable;
this.typeUtil = typeUtil;
this.parametersNonnullByDefault = parametersNonnullByDefault;
this.staticToInstance = staticToInstance;
declaration = (FieldDeclaration) fragment.getParent();
PropertyAnnotation annotation =
(PropertyAnnotation) TreeUtil.getAnnotation(Property.class, declaration.getAnnotations());
Expand Down Expand Up @@ -154,7 +168,7 @@ private void processAccessorAttributes(Set<String> attributes) {
}

private void processClassAttribute(Set<String> attributes) {
if (ElementUtil.isStatic(varElement)) {
if (ElementUtil.isStatic(varElement) && !staticToInstance) {
attributes.add("class");
} else if (attributes.contains("class")) {
ErrorUtil.error(fragment, "Only static fields can be translated to class properties");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ protected void generateInitialDeclaration() {
printMethodDeclaration((MethodDeclaration) declaration, false, true);
}
}
for (VariableDeclarationFragment fragment : getStaticFields()) {
PropertyGenerator.generate(
fragment, options, nameTable, typeUtil, parametersNonnullByDefault, true)
.ifPresent(this::println);
}
println("\n@end\n");
}

Expand Down Expand Up @@ -477,6 +482,12 @@ protected void printCompanionClassDeclaration() {
printProperties();
printStaticInterfaceMethods();
printStaticAccessors();
if (needsKotlinCompanionClass()) {
printf("\n#pragma clang diagnostic push\n");
printf("#pragma clang diagnostic ignored \"-Wincompatible-property-type\"\n");
printf("@property (readonly, class) id<%sCompanion> companion;\n", typeName);
printf("#pragma clang diagnostic pop\n");
}
println("\n@end");
}

Expand Down Expand Up @@ -753,17 +764,17 @@ private void printMethodDeclaration(
TypeElement typeElement = ElementUtil.getDeclaringClass(methodElement);
boolean allowGenerics = !typeUtil.isProtoClass(typeElement.asType());

if (typeElement.getKind().isInterface()) {
if (isKotlinCompanion) {
if (!ElementUtil.isStatic(methodElement)) {
return;
}
} else if (typeElement.getKind().isInterface()) {
// isCompanion and isStatic must be both false (i.e. this prints a non-static method decl
// in @protocol) or must both be true (i.e. this prints a static method decl in the
// companion class' @interface).
if (isCompanionClass != ElementUtil.isStatic(methodElement)) {
return;
}
} else if (isKotlinCompanion) {
if (!ElementUtil.isStatic(methodElement)) {
return;
}
}

newline();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,19 +262,20 @@ private boolean hasStaticMethods() {
Iterables.filter(ElementUtil.getMethods(typeElement), ElementUtil::isStatic));
}

private boolean hasStaticFields() {
return !Iterables.isEmpty(getStaticFields());
}

protected boolean needsKotlinCompanionClass() {
if (!ElementUtil.hasAnnotation(typeElement, GenerateObjCCompanion.class)) {
return false;
}
if (typeNode.hasPrivateDeclaration()) {
throw new IllegalStateException("@GenerateObjCCompanion not supported for private classes.");
}
if (typeElement.getKind().isInterface()) {
throw new IllegalStateException("@GenerateObjCCompanion not supported for interfaces.");
}
if (!hasStaticMethods()) {
if (!hasStaticMethods() && !hasStaticFields()) {
throw new IllegalStateException(
"@GenerateObjCCompanion not supported for types without static methods.");
"@GenerateObjCCompanion not supported for types without static members.");
}
return true;
}
Expand All @@ -286,7 +287,8 @@ protected boolean needsPublicCompanionClass() {
return hasInitializeMethod()
|| hasStaticAccessorMethods()
|| ElementUtil.isGeneratedAnnotation(typeElement)
|| hasStaticMethods();
|| hasStaticMethods()
|| needsKotlinCompanionClass();
}

protected boolean needsCompanionClass() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ protected void generate() {
syncLineNumbers(typeNode.getName()); // avoid doc-comment
printf("@implementation %s\n", typeName);
printProperties();
if (!typeElement.getKind().isInterface() && needsKotlinCompanionClass()) {
if (needsKotlinCompanionClass()) {
printf("\n+ (id<%sCompanion>)companion {", typeName);
printf("\n return (id<%sCompanion>)self;\n}\n", typeName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,68 @@ public static ThreadLocal<String> identity(ThreadLocal<String> val) {
"- (JavaLangThreadLocal<NSString *>"
+ " *)identityWithJavaLangThreadLocal:(JavaLangThreadLocal<NSString *> *)val;");
}

public void testCompanionPropertiesForStaticFields() throws IOException {
options.setClassProperties(true);
String source =
"""
@com.google.j2objc.annotations.GenerateObjCCompanion
public class Foo {
public static final int CONSTANT_VALUE = 1;
public static int mutableStaticField = 2;
private static int privateStaticField = 3;
}
""";
String header = translateSourceFile(source, "Foo", "Foo.h");
assertInTranslation(header, "@protocol FooCompanion");
// Should generate instance property for CONSTANT_VALUE in companion protocol.
assertInTranslation(
header, "@property (readonly) int32_t CONSTANT_VALUE NS_SWIFT_NAME(CONSTANT_VALUE);");
// Should generate instance property for mutableStaticField in companion protocol.
assertInTranslation(
header, "@property int32_t mutableStaticField NS_SWIFT_NAME(mutableStaticField);");
// Should NOT generate property for privateStaticField.
assertNotInTranslation(header, "privateStaticField");
}

public void testCompanionWithOnlyStaticFields() throws IOException {
options.setClassProperties(true);
String source =
"""
@com.google.j2objc.annotations.GenerateObjCCompanion
public class Foo {
public static final int CONSTANT_VALUE = 1;
}
""";
// This should compile successfully without throwing "types without static methods" exception.
String header = translateSourceFile(source, "Foo", "Foo.h");
assertInTranslation(header, "@protocol FooCompanion");
assertInTranslation(
header, "@property (readonly) int32_t CONSTANT_VALUE NS_SWIFT_NAME(CONSTANT_VALUE);");
}

public void testInterfaceCompanion() throws IOException {
options.setClassProperties(true);
String source =
"""
@com.google.j2objc.annotations.GenerateObjCCompanion
public interface Foo {
public static final int CONSTANT_VALUE = 1;
public static void doSomething() {}
}
""";
String header = translateSourceFile(source, "Foo", "Foo.h");
assertInTranslation(header, "@protocol FooCompanion");
assertInTranslation(
header, "@property (readonly) int32_t CONSTANT_VALUE NS_SWIFT_NAME(CONSTANT_VALUE);");
assertInTranslation(header, "- (void)doSomething;");
assertInTranslation(header, "@interface Foo : NSObject");
assertInTranslation(header, "@property (readonly, class) id<FooCompanion> companion;");

String impl = translateSourceFile(source, "Foo", "Foo.m");
assertInTranslation(impl, "@implementation Foo");
assertInTranslation(impl, "+ (id<FooCompanion>)companion {");
assertInTranslation(impl, "return (id<FooCompanion>)self;");
}
}