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

Enable reporting unknown types #333

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

ItsDrike
Copy link
Member

@ItsDrike ItsDrike commented Jul 13, 2024

This PR is an experiment, just to see how the code-base would look like if we enabled reporting unknown types for basedpyright.

It's more of a prompt for discussion on whether or not this is worth it, I do not intend to merge this, at least not before this discussion takes place.

The issue with these rules is that they can sometimes be incredibly annoying to satisfy, it often means having to use a bunch of type-casts or just ignoring them. However, in many cases, they can also be incredibly useful, as it's good to know when basedpyright can't recognize some type and needs some help in understanding it. Overall, it would definitely improve the type safety, but the question is whether it's even worth it.

@ItsDrike ItsDrike added t: enhancement Improvement to an existing feature p: 3 - low This can wait do-not-merge This PR can be reviewed, but cannot be merged now a: internal Related to internal API of the project labels Jul 13, 2024
@ItsDrike ItsDrike marked this pull request as ready for review July 13, 2024 10:32
@ItsDrike ItsDrike force-pushed the enable-unknown-type-reporting branch from 6cbd87e to e434651 Compare July 13, 2024 10:34
@@ -110,7 +110,7 @@
autodoc_member_order = "bysource"

# Default options for all autodoc directives
autodoc_default_options = {
autodoc_default_options: dict[str, Any] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see how these kind of type annotations are better

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, the thing is, pyright would otherwise mark this as dict[str, Unknown] and with Unknown being banned, if a wildcard type is indeed what we want, it must be declared explicitly like this. That's also one of the reasons why these rules might be a bit too strict.

@@ -153,7 +153,7 @@ def run(self) -> list[AttributeTablePlaceholder]:
def build_lookup_table(env: BuildEnvironment) -> dict[str, list[str]]:
# Given an environment, load up a lookup table of
# full-class-name: objects
result = {}
result: dict[str, list[str]] = {}
Copy link
Contributor

Choose a reason for hiding this comment

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

But for these ones it's interesting to have type checking

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree, in some cases, being forced to explicitly declare the annotations can be very helpful to the type checker, it is nice that this would now be a requirement, where types can't be easily inferred.

@@ -66,7 +66,7 @@ def __init__(self, exc: httpx.HTTPStatusError):
@property
def msg(self) -> str:
"""Produce a message for this error."""
msg_parts = []
msg_parts: list[str] = []
Copy link
Contributor

Choose a reason for hiding this comment

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

I already have things like that all over the place in more-packets

@@ -477,7 +479,7 @@ def test_nbt_bigfile():
data = bytes.fromhex(data)
buffer = Buffer(data)

expected_object = { # Name ! Level
expected_object: FromObjectType = { # Name ! Level
Copy link
Contributor

Choose a reason for hiding this comment

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

This I find useful

@LiteApplication
Copy link
Contributor

I'm not against these new rules as I feel they are not the most annoying of the bunch, but I don't really know if the extra effort is worth the while.

@ItsDrike
Copy link
Member Author

The worst / most annoying stuff are recursive types, like those in nbt.py, since pyright can only understand them up to 1 level deep. After that, they become Unknown and casts are necessary. That was definitely the most annoying part of this.

@LiteApplication
Copy link
Contributor

Type checking is useful when it uses the computer's power to mitigate our forgetfulness, but when it's going the opposite way I find it annoying 😆 .

@py-mine-ci-bot py-mine-ci-bot bot added the s: stale Has had no activity for a while (will be closed for inactivity/marked up for grabs soon) label Oct 27, 2024
Base automatically changed from strict-basedpyright to main January 7, 2025 22:08
Since we have the reportUnknownParameterType for basedpyright enabled,
we can't leave `*args` or `**kwargs` unannotated anyways, so we might
as well enforce it properly.
@ItsDrike ItsDrike force-pushed the enable-unknown-type-reporting branch from 7fa69fc to fd4a475 Compare January 7, 2025 22:20
@py-mine-ci-bot py-mine-ci-bot bot removed the s: stale Has had no activity for a while (will be closed for inactivity/marked up for grabs soon) label Jan 8, 2025
@ItsDrike
Copy link
Member Author

ItsDrike commented Jan 8, 2025

The worst / most annoying stuff are recursive types, like those in nbt.py, since pyright can only understand them up to 1 level deep. After that, they become Unknown and casts are necessary. That was definitely the most annoying part of this.

Seems that basedpyright now supports recursive types properly, so the latest rebase here removes all of those workaround casts for recursive types, making this a lot more pleasant rule.


I'm still slightly worried about enabling it though, as it also means that any untyped libs that we may depend on will cause these warnings every time they're used, and these would be pretty annoying to remove, as simply declaring the type e.g. x: int = untyped_func() isn't enough to suppress this, because pyright doesn't let you ignore the internal Unknown type quite so easily. Unknown would actually propagate here, making x a union of int | Unknown, hence triggering reportUnknownVariableType. Just a pyright ignore isn't enough either, because the type would remain a union of those 2, triggering more issues when you work with that variable in pretty much any way.

Instead, the only real way to resolve this would be to use an explicit cast e.g. x = typing.cast(int, untyped_func()). This isn't horrible, but can be annoying, especially for heavy use of untyped libraries. If we can't figure out the type of the return value easily, it might even lead to cast(Any, x) situations, just to convert the implicit any type (Unknown) into an explicit Any type, which isn't banned.

That said, these rules really are handy to have enabled and do improve the typing experience significantly in some cases. Mcproto also doesn't currently rely heavily on any such untyped libraries, and most of the popular libraries would remain usable without issues, as they are usually typed. Also, mcproto is actually fairly unlikely to pick up a lot of runtime dependencies with what it's purpose is, so it might be worth it.


Note that there is a way to compromise, and get the "best of both worlds", where instead of enabling these rules outright, making them required and checked in the validation CI & pre-commit, we could use the "hint" level for these rules, which would make basepyright report these diagnostics in the IDE, while editing, but not following them wouldn't be considered a full-on failure that prevents merging.

That said, we shouldn't just jump to this without thinking, this could still produce a lot of clutter diagnostics when opening up a file with a bunch of such untyped calls and it's also losing on the main benefit of type-checking, being the checking part. If this isn't actually enforced, we can still end up with unknown types in various places, leading to worse type-checking.

However, if we do decide against a fully enforced approach, this is definitely something to consider before throwing this PR away entirely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: internal Related to internal API of the project do-not-merge This PR can be reviewed, but cannot be merged now p: 3 - low This can wait t: enhancement Improvement to an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants