-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Diagnostic for generic classes that reference typevars in enclosing scope #20822
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
Conversation
/// list. | ||
pub(crate) fn from_base_classes( | ||
db: &'db dyn Db, | ||
definition: impl FnOnce() -> Definition<'db>, |
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.
See the comment below; this is passed in as a callback so that we don't have to query the class's definition unless it actually references legacy typevars in its base class list. Passing the definition in directly was causing some of the salsa query dependency tracking tests to fail.
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.
That's probably worth an inline comment as it's non-obvious for future readers.
However, I do think what you did here is cheating the test rather than fixing the underlying issue. The problem is that calling definition
adds a dependency on the ast node. The way you cheated the test is by early-returning in the case we're testing, a class with no type vars! But the same problem still exists for classes with type variables.
The more proper fix here is probably to make whatever calls from_base_classes
to be behind a salsa query (at least, in positions where we can reach this call across file boundaries).
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.
The more proper fix here is probably to make whatever calls
from_base_classes
to be behind a salsa query (at least, in positions where we can reach this call across file boundaries).
That was an easier real fix than I expected! Done
Diagnostic diff on typing conformance testsChanges were detected when running ty on typing conformance tests--- old-output.txt 2025-10-13 20:39:45.649900870 +0000
+++ new-output.txt 2025-10-13 20:39:48.939910978 +0000
@@ -432,6 +432,8 @@
generics_scoping.py:15:1: error[type-assertion-failure] Argument does not have asserted type `str`
generics_scoping.py:42:1: error[type-assertion-failure] Argument does not have asserted type `str`
generics_scoping.py:43:1: error[type-assertion-failure] Argument does not have asserted type `bytes`
+generics_scoping.py:65:11: error[invalid-generic-class] Generic class `MyGeneric` must not reference type variables bound in an enclosing scope: `T` referenced in class definition here
+generics_scoping.py:75:11: error[invalid-generic-class] Generic class `Bad` must not reference type variables bound in an enclosing scope: `T` referenced in class definition here
generics_self_advanced.py:11:24: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `Self@prop1`
generics_self_advanced.py:18:1: error[type-assertion-failure] Argument does not have asserted type `ParentA`
generics_self_advanced.py:19:1: error[type-assertion-failure] Argument does not have asserted type `ChildA`
@@ -896,5 +898,5 @@
typeddicts_usage.py:28:17: error[missing-typed-dict-key] Missing required key 'name' in TypedDict `Movie` constructor
typeddicts_usage.py:28:18: error[invalid-key] Invalid key access on TypedDict `Movie`: Unknown key "title"
typeddicts_usage.py:40:24: error[invalid-type-form] The special form `typing.TypedDict` is not allowed in type expressions. Did you mean to use a concrete TypedDict or `collections.abc.Mapping[str, object]` instead?
-Found 898 diagnostics
+Found 900 diagnostics
WARN A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details. |
|
|
Lint rule | Added | Removed | Changed |
---|---|---|---|
invalid-generic-class |
4 | 0 | 0 |
no-matching-overload |
1 | 0 | 0 |
Total | 5 | 0 | 0 |
CodSpeed Performance ReportMerging #20822 will not alter performanceComparing Summary
Footnotes
|
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.
Nice!
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'm surprised by the performance regression. This doesn't seem to add that much complexity. It might be worth taking a look at what's happening.
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.
heh, this seems like a pre-existing bug in ClassLiteral::header_span()
that it doesn't cover the type parameters
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.
Yep I saw that too
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
It looks like this may have been fixed by adding the Salsa caching, though there is now a memory-usage regression being reported by mypy_primer |
That is unsurprising, since we'll have a cached result for the newly cached function for every non-generic class. (And technically for every generic class that is generic via inheritance using legacy typevars) It's not a huge regression, so I would lean towards accepting 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.
Nice!
#[salsa::tracked( | ||
cycle_fn=generic_context_cycle_recover, | ||
cycle_initial=generic_context_cycle_initial, | ||
heap_size=ruff_memory_usage::heap_size, | ||
)] |
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.
Regarding the memory regression. Would it be possible to only cache the cases where we have a type var? Or is this already what this is doing :)
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 don't think we can without walking the base class in the same way
if let Some(other_typevar) = | ||
enclosing.binds_named_typevar(self.db(), self_typevar_name) | ||
{ | ||
report_rebound_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.
Should we move the check whether the rule is enabled further up (to avoid the entire scope walking?)
It might require extracting the following into a lint_severity(name: &LintName) -> Option<Severity>
helper
ruff/crates/ty_python_semantic/src/types/context.rs
Lines 404 to 431 in 2ce3aba
// The comment below was copied from the original | |
// implementation of diagnostic reporting. The code | |
// has been refactored, but this still kind of looked | |
// relevant, so I've preserved the note. ---AG | |
// | |
// TODO: Don't emit the diagnostic if: | |
// * The enclosing node contains any syntax errors | |
// * The rule is disabled for this file. We probably want to introduce a new query that | |
// returns a rule selector for a given file that respects the package's settings, | |
// any global pragma comments in the file, and any per-file-ignores. | |
if !ctx.db.should_check_file(ctx.file) { | |
return None; | |
} | |
let lint_id = LintId::of(lint); | |
// Skip over diagnostics if the rule | |
// is disabled. | |
let (severity, source) = ctx.db.rule_selection(ctx.file).get(lint_id)?; | |
// If we're not in type checking mode, | |
// we can bail now. | |
if ctx.is_in_no_type_check() { | |
return None; | |
} | |
// If this lint is being reported as part of multi-inference of a given expression, | |
// silence it to avoid duplicated diagnostics. | |
if ctx.is_in_multi_inference() { | |
return None; | |
} |
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.
Could also experiment with reviving #18327?
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.
Done
* main: (25 commits) [ty] Diagnostic for generic classes that reference typevars in enclosing scope (#20822) Update Python compatibility from 3.13 to 3.14 in README.md (#20852) [syntax-errors]: break outside loop F701 (#20556) [ty] Treat `Callable`s as bound-method descriptors in special cases (#20802) [ty] Do not bind self to non-positional parameters (#20850) Fix syntax error false positives on parenthesized context managers (#20846) [ty] Remove 'pre-release software' warning (#20817) Render unsupported syntax errors in formatter tests (#20777) [ty] Treat functions, methods, and dynamic types as function-like `Callable`s (#20842) [ty] Move logic for `super()` inference to a new `types::bound_super` submodule (#20840) [ty] Fix false-positive diagnostics on `super()` calls (#20814) [ty] Move `class_member` to `member` module (#20837) [`ruff`] Use DiagnosticTag for more flake8 and numpy rules (#20758) [ty] Prefer declared base class attribute over inferred attribute on subclass (#20764) [ty] Log files that are slow to type check (#20836) Update cargo-bins/cargo-binstall action to v1.15.7 (#20827) Update CodSpeedHQ/action action to v4.1.1 (#20828) Update Rust crate pyproject-toml to v0.13.7 (#20835) Update Rust crate anstream to v0.6.21 (#20829) Update Rust crate libc to v0.2.177 (#20832) ...
…rable * origin/main: (26 commits) [ty] Add separate type for typevar "identity" (#20813) [ty] Diagnostic for generic classes that reference typevars in enclosing scope (#20822) Update Python compatibility from 3.13 to 3.14 in README.md (#20852) [syntax-errors]: break outside loop F701 (#20556) [ty] Treat `Callable`s as bound-method descriptors in special cases (#20802) [ty] Do not bind self to non-positional parameters (#20850) Fix syntax error false positives on parenthesized context managers (#20846) [ty] Remove 'pre-release software' warning (#20817) Render unsupported syntax errors in formatter tests (#20777) [ty] Treat functions, methods, and dynamic types as function-like `Callable`s (#20842) [ty] Move logic for `super()` inference to a new `types::bound_super` submodule (#20840) [ty] Fix false-positive diagnostics on `super()` calls (#20814) [ty] Move `class_member` to `member` module (#20837) [`ruff`] Use DiagnosticTag for more flake8 and numpy rules (#20758) [ty] Prefer declared base class attribute over inferred attribute on subclass (#20764) [ty] Log files that are slow to type check (#20836) Update cargo-bins/cargo-binstall action to v1.15.7 (#20827) Update CodSpeedHQ/action action to v4.1.1 (#20828) Update Rust crate pyproject-toml to v0.13.7 (#20835) Update Rust crate anstream to v0.6.21 (#20829) ...
Generic classes are not allowed to bind or reference a typevar from an enclosing scope:
It does not matter if the class uses PEP 695 or legacy syntax. It does not matter if the enclosing scope is a generic class or function. The generic class cannot even reference an enclosing typevar in its base class list.
This PR adds diagnostics for these cases.
In addition, the PR adds better fallback behavior for generic classes that violate this rule: any enclosing typevars are not included in the class's generic context. (That ensures that we don't inadvertently try to infer specializations for those typevars in places where we shouldn't.) The
dulwich
ecosystem project has examples of this that were causing new false positives on #20677.