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 @@ -80,4 +80,10 @@
* The class to map types on the method to Kotlin/Native interop types. This field is required.
*/
Class<?> adapter();

/**
* The Swift name to use for the adapter method. If not specified, a default Swift name will be
* generated based on the selector.
*/
String swiftName() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.google.devtools.j2objc.types.NativeType;
import com.google.devtools.j2objc.types.PointerType;
import com.google.j2objc.annotations.ObjectiveCAdapterMethod;
import com.google.j2objc.annotations.ObjectiveCKmpMethod;
import com.google.j2objc.annotations.ObjectiveCName;
import com.google.j2objc.annotations.ObjectiveCNativeEnumName;
import com.google.j2objc.annotations.SwiftName;
Expand Down Expand Up @@ -560,7 +561,10 @@ public String getMethodNameFromAnnotation(ExecutableElement method) {

public @Nullable String getSwiftMethodNameFromAnnotation(MethodDeclaration method) {
// Check if the method has the annotation
String annotationName = swiftNameFromAnnotation(method.getExecutableElement());
String annotationName = swiftNameFromObjectiveCKmpMethodAnnotation(method.getExecutableElement());
if (annotationName == null) {
annotationName = swiftNameFromAnnotation(method.getExecutableElement());
}
if (annotationName != null) {
return annotationName;
}
Expand Down Expand Up @@ -637,9 +641,19 @@ public String getMethodNameFromAnnotation(ExecutableElement method) {
return null;
}

boolean hasKmpSwiftName = false;
AnnotationMirror kmpAnnotation = ElementUtil.getAnnotation(method, ObjectiveCKmpMethod.class);
if (kmpAnnotation != null) {
String kmpSwiftName = (String) ElementUtil.getAnnotationValue(kmpAnnotation, "swiftName");
if (kmpSwiftName != null && !kmpSwiftName.isEmpty()) {
hasKmpSwiftName = true;
}
}

if (!packageHasSwiftNameAnnotation(owner)
&& !elementHasSwiftNameAnnotation(owner)
&& !elementHasSwiftNameAnnotation(method)) {
&& !elementHasSwiftNameAnnotation(method)
&& !hasKmpSwiftName) {
return null;
}

Expand All @@ -655,29 +669,45 @@ public String getMethodNameFromAnnotation(ExecutableElement method) {

StringBuilder sb = new StringBuilder();

String annotationName = function.getExecutableElement().getSimpleName().toString();

// Constructors are `<init>`
annotationName = annotationName.replace("<", "").replace(">", "");

if (annotationName.contains(":")) {
annotationName = annotationName.replace(":", "");
String customSwiftName = swiftNameFromObjectiveCKmpMethodAnnotation(method);
boolean isKmpSwiftName = customSwiftName != null;
if (customSwiftName == null) {
customSwiftName = swiftNameFromAnnotation(method);
}

annotationName = addSwiftParamNames(function.getParameters(), annotationName, ':');
if (!functionName.contains("_init") && annotationName != null) {
sb.append(" NS_SWIFT_NAME(");
sb.append(className).append(".").append(annotationName);
sb.append(")");
} else if (functionName.contains("new_")) {
sb.append(" NS_SWIFT_NAME(");

if (annotationName != null) {
sb.append(className).append(".").append(annotationName);
} else {
if (customSwiftName != null) {
if (functionName.contains("new_")) {
if (isKmpSwiftName) {
sb.append(" NS_SWIFT_NAME(");
sb.append(className).append(".").append(customSwiftName);
sb.append(")");
} else {
sb.append(" NS_SWIFT_NAME(");
sb.append(addSwiftParamNames(function.getParameters(), className + ".init", ':'));
sb.append(")");
}
} else if (!functionName.contains("_init")) {
sb.append(" NS_SWIFT_NAME(");
sb.append(className).append(".").append(customSwiftName);
sb.append(")");
}
} else {
String derivedName = method.getSimpleName().toString();
derivedName = derivedName.replace("<", "").replace(">", "");
if (derivedName.contains(":")) {
derivedName = derivedName.replace(":", "");
}
derivedName = addSwiftParamNames(function.getParameters(), derivedName, ':');

if (!functionName.contains("_init")) {
sb.append(" NS_SWIFT_NAME(");
sb.append(className).append(".").append(derivedName);
sb.append(")");
} else if (functionName.contains("new_")) {
sb.append(" NS_SWIFT_NAME(");
sb.append(addSwiftParamNames(function.getParameters(), className + ".init", ':'));
sb.append(")");
}
sb.append(")");
}

return sb.toString();
Expand All @@ -691,6 +721,17 @@ public String getMethodNameFromAnnotation(ExecutableElement method) {
return null;
}

public static @Nullable String swiftNameFromObjectiveCKmpMethodAnnotation(Element element) {
AnnotationMirror annotation = ElementUtil.getAnnotation(element, ObjectiveCKmpMethod.class);
if (annotation != null) {
String value = (String) ElementUtil.getAnnotationValue(annotation, "swiftName");
if (value != null && !value.isEmpty()) {
return value;
}
}
return null;
}

public Boolean packageHasSwiftNameAnnotation(TypeElement classType) {
if (options.swiftNaming()) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1993,5 +1993,158 @@ public void verifyList(List<List<List<Foo>>> list) {}
}
""");
}

public void testSwiftName() throws IOException {
addSourceFile(
"""
import com.google.j2objc.annotations.ObjectiveCKmpMethod;
import java.util.List;

public class SwiftNameTest {
@ObjectiveCKmpMethod(
selector="initWithList:",
adapter=Adapter.class,
swiftName="init(myList:)")
public SwiftNameTest(List<String> list) {}

@ObjectiveCKmpMethod(
selector="setList:",
adapter=Adapter.class,
swiftName="set(myList:)")
public void setList(List<String> list) {}

@ObjectiveCKmpMethod(
selector="staticSetList:",
adapter=Adapter.class,
swiftName="staticSet(myList:)")
public static void staticSetList(List<String> list) {}
}
""",
"SwiftNameTest.java");

String header = translateSourceFile("SwiftNameTest", "SwiftNameTest.h");
// ObjC methods should have NS_SWIFT_NAME
assertInTranslation(
header,
"- (instancetype)initWithList:(NSArray<NSString *> *)list NS_SWIFT_NAME(init(myList:));");
assertInTranslation(
header, "- (void)setList:(NSArray<NSString *> *)list NS_SWIFT_NAME(set(myList:));");
assertInTranslation(
header,
"+ (void)staticSetList:(NSArray<NSString *> *)list NS_SWIFT_NAME(staticSet(myList:));");

// C functions should NOT have NS_SWIFT_NAME when wrapper methods are enabled
assertInTranslation(
header,
"FOUNDATION_EXPORT SwiftNameTest *new_SwiftNameTest_initWithList_(NSArray<NSString *>"
+ " *list);");
assertNotInTranslation(
header, "new_SwiftNameTest_initWithList_(NSArray<NSString *> *list) NS_SWIFT_NAME");
assertInTranslation(
header, "FOUNDATION_EXPORT void SwiftNameTest_staticSetList_(NSArray<NSString *> *list);");
assertNotInTranslation(
header, "SwiftNameTest_staticSetList_(NSArray<NSString *> *list) NS_SWIFT_NAME");
}

public void testSwiftNameNoWrappers() throws IOException {
options.load(new String[] {"--no-wrapper-methods"});
addSourceFile(
"""
import com.google.j2objc.annotations.ObjectiveCKmpMethod;
import java.util.List;

public class SwiftNameTestNoWrappers {
@ObjectiveCKmpMethod(
selector="initWithList:",
adapter=Adapter.class,
swiftName="init(myList:)")
public SwiftNameTestNoWrappers(List<String> list) {}

@ObjectiveCKmpMethod(
selector="setList:",
adapter=Adapter.class,
swiftName="set(myList:)")
public void setList(List<String> list) {}

@ObjectiveCKmpMethod(
selector="staticSetList:",
adapter=Adapter.class,
swiftName="staticSet(myList:)")
public static void staticSetList(List<String> list) {}
}
""",
"SwiftNameTestNoWrappers.java");

String header = translateSourceFile("SwiftNameTestNoWrappers", "SwiftNameTestNoWrappers.h");
// ObjC instance methods still exist and should have NS_SWIFT_NAME
assertInTranslation(
header, "- (void)setList:(NSArray<NSString *> *)list NS_SWIFT_NAME(set(myList:));");

// ObjC class methods for static methods do NOT exist when wrapper methods are disabled
assertNotInTranslation(header, "+ (void)staticSetList");

// C functions SHOULD have NS_SWIFT_NAME when wrapper methods are disabled
assertInTranslation(
header,
"FOUNDATION_EXPORT SwiftNameTestNoWrappers"
+ " *new_SwiftNameTestNoWrappers_initWithList_(NSArray<NSString *> *list) "
+ "NS_SWIFT_NAME(SwiftNameTestNoWrappers.init(myList:));");
assertInTranslation(
header,
"FOUNDATION_EXPORT void SwiftNameTestNoWrappers_staticSetList_(NSArray<NSString *> *list) "
+ "NS_SWIFT_NAME(SwiftNameTestNoWrappers.staticSet(myList:));");
}

public void testPrecedenceOfObjectiveCKmpMethodSwiftName() throws IOException {
addSourceFile(
"""
import com.google.j2objc.annotations.ObjectiveCKmpMethod;
import com.google.j2objc.annotations.SwiftName;
import java.util.List;

public class PrecedenceTest {
@ObjectiveCKmpMethod(
selector="setList:",
adapter=Adapter.class,
swiftName="kmpSwiftName(myList:)")
@SwiftName("standaloneSwiftName(myList:)")
public void setList(List<String> list) {}
}
""",
"PrecedenceTest.java");

String header = translateSourceFile("PrecedenceTest", "PrecedenceTest.h");
assertInTranslation(
header, "- (void)setList:(NSArray<NSString *> *)list NS_SWIFT_NAME(kmpSwiftName(myList:));");
assertNotInTranslation(header, "standaloneSwiftName");
}

public void testObjectiveCKmpMethodWithSwiftNameFallback() throws IOException {
addSourceFile(
"""
import com.google.j2objc.annotations.ObjectiveCKmpMethod;
import com.google.j2objc.annotations.SwiftName;
import java.util.List;

public class FallbackTest {
@ObjectiveCKmpMethod(selector="initWithList:", adapter=Adapter.class)
@SwiftName("init(myListFallback:)")
public FallbackTest(List<String> list) {}

@ObjectiveCKmpMethod(selector="setList:", adapter=Adapter.class)
@SwiftName("set(myListFallback:)")
public void setList(List<String> list) {}
}
""",
"FallbackTest.java");

String header = translateSourceFile("FallbackTest", "FallbackTest.h");
assertInTranslation(
header,
"- (instancetype)initWithList:(NSArray<NSString *> *)list"
+ " NS_SWIFT_NAME(init(myListFallback:));");
assertInTranslation(
header, "- (void)setList:(NSArray<NSString *> *)list NS_SWIFT_NAME(set(myListFallback:));");
}
}