-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[red-knot] Add __init__
arguments check when doing try_call
on a class literal
#16512
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
[red-knot] Add __init__
arguments check when doing try_call
on a class literal
#16512
Conversation
This is still a draft, as I wanted to discuss a couple of things first. First of all, @sharkdp did all the hard work and it seems almost too easy now. However, there are a couple of caveats.
|
I'm not convinced.
Agreed, should be a pretty easy change I think? |
I'm not sure I understand your first point. It's both possible and common to have a def f() -> int:
return 1
x = f(1) # error: [too-many-positional-arguments]
reveal_type(x) # revealed: int What's unusual about Those are just some general comments, haven't looked at the code in the PR yet, will do that tomorrow! |
Maybe this is what you meant by "We don't have to model things totally correctly", but I guess we should (eventually) emit a diagnostic if |
First, I realized I confused everyone by saying
Hence, I suggest adding a method of a function in Any objections?
I haven't looked yet, I'll apply the change since everyone is fine. But this means that more tests will change in this PR than just those related to |
(Didn't get to this today, sorry; will come back to it tomorrow.) |
crates/red_knot_python_semantic/resources/mdtest/call/constructor.md
Outdated
Show resolved
Hide resolved
crates/red_knot_python_semantic/resources/mdtest/call/constructor.md
Outdated
Show resolved
Hide resolved
@sharkdp agree. but IMHO the diagnostic should be emitted when traversing the |
c495452
to
598ac2d
Compare
@carljm never mind my previous comments, I found an API I needed. I think this is ready for review. I had to temporarily add 3 false positive errors in two unrelated mdtest I also fixed the argument counting in the diagnostics when calling bound methods |
bummer... bench is failing due to tomlib using a lot of I'd rather do a bandaid - add a hard-coded handler for |
4ae950a
to
9ca7653
Compare
copy pasting from discord. Looks like I will not be able to get away with a two PR approach for Many builtin types & stdlib (e.g. At the same time, both So in order for me to avoid false positives on the benchmark, I would still need to at least check if Which also brings another necessity - I'll need to extend class_member API with an optional argument to stop checking MRO before object... Luckily this API is only used in 2 places now, so doesn't seem like a big deal. In short, two options here
team, leaning on to you for decision here. Normally I'd prefer more smaller PR's, but this may not be the right time. |
Thanks for digging into this! Given those choices, I think we may as well just add If even that turns out to be harder than expected, we can also just check for if I think you are right that we will need the ability to query for a member while ignoring its definition on the (For future reference, another wrinkle we should add a TODO for is if the class' metaclass defines |
Yes, it was already there and was step 3 in my initial plan. will be step 2 (hopefully, I'll manage to get away without). Will work on |
crates/red_knot_python_semantic/resources/mdtest/call/constructor.md
Outdated
Show resolved
Hide resolved
crates/red_knot_python_semantic/resources/mdtest/call/constructor.md
Outdated
Show resolved
Hide resolved
Awesome, thank you; you too! |
9ca7653
to
f005cec
Compare
temp wip special case typevar & object in try_call to avoid salsa cycle Revert to setting call outcome type, instead of injecting errors fix __new__ handling use enum for mro search policy
… object fallback properly handle subclasses properly handle init method when new is present
ignore GenericAlias.__getattr__ for now ensure `type.__new__` doesn't popup during class instantiation
e1612bd
to
8331fd5
Compare
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.
Looks great, thank you for all your work on this!
I've pushed some changes. The most significant one is to add find_name_in_mro_with_policy
and class_member_with_policy
, to mirror the existing member_lookup_with_policy
, and have the find_name_in_mro
and class_member
methods be thin wrappers that just use the default policy. This arguably makes writing internal Type code a bit more error-prone, as it means we can more easily accidentally drop a policy we should have propagated. But I think it's worth it because it removes so much MemberLookupPolicy::default()
noise all over the place.
Since I've made significant changes, I won't merge this right away but will leave it overnight in case you or anyone else has comments on the changes I've made; otherwise I'll merge it first thing in the morning my time.
// Make sure `__mro__` is always searched all the way to object. | ||
let policy = if name == "__mro__" { | ||
policy & !MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK | ||
} else { | ||
policy | ||
}; | ||
|
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.
This doesn't seem necessary; all tests pass without it. It seems to me it shouldn't be necessary unless we have a bug somewhere else; we shouldn't look up __mro__
with this flag in the first place.
@@ -3143,6 +3227,56 @@ impl<'db> Type<'db> { | |||
); | |||
Signatures::single(signature) | |||
} | |||
Some(KnownClass::TypeVar) => { |
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.
This is no longer needed on current main, with fixpoint iteration.
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.
awesome. I think it was still failing when I added it, but great that it is already unnecessary
Signatures::single(signature) | ||
} | ||
Some(KnownClass::Object) => { | ||
// // Added here to avoid salsa query cycle in typeshed definition |
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.
This is no longer needed to avoid a cycle, so I'll remove this comment. There is a test that fails without it, and I think this is a reasonable one to special-case rather than relying on fixpoint iteration, so I'll leave the arm here for now.
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.
LGTM. especially since it doesn't have a corresponding code in bind
, so not as painful as other cases which must be kept in sync with a completely different module
// Lookup `__new__` method in the MRO up to, but not including, `object`. Also, we must | ||
// avoid `__new__` on `type` since per descriptor protocol, if `__new__` is not defined on | ||
// a class, metaclass attribute would take precedence. But by avoiding `__new__` on `object` | ||
// we would inadvertently unhide `__new__` on `type`, which is not what we want. |
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.
This bit about type.__new__
is interesting. I wonder if an alternative approach here could be to not skip object.__new__
but instead mark it in a way that makes it easy to check if that's the one we found. Then we wouldn't have the side effect of unhiding type.__new__
, meaning we need two lookup flags.
Not going to look into that now, but might add a comment about 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.
Thanks for the idea. I'll look into it when I do a follow up for __call__
handling. Or it may retackled separately in try_call_dunder
unification #16371 - because this essentially about trying to create a unified API that everyone can call without too much thinking how the lookups should behave.
Actually I might try a few things to see if we can bring down the perf regression before merging this. |
Looking at the mypy-primer results, there are a lot of new false positives related to dataclass Some other false positives appear to be because we lack handling of assignability/subtyping of a class literal or subclass-of type to a |
sounds like another follow-up candidate to me |
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.
This is really great, thank you!
crates/red_knot_python_semantic/resources/mdtest/call/constructor.md
Outdated
Show resolved
Hide resolved
Co-authored-by: David Peter <[email protected]>
thanks everyone for patiently waiting on this one! 🚀 |
Thanks for the PR! |
* origin/main: [red-knot] Default `python-platform` to current platform (#17183) [red-knot] Add new 'unreachable code' test case (#17306) [red-knot] mypy_primer: Run on `async-utils` (#17303) [red-knot] Add custom `__setattr__` support (#16748) [red-knot] Add `__init__` arguments check when doing `try_call` on a class literal (#16512) [`flake8-pie`] Avoid false positive for multiple assignment with `auto()` (`PIE796`) (#17274) [syntax-errors] Async comprehension in sync comprehension (#17177) [`airflow`] Expand module path check to individual symbols (`AIR302`) (#17278) [syntax-errors] Check annotations in annotated assignments (#17283) [syntax-errors] Extend annotation checks to `await` (#17282) [red-knot] Add support for `assert_never` (#17287) [`flake8-pytest-style`] Avoid false positive for legacy form of `pytest.raises` (`PT011`) (#17231) [red-knot] Do not show types for literal expressions on hover (#17290) [red-knot] Fix dead-code clippy warning (#17291) [red-knot] Reachability analysis (#17199) [red-knot] Don't use latency-sensitive for handlers (#17227)
* dcreager/special-class: (26 commits) lint Add TODO about property test data Better todos Narrow type(generic) better More Python-like displays for specializations Add xfail for generic method inside generic class Comment other non-specializations Explain self_instance not being specialized Generic aliases are literals in type display Better TODO fallback type [red-knot] Default `python-platform` to current platform (#17183) [red-knot] Add new 'unreachable code' test case (#17306) [red-knot] mypy_primer: Run on `async-utils` (#17303) [red-knot] Add custom `__setattr__` support (#16748) [red-knot] Add `__init__` arguments check when doing `try_call` on a class literal (#16512) [`flake8-pie`] Avoid false positive for multiple assignment with `auto()` (`PIE796`) (#17274) [syntax-errors] Async comprehension in sync comprehension (#17177) [`airflow`] Expand module path check to individual symbols (`AIR302`) (#17278) [syntax-errors] Check annotations in annotated assignments (#17283) [syntax-errors] Extend annotation checks to `await` (#17282) ...
…class literal (astral-sh#16512) ## Summary * Addresses #16511 for simple cases where only `__init__` method is bound on class or doesn't exist at all. * fixes a bug with argument counting in bound method diagnostics Caveats: * No handling of `__new__` or modified `__call__` on metaclass. * This leads to a couple of false positive errors in tests ## Test Plan - A couple new cases in mdtests - cargo nextest run -p red_knot_python_semantic --no-fail-fast --------- Co-authored-by: Carl Meyer <[email protected]> Co-authored-by: David Peter <[email protected]>
## Summary Add very early support for dataclasses. This is mostly to make sure that we do not emit false positives on dataclass construction, but it also lies some foundations for future extensions. This seems like a good initial step to merge to me, as it basically removes all false positives on dataclass constructor calls. This allows us to use the ecosystem checks for making sure we don't introduce new false positives as we continue to work on dataclasses. ## Ecosystem analysis I re-ran the mypy_primer evaluation of [the `__init__` PR](#16512) locally with our current mypy_primer version and project selection. It introduced 1597 new diagnostics. Filtering those by searching for `__init__` and rejecting those that contain `invalid-argument-type` (those could not possibly be solved by this PR) leaves 1281 diagnostics. The current version of this PR removes 1171 diagnostics, which leaves 110 unaccounted for. I extracted the lint + file path for all of these diagnostics and generated a diff (of diffs), to see which `__init__`-diagnostics remain. I looked at a subset of these: There are a lot of `SomeClass(*args)` calls where we don't understand the unpacking yet (this is not even related to `__init__`). Some others are related to `NamedTuple`, which we also don't support yet. And then there are some errors related to `@attrs.define`-decorated classes, which would probably require support for `dataclass_transform`, which I made no attempt to include in this PR. ## Test Plan New Markdown tests.
Summary
__init__
method is bound on class or doesn't exist at all.Caveats:
__new__
or modified__call__
on metaclass.Test Plan