From d23edcee9fd78eecd0f1e538d844c679ea380246 Mon Sep 17 00:00:00 2001 From: J2ObjC Team Date: Mon, 18 May 2026 01:25:21 -0700 Subject: [PATCH] Use `id` instead of `MyJavaInterface *` in ObjectiveCKmpMethod Map java interfaces (translated to ObjC protocols) to `id` rather than `MyJavaInterface *`, which is incorrect in ObjC. Types that are specifically mapped, such as `java.util.List`, are not affected. PiperOrigin-RevId: 917081023 --- .../ObjectiveCKmpMethodTranslator.java | 31 ++++++-- .../ObjectiveCKmpMethodTranslatorTest.java | 78 +++++++++++++++++++ 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/translator/src/main/java/com/google/devtools/j2objc/translate/ObjectiveCKmpMethodTranslator.java b/translator/src/main/java/com/google/devtools/j2objc/translate/ObjectiveCKmpMethodTranslator.java index 79c896bdc8..a05e5fef21 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/translate/ObjectiveCKmpMethodTranslator.java +++ b/translator/src/main/java/com/google/devtools/j2objc/translate/ObjectiveCKmpMethodTranslator.java @@ -787,17 +787,18 @@ private NativeType calculateNativeType( return new NativeType(builder.toString(), null, null, referencedTypes); } - private String toNativeType( + private PrintableNativeType toNativeType( TypeMirror typeMirror, ExecutableElement methodExecutable, List referencedTypes) { String fullyQualifiedNameJavaName = TypeUtil.getQualifiedName(typeMirror); String nativeType = JAVA_TO_NATIVE_TYPE_MAP.get(fullyQualifiedNameJavaName); if (nativeType != null) { - return nativeType; + return new PrintableNativeType(nativeType, false); } TypeElement typeElement = TypeUtil.asTypeElement(typeMirror); if (typeElement != null) { referencedTypes.add(typeMirror); - return nameTable.getFullName(typeElement); + return new PrintableNativeType( + nameTable.getFullName(typeElement), TypeUtil.isInterface(typeMirror)); } throw new IllegalArgumentException( "Unsupported type: " + fullyQualifiedNameJavaName + " in method: " + methodExecutable); @@ -820,6 +821,8 @@ private boolean isTypeSupported(TypeMirror typeMirror) { *
  • {@code List} becomes {@code NSArray *} *
  • {@code Map} becomes {@code NSDictionary *} *
  • {@code Set>} becomes {@code NSSet *> *} + *
  • {@code JavaInterface} becomes {@code id} + *
  • {@code List} becomes {@code NSArray> *} * */ private class NativeTypeVisitor extends SimpleTypeVisitor9 { @@ -835,13 +838,21 @@ private NativeTypeVisitor( @Override public Void visitDeclared(DeclaredType type, StringBuilder builder) { - builder.append(toNativeType(type, methodExecutable, referencedTypes)); + PrintableNativeType nativeType = toNativeType(type, methodExecutable, referencedTypes); + if (nativeType.isUntranslatedInterface) { + builder.append("id<"); + } + builder.append(nativeType.nativeTypeName); List typeArguments = type.getTypeArguments(); if (options.asObjCGenericDecl() && !typeArguments.isEmpty()) { String typeArgsString = buildTypeArgumentString(typeArguments); builder.append("<").append(typeArgsString).append(">"); } - builder.append(" *"); + if (nativeType.isUntranslatedInterface) { + builder.append(">"); + } else { + builder.append(" *"); + } return null; } @@ -937,4 +948,14 @@ abstract AdapterLookup.ConverterMatch findParamConverter( abstract TypeMirror getReturnCastType( TypeMirror originalMethodReturnType, TypeMirror adapterReturnType); } + + private static final class PrintableNativeType { + private final String nativeTypeName; + private final boolean isUntranslatedInterface; + + PrintableNativeType(String nativeTypeName, boolean isUntranslatedInterface) { + this.nativeTypeName = nativeTypeName; + this.isUntranslatedInterface = isUntranslatedInterface; + } + } } diff --git a/translator/src/test/java/com/google/devtools/j2objc/translate/ObjectiveCKmpMethodTranslatorTest.java b/translator/src/test/java/com/google/devtools/j2objc/translate/ObjectiveCKmpMethodTranslatorTest.java index 412d3397ae..54b44a4fe0 100644 --- a/translator/src/test/java/com/google/devtools/j2objc/translate/ObjectiveCKmpMethodTranslatorTest.java +++ b/translator/src/test/java/com/google/devtools/j2objc/translate/ObjectiveCKmpMethodTranslatorTest.java @@ -1595,4 +1595,82 @@ public void setListSet(Set> set) {} assertThrows(Throwable.class, () -> translateSourceFile("FailList", "FailList.m")); assertTrue(e.getMessage().contains("Exact converter required for mapped type")); } + + public void testInterfaceTypes() throws IOException { + addSourceFile( + """ + public interface TestInterface { + } + """, + "TestInterface.java"); + addSourceFile( + """ + import com.google.j2objc.annotations.ObjectiveCKmpMethod; + import java.util.Set; + import java.util.List; + public class InterfaceUsage { + @ObjectiveCKmpMethod(selector="getInterface", adapter=Adapter.class) + public TestInterface getInterface() { + return null; + } + + @ObjectiveCKmpMethod(selector="setInterface:", adapter=Adapter.class) + public void setInterface(TestInterface value) { + return; + } + } + """, + "InterfaceUsage.java"); + + String header = translateSourceFile("InterfaceUsage", "InterfaceUsage.h"); + assertInTranslation( + header, + """ + - (id)getInterface; + """); + assertInTranslation( + header, + """ + - (void)setInterface:(id)value; + """); + } + + public void testListOfInterface() throws IOException { + addSourceFile( + """ + public interface TestInterface { + } + """, + "TestInterface.java"); + addSourceFile( + """ + import com.google.j2objc.annotations.ObjectiveCKmpMethod; + import java.util.Set; + import java.util.List; + public class ListOfInterface { + @ObjectiveCKmpMethod(selector="getListOfInterface", adapter=Adapter.class) + public List getListOfInterface() { + return null; + } + + @ObjectiveCKmpMethod(selector="setListOfInterface:", adapter=Adapter.class) + public void setListOfInterface(List list) { + return; + } + } + """, + "ListOfInterface.java"); + + String header = translateSourceFile("ListOfInterface", "ListOfInterface.h"); + assertInTranslation( + header, + """ + - (NSArray> *)getListOfInterface; + """); + assertInTranslation( + header, + """ + - (void)setListOfInterface:(NSArray> *)list; + """); + } }