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

[red-knot] Add support for typing.ClassVar #15550

Merged
merged 27 commits into from
Jan 18, 2025
Merged

[red-knot] Add support for typing.ClassVar #15550

merged 27 commits into from
Jan 18, 2025

Conversation

sharkdp
Copy link
Contributor

@sharkdp sharkdp commented Jan 17, 2025

Summary

Add support for typing.ClassVar, i.e. emit a diagnostic in this scenario:

from typing import ClassVar

class C:
    x: ClassVar[int] = 1

c = C()
c.x = 3  # error: "Cannot assign to pure class variable `x` from an instance of type `C`"

Test Plan

  • New tests for the typing.ClassVar qualifier
  • Fixed one TODO in attributes.md

@sharkdp sharkdp added the red-knot Multi-file analysis & type inference label Jan 17, 2025

This comment was marked as resolved.

@sharkdp sharkdp marked this pull request as ready for review January 17, 2025 14:36
Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

Looks good to me from a salsa/rust perspective.

crates/red_knot_python_semantic/src/types.rs Outdated Show resolved Hide resolved
Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

Nice!

crates/red_knot_python_semantic/src/types.rs Outdated Show resolved Hide resolved
crates/red_knot_python_semantic/src/types.rs Outdated Show resolved Hide resolved
crates/red_knot_python_semantic/src/types/infer.rs Outdated Show resolved Hide resolved
crates/red_knot_python_semantic/src/types/infer.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

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

This looks great!


## Conflicting type qualifiers

We currently ignore conflicting qualifiers and simply union them, which is more conservative than
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this should be a diagnostic? But it isn't in pyright (which has the same behavior you implemented here).

It is in mypy, but for different reasons than it would be for us, since mypy just generally prohibits re-declaration.

Not sure, don't feel clear enough about it to even suggest a TODO comment. We can always add this diagnostic if we decide we need it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The "currently" was my way to try and describe that this is probably an open design decision. It feels to me like a not very common edge case, so in spirit of "every lint rule generates maintenance work", I'll leave this as-is for now, but it should be straightforward to add a lint for this later if we want to.

@@ -246,7 +247,7 @@ pub(crate) fn binding_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> T
}

/// Infer the type of a declaration.
fn declaration_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> Type<'db> {
fn declaration_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> TypeAndQualifiers<'db> {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: is it valuable to maintain an invariant that a variable named *_ty contains a Type, and a function named *_ty returns a Type? If so, should we establish a suffix like *_tyq for a "type and qualifiers"? Or is this all too pedantic and veering unnecessarily into Hungarian notation? (I don't feel strongly.)

Copy link
Contributor Author

@sharkdp sharkdp Jan 18, 2025

Choose a reason for hiding this comment

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

I'm afraid I broke this invariant before, and never cleaned it up. For example, bindings_ty returns a Symbol. Most call sites that call functions returning Symbol/TypeAndQualifiers are only interested in the contained type, but it's probably still better to clean up those names. I opened #15569 as a follow-up task for me.

crates/red_knot_python_semantic/src/types.rs Outdated Show resolved Hide resolved
ExprContext::Store => {
let value_ty = self.infer_expression(value);

if let (ast::ExprContext::Store, Type::Instance(instance)) = (ctx, value_ty) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We're already inside a match arm for ctx being Store, why do we need to match on ctx again?

Copy link
Contributor Author

@sharkdp sharkdp Jan 18, 2025

Choose a reason for hiding this comment

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

Thanks for catching that. I previously had this code block in an entirely different place (where the match on ctx was necessary) and forgot to remove it when I moved it here.

With type narrowing, clippy could have been able to catch this 😄

@sharkdp sharkdp merged commit fb15da5 into main Jan 18, 2025
21 checks passed
@sharkdp sharkdp deleted the david/classvar branch January 18, 2025 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
red-knot Multi-file analysis & type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants