Skip to content

Commit 86d9f62

Browse files
authored
Merge pull request #429 from ReactiveCocoa/never-guard
Warning for observation APIs when used with inhabitable types & `promoteValue`.
2 parents 291a914 + e16da4a commit 86d9f62

File tree

6 files changed

+223
-0
lines changed

6 files changed

+223
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# master
22
*Please add new entries at the top.*
33

4+
1. When composing `Signal` and `SignalProducer` of inhabitable types, e.g. `Never` or `NoError`, ReactiveSwift now warns about operators that are illogical to use, and traps at runtime when such operators attempt to instantiate an instance. (#429, kudos to @andersio)
5+
46
1. N-ary `SignalProducer` operators are now generic and accept any type that can be expressed as `SignalProducer`. (#410, kudos to @andersio)
57
Types may conform to `SignalProducerConvertible` to be an eligible operand.
68

ReactiveSwift.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@
6868
9A1D067D1D948A2300ACF44C /* UnidirectionalBindingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D067C1D948A2200ACF44C /* UnidirectionalBindingSpec.swift */; };
6969
9A1D067E1D948A2300ACF44C /* UnidirectionalBindingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D067C1D948A2200ACF44C /* UnidirectionalBindingSpec.swift */; };
7070
9A1D067F1D948A2300ACF44C /* UnidirectionalBindingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D067C1D948A2200ACF44C /* UnidirectionalBindingSpec.swift */; };
71+
9A5D93731EE5733300438925 /* InhabitableTypeGuards.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5D93721EE5733300438925 /* InhabitableTypeGuards.swift */; };
72+
9A5D93741EE5733300438925 /* InhabitableTypeGuards.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5D93721EE5733300438925 /* InhabitableTypeGuards.swift */; };
73+
9A5D93751EE5733300438925 /* InhabitableTypeGuards.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5D93721EE5733300438925 /* InhabitableTypeGuards.swift */; };
74+
9A5D93761EE5733300438925 /* InhabitableTypeGuards.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5D93721EE5733300438925 /* InhabitableTypeGuards.swift */; };
7175
9A681A9E1E5A241B00B097CF /* DeprecationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A681A9D1E5A241B00B097CF /* DeprecationSpec.swift */; };
7276
9A681A9F1E5A241B00B097CF /* DeprecationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A681A9D1E5A241B00B097CF /* DeprecationSpec.swift */; };
7377
9A681AA01E5A241B00B097CF /* DeprecationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A681A9D1E5A241B00B097CF /* DeprecationSpec.swift */; };
@@ -245,6 +249,7 @@
245249
9A090C131DA0309E00EE97CA /* Reactive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reactive.swift; sourceTree = "<group>"; };
246250
9A1A4F981E16961C006F3039 /* ValidatingPropertySpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidatingPropertySpec.swift; sourceTree = "<group>"; };
247251
9A1D067C1D948A2200ACF44C /* UnidirectionalBindingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnidirectionalBindingSpec.swift; sourceTree = "<group>"; };
252+
9A5D93721EE5733300438925 /* InhabitableTypeGuards.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InhabitableTypeGuards.swift; sourceTree = "<group>"; };
248253
9A681A9D1E5A241B00B097CF /* DeprecationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeprecationSpec.swift; sourceTree = "<group>"; };
249254
9A9100DE1E0E6E620093E346 /* ValidatingProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidatingProperty.swift; sourceTree = "<group>"; };
250255
9ABCB1841D2A5B5A00BCA243 /* Deprecations+Removals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deprecations+Removals.swift"; sourceTree = "<group>"; };
@@ -412,6 +417,7 @@
412417
D08C54B11A69A2AC00AD8286 /* Signal.swift */,
413418
D08C54B21A69A2AC00AD8286 /* SignalProducer.swift */,
414419
BE9CF3941D751B6B003AE479 /* UnidirectionalBinding.swift */,
420+
9A5D93721EE5733300438925 /* InhabitableTypeGuards.swift */,
415421
);
416422
name = Signals;
417423
sourceTree = "<group>";
@@ -884,6 +890,7 @@
884890
9A090C171DA0309E00EE97CA /* Reactive.swift in Sources */,
885891
57A4D1BB1BA13D7A00F7D4B1 /* Signal.swift in Sources */,
886892
57A4D1BC1BA13D7A00F7D4B1 /* SignalProducer.swift in Sources */,
893+
9A5D93761EE5733300438925 /* InhabitableTypeGuards.swift in Sources */,
887894
57A4D1BD1BA13D7A00F7D4B1 /* Atomic.swift in Sources */,
888895
57A4D1BE1BA13D7A00F7D4B1 /* Bag.swift in Sources */,
889896
57A4D1C01BA13D7A00F7D4B1 /* FoundationExtensions.swift in Sources */,
@@ -938,6 +945,7 @@
938945
9A090C161DA0309E00EE97CA /* Reactive.swift in Sources */,
939946
A9B315C31B3940810001CB9C /* Signal.swift in Sources */,
940947
A9B315C41B3940810001CB9C /* SignalProducer.swift in Sources */,
948+
9A5D93751EE5733300438925 /* InhabitableTypeGuards.swift in Sources */,
941949
A9B315C51B3940810001CB9C /* Atomic.swift in Sources */,
942950
A9B315C61B3940810001CB9C /* Bag.swift in Sources */,
943951
A9B315C81B3940810001CB9C /* FoundationExtensions.swift in Sources */,
@@ -965,6 +973,7 @@
965973
D08C54B31A69A2AE00AD8286 /* Signal.swift in Sources */,
966974
D85C652A1C0D84C7005A77AD /* Flatten.swift in Sources */,
967975
D0C312CF19EF2A5800984962 /* Bag.swift in Sources */,
976+
9A5D93731EE5733300438925 /* InhabitableTypeGuards.swift in Sources */,
968977
4A0E10FF1D2A92720065D310 /* Lifetime.swift in Sources */,
969978
D0C312E719EF2A5800984962 /* Scheduler.swift in Sources */,
970979
D0C312CD19EF2A5800984962 /* Atomic.swift in Sources */,
@@ -1019,6 +1028,7 @@
10191028
9A090C151DA0309E00EE97CA /* Reactive.swift in Sources */,
10201029
D85C652B1C0E70E3005A77AD /* Flatten.swift in Sources */,
10211030
4A0E11001D2A92720065D310 /* Lifetime.swift in Sources */,
1031+
9A5D93741EE5733300438925 /* InhabitableTypeGuards.swift in Sources */,
10221032
D08C54BB1A69C54400AD8286 /* Property.swift in Sources */,
10231033
D03B4A3E19F4C39A009E02AC /* FoundationExtensions.swift in Sources */,
10241034
D08C54B71A69A3DB00AD8286 /* Event.swift in Sources */,

Sources/InhabitableTypeGuards.swift

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import Result
2+
3+
// Observation
4+
extension SignalProducer where Value == Never {
5+
@discardableResult
6+
@available(*, deprecated, message:"`Result.success` is never delivered - `Value` is inhabitable (Instantiation at runtime would trap)")
7+
public func startWithResult(_ action: @escaping (Result<Value, Error>) -> Void) -> Disposable { observingInhabitableTypeError() }
8+
9+
@discardableResult
10+
@available(*, deprecated, message:"Observer is never called - `Value` is inhabitable (Instantiation at runtime would trap)")
11+
public func startWithValues(_ action: @escaping (Value) -> Void) -> Disposable { observingInhabitableTypeError() }
12+
}
13+
14+
extension SignalProducer where Value == Never, Error == NoError {
15+
@discardableResult
16+
@available(*, deprecated, message:"Observer is never called - `Value` and `Error` are inhabitable (Instantiation at runtime would trap)")
17+
public func startWithResult(_ action: @escaping (Result<Value, Error>) -> Void) -> Disposable { observingInhabitableTypeError() }
18+
}
19+
20+
extension SignalProducer where Error == NoError {
21+
@discardableResult
22+
@available(*, deprecated, message:"`Error` is inhabitable so the observer is never called (Instantiation at runtime would trap)")
23+
public func startWithFailed(_ action: @escaping (Error) -> Void) -> Disposable { observingInhabitableTypeError() }
24+
}
25+
26+
extension Signal where Value == Never {
27+
@discardableResult
28+
@available(*, deprecated, message:"`Result.success` is never delivered - `Value` is inhabitable (Instantiation at runtime would trap)")
29+
public func observeResult(_ action: @escaping (Result<Value, Error>) -> Void) -> Disposable? { observingInhabitableTypeError() }
30+
31+
@discardableResult
32+
@available(*, deprecated, message:"Observer is never called - `Value` is inhabitable (Instantiation at runtime would trap)")
33+
public func observeValues(_ action: @escaping (Value) -> Void) -> Disposable? { observingInhabitableTypeError() }
34+
}
35+
36+
extension Signal where Value == Never, Error == NoError {
37+
@discardableResult
38+
@available(*, deprecated, message:"Observer is never called - `Value` and `Error` are inhabitable (Instantiation at runtime would trap)")
39+
public func observeResult(_ action: @escaping (Result<Value, Error>) -> Void) -> Disposable? { observingInhabitableTypeError() }
40+
}
41+
42+
extension Signal where Error == NoError {
43+
@discardableResult
44+
@available(*, deprecated, message:"Observer is never invoked - `Error` is inhabitable (Instantiation at runtime would trap)")
45+
public func observeFailed(_ action: @escaping (Error) -> Void) -> Disposable? { observingInhabitableTypeError() }
46+
}
47+
48+
// flatMap
49+
extension SignalProducer where Value == Never {
50+
@discardableResult
51+
@available(*, deprecated, message:"Use `promoteValue` instead - `Value` is inhabitable (Instantiation at runtime would trap)")
52+
public func flatMap<Inner: SignalProducerConvertible>(_ strategy: FlattenStrategy, _ transform: @escaping (Value) -> Inner) -> SignalProducer<Inner.Value, Error> where Inner.Error == Error { observingInhabitableTypeError() }
53+
54+
@discardableResult
55+
@available(*, deprecated, message:"Use `promoteValue` instead - `Value` is inhabitable (Instantiation at runtime would trap)")
56+
public func flatMap<Inner: SignalProducerConvertible>(_ strategy: FlattenStrategy, _ transform: @escaping (Value) -> Inner) -> SignalProducer<Inner.Value, Error> where Inner.Error == NoError { observingInhabitableTypeError() }
57+
}
58+
59+
extension SignalProducer where Value == Never, Error == NoError {
60+
@discardableResult
61+
@available(*, deprecated, message:"Use `promoteValue` instead - `Value` and `Error` are inhabitable (Instantiation at runtime would trap)")
62+
public func flatMap<Inner: SignalProducerConvertible>(_ strategy: FlattenStrategy, _ transform: @escaping (Value) -> Inner) -> SignalProducer<Inner.Value, Inner.Error> { observingInhabitableTypeError() }
63+
64+
@discardableResult
65+
@available(*, deprecated, message:"Use `promoteValue` instead - `Value` and `Error` are inhabitable (Instantiation at runtime would trap)")
66+
public func flatMap<Inner: SignalProducerConvertible>(_ strategy: FlattenStrategy, _ transform: @escaping (Value) -> Inner) -> SignalProducer<Inner.Value, Inner.Error> where Inner.Error == Error { observingInhabitableTypeError() }
67+
}
68+
69+
extension SignalProducer where Error == NoError {
70+
@discardableResult
71+
@available(*, deprecated, message:"Use `promoteError` instead - `Error` is inhabitable (Instantiation at runtime would trap)")
72+
public func flatMapError<NewError>(_ transform: @escaping (Error) -> SignalProducer<Value, NewError>) -> SignalProducer<Value, NewError> { observingInhabitableTypeError() }
73+
}
74+
75+
extension Signal where Value == Never {
76+
@discardableResult
77+
@available(*, deprecated, message:"Use `promoteValue` instead - `Value` is inhabitable (Instantiation at runtime would trap)")
78+
public func flatMap<Inner: SignalProducerConvertible>(_ strategy: FlattenStrategy, _ transform: @escaping (Value) -> Inner) -> Signal<Inner.Value, Error> where Inner.Error == Error { observingInhabitableTypeError() }
79+
80+
@discardableResult
81+
@available(*, deprecated, message:"Use `promoteValue` instead - `Value` is inhabitable (Instantiation at runtime would trap)")
82+
public func flatMap<Inner: SignalProducerConvertible>(_ strategy: FlattenStrategy, _ transform: @escaping (Value) -> Inner) -> Signal<Inner.Value, Error> where Inner.Error == NoError { observingInhabitableTypeError() }
83+
84+
}
85+
86+
extension Signal where Value == Never, Error == NoError {
87+
@discardableResult
88+
@available(*, deprecated, message:"Use `promoteValue` instead - `Value` and `Error` are inhabitable (Instantiation at runtime would trap)")
89+
public func flatMap<Inner: SignalProducerConvertible>(_ strategy: FlattenStrategy, _ transform: @escaping (Value) -> Inner) -> Signal<Inner.Value, Inner.Error> { observingInhabitableTypeError() }
90+
91+
@discardableResult
92+
@available(*, deprecated, message:"Use `promoteValue` instead - `Value` and `Error` are inhabitable (Instantiation at runtime would trap)")
93+
public func flatMap<Inner: SignalProducerConvertible>(_ strategy: FlattenStrategy, _ transform: @escaping (Value) -> Inner) -> Signal<Inner.Value, Inner.Error> where Inner.Error == Error { observingInhabitableTypeError() }
94+
}
95+
96+
extension Signal where Error == NoError {
97+
@discardableResult
98+
@available(*, deprecated, message:"Use `promoteError` instead - `Error` is inhabitable (Instantiation at runtime would trap)")
99+
public func flatMapError<NewError>(_ transform: @escaping (Error) -> SignalProducer<Value, NewError>) -> Signal<Value, NewError> { observingInhabitableTypeError() }
100+
}
101+
102+
@inline(never)
103+
private func observingInhabitableTypeError() -> Never {
104+
fatalError("Detected an attempt to instantiate a `Signal` or `SignalProducer` that observes an inhabitable type, e.g. `Never` or `NoError`. This is considered a logical error, and appropriate operators should be used instead. Please refer to the warnings raised by the compiler.")
105+
}
106+
107+
/*
108+
func test() {
109+
SignalProducer<Never, AnyError>.never.startWithResult { _ in }
110+
SignalProducer<Never, NoError>.never.startWithResult { _ in }
111+
SignalProducer<Any, NoError>.never.startWithFailed { _ in }
112+
SignalProducer<Never, NoError>.never.startWithFailed { _ in }
113+
Signal<Never, AnyError>.never.observeResult { _ in }
114+
Signal<Never, NoError>.never.observeResult { _ in }
115+
Signal<Any, NoError>.never.observeFailed { _ in }
116+
Signal<Never, NoError>.never.observeFailed { _ in }
117+
118+
SignalProducer<Never, AnyError>.never.flatMap(.latest) { _ in SignalProducer<Int, AnyError>.empty }
119+
SignalProducer<Never, AnyError>.never.flatMap(.latest) { _ in SignalProducer<Int, NoError>.empty }
120+
SignalProducer<Never, NoError>.never.flatMap(.latest) { _ in SignalProducer<Int, AnyError>.empty }
121+
SignalProducer<Never, NoError>.never.flatMap(.latest) { _ in SignalProducer<Int, NoError>.empty }
122+
SignalProducer<Never, NoError>.never.flatMapError { _ in SignalProducer<Never, AnyError>.empty }
123+
SignalProducer<Never, NoError>.never.flatMapError { _ in SignalProducer<Never, NoError>.empty }
124+
125+
Signal<Never, AnyError>.never.flatMap(.latest) { _ in SignalProducer<Int, AnyError>.empty }
126+
Signal<Never, AnyError>.never.flatMap(.latest) { _ in SignalProducer<Int, NoError>.empty }
127+
Signal<Never, NoError>.never.flatMap(.latest) { _ in SignalProducer<Int, AnyError>.empty }
128+
Signal<Never, NoError>.never.flatMap(.latest) { _ in SignalProducer<Int, NoError>.empty }
129+
Signal<Never, NoError>.never.flatMapError { _ in SignalProducer<Never, AnyError>.empty }
130+
Signal<Never, NoError>.never.flatMapError { _ in SignalProducer<Never, NoError>.empty }
131+
}
132+
*/

Sources/Signal.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2473,6 +2473,47 @@ extension Signal where Error == NoError {
24732473
}
24742474
}
24752475

2476+
extension Signal where Value == Never {
2477+
/// Promote a signal that does not generate values, as indicated by `Never`, to be
2478+
/// a signal of the given type of value.
2479+
///
2480+
/// - note: The promotion does not result in any value being generated.
2481+
///
2482+
/// - parameters:
2483+
/// - _ The type of value to promote to.
2484+
///
2485+
/// - returns: A signal that forwards all terminal events from `self`.
2486+
public func promoteValue<U>(_: U.Type = U.self) -> Signal<U, Error> {
2487+
return Signal<U, Error> { observer in
2488+
return self.observe { event in
2489+
switch event {
2490+
case .value:
2491+
fatalError("Never is impossible to construct")
2492+
case let .failed(error):
2493+
observer.send(error: error)
2494+
case .completed:
2495+
observer.sendCompleted()
2496+
case .interrupted:
2497+
observer.sendInterrupted()
2498+
}
2499+
}
2500+
}
2501+
}
2502+
2503+
/// Promote a signal that does not generate values, as indicated by `Never`, to be
2504+
/// a signal of the given type of value.
2505+
///
2506+
/// - note: The promotion does not result in any value being generated.
2507+
///
2508+
/// - parameters:
2509+
/// - _ The type of value to promote to.
2510+
///
2511+
/// - returns: A signal that forwards all terminal events from `self`.
2512+
public func promoteValue(_: Value.Type = Value.self) -> Signal<Value, Error> {
2513+
return self
2514+
}
2515+
}
2516+
24762517
extension Signal where Value == Bool {
24772518
/// Create a signal that computes a logical NOT in the latest values of `self`.
24782519
///

Sources/SignalProducer.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,6 +1392,34 @@ extension SignalProducer where Error == AnyError {
13921392
}
13931393
}
13941394

1395+
extension SignalProducer where Value == Never {
1396+
/// Promote a signal that does not generate values, as indicated by `Never`, to be
1397+
/// a signal of the given type of value.
1398+
///
1399+
/// - note: The promotion does not result in any value being generated.
1400+
///
1401+
/// - parameters:
1402+
/// - _ The type of value to promote to.
1403+
///
1404+
/// - returns: A signal that forwards all terminal events from `self`.
1405+
public func promoteValue<U>(_: U.Type = U.self) -> SignalProducer<U, Error> {
1406+
return lift { $0.promoteValue(U.self) }
1407+
}
1408+
1409+
/// Promote a signal that does not generate values, as indicated by `Never`, to be
1410+
/// a signal of the given type of value.
1411+
///
1412+
/// - note: The promotion does not result in any value being generated.
1413+
///
1414+
/// - parameters:
1415+
/// - _ The type of value to promote to.
1416+
///
1417+
/// - returns: A signal that forwards all terminal events from `self`.
1418+
public func promoteValue(_: Value.Type = Value.self) -> SignalProducer<Value, Error> {
1419+
return self
1420+
}
1421+
}
1422+
13951423
extension SignalProducer where Value: Equatable {
13961424
/// Forward only values from `self` that are not equal to its immediately preceding
13971425
/// value.

Tests/ReactiveSwiftTests/SignalSpec.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3105,6 +3105,16 @@ class SignalSpec: QuickSpec {
31053105
expect(combined is Signal<(Int, Double, Float, UInt), TestError>) == true
31063106
}
31073107
}
3108+
3109+
describe("promoteValue") {
3110+
it("should infer the value type from the context") {
3111+
let completable = Signal<Never, NoError>.never
3112+
let producer: Signal<Int, NoError> = Signal<Int, NoError>.never
3113+
.flatMap(.latest) { _ in completable.promoteValue() }
3114+
3115+
expect((producer as Any) is Signal<Int, NoError>) == true
3116+
}
3117+
}
31083118
}
31093119
}
31103120

0 commit comments

Comments
 (0)