-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use opaque reducers in 5.7 to fix builder inference #1591
Conversation
I've tested this in isowords and things still build OK...hopefully CI here is also agreeable. |
public static func buildEither<R0: ReducerProtocol, R1: ReducerProtocol>( | ||
second reducer: R1 | ||
) -> _Conditional<R0, R1> | ||
where R0.State == State, R0.Action == Action, R1.State == State, R1.Action == Action { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
buildEither
was the one spot I couldn't get some ReducerProtocol
to work...so there may be some inference issues when it comes to if
-else
...but that's rare and I guess we can revisit if/when it comes up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stephencelis were you using _Conditional<R0, R1>.first(reducer)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to, but couldn't figure out how to get R0
to be a some
and R1
to be a generic that propagates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I get you.
The only way I can think of at the moment would be to use custom inits. eg: _Conditional(first: reducer, secondType: R1.self)
but not sure if that's any better than just passing the two generics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fun idea! Seems to work and I imagine is better on the type checker than the alternative, so pushed, thanks! 😄
case first(First, Second.Type = Second.self) | ||
case second(Second, First.Type = First.self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hadn't thought to include the type in the case. Did you try a static func as well?
@inlinable
static func first(_ reducer: First, _ secondType: Second.Type) -> Self {
.first(reducer)
}
Is there a benefit including it as an associated value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@iampatbrown I spoke too soon, using it as either associated value or a static func doesn't appear to compile, unfortunately. Definitely down to merge something that compiles, but both my commit and your code above crashes the compiler 😞
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stephencelis So close.. yet so far...
buildEither
probably wants a concrete type in order to match first:
and second:
.
I think this PR is great without it anyway.
This reverts commit f51e569.
} | ||
|
||
@inlinable | ||
public static func buildOptional<R: ReducerProtocol>(_ wrapped: R?) -> _Optional<R> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SwiftUI makes Optional<some View>
a View
itself. Was making Optional<some ReducerProtocol>
a ReducerProtocol
instead of using this _Optional<some Reducer>
considered at some point?
EDIT: I made the comment in the 5.6 branch, but it's only because diffing is all messed up. My remark applies to both branches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, with
extension Optional: ReducerProtocol where Wrapped: ReducerProtocol {
public func reduce(into state: inout Wrapped.State, action: Wrapped.Action)
-> EffectTask<Wrapped.Action>
{
switch self {
case .none:
return .none
case .some(let wrapped):
return wrapped.reduce(into: &state, action: action)
}
}
}
We can return directly wrapped
in buildOptional
, and Optional.some(wrapped)
in buildLimitedAvailability
, while using opaque reducers.
BTW, I've just spotted this thing while checking in SwiftUI interface:
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@inlinable @inline(__always) internal func asOptional<T>(_ value: T) -> T? {
func unwrap<T>() -> T { value as! T }
let optionalValue: T? = unwrap()
return optionalValue
}
This is some weird gymnastic and I'm not getting why they don't use Optional.some
instead. But this is mostly trivia and unrelated to the PR.
EDIT: I think this is to make sure they don't create T??
, etc. So we can probably use this too for reducers.
EDIT2: Well, it still creates T??
as I'm getting it, so I don't know. I'm not 100% on nested optionals, as I think there is some automatic coalescing in some places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tgrapperon Good find! I'm not sure why we didn't conform optional directly, maybe because of a bug with type aliases in pre-5.7. Added support and removed _Optional
, thanks!
We also realized our buildLimitedAvailability
wasn't right. This function needs to erase the reducer somehow. I think we snuck a fix into this PR, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think we still need the buildOptional
block? As I'm seeing it, it should be handled by the buildBlock(reducer: R?)
now that R? is a reducer, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, it is required to work with buildLimitedAvailability
at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tgrapperon Just merged and missed this. What hit main
should at least be an improvement, but if you find other tweaks that can be made def PR!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I think we need this. As I'm getting it now:
if #available(iOS 16, *) {
R()
}
will buildLimitedAvailability
R
into AnyReducer
and then buildOptional
it to produce AnyReducer?
. If you remove buildOptional
, the test for buildLimitedAvailability
doesn't pass anymore, so I guess this is what happens (and it is maybe involved with other blocks
/situations).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stephencelis buildOptional
is involved as soon as we have unbalanced if
checks of some sort, so it is required for sure.
* add dependencies and its test * update effect and reducer * add reducers, rename anyreducer, delete xct related files * extract storeconfig * task result and task cancellable value * observable * effect * fix package swift * any disposable * import xctdynamicoverlay * update effects * make any disposable public * fix any reducer compatibility * first passed test 5.7 * refactor package.swift * remove anyequatable double * update store * wrap in discardable * [TestStore] - Update to conform changes ReducerProtocol (#65) * progress update teststore * progressing update TestStore * Fixing TestStore & make it success run test * update indent code TestStore, add comment for send action assert testStore * change observable * update observable * rename conflicted name * undo * add flatmap * add xctest dynamic overlay podspec * add xctdo podspec * update exports.swift * update podspec * update typo podspec * fix dispose issue * restructure dependency module, fix test, update podspec and package.swift * update store old behaviour, fix store tests * remove unused code * fix flatmap, add StoreOf * update teststore generics order, remove all spi internals * Rewrite example using Reducer Protocol style (#63) * update store, rewrite basic and scoping example * rewrite environment example * rewrite pullback vc example * update counterview, rewrite iflet * rewrite never equal * update timer example * update test * update old scope and store tests * use OptionalPath * remove duplicated StoreConfig * add ACL for TestReducer class & property * fix default value for useNewScope in StoreConfig * [Reducer Protocol] - Add New Test Cases (#68) * fix passing useNewScope to Store from TestStore * add new test to TestStore * add new TestStoreTest cases * update to fix async task cancel * update StoreTest test case * add more StoreTests test cases * update failingWhenNothingChanges to true by default on new TestStore code * update & add new cases for TestStoreTest * remove import combine * separate oldScope & newScope test on Storetest and TestStoreTest * update testcases based on new default value for for Store and TestStore * [Reducer Protocol] - Support Bootstrap MockKit (#70) Support our Bootstrap MockKit when using ReducerProtocol Dependency Environment * update pullback vc * bump xcode versions * update ci.yml to use xcode 14.1 for example tests * update ci yml again for xcode 14.1 * refactor ci.yml * update xcode 14 1 name * sync with 0.44.1 minus PR 1510 and EffectTask * fix warning * Fix Reducer builder inference issue: pointfreeco/swift-composable-architecture#1591 Implement Test Pt1 * adding more test, still failing dependency test on both store and teststore * pointfreeco/swift-composable-architecture#1570 pointfreeco/swift-composable-architecture#1575 * add usenewscope * Sync Cancellation * Readd PR #1510 and add tests, fix tests examples * add optional reducer, update dependency values tests with binding * sync up to 0.46.0 * sync up to 0.47.2 * sync up to 0.48.0 * bump xctestdynamicoverlay * remove assert function on teststore * add failing when nothing change on scope teststore * update store send and mainqueue syntax --------- Co-authored-by: Andhika Setiadi <[email protected]> Co-authored-by: Jefferson Setiawan <[email protected]>
Turns out a lot of the bugs we were working around with
CombineReducers
inference, as well asbuildFinalResult
warnings (previously: #1467) go away if we usesome ReducerProtocol<State, Action>
on Swift 5.7 instead of the concrete alternative.@slavapestov Not sure if you're aware of these functional differences between inference of generics vs. opaque constrained types or would like some fresh bug reports. It can take some time to file them, but if they're not being tracked yet we can try to file down a repro.