Skip to content

Commit

Permalink
Method/Initializer parameter types now resolve to the local type if i…
Browse files Browse the repository at this point in the history
…t exists (#1347)

* Add spec to assert expected typeName when using nested type that shadows global type

* Update MethodParameter to correctly resolve typeName to local type when initialising from syntax tree

* Apply same fix for return type
  • Loading branch information
liamnichols authored Jul 15, 2024
1 parent 070d0b1 commit 9062519
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension SourceryMethod {
parent: parent,
identifier: node.name.text.trimmed,
typeName: typeName,
signature: Signature(node.signature, annotationsParser: annotationsParser),
signature: Signature(node.signature, annotationsParser: annotationsParser, parent: parent),
modifiers: node.modifiers,
attributes: node.attributes,
genericParameterClause: node.genericParameterClause,
Expand All @@ -30,7 +30,8 @@ extension SourceryMethod {
output: nil,
asyncKeyword: nil,
throwsOrRethrowsKeyword: signature.effectSpecifiers?.throwsSpecifier?.description.trimmed,
annotationsParser: annotationsParser
annotationsParser: annotationsParser,
parent: parent
),
modifiers: node.modifiers,
attributes: node.attributes,
Expand All @@ -46,7 +47,7 @@ extension SourceryMethod {
parent: parent,
identifier: "deinit",
typeName: typeName,
signature: Signature(parameters: nil, output: nil, asyncKeyword: nil, throwsOrRethrowsKeyword: nil, annotationsParser: annotationsParser),
signature: Signature(parameters: nil, output: nil, asyncKeyword: nil, throwsOrRethrowsKeyword: nil, annotationsParser: annotationsParser, parent: parent),
modifiers: node.modifiers,
attributes: node.attributes,
genericParameterClause: nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,24 @@ import SwiftSyntax
import SourceryRuntime

extension MethodParameter {
convenience init(_ node: FunctionParameterSyntax, index: Int, annotationsParser: AnnotationsParser) {
convenience init(_ node: FunctionParameterSyntax, index: Int, annotationsParser: AnnotationsParser, parent: Type?) {
let firstName = node.firstName.text.trimmed.nilIfNotValidParameterName

let typeName = TypeName(node.type)
let isVisitingTypeSourceryProtocol = parent is SourceryProtocol
let specifiers = TypeName.specifiers(from: node.type)


// NOTE: This matches implementation in Variable+SwiftSyntax.swift
// TODO: Walk up the `parent` in the event that there are multiple levels of nested types
var typeName = TypeName(node.type)
if !isVisitingTypeSourceryProtocol {
// we are in a custom type, which may contain other types
// in order to assign correct type to the variable, we need to match
// all of the contained types against the variable type
if let matchingContainedType = parent?.containedTypes.first(where: { $0.localName == typeName.name }) {
typeName = TypeName(matchingContainedType.name)
}
}

if specifiers.isInOut {
// TODO: TBR
typeName.name = "inout \(typeName.name)"
Expand Down
32 changes: 27 additions & 5 deletions SourceryFramework/Sources/Parsing/SwiftSyntax/AST/Signature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,42 @@ public struct Signature {
/// The `throws` or `rethrows` keyword, if any.
public let throwsOrRethrowsKeyword: String?

public init(_ node: FunctionSignatureSyntax, annotationsParser: AnnotationsParser) {
public init(_ node: FunctionSignatureSyntax, annotationsParser: AnnotationsParser, parent: Type?) {
let isVisitingTypeSourceryProtocol = parent is SourceryProtocol

// NOTE: This matches implementation in Variable+SwiftSyntax.swift
// TODO: Walk up the `parent` in the event that there are multiple levels of nested types
var returnTypeName = node.returnClause.map { TypeName($0.type) }
if !isVisitingTypeSourceryProtocol {
// we are in a custom type, which may contain other types
// in order to assign correct type to the variable, we need to match
// all of the contained types against the variable type
if let matchingContainedType = parent?.containedTypes.first(where: { $0.localName == returnTypeName?.name }) {
returnTypeName = TypeName(matchingContainedType.name)
}
}

self.init(parameters: node.parameterClause.parameters,
output: node.returnClause.map { TypeName($0.type) },
output: returnTypeName,
asyncKeyword: node.effectSpecifiers?.asyncSpecifier?.text,
throwsOrRethrowsKeyword: node.effectSpecifiers?.throwsSpecifier?.description.trimmed,
annotationsParser: annotationsParser
annotationsParser: annotationsParser,
parent: parent
)
}

public init(parameters: FunctionParameterListSyntax?, output: TypeName?, asyncKeyword: String?, throwsOrRethrowsKeyword: String?, annotationsParser: AnnotationsParser) {
public init(
parameters: FunctionParameterListSyntax?,
output: TypeName?,
asyncKeyword: String?,
throwsOrRethrowsKeyword: String?,
annotationsParser: AnnotationsParser,
parent: Type?
) {
var methodParameters: [MethodParameter] = []
if let parameters {
for (idx, param) in parameters.enumerated() {
methodParameters.append(MethodParameter(param, index: idx, annotationsParser: annotationsParser))
methodParameters.append(MethodParameter(param, index: idx, annotationsParser: annotationsParser, parent: parent))
}
}
input = methodParameters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ extension Subscript {

var parameters: [MethodParameter] = []
for (idx, param) in node.parameterClause.parameters.enumerated() {
parameters.append(MethodParameter(param, index: idx, annotationsParser: annotationsParser))
parameters.append(MethodParameter(param, index: idx, annotationsParser: annotationsParser, parent: parent))
}
self.init(
parameters: parameters,
Expand Down
32 changes: 32 additions & 0 deletions SourceryTests/Parsing/FileParser_MethodsSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,38 @@ class FileParserMethodsSpec: QuickSpec {
]))
}

it("extracts correct typeName when a nested type shadows a global type") {
let code = """
protocol Foo {
}
class Bar {
struct Foo {
}
func doSomething(with foo: Foo) -> Foo {
}
}
"""

let fooProtocol = Protocol(name: "Foo")
let fooStruct = Struct(name: "Foo")
let barClass = Class(
name: "Bar",
methods: [
Method(name: "doSomething(with foo: Bar.Foo)", selectorName: "doSomething(with:)", parameters: [
MethodParameter(argumentLabel: "with", name: "foo", index: 0, typeName: TypeName("Bar.Foo"), type: fooStruct)
], returnTypeName: TypeName("Bar.Foo"), definedInTypeName: TypeName("Bar"))
],
containedTypes: [
fooStruct
]
)

let result = parse(code)
expect(result).to(equal([fooProtocol, barClass, fooStruct]))
}

context("given parameter default value") {
it("extracts simple default value") {
expect(parse("class Foo { func foo(a: Int? = nil) {} }")).to(equal([
Expand Down

0 comments on commit 9062519

Please sign in to comment.