yandex / yandex/implicits

SwiftUI support: statically-checked alternative to @Environment/EnvironmentValues

Open
#52 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement help wanted
Dominant language
Swift
Stars
43
Forks
5
PR merge metrics
No merged PRs in 30d

Description

Problem

SwiftUI's @Environment / EnvironmentValues is the idiomatic way to propagate values down a view tree, but it has known ergonomic issues that Implicits was designed to solve:

  1. No compile-time checks. If a required environment value isn't injected, the view silently falls back to a default (or crashes at runtime for non-optional custom keys). There's no equivalent of Implicits' "missing implicit = compile-time error."
  2. Boilerplate per key. Each value requires an EnvironmentKey conformance plus an EnvironmentValues extension.
  3. Default values are mandatory. EnvironmentKey.defaultValue forces you to provide a fallback even when there isn't a meaningful one — usually fatalError() in practice.

Implicits already solves (1) and (3) for regular call-stack code, but there's no story for the SwiftUI view tree.

Goal

First-class SwiftUI integration where:

  • Declaring and retrieving values uses the existing @Implicit vocabulary.
  • The analyzer proves that every @Implicit read inside a View has a matching declaration somewhere above it in the view tree.
  • No defaultValue required; missing = build error, matching the rest of the library.

Design Sketch (not final)

A view modifier that declares implicits for its subtree, and a property wrapper/macro that reads them inside body:

struct RootView: View {
  var body: some View {
    ContentView()
      .implicit(NetworkService())
      .implicit(\.guestMode, true)
  }
}

struct ContentView: View {
  @Implicit var network: NetworkService
  @Implicit(\.guestMode) var guest: Bool

  var body: some View { ... }
}

Open question whether @Implicit inside a View can reuse the existing property wrapper or needs a SwiftUI-specific sibling (e.g. @ImplicitEnvironment) that bridges through EnvironmentValues under the hood. Using EnvironmentValues as the transport keeps us aligned with SwiftUI's invalidation model (body re-runs when a read environment key changes) rather than inventing a parallel one.

Challenges

Lifecycle mismatch

Implicits uses explicit scope lifetime (ImplicitScope + defer { scope.end() }). SwiftUI views have no symmetric enter/exit — body is recomputed on state change, views are ephemeral structs. The SwiftUI integration likely needs to route through EnvironmentValues rather than ImplicitScope, since SwiftUI already solved the "propagate down a tree with invalidation" problem.

Static analysis through View

The analyzer currently can't trace dynamic dispatch — and View.body is exactly that (associated type, protocol dispatch through some View, ViewBuilder result builders). To statically prove a @Implicit read is satisfied, the analyzer would need to:

  • Treat .implicit(...) modifier chains as scope declarations for their subtree.
  • Walk from each View type's body back through its call sites (its parent views) to find the declaration.
  • Handle ViewBuilder, ForEach, conditional views, AnyView, and generic container views.

This is substantially harder than the current call-graph analysis and may require either restricting the analysis to a subset of SwiftUI patterns, or accepting some cases as unanalyzable (and erroring/warning on them).

Property wrapper composition

@Implicit currently works in types whose init receives an ImplicitScope. SwiftUI views are initialized by the framework with user-supplied args only — no scope parameter. The wrapper would need a variant that reads lazily from EnvironmentValues at body evaluation time instead of storing at init.

Possible Approaches

A. Thin wrapper over EnvironmentValues. Code-generate EnvironmentKey + extension per ImplicitsKeys entry; @Implicit inside a View expands to @Environment(...). Analyzer proves every read is declared somewhere upstream. Simplest; stays close to SwiftUI semantics.

B. Parallel storage. Use ImplicitScope snapshots attached to views via a custom modifier, stored in a single EnvironmentValue. More control, but we reimplement invalidation and risk fighting the framework.

C. Opt-in only. Don't try to support arbitrary SwiftUI code; define a small DSL (ImplicitView?) that the analyzer understands, and require users to adopt it at integration points. Narrower scope, lower analyzer complexity.

Leaning toward (A) as the starting point.

Out of Scope (initially)

  • UIViewControllerRepresentable / UIViewRepresentable bridges
  • @FocusedValue, @ScaledMetric, and other specialized propagation mechanisms
  • PreferenceKey (bottom-up propagation)

Related

  • #3 (Xcode project integration) — SwiftUI users predominantly use .xcodeproj, so this work depends on or at least interacts with Xcode project support.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the existing @Implicit property wrapper, ImplicitScope lifecycle, and current call-graph analyzer, then review the Xcode project integration dependency in #3. Compare the proposed EnvironmentValues, parallel-storage, and opt-in approaches against SwiftUI invalidation and ViewBuilder analysis. Done means a defined integration design with a bounded analyzer scope and clear handling for unsupported view patterns.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
compilers, mobile
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.