Skip to content

Commit

Permalink
feat: RoutingController macro improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
maximkrouk committed Dec 31, 2023
1 parent 97881a2 commit dd6a47e
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -118,48 +118,40 @@ extension RoutingControllerMacro: ExtensionMacro {
return decl
}


typealias StackDestination = (identifier: String, idType: String)
let stackDestinations: [StackDestination] = try navigationDestinations.compactMap { decl in
let stackDestinations: [String] = navigationDestinations.compactMap { decl in
guard
let attribute = decl.attributes.first?.attribute,
attribute.name.name.hasSuffix("StackDestination"),
attribute.name.name.contains("StackDestination"),
let identifier = decl.bindings.first?.identifier
else { return StackDestination?.none }

let idType: String = if let genericArgument = attribute.name.genericArguments?.first {
genericArgument.description
} else if let typeDecl = decl.bindings.first?.type?._syntax.as(DictionaryTypeSyntax.self) {
typeDecl.key.description
} else {
throw DiagnosticsError(diagnostics: [
.requiresDictionaryLiteralForStackDestination(attribute._syntax)
])
}
else { return .none }

return StackDestination(identifier, idType)
return identifier
}



if let firstStackDestination = stackDestinations.first {
if !stackDestinations.isEmpty {
let erasedIDType = "some Hashable"
let commonIDType = stackDestinations.allSatisfy { destination in
destination.idType == firstStackDestination.idType
} ? firstStackDestination.idType : nil

let castedIDDecl: DeclSyntax = """
func controller<ID, Controller>(
for s: StackDestination<ID, Controller>
) -> UIViewController? {
return (id as? ID).flatMap {
s.wrappedValue[$0]
}
}
"""

var stackDestinationsCoalecing: String {
let subscriptCall = commonIDType.map { _ in "[id]" } ?? "[id as! $0.idType]"
return stackDestinations
.map { "\($0.identifier)\(subscriptCall)" }
.map { "controller(for: _\($0))" }
.joined(separator: "\n\t?? ")
}

let inputType = commonIDType ?? erasedIDType

let destinationsStructSubscriptDecl: DeclSyntax =
"""
\npublic subscript(_ id: \(raw: inputType)) -> UIViewController? {
\npublic subscript(_ id: \(raw: erasedIDType)) -> UIViewController? {
\(castedIDDecl)
return \(raw: stackDestinationsCoalecing)
}
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,17 @@ final class RoutingControllerMacroTests: XCTestCase {
@StackDestination
var secondDetailController: [Int: CocoaViewController]
public subscript(_ id: Int) -> UIViewController? {
return firstDetailController[id]
?? secondDetailController[id]
public subscript(_ id: some Hashable) -> UIViewController? {
func controller<ID, Controller>(
for s: StackDestination<ID, Controller>
) -> UIViewController? {
return (id as? ID).flatMap {
s.wrappedValue[$0]
}
}
return controller(for: _firstDetailController)
?? controller(for: _secondDetailController)
}
}
Expand All @@ -202,14 +210,43 @@ final class RoutingControllerMacroTests: XCTestCase {
var firstDetailController: Dictionary<Int, CocoaViewController>
}
"""
} diagnostics: {
} expansion: {
"""
@RoutingController
final class CustomController {
@StackDestination
╰─ 🛑 `@StackDestination` requires explicit wrapper type or dictionary type literal declaration for value.
var firstDetailController: Dictionary<Int, CocoaViewController>
}
extension CustomController: CombineNavigation.RoutingController {
/// Container for captured destinations without referring to self
///
/// > Generated by `CombineNavigationMacros.RoutingController` macro
///
/// Use in `navigationDestination`/`navigationStack` methods to map
/// routes to specific destinations using `destinations` method
public struct Destinations {
@StackDestination
var firstDetailController: Dictionary<Int, CocoaViewController>
public subscript(_ id: some Hashable) -> UIViewController? {
func controller<ID, Controller>(
for s: StackDestination<ID, Controller>
) -> UIViewController? {
return (id as? ID).flatMap {
s.wrappedValue[$0]
}
}
return controller(for: _firstDetailController)
}
}
public func _makeDestinations() -> Destinations {
return Destinations(
firstDetailController: $firstDetailController
)
}
}
"""
}
}
Expand Down Expand Up @@ -272,7 +309,7 @@ final class RoutingControllerMacroTests: XCTestCase {
@CustomStackDestination
var firstDetailController: [Int: CocoaViewController]
@CustomTreeDestinationOf<MyCoolIdentifableVC>
@CustomStackDestinationOf<MyCoolIdentifableVC>
var secondDetailController
}
"""
Expand All @@ -282,7 +319,7 @@ final class RoutingControllerMacroTests: XCTestCase {
@CustomStackDestination
var firstDetailController: [Int: CocoaViewController]
@CustomTreeDestinationOf<MyCoolIdentifableVC>
@CustomStackDestinationOf<MyCoolIdentifableVC>
var secondDetailController
}
Expand All @@ -298,11 +335,20 @@ final class RoutingControllerMacroTests: XCTestCase {
var firstDetailController: [Int: CocoaViewController]
@CustomTreeDestinationOf<MyCoolIdentifableVC>
@CustomStackDestinationOf<MyCoolIdentifableVC>
var secondDetailController
public subscript(_ id: Int) -> UIViewController? {
return firstDetailController[id]
public subscript(_ id: some Hashable) -> UIViewController? {
func controller<ID, Controller>(
for s: StackDestination<ID, Controller>
) -> UIViewController? {
return (id as? ID).flatMap {
s.wrappedValue[$0]
}
}
return controller(for: _firstDetailController)
?? controller(for: _secondDetailController)
}
}
Expand Down

0 comments on commit dd6a47e

Please sign in to comment.