-
Notifications
You must be signed in to change notification settings - Fork 90
javakit-macros: Support wrapping generic arguments #601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b7d156b
860fe50
11fc146
ef2e5be
9f9785a
86ec53d
c17077b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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(method: funcName, paramSyntax: param.trimmedDescription) | ||||||
| } | ||||||
| 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,23 +160,60 @@ extension JavaMethodMacro: BodyMacro { | |||||
| """ | ||||||
|
|
||||||
| if let genericResultType { | ||||||
| return [ | ||||||
| resultStatements.append( | ||||||
| """ | ||||||
| /* 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 { | ||||||
| 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 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, | ||||||
| in context: some MacroExpansionContext | ||||||
| ) -> Bool { | ||||||
| let baseType = type.optionalUnwrappedType() ?? type | ||||||
| guard let identifier = baseType.as(IdentifierTypeSyntax.self) else { | ||||||
| return false | ||||||
| } | ||||||
| let typeName = identifier.name.text | ||||||
|
|
||||||
| 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 { | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need to
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah i see |
||||||
| return true | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| return false | ||||||
| } | ||||||
|
|
||||||
| /// Bridge an initializer into a call to Java. | ||||||
|
|
@@ -238,4 +298,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 | ||||||
| } | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's document that this is a heuristic please, since it may get things wrong. It's good to mark this as source of potential mixups
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upon further review, I noticed that
typeErasedResultreferences JavaClass information, which I believe is more preferable. I've added a comment regarding this point.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice, thank you