From b7d156ba44fb2d19ef16b7da5844332592142848 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 6 Mar 2026 14:04:21 +0900 Subject: [PATCH 1/7] Add test for JavaOptional --- Tests/SwiftJavaTests/BasicRuntimeTests.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/SwiftJavaTests/BasicRuntimeTests.swift b/Tests/SwiftJavaTests/BasicRuntimeTests.swift index 0a139ba2..1dace398 100644 --- a/Tests/SwiftJavaTests/BasicRuntimeTests.swift +++ b/Tests/SwiftJavaTests/BasicRuntimeTests.swift @@ -108,6 +108,18 @@ class BasicRuntimeTests: XCTestCase { XCTAssertEqual(javaList.map { $0.intValue() }, [0, 1, 2]) } + + func testJavaOptional() throws { + let environment = try jvm.environment() + + let value = JavaInteger(42, environment: environment) + let javaOptional = Optional.some(value).toJavaOptional() + if javaOptional.isPresent() { + XCTAssertEqual(javaOptional.get().intValue(), 42) + } else { + XCTFail("javaOptional is empty") + } + } } @JavaClass("org.swift.javakit.Nonexistent") From 860fe501349ca8d33c658f7668c5d003e41380cc Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 6 Mar 2026 16:00:28 +0900 Subject: [PATCH 2/7] Erase generic argument type in JNI call --- Sources/SwiftJava/Optional+JavaOptional.swift | 4 +- .../SwiftJava/generated/JavaOptional.swift | 92 ++++++++++++++++--- Sources/SwiftJavaMacros/JavaMethodMacro.swift | 78 ++++++++++++++-- Sources/SwiftJavaMacros/MacroErrors.swift | 1 + .../JavaClassMacroTests.swift | 62 +++++++++++++ 5 files changed, 213 insertions(+), 24 deletions(-) diff --git a/Sources/SwiftJava/Optional+JavaOptional.swift b/Sources/SwiftJava/Optional+JavaOptional.swift index e93afb12..f6dd423e 100644 --- a/Sources/SwiftJava/Optional+JavaOptional.swift +++ b/Sources/SwiftJava/Optional+JavaOptional.swift @@ -14,7 +14,7 @@ extension Optional where Wrapped: AnyJavaObject { public func toJavaOptional() -> JavaOptional { - try! JavaClass>().ofNullable(self?.as(JavaObject.self)).as(JavaOptional.self)! + try! JavaClass>().ofNullable(self) } public init(javaOptional: JavaOptional?) { @@ -29,7 +29,7 @@ extension Optional where Wrapped: AnyJavaObject { extension Optional where Wrapped == String { public func toJavaOptional() -> JavaOptional { if let self { - return try! JavaClass>().of(JavaString(self).as(JavaObject.self)).as(JavaOptional.self)! + return try! JavaClass>().of(JavaString(self)) } else { return try! JavaClass>().empty().as(JavaOptional.self)! } diff --git a/Sources/SwiftJava/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift index 6ec584af..1726fbc5 100644 --- a/Sources/SwiftJava/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -3,51 +3,115 @@ import SwiftJavaJNICore @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { - @JavaMethod(typeErasedResult: "T") + /// Java method `get`. + /// + /// ### Java method signature + /// ```java + /// public T java.util.Optional.get() + /// ``` + @JavaMethod(typeErasedResult: "T!") open func get() -> T! + /// Java method `equals`. + /// + /// ### Java method signature + /// ```java + /// public boolean java.util.Optional.equals(java.lang.Object) + /// ``` @JavaMethod open override func equals(_ arg0: JavaObject?) -> Bool + /// Java method `toString`. + /// + /// ### Java method signature + /// ```java + /// public java.lang.String java.util.Optional.toString() + /// ``` @JavaMethod open override func toString() -> String + /// Java method `hashCode`. + /// + /// ### Java method signature + /// ```java + /// public int java.util.Optional.hashCode() + /// ``` @JavaMethod open override func hashCode() -> Int32 + /// Java method `isEmpty`. + /// + /// ### Java method signature + /// ```java + /// public boolean java.util.Optional.isEmpty() + /// ``` @JavaMethod open func isEmpty() -> Bool - @JavaMethod - open func isPresent() -> Bool + /// Java method `orElse`. + /// + /// ### Java method signature + /// ```java + /// public T java.util.Optional.orElse(T) + /// ``` + @JavaMethod(typeErasedResult: "T!") + open func orElse(_ arg0: T?) -> T! + /// Java method `isPresent`. + /// + /// ### Java method signature + /// ```java + /// public boolean java.util.Optional.isPresent() + /// ``` @JavaMethod - open func orElse(_ arg0: JavaObject?) -> JavaObject! + open func isPresent() -> Bool - @JavaMethod - open func orElseThrow() -> JavaObject! + /// Java method `orElseThrow`. + /// + /// ### Java method signature + /// ```java + /// public T java.util.Optional.orElseThrow() + /// ``` + @JavaMethod(typeErasedResult: "T!") + open func orElseThrow() -> T! } extension JavaClass { + /// Java method `of`. + /// + /// ### Java method signature + /// ```java + /// public static java.util.Optional java.util.Optional.of(T) + /// ``` @JavaStaticMethod - public func of(_ arg0: JavaObject?) -> JavaOptional! where ObjectType == JavaOptional + public func of(_ arg0: T?) -> JavaOptional! where ObjectType == JavaOptional - public func ofOptional(_ arg0: JavaObject?) -> JavaObject? where ObjectType == JavaOptional { + public func ofOptional(_ arg0: T?) -> T? where ObjectType == JavaOptional { Optional(javaOptional: of(arg0)) } + /// Java method `empty`. + /// + /// ### Java method signature + /// ```java + /// public static java.util.Optional java.util.Optional.empty() + /// ``` @JavaStaticMethod - public func empty() -> JavaOptional! where ObjectType == JavaOptional + public func empty() -> JavaOptional! where ObjectType == JavaOptional - public func emptyOptional() -> JavaObject? where ObjectType == JavaOptional { + public func emptyOptional() -> T? where ObjectType == JavaOptional { Optional(javaOptional: empty()) } + /// Java method `ofNullable`. + /// + /// ### Java method signature + /// ```java + /// public static java.util.Optional java.util.Optional.ofNullable(T) + /// ``` @JavaStaticMethod - public func ofNullable(_ arg0: JavaObject?) -> JavaOptional! - where ObjectType == JavaOptional + public func ofNullable(_ arg0: T?) -> JavaOptional! where ObjectType == JavaOptional - public func ofNullableOptional(_ arg0: JavaObject?) -> JavaObject? - where ObjectType == JavaOptional { + public func ofNullableOptional(_ arg0: T?) -> T? where ObjectType == JavaOptional { Optional(javaOptional: ofNullable(arg0)) } } diff --git a/Sources/SwiftJavaMacros/JavaMethodMacro.swift b/Sources/SwiftJavaMacros/JavaMethodMacro.swift index 9dd2c636..2e9878d8 100644 --- a/Sources/SwiftJavaMacros/JavaMethodMacro.swift +++ b/Sources/SwiftJavaMacros/JavaMethodMacro.swift @@ -50,6 +50,8 @@ extension JavaMethodMacro: BodyMacro { fatalError("not a function: \(declaration)") } + var resultStatements: [CodeBlockItemSyntax] = [] + let funcName = if case .argumentList(let arguments) = node.arguments, let argument = arguments.first, @@ -65,7 +67,28 @@ extension JavaMethodMacro: BodyMacro { let isStatic = node.attributeName.trimmedDescription == "JavaStaticMethod" let params = funcDecl.signature.parameterClause.parameters - let paramNames = params.map { param in param.parameterName?.text ?? "" }.joined(separator: ", ") + + var paramNames: [String] = [] + for param in params { + guard let name = param.parameterName else { + throw MacroErrors.parameterMustHaveName + } + if isJNIGenericParameter(param.type, funcDecl: funcDecl, in: context) { + let erasedName: TokenSyntax = "\(name)$erased" + if param.type.optionalUnwrappedType() != nil { + resultStatements.append( + "let \(erasedName) = \(name).map { JavaObject(javaHolder: $0.javaHolder) }" + ) + } else { + resultStatements.append( + "let \(erasedName) = JavaObject(javaHolder: \(name).javaHolder)" + ) + } + paramNames.append(erasedName.text) + } else { + paramNames.append(name.text) + } + } let genericResultType: String? = if case let .argumentList(arguments) = node.arguments, @@ -106,7 +129,7 @@ extension JavaMethodMacro: BodyMacro { if paramNames.isEmpty { parametersAsArgs = "" } else { - parametersAsArgs = ", arguments: \(paramNames)" + parametersAsArgs = ", arguments: \(paramNames.joined(separator: ", "))" } let canRethrowError = funcDecl.signature.effectSpecifiers?.throwsClause != nil @@ -137,7 +160,7 @@ extension JavaMethodMacro: BodyMacro { """ if let genericResultType { - return [ + resultStatements.append( """ /* convert erased return value to \(raw: genericResultType) */ let result$ = \(resultSyntax) @@ -147,13 +170,33 @@ extension JavaMethodMacro: BodyMacro { return nil } """ - ] + ) + } else { + // no return type conversions + resultStatements.append("return \(resultSyntax)") } - // no return type conversions - return [ - "return \(resultSyntax)" - ] + return resultStatements + } + + /// Determines whether an argument is generic. + /// Since Optional does not appear in JNI signatures, it is removed before checking. + private static func isJNIGenericParameter( + _ type: TypeSyntax, + funcDecl: FunctionDeclSyntax, + in context: some MacroExpansionContext + ) -> Bool { + guard let genericParams = funcDecl.genericParameterClause?.parameters else { + return false + } + + let baseType = type.optionalUnwrappedType() ?? type + guard let identifier = baseType.as(IdentifierTypeSyntax.self) else { + return false + } + let typeName = identifier.name.text + + return genericParams.contains(where: { $0.name.text == typeName }) } /// Bridge an initializer into a call to Java. @@ -238,4 +281,23 @@ extension TypeSyntaxProtocol { var typeReferenceString: String { typeReference.description } + + func optionalUnwrappedType() -> TypeSyntax? { + if let optionalType = self.as(OptionalTypeSyntax.self) { + return optionalType.wrappedType + } + + if let implicitlyUnwrappedType = self.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) { + return implicitlyUnwrappedType.wrappedType + } + + if let identifierType = self.as(IdentifierTypeSyntax.self), + identifierType.name.text == "Optional", + let genericArgumentClause = identifierType.genericArgumentClause + { + return genericArgumentClause.arguments.first?.argument.as(TypeSyntax.self) + } + + return nil + } } diff --git a/Sources/SwiftJavaMacros/MacroErrors.swift b/Sources/SwiftJavaMacros/MacroErrors.swift index 2fcbfa91..f7da985b 100644 --- a/Sources/SwiftJavaMacros/MacroErrors.swift +++ b/Sources/SwiftJavaMacros/MacroErrors.swift @@ -21,4 +21,5 @@ enum MacroErrors: Error { case methodNotOnFunction case missingEnvironment case macroOutOfContext(String) + case parameterMustHaveName } diff --git a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift index d79b6bd4..7506f607 100644 --- a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift +++ b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift @@ -24,6 +24,7 @@ class JavaKitMacroTests: XCTestCase { "JavaClass": JavaClassMacro.self, "JavaMethod": JavaMethodMacro.self, "JavaField": JavaFieldMacro.self, + "JavaStaticMethod": JavaMethodMacro.self, "JavaStaticField": JavaFieldMacro.self, ] @@ -405,4 +406,65 @@ class JavaKitMacroTests: XCTestCase { macros: Self.javaKitMacros ) } + + func testJavaGenericMethodParameter() throws { + assertMacroExpansion( + """ + extension JavaClass { + @JavaStaticMethod + public func ofNullable(_ arg0: T?) -> JavaOptional! + where ObjectType == JavaOptional + + @JavaStaticMethod + public func ofNullable2(arg0: T!, arg1: Optional, arg2: T, arg3: Int) + } + """, + expandedSource: #""" + extension JavaClass { + public func ofNullable(_ arg0: T?) -> JavaOptional! + where ObjectType == JavaOptional { + let arg0$erased = arg0.map { + JavaObject(javaHolder: $0.javaHolder) + } + return { + do { + return try dynamicJavaStaticMethodCall(methodName: "ofNullable", arguments: arg0$erased, resultType: JavaOptional?.self) + } catch { + if let throwable = error as? Throwable { + let sw = StringWriter() + let pw = PrintWriter(sw) + throwable.printStackTrace(pw) + fatalError("Java call threw unhandled exception: \(error)\n\(sw.toString())") + } + fatalError("Java call threw unhandled exception: \(error)") + } + }() + } + public func ofNullable2(arg0: T!, arg1: Optional, arg2: T, arg3: Int) { + let arg0$erased = arg0.map { + JavaObject(javaHolder: $0.javaHolder) + } + let arg1$erased = arg1.map { + JavaObject(javaHolder: $0.javaHolder) + } + let arg2$erased = JavaObject(javaHolder: arg2.javaHolder) + return { + do { + return try dynamicJavaStaticMethodCall(methodName: "ofNullable2", arguments: arg0$erased, arg1$erased, arg2$erased, arg3) + } catch { + if let throwable = error as? Throwable { + let sw = StringWriter() + let pw = PrintWriter(sw) + throwable.printStackTrace(pw) + fatalError("Java call threw unhandled exception: \(error)\n\(sw.toString())") + } + fatalError("Java call threw unhandled exception: \(error)") + } + }() + } + } + """#, + macros: Self.javaKitMacros + ) + } } From 11fc1460b8ff47bd74e16ec8b89e2ffdd26849a2 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 6 Mar 2026 16:32:22 +0900 Subject: [PATCH 3/7] Considering class scope generic parameter --- Sources/SwiftJavaMacros/JavaMethodMacro.swift | 22 +++++++-- .../JavaClassMacroTests.swift | 48 +++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftJavaMacros/JavaMethodMacro.swift b/Sources/SwiftJavaMacros/JavaMethodMacro.swift index 2e9878d8..a788a2f3 100644 --- a/Sources/SwiftJavaMacros/JavaMethodMacro.swift +++ b/Sources/SwiftJavaMacros/JavaMethodMacro.swift @@ -186,17 +186,29 @@ extension JavaMethodMacro: BodyMacro { funcDecl: FunctionDeclSyntax, in context: some MacroExpansionContext ) -> Bool { - guard let genericParams = funcDecl.genericParameterClause?.parameters else { - return false - } - let baseType = type.optionalUnwrappedType() ?? type guard let identifier = baseType.as(IdentifierTypeSyntax.self) else { return false } let typeName = identifier.name.text - return genericParams.contains(where: { $0.name.text == typeName }) + if let genericParams = funcDecl.genericParameterClause?.parameters { + if genericParams.contains(where: { $0.name.text == typeName }) { + return true + } + } + + for contextNode in context.lexicalContext { + if let decl = contextNode.asProtocol(WithGenericParametersSyntax.self) { + if decl.genericParameterClause?.parameters.contains(where: { + $0.name.text == typeName + }) == true { + return true + } + } + } + + return false } /// Bridge an initializer into a call to Java. diff --git a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift index 7506f607..415dfc89 100644 --- a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift +++ b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift @@ -467,4 +467,52 @@ class JavaKitMacroTests: XCTestCase { macros: Self.javaKitMacros ) } + + func testJavaGenericClassGenericMethodParameter() throws { + assertMacroExpansion( + """ + @JavaClass("java.util.ArrayList") + open class ArrayList: JavaObject { + @JavaMethod + open func add(_ arg0: E?) -> Bool + } + """, + expandedSource: #""" + open class ArrayList: JavaObject { + open func add(_ arg0: E?) -> Bool { + let arg0$erased = arg0.map { + JavaObject(javaHolder: $0.javaHolder) + } + return { + do { + return try dynamicJavaMethodCall(methodName: "add", arguments: arg0$erased, resultType: Bool.self) + } catch { + if let throwable = error as? Throwable { + let sw = StringWriter() + let pw = PrintWriter(sw) + throwable.printStackTrace(pw) + fatalError("Java call threw unhandled exception: \(error)\n\(sw.toString())") + } + fatalError("Java call threw unhandled exception: \(error)") + } + }() + } + + /// The full Java class name for this Swift type. + open override class var fullJavaClassName: String { + #if os(Android) && AndroidCoreLibraryDesugaring + AndroidSupport.androidDesugarClassNameConversion(for: "java.util.ArrayList") + #else + "java.util.ArrayList" + #endif + } + + public required init(javaHolder: JavaObjectHolder) { + super.init(javaHolder: javaHolder) + } + } + """#, + macros: Self.javaKitMacros + ) + } } From ef2e5be81b4d116de2d8e432ba69126b0a6a8ae6 Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 10 Mar 2026 12:44:08 +0900 Subject: [PATCH 4/7] Update List.swift partially --- Sources/SwiftJava/generated/List.swift | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftJava/generated/List.swift b/Sources/SwiftJava/generated/List.swift index 9fa12e82..07d4c063 100644 --- a/Sources/SwiftJava/generated/List.swift +++ b/Sources/SwiftJava/generated/List.swift @@ -33,11 +33,23 @@ public struct List { @JavaMethod public func isEmpty() -> Bool - @JavaMethod - public func add(_ arg0: JavaObject?) -> Bool - - @JavaMethod - public func add(_ arg0: Int32, _ arg1: JavaObject?) + /// Java method `add`. + /// + /// ### Java method signature + /// ```java + /// public abstract void java.util.List.add(int,E) + /// ``` + @JavaMethod + public func add(_ arg0: Int32, _ arg1: E?) + + /// Java method `add`. + /// + /// ### Java method signature + /// ```java + /// public abstract boolean java.util.List.add(E) + /// ``` + @JavaMethod + public func add(_ arg0: E?) -> Bool @JavaMethod public func subList(_ arg0: Int32, _ arg1: Int32) -> List! From 9f9785a05ac8333e0b891f9aea9d013939f75c76 Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 10 Mar 2026 14:38:17 +0900 Subject: [PATCH 5/7] Fix test macro fixture --- .../JavaClassMacroTests.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift index 415dfc89..130c38be 100644 --- a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift +++ b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift @@ -100,7 +100,7 @@ class JavaKitMacroTests: XCTestCase { public init(_ value: Int32, environment: JNIEnvironment? = nil) @JavaMethod - public func isBigEnough(_: Int32) -> Bool + public func isBigEnough(_ v: Int32) -> Bool @JavaField public var myField: Int64 @@ -131,10 +131,10 @@ class JavaKitMacroTests: XCTestCase { } self = try! Self.dynamicJavaNewObject(in: _environment, arguments: value.self) } - public func isBigEnough(_: Int32) -> Bool { + public func isBigEnough(_ v: Int32) -> Bool { return { do { - return try dynamicJavaMethodCall(methodName: "isBigEnough", resultType: Bool.self) + return try dynamicJavaMethodCall(methodName: "isBigEnough", arguments: v, resultType: Bool.self) } catch { if let throwable = error as? Throwable { let sw = StringWriter() @@ -207,7 +207,7 @@ class JavaKitMacroTests: XCTestCase { public init(_ value: Int32, environment: JNIEnvironment? = nil) @JavaMethod - public func isBigEnough(_: Int32) -> Bool + public func isBigEnough(_ v: Int32) -> Bool @JavaField public var myField: Int64 @@ -240,10 +240,10 @@ class JavaKitMacroTests: XCTestCase { let javaThis = try! Self.dynamicJavaNewObjectInstance(in: _environment, arguments: value.self) self.init(javaThis: javaThis, environment: _environment) } - public func isBigEnough(_: Int32) -> Bool { + public func isBigEnough(_ v: Int32) -> Bool { return { do { - return try dynamicJavaMethodCall(methodName: "isBigEnough", resultType: Bool.self) + return try dynamicJavaMethodCall(methodName: "isBigEnough", arguments: v, resultType: Bool.self) } catch { if let throwable = error as? Throwable { let sw = StringWriter() @@ -304,7 +304,7 @@ class JavaKitMacroTests: XCTestCase { public init(environment: JNIEnvironment? = nil) @JavaMethod - public func isBigEnough(_: Int32) -> Bool + public func isBigEnough(_ v: Int32) -> Bool } """, expandedSource: """ @@ -319,10 +319,10 @@ class JavaKitMacroTests: XCTestCase { let javaThis = try! Self.dynamicJavaNewObjectInstance(in: _environment) self.init(javaThis: javaThis, environment: _environment) } - public func isBigEnough(_: Int32) -> Bool { + public func isBigEnough(_ v: Int32) -> Bool { return { do { - return try dynamicJavaMethodCall(methodName: "isBigEnough", resultType: Bool.self) + return try dynamicJavaMethodCall(methodName: "isBigEnough", arguments: v, resultType: Bool.self) } catch { if let throwable = error as? Throwable { let sw = StringWriter() From 86ec53d2aeeebae940ef8e92ec48d67d05957791 Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 10 Mar 2026 14:49:15 +0900 Subject: [PATCH 6/7] Avoid swift-syntax error log --- Sources/SwiftJavaMacros/JavaMethodMacro.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/SwiftJavaMacros/JavaMethodMacro.swift b/Sources/SwiftJavaMacros/JavaMethodMacro.swift index a788a2f3..2caf8581 100644 --- a/Sources/SwiftJavaMacros/JavaMethodMacro.swift +++ b/Sources/SwiftJavaMacros/JavaMethodMacro.swift @@ -164,6 +164,10 @@ extension JavaMethodMacro: BodyMacro { """ /* convert erased return value to \(raw: genericResultType) */ let result$ = \(resultSyntax) + """ + ) + resultStatements.append( + """ if let result$ { return \(raw: genericResultType)(javaThis: result$.javaThis, environment: try! JavaVirtualMachine.shared().environment()) } else { From c17077b3ec0471e9da5fd9ac8cda49799d26c68a Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 10 Mar 2026 16:40:23 +0900 Subject: [PATCH 7/7] Add comment and error details --- Sources/SwiftJavaMacros/JavaMethodMacro.swift | 5 +++-- Sources/SwiftJavaMacros/MacroErrors.swift | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftJavaMacros/JavaMethodMacro.swift b/Sources/SwiftJavaMacros/JavaMethodMacro.swift index 2caf8581..9996127d 100644 --- a/Sources/SwiftJavaMacros/JavaMethodMacro.swift +++ b/Sources/SwiftJavaMacros/JavaMethodMacro.swift @@ -71,7 +71,7 @@ extension JavaMethodMacro: BodyMacro { var paramNames: [String] = [] for param in params { guard let name = param.parameterName else { - throw MacroErrors.parameterMustHaveName + throw MacroErrors.parameterMustHaveName(method: funcName, paramSyntax: param.trimmedDescription) } if isJNIGenericParameter(param.type, funcDecl: funcDecl, in: context) { let erasedName: TokenSyntax = "\(name)$erased" @@ -183,8 +183,9 @@ extension JavaMethodMacro: BodyMacro { return resultStatements } - /// Determines whether an argument is generic. + /// Determines whether an argument is generic in heuristic way. /// Since Optional does not appear in JNI signatures, it is removed before checking. + /// FIXME: It might be preferable to explicitly specify the type from JavaClass, similar to `typeErasedResult`. private static func isJNIGenericParameter( _ type: TypeSyntax, funcDecl: FunctionDeclSyntax, diff --git a/Sources/SwiftJavaMacros/MacroErrors.swift b/Sources/SwiftJavaMacros/MacroErrors.swift index f7da985b..97859c6e 100644 --- a/Sources/SwiftJavaMacros/MacroErrors.swift +++ b/Sources/SwiftJavaMacros/MacroErrors.swift @@ -21,5 +21,5 @@ enum MacroErrors: Error { case methodNotOnFunction case missingEnvironment case macroOutOfContext(String) - case parameterMustHaveName + case parameterMustHaveName(method: String, paramSyntax: String) }