diff --git a/translator/src/main/java/com/google/devtools/j2objc/util/NameTable.java b/translator/src/main/java/com/google/devtools/j2objc/util/NameTable.java index b778ae83dc..cde24bf0ba 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/util/NameTable.java +++ b/translator/src/main/java/com/google/devtools/j2objc/util/NameTable.java @@ -23,6 +23,7 @@ import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.devtools.j2objc.J2ObjC; import com.google.devtools.j2objc.Options; import com.google.devtools.j2objc.ast.EnumConstantDeclaration; @@ -560,23 +561,29 @@ public String getMethodNameFromAnnotation(ExecutableElement method) { } public @Nullable String getSwiftMethodNameFromAnnotation(MethodDeclaration method) { + ExecutableElement element = method.getExecutableElement(); // Check if the method has the annotation - String annotationName = swiftNameFromAnnotation(method.getExecutableElement()); + String annotationName = swiftNameFromAnnotation(element); if (annotationName != null) { return annotationName; } - if (method.getParameters().isEmpty()) { - return null; - } - + TypeElement clazz = ElementUtil.getDeclaringClass(element); // Check if the class or package has the annotation - TypeElement clazz = ElementUtil.getDeclaringClass(method.getExecutableElement()); if (!packageHasSwiftNameAnnotation(clazz) && !elementHasSwiftNameAnnotation(clazz)) { return null; } - ExecutableElement element = method.getExecutableElement(); + String selector = getMethodSelector(element); + if (ElementUtil.isConstructor(element)) { + if (hasSuperConstructorWithSameSelector(selector, clazz)) { + return null; + } + } else { + if (hasSuperMethodWithSameSelector(selector, clazz)) { + return null; + } + } // If the annotation is still null after checking the names then it doesn't exist String methodName = element.getSimpleName().toString(); @@ -591,6 +598,42 @@ public String getMethodNameFromAnnotation(ExecutableElement method) { return addSwiftParamNames(method.getParameters(), methodName, ':'); } + private boolean hasSuperConstructorWithSameSelector(String selector, TypeElement clazz) { + TypeElement superClass = ElementUtil.getSuperclass(clazz); + if (superClass == null) { + return false; + } + if (Iterables.any( + ElementUtil.getConstructors(superClass), c -> getMethodSelector(c).equals(selector))) { + return true; + } + return hasSuperConstructorWithSameSelector(selector, superClass); + } + + private boolean hasSuperMethodWithSameSelector(String selector, TypeElement clazz) { + TypeElement superClass = ElementUtil.getSuperclass(clazz); + if (superClass != null) { + if (hasMethodWithSelector(superClass, selector) + || hasSuperMethodWithSameSelector(selector, superClass)) { + return true; + } + } + for (TypeMirror interfaceMirror : clazz.getInterfaces()) { + TypeElement interfaceElement = TypeUtil.asTypeElement(interfaceMirror); + if (interfaceElement != null) { + if (hasMethodWithSelector(interfaceElement, selector) + || hasSuperMethodWithSameSelector(selector, interfaceElement)) { + return true; + } + } + } + return false; + } + + private boolean hasMethodWithSelector(TypeElement clazz, String selector) { + return Iterables.any(ElementUtil.getMethods(clazz), m -> getMethodSelector(m).equals(selector)); + } + public @Nullable String getSwiftClassNameFromAnnotation(TypeElement clazz, boolean getParents) { String annotationName = swiftNameFromAnnotation(clazz); if (annotationName != null) { @@ -605,8 +648,8 @@ public String getMethodNameFromAnnotation(ExecutableElement method) { NestingKind nesting = clazz.getNestingKind(); if (nesting == NestingKind.MEMBER) { Element parent = clazz.getEnclosingElement(); - if (parent instanceof TypeElement && !parent.getKind().isInterface()) { - String parentName = getSwiftClassNameFromAnnotation((TypeElement) parent, false); + if (parent instanceof TypeElement parentElement && !parent.getKind().isInterface()) { + String parentName = getSwiftClassNameFromAnnotation(parentElement, false); if (parentName != null) { return (parentName + "." + clazz.getSimpleName()).replaceAll("$", ""); } diff --git a/translator/src/test/java/com/google/devtools/j2objc/gen/ObjectiveCHeaderGeneratorTest.java b/translator/src/test/java/com/google/devtools/j2objc/gen/ObjectiveCHeaderGeneratorTest.java index 4b84fd6f98..d8d78249af 100644 --- a/translator/src/test/java/com/google/devtools/j2objc/gen/ObjectiveCHeaderGeneratorTest.java +++ b/translator/src/test/java/com/google/devtools/j2objc/gen/ObjectiveCHeaderGeneratorTest.java @@ -1102,6 +1102,359 @@ public void testSwiftNameClassAnnotation() throws IOException { assertInTranslation(translation, "NS_SWIFT_NAME(FooBar)"); } + public void testSwiftNamePackagePrivateConstructor() throws IOException { + String sourceContent = + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class FooBar { + FooBar(int x) {} + } + """; + String translation = translateSourceFile(sourceContent, "FooBar", "com/foo/bar/FooBar.h"); + // Package-private constructors should still generate NS_SWIFT_NAME annotations. + assertTranslatedLines( + translation, + "NS_SWIFT_NAME(FooBar)", + "@interface ComFooBarFooBar : NSObject", + "", + "#pragma mark Package-Private", + "", + "- (instancetype)initWithInt:(int32_t)x NS_SWIFT_NAME(init(x:));", + "", + "// Disallowed inherited constructors, do not use.", + "", + "- (instancetype)init NS_UNAVAILABLE;", + "", + "@end"); + } + + public void testSwiftNameConstructorSuperConflictGrandparent() throws IOException { + addSourceFile( + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class GrandSuper { + public GrandSuper() {} + public GrandSuper(int x) {} + } + """, + "com/foo/bar/GrandSuper.java"); + addSourceFile( + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class Super extends GrandSuper { + } + """, + "com/foo/bar/Super.java"); + String sourceContent = + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class Sub extends Super { + public Sub(int y) { + super(); + } + } + """; + String translation = translateSourceFile(sourceContent, "Sub", "com/foo/bar/Sub.h"); + // Constructors should not generate NS_SWIFT_NAME annotations if there is a name conflict with a + // grandparent. + assertTranslatedLines( + translation, + "NS_SWIFT_NAME(Sub)", + "@interface ComFooBarSub : ComFooBarSuper", + "", + "#pragma mark Public", + "", + "- (instancetype)initWithInt:(int32_t)y;", + "", + "// Disallowed inherited constructors, do not use.", + "", + "- (instancetype)init NS_UNAVAILABLE;", + "", + "@end"); + } + + public void testSwiftNameConstructorSuperConflict() throws IOException { + addSourceFile( + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class Super { + public Super(int x) {} + } + """, + "com/foo/bar/Super.java"); + String sourceContent = + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class Sub extends Super { + public Sub(int y) { + super(y); + } + } + """; + String translation = translateSourceFile(sourceContent, "Sub", "com/foo/bar/Sub.h"); + assertTranslatedLines( + translation, + "NS_SWIFT_NAME(Sub)", + "@interface ComFooBarSub : ComFooBarSuper", + "", + "#pragma mark Public", + "", + "- (instancetype)initWithInt:(int32_t)y;", + "", + "@end"); + } + + public void testSwiftNameMethodSuperConflict() throws IOException { + addSourceFile( + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class GrandSuper { + public void foo(int x) {} + } + """, + "com/foo/bar/GrandSuper.java"); + addSourceFile( + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class Super extends GrandSuper { + } + """, + "com/foo/bar/Super.java"); + String sourceContent = + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class Sub extends Super { + @Override + public void foo(int y) {} + } + """; + String translation = translateSourceFile(sourceContent, "Sub", "com/foo/bar/Sub.h"); + assertTranslatedLines( + translation, + "NS_SWIFT_NAME(Sub)", + "@interface ComFooBarSub : ComFooBarSuper", + "", + "#pragma mark Public", + "", + "- (instancetype)init;", + "", + "- (void)fooWithInt:(int32_t)y;", + "", + "@end"); + } + + public void testSwiftNameSuperClassNotAnnotated() throws IOException { + addSourceFile( + """ + package com.foo.bar; + public class Super { + public void foo(int x) {} + } + """, + "com/foo/bar/Super.java"); + String sourceContent = + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class Sub extends Super { + @Override + public void foo(int y) {} + } + """; + String translation = translateSourceFile(sourceContent, "Sub", "com/foo/bar/Sub.h"); + assertTranslatedLines( + translation, + "NS_SWIFT_NAME(Sub)", + "@interface ComFooBarSub : ComFooBarSuper", + "", + "#pragma mark Public", + "", + "- (instancetype)init;", + "", + "- (void)fooWithInt:(int32_t)y;", + "", + "@end"); + } + + public void testSwiftNameClassImplementingInterface() throws IOException { + String interfaceSource = + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public interface MyInterface { + void foo(); + } + """; + addSourceFile(interfaceSource, "com/foo/bar/MyInterface.java"); + + String classSource = + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class MyClass implements MyInterface { + public void foo() {} + } + """; + + // Translate and assert interface header + String interfaceTranslation = + translateSourceFile(interfaceSource, "MyInterface", "com/foo/bar/MyInterface.h"); + assertTranslatedLines( + interfaceTranslation, + "NS_SWIFT_NAME(MyInterface)", + "@protocol ComFooBarMyInterface < JavaObject >", + "", + "- (void)foo NS_SWIFT_NAME(foo());", + "", + "@end"); + + // Translate and assert class header + String classTranslation = translateSourceFile(classSource, "MyClass", "com/foo/bar/MyClass.h"); + assertInTranslation(classTranslation, "#include \"com/foo/bar/MyInterface.h\""); + // Verify that ComFooBarMyClass has NS_SWIFT_NAME, but its foo method + // does not have NS_SWIFT_NAME because it overrides MyInterface.foo(). + assertTranslatedLines( + classTranslation, + "NS_SWIFT_NAME(MyClass)", + "@interface ComFooBarMyClass : NSObject < ComFooBarMyInterface >", + "", + "#pragma mark Public", + "", + "- (instancetype)init;", + "", + "- (void)foo;", + "", + "@end"); + } + + public void testSwiftNamePackagePrivateClassConstructor() throws IOException { + String sourceContent = + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + class FooBar { + FooBar(int x) {} + } + """; + String translation = translateSourceFile(sourceContent, "FooBar", "com/foo/bar/FooBar.h"); + assertTranslatedLines( + translation, + "NS_SWIFT_NAME(FooBar)", + "@interface ComFooBarFooBar : NSObject", + "", + "#pragma mark Package-Private", + "", + "- (instancetype)initPackagePrivateWithInt:(int32_t)x NS_SWIFT_NAME(init(x:));", + "", + "// Disallowed inherited constructors, do not use.", + "", + "- (instancetype)init NS_UNAVAILABLE;", + "", + "@end"); + } + + public void testSwiftNamePackagePrivateConstructorZeroParams() throws IOException { + String sourceContent = + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class FooBar { + FooBar() {} + } + """; + String translation = translateSourceFile(sourceContent, "FooBar", "com/foo/bar/FooBar.h"); + // Constructors package private constructors on public classes should not generate + // NS_SWIFT_NAME annotations, if it did it would conflict with the parent class. + assertTranslatedLines( + translation, + "NS_SWIFT_NAME(FooBar)", + "@interface ComFooBarFooBar : NSObject", + "", + "#pragma mark Package-Private", + "", + "- (instancetype)init;", + "", + "@end"); + } + + public void testSwiftNamePackagePrivateClassConstructorZeroParams() throws IOException { + String sourceContent = + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + class FooBar { + FooBar() {} + } + """; + String translation = translateSourceFile(sourceContent, "FooBar", "com/foo/bar/FooBar.h"); + // Constructors package private constructors on package-private classes should generate + // NS_SWIFT_NAME annotations, it doesn't conflict with the parent class because the parent class + // has a different constructor name (init). + assertTranslatedLines( + translation, + "NS_SWIFT_NAME(FooBar)", + "@interface ComFooBarFooBar : NSObject", + "", + "#pragma mark Package-Private", + "", + "- (instancetype)initPackagePrivate NS_SWIFT_NAME(init());", + "", + "// Disallowed inherited constructors, do not use.", + "", + "- (instancetype)init NS_UNAVAILABLE;", + "", + "@end"); + } + + public void testSwiftNameZeroParamMethod() throws IOException { + String sourceContent = + """ + package com.foo.bar; + import com.google.j2objc.annotations.SwiftName; + @SwiftName + public class FooBar { + public void foo() {} + } + """; + String translation = translateSourceFile(sourceContent, "FooBar", "com/foo/bar/FooBar.h"); + assertTranslatedLines( + translation, + "NS_SWIFT_NAME(FooBar)", + "@interface ComFooBarFooBar : NSObject", + "", + "#pragma mark Public", + "", + "- (instancetype)init;", + "", + "- (void)foo NS_SWIFT_NAME(foo());", + "", + "@end"); + } + public void testSwiftNameClassAnnotationOverride() throws IOException { String sourceContent = " package com.foo.bar;" @@ -1186,11 +1539,13 @@ public void testSwiftNamePackageAnnotationWithObjCNameMethods() throws IOExcepti + "}"; String translation = translateSourceFile(sourceContent, "FooBar", "com/foo/bar/FooBar.h"); assertInTranslation( - translation, "- (void)objcSetFooField:(NSString *)fooField NS_SWIFT_NAME(setFooField(fooField:));"); + translation, + "- (void)objcSetFooField:(NSString *)fooField NS_SWIFT_NAME(setFooField(fooField:));"); assertInTranslation( translation, - "+ (void)objcBuilderWithSize:(int32_t)expectedSize NS_SWIFT_NAME(builderWithExpectedSize(expectedSize:));"); - assertInTranslation(translation, "- (void)objcDoAction;"); + "+ (void)objcBuilderWithSize:(int32_t)expectedSize" + + " NS_SWIFT_NAME(builderWithExpectedSize(expectedSize:));"); + assertInTranslation(translation, "- (void)objcDoAction NS_SWIFT_NAME(doAction());"); } public void testSwiftNameAnnotationWithNestedTypes() throws IOException { @@ -1269,10 +1624,7 @@ public void testSwiftNameInheritedInterface() throws IOException { String fooHeader = translateCombinedFiles("com/Foo", ".h", "foo/FooBar.java"); String barHeader = translateCombinedFiles("com/Bar", ".h", "bar/BarFoo.java"); - assertTranslatedLines( - barHeader, - "- (NSString *)getElementByIndexWithInt:(int32_t)index" - + " NS_SWIFT_NAME(getElementByIndexWithInt(index:));"); + assertTranslatedLines(barHeader, "- (NSString *)getElementByIndexWithInt:(int32_t)index;"); assertTranslatedLines( fooHeader, "- (id)getElementByIndexWithInt:(int32_t)index NS_SWIFT_NAME(getElementByIndex(index:));"); @@ -1299,7 +1651,8 @@ public void testSwiftNameAbnormalArgumentNames() throws IOException { "- (NSString *)fooBarWithNSString:(NSString *)fooBar NS_SWIFT_NAME(fooBar(fooBar:));"); assertTranslatedLines( fooHeader, - "- (NSString *)setSomeValueWithNSString:(NSString *)value NS_SWIFT_NAME(setSomeValue(value:));"); + "- (NSString *)setSomeValueWithNSString:(NSString *)value" + + " NS_SWIFT_NAME(setSomeValue(value:));"); assertTranslatedLines( fooHeader, "- (NSString *)setSomeArgumentWithNSString:(NSString *)arg0" @@ -1330,7 +1683,8 @@ public void testSwiftNameAnnotationWithStaticFunctions() throws IOException { + "}"; String translation = translateSourceFile(sourceContent, "FooBar", "com/foo/bar/FooBar.h"); assertInTranslation(translation, "NS_SWIFT_NAME(FooBar.init())"); - assertInTranslation(translation, "NS_SWIFT_NAME(FooBar.builderWithExpectedSize(expectedSize:))"); + assertInTranslation( + translation, "NS_SWIFT_NAME(FooBar.builderWithExpectedSize(expectedSize:))"); assertInTranslation(translation, "NS_SWIFT_NAME(FooBar.builderWithName(name:))"); }