Error generating generics with keypath

I'm trying to create a macro that creates an array of keypaths from all the names of the variables in a class. Like in the example below.

@SomeMacro
class Some {
	var name: String
}
// Generated with macro
extension Some {
	var someArray: [KeyPath<Generic<Some>, String>] {
		[\Generic<Some>.name]
	}
}

The result I get is the following, and the macro adds a space before the period, marking an error in the compiler

...
// Generated with macro
var someArray: [KeyPath<Generic<Some>, String>] {
	[\Generic<Some> .name]
}

My implementation to test that it works is the following

public struct SomeMacroMacro: ExtensionMacro {
	public static func expansion(...) throws -> [SwiftSyntax.ExtensionDeclSyntax] {
		
		let sendableExtension: DeclSyntax =
		"""
		extension \(type.trimmed) {}
		"""
		
		guard
			let classDecl = declaration .as(ClassDeclSyntax.self),
			var extensionDecl = sendableExtension.as(ExtensionDeclSyntax.self)
		else { return [] }
		
		let membersDecl = classDecl.memberBlock.members
		
		let strLiteral = membersDecl.compactMap({ m in
			guard let varDecl = m.decl.as(VariableDeclSyntax.self) else { return nil }
			let names = varDecl.bindings.compactMap({ n in
				return "\\Generic<\(type.trimmed)>.\(n.pattern)"
			}) as [String]

			return "\(names.joined(separator: ","))"
		}).joined(separator: ",")
		
		let memberListSyntax = MemberBlockItemListSyntax(stringLiteral: "var someArray: [KeyPath<Generic<\(type.trimmed)>, String>] { [\(strLiteral)] }")
		extensionDecl.memberBlock.members = memberListSyntax
		return [extensionDecl]
	}
}

And I don't know what might be causing it, because while debugin the stringLiteral is correctly formatted, and I couldn't see anything wrong in the extensionDecl

If I am not wrong, for string interpolation in swift syntax you have to apply prefix raw before string literals. Try this instead:

"var someArray: [KeyPath<Generic<\(type.trimmed)>, String>] { [\(raw: strLiteral)] }"

I was using the incorrect initialicer

MemberBlockItemListSyntax(stringLiteral:...)

When I should have been using

MemberBlockItemListSyntax(...)

But that didn't fix the problem, when using \(raw: ...) I got the same result as before, and if i tried using \(literal: ...) I got the correct result, just in a string literal, not in a syntax node, so maybe is just an error on the compiler

I've figured out something, the error does not come from using generics, it comes from using a > in general, every time I try adding it with a macro the same space gets added

As a workaround, have you tried putting parens around the generic?

Yeah, I get the same error, I've been testing a little using > in diferent ways, but so far it seems to be the cause of my problem, somehow when creating the macro it adds a space afterwards, because in the SyntaxTree everything is fine

1 Like

Looks like this is a bug in formatting the expansion, which happens by default. See eg.

let expr: ExprSyntax = "Foo<T>.bar"
let formatted = expr.formatted()
XCTAssertEqual(expr.description, formatted.description)

You can work around this for now by disabling formatting in your macro:

public static var formatMode: FormatMode = .disabled

I've put up a PR for this Do not add whitespace between right angle and period by bnbarham · Pull Request #2224 · apple/swift-syntax · GitHub.

3 Likes

Thanks, that worked perfectly