Skip to content

Commit

Permalink
Fix Prop Infos generated for function params and return types #261 (#262
Browse files Browse the repository at this point in the history
)

* fix generation of params for callable macro

* fix indentation

* remove prints

* add test for wildcard param, and param with first and last names

* add tests for exporting Callables with params/return types Int, Float, and Bool should all be converted to int, float, bool in GDScript

* Noticed that extra propInfos are being generated when a wildcard param is present

* updates tests to validate -> Bool is converted to bool correctly

* fixes some syntax errors that were cluttering the test log

* fix whitespace / indentation
  • Loading branch information
EstevanBR authored Dec 1, 2023
1 parent ef1e789 commit b0614d3
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 49 deletions.
31 changes: 24 additions & 7 deletions Sources/SwiftGodotMacroLibrary/MacroGodot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class GodotMacroProcessor {
}

var propertyDeclarations: [String: String] = [:]
func lookupProp (parameterTypeName: String, parameterName: String) -> String {
func lookupPropParam (parameterTypeName: String, parameterName: String) -> String {
let key = "\(parameterTypeName)/\(parameterName)"
if let v = propertyDeclarations [key] {
return v
Expand All @@ -37,7 +37,23 @@ class GodotMacroProcessor {

// TODO: perhaps for these prop infos that are parameters to functions, we should not bother making them unique
// and instead share all the Ints, all the Floats and so on.
ctor.append ("\tlet \(name) = PropInfo (propertyType: \(propType), propertyName: \"\(parameterName)\(parameterTypeName)\", className: className, hint: .none, hintStr: \"\", usage: .default)\n")
ctor.append ("\tlet \(name) = PropInfo (propertyType: \(propType), propertyName: \"\(parameterName)\", className: StringName(\"\(propType == ".object" ? parameterTypeName : "")\"), hint: .none, hintStr: \"\", usage: .default)\n")
propertyDeclarations [key] = name
return name
}

func lookupPropReturn (parameterTypeName: String, parameterName: String) -> String {
let key = "\(parameterTypeName)/\(parameterName)"
if let v = propertyDeclarations [key] {
return v
}
let propType = godotTypeToProp (typeName: parameterTypeName)

let name = "prop_\(propertyDeclarations.count)"

// TODO: perhaps for these prop infos that are parameters to functions, we should not bother making them unique
// and instead share all the Ints, all the Floats and so on.
ctor.append ("\tlet \(name) = PropInfo (propertyType: \(propType), propertyName: \"\", className: StringName(\"\(propType == ".object" ? parameterTypeName : "")\"), hint: .none, hintStr: \"\", usage: .default)\n")
propertyDeclarations [key] = name
return name
}
Expand Down Expand Up @@ -71,14 +87,15 @@ class GodotMacroProcessor {
var funcArgs = ""
var retProp: String? = nil
if let (retType, _) = getIdentifier (funcDecl.signature.returnClause?.type) {
retProp = lookupProp(parameterTypeName: retType, parameterName: "")
retProp = lookupPropReturn(parameterTypeName: retType, parameterName: "")
}

for parameter in funcDecl.signature.parameterClause.parameters {
guard let ptype = getTypeName(parameter) else {
throw MacroError.typeName (parameter)
}
let propInfo = lookupProp (parameterTypeName: ptype, parameterName: "")
let pname = getParamName(parameter)
let propInfo = lookupPropParam (parameterTypeName: ptype, parameterName: pname)
if funcArgs == "" {
funcArgs = "\tlet \(funcName)Args = [\n"
}
Expand All @@ -88,7 +105,7 @@ class GodotMacroProcessor {
funcArgs.append ("\t]\n")
}
ctor.append (funcArgs)
ctor.append ("\tclassInfo.registerMethod(name: StringName(\"\(funcName)\"), flags: .default, returnValue: \(retProp ?? "nil"), arguments: \(funcArgs == "" ? "[]" : "\(funcName)Args"), function: \(className)._mproxy_\(funcName))")
ctor.append ("\tclassInfo.registerMethod(name: StringName(\"\(funcName)\"), flags: .default, returnValue: \(retProp ?? "nil"), arguments: \(funcArgs == "" ? "[]" : "\(funcName)Args"), function: \(className)._mproxy_\(funcName))\n")
}

func processVariable (_ varDecl: VariableDeclSyntax) throws {
Expand Down Expand Up @@ -173,7 +190,7 @@ class GodotMacroProcessor {

ctor.append("\tclassInfo.registerMethod (name: \"\(getterName)\", flags: .default, returnValue: \(pinfo), arguments: [], function: \(className).\(getterName))\n")
ctor.append("\tclassInfo.registerMethod (name: \"\(setterName)\", flags: .default, returnValue: nil, arguments: [\(pinfo)], function: \(className).\(setterName))\n")
ctor.append("\tclassInfo.registerProperty (\(pinfo), getter: \"\(getterName)\", setter: \"\(setterName)\")")
ctor.append("\tclassInfo.registerProperty (\(pinfo), getter: \"\(getterName)\", setter: \"\(setterName)\")\n")
}
}

Expand All @@ -198,7 +215,7 @@ class GodotMacroProcessor {
try classInitSignals(macroDecl)
}
}
ctor.append("} ()")
ctor.append("} ()\n")
return ctor
}

Expand Down
4 changes: 4 additions & 0 deletions Sources/SwiftGodotMacroLibrary/MacroSharedApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ func getTypeName (_ parameter: FunctionParameterSyntax) -> String? {
return typeName
}

func getParamName(_ parameter: FunctionParameterSyntax) -> String {
parameter.secondName?.text ?? parameter.firstName.text
}

var godotVariants = [
"Int": ".int",
"Float": ".float",
Expand Down
180 changes: 138 additions & 42 deletions Tests/SwiftGodotMacrosTests/MacroGodotTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ final class MacroGodotTests: XCTestCase {
)
}

func testGodotMacroWithCallableFunc() {
func testGodotMacroWithCallableFuncWithObjectParams() {
// Note when editing: Xcode loves to change all indentation to be consistent as either tabs or spaces, but the macro expansion produces a mix.
// I had to set Settings->Text Editing->Tab Key to "Inserts a Tab Character" in order to resolve this.
assertMacroExpansion(
Expand All @@ -186,54 +186,150 @@ final class MacroGodotTests: XCTestCase {
@Callable func deleteEpisode() {}
@Callable func subscribe(podcast: Podcast) {}
@Callable func removeSilences(from: Variant) {}
@Callable func getLatestEpisode(podcast: Podcast) -> Episode {}
@Callable func queue(_ podcast: Podcast, after preceedingPodcast: Podcast) {}
}
""",
expandedSource: """
class Castro: Node {
func deleteEpisode() {}
func _mproxy_deleteEpisode (args: [Variant]) -> Variant? {
deleteEpisode ()
return nil
}
func subscribe(podcast: Podcast) {}
func _mproxy_subscribe (args: [Variant]) -> Variant? {
subscribe (podcast: Podcast.makeOrUnwrap (args [0])!)
return nil
}
func removeSilences(from: Variant) {}
func _mproxy_removeSilences (args: [Variant]) -> Variant? {
removeSilences (from: args [0])
return nil
}
override open class var classInitializer: Void {
let _ = super.classInitializer
return _initializeClass
expandedSource:
"""
class Castro: Node {
func deleteEpisode() {}
func _mproxy_deleteEpisode (args: [Variant]) -> Variant? {
deleteEpisode ()
return nil
}
func subscribe(podcast: Podcast) {}
func _mproxy_subscribe (args: [Variant]) -> Variant? {
subscribe (podcast: Podcast.makeOrUnwrap (args [0])!)
return nil
}
func removeSilences(from: Variant) {}
func _mproxy_removeSilences (args: [Variant]) -> Variant? {
removeSilences (from: args [0])
return nil
}
func getLatestEpisode(podcast: Podcast) -> Episode {}
func _mproxy_getLatestEpisode (args: [Variant]) -> Variant? {
let result = getLatestEpisode (podcast: Podcast.makeOrUnwrap (args [0])!)
return Variant (result)
}
func queue(_ podcast: Podcast, after preceedingPodcast: Podcast) {}
func _mproxy_queue (args: [Variant]) -> Variant? {
queue (Podcast.makeOrUnwrap (args [0])!, after: Podcast.makeOrUnwrap (args [1])!)
return nil
}
override open class var classInitializer: Void {
let _ = super.classInitializer
return _initializeClass
}
private static var _initializeClass: Void = {
let className = StringName("Castro")
let classInfo = ClassInfo<Castro> (name: className)
classInfo.registerMethod(name: StringName("deleteEpisode"), flags: .default, returnValue: nil, arguments: [], function: Castro._mproxy_deleteEpisode)
let prop_0 = PropInfo (propertyType: .object, propertyName: "podcast", className: StringName("Podcast"), hint: .none, hintStr: "", usage: .default)
let subscribeArgs = [
prop_0,
]
classInfo.registerMethod(name: StringName("subscribe"), flags: .default, returnValue: nil, arguments: subscribeArgs, function: Castro._mproxy_subscribe)
let prop_1 = PropInfo (propertyType: .object, propertyName: "from", className: StringName("Variant"), hint: .none, hintStr: "", usage: .default)
let removeSilencesArgs = [
prop_1,
]
classInfo.registerMethod(name: StringName("removeSilences"), flags: .default, returnValue: nil, arguments: removeSilencesArgs, function: Castro._mproxy_removeSilences)
let prop_2 = PropInfo (propertyType: .object, propertyName: "", className: StringName("Episode"), hint: .none, hintStr: "", usage: .default)
let getLatestEpisodeArgs = [
prop_0,
]
classInfo.registerMethod(name: StringName("getLatestEpisode"), flags: .default, returnValue: prop_2, arguments: getLatestEpisodeArgs, function: Castro._mproxy_getLatestEpisode)
let prop_3 = PropInfo (propertyType: .object, propertyName: "preceedingPodcast", className: StringName("Podcast"), hint: .none, hintStr: "", usage: .default)
let queueArgs = [
prop_0,
prop_3,
]
classInfo.registerMethod(name: StringName("queue"), flags: .default, returnValue: nil, arguments: queueArgs, function: Castro._mproxy_queue)
} ()
}
private static var _initializeClass: Void = {
let className = StringName("Castro")
let classInfo = ClassInfo<Castro> (name: className)
classInfo.registerMethod(name: StringName("deleteEpisode"), flags: .default, returnValue: nil, arguments: [], function: Castro._mproxy_deleteEpisode)
let prop_0 = PropInfo (propertyType: .object, propertyName: "Podcast", className: className, hint: .none, hintStr: "", usage: .default)
let subscribeArgs = [
prop_0,
]
classInfo.registerMethod(name: StringName("subscribe"), flags: .default, returnValue: nil, arguments: subscribeArgs, function: Castro._mproxy_subscribe)
let prop_1 = PropInfo (propertyType: .object, propertyName: "Variant", className: className, hint: .none, hintStr: "", usage: .default)
let removeSilencesArgs = [
prop_1,
]
classInfo.registerMethod(name: StringName("removeSilences"), flags: .default, returnValue: nil, arguments: removeSilencesArgs, function: Castro._mproxy_removeSilences)
} ()
""",
macros: testMacros
)
}

func testGodotMacroWithCallableFuncWithValueParams() {
assertMacroExpansion(
"""
@Godot class MathHelper: Node {
@Callable func multiply(_ a: Int, by b: Int) -> Int { a * b}
@Callable func divide(_ a: Float, by b: Float) -> Float { a / b }
@Callable func areBothTrue(_ a: Bool, and b: Bool) -> Bool { a == b }
}
""",
expandedSource:
"""
class MathHelper: Node {
func multiply(_ a: Int, by b: Int) -> Int { a * b}
func _mproxy_multiply (args: [Variant]) -> Variant? {
let result = multiply (Int.makeOrUnwrap (args [0])!, by: Int.makeOrUnwrap (args [1])!)
return Variant (result)
}
func divide(_ a: Float, by b: Float) -> Float { a / b }
func _mproxy_divide (args: [Variant]) -> Variant? {
let result = divide (Float.makeOrUnwrap (args [0])!, by: Float.makeOrUnwrap (args [1])!)
return Variant (result)
}
func areBothTrue(_ a: Bool, and b: Bool) -> Bool { a == b }
func _mproxy_areBothTrue (args: [Variant]) -> Variant? {
let result = areBothTrue (Bool.makeOrUnwrap (args [0])!, and: Bool.makeOrUnwrap (args [1])!)
return Variant (result)
}
override open class var classInitializer: Void {
let _ = super.classInitializer
return _initializeClass
}
private static var _initializeClass: Void = {
let className = StringName("MathHelper")
let classInfo = ClassInfo<MathHelper> (name: className)
let prop_0 = PropInfo (propertyType: .int, propertyName: "", className: StringName(""), hint: .none, hintStr: "", usage: .default)
let prop_1 = PropInfo (propertyType: .int, propertyName: "a", className: StringName(""), hint: .none, hintStr: "", usage: .default)
let prop_2 = PropInfo (propertyType: .int, propertyName: "b", className: StringName(""), hint: .none, hintStr: "", usage: .default)
let multiplyArgs = [
prop_1,
prop_2,
]
classInfo.registerMethod(name: StringName("multiply"), flags: .default, returnValue: prop_0, arguments: multiplyArgs, function: MathHelper._mproxy_multiply)
let prop_3 = PropInfo (propertyType: .float, propertyName: "", className: StringName(""), hint: .none, hintStr: "", usage: .default)
let prop_4 = PropInfo (propertyType: .float, propertyName: "a", className: StringName(""), hint: .none, hintStr: "", usage: .default)
let prop_5 = PropInfo (propertyType: .float, propertyName: "b", className: StringName(""), hint: .none, hintStr: "", usage: .default)
let divideArgs = [
prop_4,
prop_5,
]
classInfo.registerMethod(name: StringName("divide"), flags: .default, returnValue: prop_3, arguments: divideArgs, function: MathHelper._mproxy_divide)
let prop_6 = PropInfo (propertyType: .bool, propertyName: "", className: StringName(""), hint: .none, hintStr: "", usage: .default)
let prop_7 = PropInfo (propertyType: .bool, propertyName: "a", className: StringName(""), hint: .none, hintStr: "", usage: .default)
let prop_8 = PropInfo (propertyType: .bool, propertyName: "b", className: StringName(""), hint: .none, hintStr: "", usage: .default)
let areBothTrueArgs = [
prop_7,
prop_8,
]
classInfo.registerMethod(name: StringName("areBothTrue"), flags: .default, returnValue: prop_6, arguments: areBothTrueArgs, function: MathHelper._mproxy_areBothTrue)
} ()
}
""",
macros: testMacros
)
}
}

func testExportGodotMacro() {
assertMacroExpansion(
Expand Down

0 comments on commit b0614d3

Please sign in to comment.