Repository navigation
2.1.1
2.1.1 is a bug-fix release. It fixes generated code that did not compile, reports several impossible dependency graphs as SafeDI errors at your declaration instead of as compiler errors inside generated code, and makes everything SafeDI produces the same on every run.
Fixes
onlyIfAvailable dependencies
- A receiver is now handed
nilin every case where its dependency cannot reach it, instead of generated code that names something not yet declared:- the dependency is built later among the same parent's properties, and building it first would create a cycle;
- the receiver is built inside the dependency it receives;
- the dependency is a renamed one (
fulfilledByDependencyNamed:) that is not created in that tree.
- A dependency one type requires and another receives only if available is built first, so both get it.
- A required receive of a renamed
onlyIfAvailabledependency is fulfilled when it can be, and reported when it cannot. - A
@Forwardedpropertyx: Xnow fulfills anx: X?received beneath it. Mocks previously built a second instance, or passednil. - Generated mocks take the right parameters for dependencies received only if available.
Generated mocks
- Mock parameters that share a label and whose types are not plain identifiers (
items: [String]anditems: Set<String>) get valid names. - A renamed dependency that keeps its label (
@Received(fulfilledByDependencyNamed: "analytics", ofType: Analytics.self) let analytics: EventTracker) no longer declaresanalyticstwice in the mock. - A received type that has
@Forwardedproperties is taken as amock()parameter. SafeDI cannot build it without the forwarded values, and used to generate code that did not compile. - A hand-written mock that returns the protocol an
ErasedInstantiatorbuilds is used. - When different properties erase different concrete types to the same type-erased wrapper, a mock that receives the wrapper takes it as a parameter. SafeDI used to build one of them, chosen differently from build to build.
Renaming and type erasure
@Received(fulfilledByDependencyNamed:ofType:erasedToConcreteExistential: true)wraps the renamed dependency in the property's type, as documented. WithonlyIfAvailable: trueit wraps in the type inside the optional.
Build integration
- A
#SafeDIConfiguration(additionalDirectoriesToInclude:)entry that names a directory whose files are already inputs (a dependency module's sources, for example) no longer reports every type in it as declared twice on a clean build.
Errors SafeDI now reports
Each of these graphs could never be built. They used to pass SafeDI and fail in the compiler, inside generated code. They are now reported at your declaration, with the chain that causes them:
- Two
Instantiatorproperties of one parent whose types receive each other. - Loops and chains through renamed dependencies, and cycles that pass through a lazy
Instantiatorcycle. - A required dependency that only something unavailable could provide.
- A received dependency hidden by a closer property with the same label and a different type. Declaring or renaming to the same label and type again is still fine: it provides the closer instance.
Deterministic output
Generated code, diagnostics, which error is reported first, fix-its, and the bytes of .safedi files are now the same on every run over the same inputs. Several choices used to follow the order of an unordered collection, which changes from process to process. Identical output keeps build caches warm and makes a failure reproducible.
Performance
generateis about 5% faster than 2.1.0 on a 15-module, 3,000-file production app.- Validating received dependencies stays fast when a tree contains an unrelated cycle.
Upgrading from 2.1.0
Most projects need no changes. On a 15-module, 3,000-file production app, every file SafeDI generates is byte-identical between 2.1.0 and 2.1.1.
Changes that can require an edit:
- A hidden dependency is now an error. If a type declares a property with the same label as one above it but a different type, and a type below receives that label expecting the outer type, SafeDI reports it and names the property to relabel. One shape of this compiled before, when the types happened to be compatible, and silently received a different instance than the one named.
- Some generated
mock()methods take a new required parameter. Pass the dependency you want the mock to use.- A received type-erased wrapper that different concrete types are erased to. This compiled before: the mock built one of the concrete types, and which one changed from build to build.
- A received type that has
@Forwardedproperties. This almost never compiled before. Where it did, the mock had bound the forwarded value to an unrelated dependency that happened to share its label and type.
- The order of
SafeDIOverridesinitializer arguments can change in a mock where 2.1.1 changed the order its dependencies are built in, since the arguments are listed in build order. If you pass several overrides and the compiler reports an argument out of order, reorder them to match. This is rare: no initializer moved in a 15-module production app or in the example projects.
A graph SafeDI now rejects did not compile before, with the one exception in item 1.
What's Changed
- Compile generated code in tests far enough to catch SIL-stage errors by @dfed in #373
- Use a hand-written mock that returns the protocol an ErasedInstantiator builds by @dfed in #381
- Require a received type that different concrete types are erased to in mocks by @dfed in #367
- Wrap an erased aliased @received in its existential type by @dfed in #366
- Write the same .safedi bytes for the same input files by @dfed in #375
- Generate the same code every run when dependent modules or imports could be combined in more than one order by @dfed in #378
- Keep received-cycle validation polynomial when walks stop at an unrelated cycle by @dfed in #376
- Skip input files when generate parses additional directories without scan's cache by @dfed in #369
- Name same-label mock parameters and overrides with identifier-safe type suffixes by @dfed in #382
- Pin the Tuist example to SafeDI 2.1.0 by @dfed in #386
- Make generated code, diagnostics, and fix-its the same on every run when several candidates exist by @dfed in #390
- Fix generated mocks for received types with forwarded properties and for aliases that keep their label, and report validation errors in a stable order by @dfed in #388
- Hash a property once, when it is created by @dfed in #392
- Fix what an onlyIfAvailable receiver is handed: required receives, ancestors being built, and aliases not created by @dfed in #370
- Fix mock parameters for dependencies received onlyIfAvailable by @dfed in #371
- Fulfill or reject a required receive of an onlyIfAvailable alias by @dfed in #384
- Pass nil to an onlyIfAvailable receive of the property being built, and reject required receives that rely on an unavailable one by @dfed in #385
- Report a received dependency hidden by a closer property with the same label by @dfed in #383
- Report sibling Instantiators that receive each other by @dfed in #379
- Detect alias loops, alias-chain and self-alias sibling cycles, and cycles through lazy Instantiator cycles by @dfed in #389
- Let forwarded properties fulfill optional receives and create onlyIfAvailable aliases after their fulfilling sibling by @dfed in #393
- Pin the Tuist example to SafeDI 2.1.1-rc-1 by @dfed in #396
Full Changelog: 2.1.0...2.1.1