Skip to content

Add support for conditionally compiled endpoints#430

Open
fruitcoder wants to merge 1 commit intopointfreeco:mainfrom
fruitcoder:endpoint-conditional-compilation
Open

Add support for conditionally compiled endpoints#430
fruitcoder wants to merge 1 commit intopointfreeco:mainfrom
fruitcoder:endpoint-conditional-compilation

Conversation

@fruitcoder
Copy link
Copy Markdown

Support conditionally compiled endpoints in @DependencyClient

Previously, @DependencyClient only recognized endpoints declared directly in the struct body. Endpoints wrapped in #if/#endif blocks were silently ignored — no @DependencyEndpoint was applied, and the generated member-wise initializer made no attempt to initialize them.

What changed

DependencyClientMacro now handles IfConfigDeclSyntax during member iteration. Each #if clause is collected into a ConditionalGroup (keyed by its condition string, e.g. os(iOS)), and its properties are processed with the same logic as unconditional members.

Initializer generation is updated to account for conditional endpoints:

  • Each conditional group that contains endpoints gets its own #if-wrapped init whose parameters include both the unconditional endpoints and that group's endpoints.
  • Every other generated init gets a #if-guarded body block that assigns an unimplemented closure to each conditional endpoint it doesn't take as a parameter, so Swift's definite initialization is satisfied on all platforms.
  • The empty/no-endpoint init is always generated, and also contains the conditional unimplemented assignments.

For example:

@DependencyClient
struct Client {
  var fetch: () -> Void
  #if os(iOS)
  var fetchiOS: () -> Void
  #endif
}

expands to three inits:

// Unconditional (always compiled) — unimplemented default for the iOS-only endpoint
init(fetch: @escaping () -> Void) {
  self.fetch = fetch
  #if os(iOS)
  self.fetchiOS = { IssueReporting.reportIssue("Unimplemented: '\(Self.self).fetchiOS'") }
  #endif
}

// iOS-only — takes both endpoints
#if os(iOS)
init(fetch: @escaping () -> Void, fetchiOS: @escaping () -> Void) {
  self.fetch = fetch
  self.fetchiOS = fetchiOS
}
#endif

// No-endpoint init — unimplemented defaults for all endpoints
init() {
  #if os(iOS)
  self.fetchiOS = { IssueReporting.reportIssue("Unimplemented: '\(Self.self).fetchiOS'") }
  #endif
}

Unimplemented closures delegate to DependencyEndpointMacro rather than being hand-rolled. unimplementedAssignment(for:) calls DependencyEndpointMacro.expansion(of:providingPeersOf:in:) and extracts the initializer from the generated _property peer, so the closure is always in sync with what @DependencyEndpoint itself produces (including the \(Self.self) dynamic type name in the issue message).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant