mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-09 21:28:21 +00:00
Add semantic analysis of type aliases and parameters (#6109)
Requires https://github.com/astral-sh/RustPython-Parser/pull/42 Related https://github.com/PyCQA/pyflakes/pull/778 [PEP-695](https://peps.python.org/pep-0695) Part of #5062 <!-- Thank you for contributing to Ruff! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> ## Summary <!-- What's the purpose of the change? What does it do, and why? --> Adds a scope for type parameters, a type parameter binding kind, and checker visitation of type parameters in type alias statements, function definitions, and class definitions. A few changes were necessary to ensure correctness following the insertion of a new scope between function and class scopes and their parent. ## Test Plan <!-- How was it tested? --> Undefined name snapshots. Unused type parameter rule will be added as follow-up.
This commit is contained in:
parent
134d447d4c
commit
047c211837
11 changed files with 405 additions and 18 deletions
|
@ -308,6 +308,7 @@ impl<'a> SemanticModel<'a> {
|
|||
|
||||
let mut seen_function = false;
|
||||
let mut import_starred = false;
|
||||
let mut class_variables_visible = true;
|
||||
for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() {
|
||||
let scope = &self.scopes[scope_id];
|
||||
if scope.kind.is_class() {
|
||||
|
@ -321,7 +322,8 @@ impl<'a> SemanticModel<'a> {
|
|||
if seen_function && matches!(name.id.as_str(), "__class__") {
|
||||
return ReadResult::ImplicitGlobal;
|
||||
}
|
||||
// Do not allow usages of class symbols unless it is the immediate parent, e.g.:
|
||||
// Do not allow usages of class symbols unless it is the immediate parent
|
||||
// (excluding type scopes), e.g.:
|
||||
//
|
||||
// ```python
|
||||
// class Foo:
|
||||
|
@ -334,12 +336,16 @@ impl<'a> SemanticModel<'a> {
|
|||
// def d(self):
|
||||
// print(a) # not allowed
|
||||
// ```
|
||||
//
|
||||
if index > 0 {
|
||||
if !class_variables_visible {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow class variables to be visible for an additional scope level
|
||||
// when a type scope is seen — this covers the type scope present between
|
||||
// function and class definitions and their parent class scope.
|
||||
class_variables_visible = scope.kind.is_type() && index == 0;
|
||||
|
||||
if let Some(binding_id) = scope.get(name.id.as_str()) {
|
||||
// Mark the binding as used.
|
||||
let reference_id =
|
||||
|
@ -500,17 +506,20 @@ impl<'a> SemanticModel<'a> {
|
|||
}
|
||||
|
||||
let mut seen_function = false;
|
||||
let mut class_variables_visible = true;
|
||||
for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() {
|
||||
let scope = &self.scopes[scope_id];
|
||||
if scope.kind.is_class() {
|
||||
if seen_function && matches!(symbol, "__class__") {
|
||||
return None;
|
||||
}
|
||||
if index > 0 {
|
||||
if !class_variables_visible {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
class_variables_visible = scope.kind.is_type() && index == 0;
|
||||
|
||||
if let Some(binding_id) = scope.get(symbol) {
|
||||
match self.bindings[binding_id].kind {
|
||||
BindingKind::Annotation => continue,
|
||||
|
@ -856,6 +865,24 @@ impl<'a> SemanticModel<'a> {
|
|||
&self.scopes[self.scope_id]
|
||||
}
|
||||
|
||||
/// Returns the parent of the given scope, if any.
|
||||
pub fn parent_scope(&self, scope: &Scope) -> Option<&Scope<'a>> {
|
||||
scope.parent.map(|scope_id| &self.scopes[scope_id])
|
||||
}
|
||||
|
||||
/// Returns the first parent of the given scope that is not a [`ScopeKind::Type`] scope, if any.
|
||||
pub fn first_non_type_parent_scope(&self, scope: &Scope) -> Option<&Scope<'a>> {
|
||||
let mut current_scope = scope;
|
||||
while let Some(parent) = self.parent_scope(current_scope) {
|
||||
if parent.kind.is_type() {
|
||||
current_scope = parent;
|
||||
} else {
|
||||
return Some(parent);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the current top most scope.
|
||||
pub fn scope_mut(&mut self) -> &mut Scope<'a> {
|
||||
&mut self.scopes[self.scope_id]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue