Skip to content
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

Handle type guards properly in Receiver.filter() #332

Merged
merged 1 commit into from
Nov 15, 2024

Conversation

llucax
Copy link
Contributor

@llucax llucax commented Nov 14, 2024

Now the Receiver type returned by Receiver.filter() will have the narrowed type when a TypeGuard is used.

@llucax llucax requested a review from a team as a code owner November 14, 2024 13:06
@llucax llucax self-assigned this Nov 14, 2024
@github-actions github-actions bot added part:docs Affects the documentation part:tests Affects the unit, integration and performance (benchmarks) tests part:core Affects the core types (`Sender`, `Receiver`, exceptions, etc.) labels Nov 14, 2024
Comment on lines 319 to 327
# We need to use Any here because otherwise _Filter would have to deal with two
# different signatures. We can create two filter classes, one for regular functions
# and one for type guards, but then there is no way to tell at runtime which
# function is a type guard and which isn't to instantiate the correct class.
# Using Any here has no impact though, as thanks to the overloads, only the
# overloaded types will be accepted.
def filter(
self, filter_function: Callable[[ReceiverMessageT_co], Any], /
) -> Receiver[Any]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to change _Filter because TypeGuard is just another name for bool, with some additional semantic meaning. The type system knows assigning a TypeGuard value to a bool is fine.

Suggested change
# We need to use Any here because otherwise _Filter would have to deal with two
# different signatures. We can create two filter classes, one for regular functions
# and one for type guards, but then there is no way to tell at runtime which
# function is a type guard and which isn't to instantiate the correct class.
# Using Any here has no impact though, as thanks to the overloads, only the
# overloaded types will be accepted.
def filter(
self, filter_function: Callable[[ReceiverMessageT_co], Any], /
) -> Receiver[Any]:
def filter(
self,
filter_function: (
Callable[[ReceiverMessageT_co], bool]
| Callable[[ReceiverMessageT_co], TypeGuard[FilteredMessageT_co]]
),
/,
) -> Receiver[ReceiverMessageT_co] | Receiver[FilteredMessageT_co]:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I think what I tried was: filter_function: Callable[[ReceiverMessageT_co], bool | TypeGuard[FilteredMessageT_co]] and that didn't work.

@llucax
Copy link
Contributor Author

llucax commented Nov 15, 2024

Updated.

@llucax llucax requested a review from shsms November 15, 2024 08:19
@llucax llucax enabled auto-merge November 15, 2024 08:19
@llucax llucax added this to the v1.3.0 milestone Nov 15, 2024
Copy link
Contributor

@ela-kotulska-frequenz ela-kotulska-frequenz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, this is great!

Comment on lines 267 to 270
assert message == 8
match message:
case int():
assert message == 8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it necessary to have this check twice? (in line 267 and 270 ? assert message == 8 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it could just be a pass inside the case, true. I just needed to put something :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just changed the test to make sure the case int() is executed. mypy should verify this too thannks to the assert_never, but just in case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Now it is clear for me why it was necessary :)

@llucax
Copy link
Contributor Author

llucax commented Nov 15, 2024

Updated again.

Now the `Receiver` type returned by `Receiver.filter()` will have the
narrowed type when a `TypeGuard` is used.

Signed-off-by: Leandro Lucarella <[email protected]>
Copy link
Contributor

@shsms shsms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@llucax llucax added this pull request to the merge queue Nov 15, 2024
Merged via the queue into frequenz-floss:v1.x.x with commit 340d164 Nov 15, 2024
14 checks passed
@llucax llucax deleted the filter-guard branch November 15, 2024 13:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
part:core Affects the core types (`Sender`, `Receiver`, exceptions, etc.) part:docs Affects the documentation part:tests Affects the unit, integration and performance (benchmarks) tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants