[flake8-pyi] Also remove self and cls's annotation (PYI034) (#14801)

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
InSync 2024-12-09 21:59:12 +07:00 committed by GitHub
parent 0e9427255f
commit aa6b812a73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 785 additions and 73 deletions

View file

@ -1,10 +1,11 @@
use rustc_hash::FxHashSet;
use crate::analyze::typing;
use crate::{BindingId, SemanticModel};
use ruff_python_ast as ast;
use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::Expr;
use ruff_python_ast::{Expr, ExprName, ExprStarred, ExprSubscript, ExprTuple};
/// Return `true` if any base class matches a [`QualifiedName`] predicate.
pub fn any_qualified_base_class(
@ -129,9 +130,9 @@ pub enum IsMetaclass {
Maybe,
}
impl From<IsMetaclass> for bool {
fn from(value: IsMetaclass) -> Self {
matches!(value, IsMetaclass::Yes)
impl IsMetaclass {
pub const fn is_yes(self) -> bool {
matches!(self, IsMetaclass::Yes)
}
}
@ -170,3 +171,57 @@ pub fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) ->
(false, _) => IsMetaclass::No,
}
}
/// Returns true if a class might generic.
///
/// A class is considered generic if at least one of its direct bases
/// is subscripted with a `TypeVar`-like,
/// or if it is defined using PEP 695 syntax.
///
/// Therefore, a class *might* be generic if it uses PEP-695 syntax
/// or at least one of its direct bases is a subscript expression that
/// is subscripted with an object that *might* be a `TypeVar`-like.
pub fn might_be_generic(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
if class_def.type_params.is_some() {
return true;
}
class_def.bases().iter().any(|base| {
let Expr::Subscript(ExprSubscript { slice, .. }) = base else {
return false;
};
let Expr::Tuple(ExprTuple { elts, .. }) = slice.as_ref() else {
return expr_might_be_typevar_like(slice, semantic);
};
elts.iter()
.any(|elt| expr_might_be_typevar_like(elt, semantic))
})
}
fn expr_might_be_typevar_like(expr: &Expr, semantic: &SemanticModel) -> bool {
is_known_typevar(expr, semantic) || expr_might_be_old_style_typevar_like(expr, semantic)
}
fn is_known_typevar(expr: &Expr, semantic: &SemanticModel) -> bool {
semantic.match_typing_expr(expr, "AnyStr")
}
fn expr_might_be_old_style_typevar_like(expr: &Expr, semantic: &SemanticModel) -> bool {
match expr {
Expr::Attribute(..) => true,
Expr::Name(name) => might_be_old_style_typevar_like(name, semantic),
Expr::Starred(ExprStarred { value, .. }) => {
expr_might_be_old_style_typevar_like(value, semantic)
}
_ => false,
}
}
fn might_be_old_style_typevar_like(name: &ExprName, semantic: &SemanticModel) -> bool {
let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else {
return !semantic.has_builtin_binding(&name.id);
};
typing::is_type_var_like(binding, semantic)
}

View file

@ -778,6 +778,40 @@ impl TypeChecker for PathlibPathChecker {
}
}
pub struct TypeVarLikeChecker;
impl TypeVarLikeChecker {
/// Returns `true` if an [`Expr`] is a `TypeVar`, `TypeVarTuple`, or `ParamSpec` call.
///
/// See also [`ruff_linter::rules::flake8_pyi::rules::simple_defaults::is_type_var_like_call`].
fn is_type_var_like_call(expr: &Expr, semantic: &SemanticModel) -> bool {
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
return false;
};
let Some(qualified_name) = semantic.resolve_qualified_name(func) else {
return false;
};
matches!(
qualified_name.segments(),
[
"typing" | "typing_extensions",
"TypeVar" | "TypeVarTuple" | "ParamSpec"
]
)
}
}
impl TypeChecker for TypeVarLikeChecker {
fn match_annotation(_annotation: &Expr, _semantic: &SemanticModel) -> bool {
false
}
fn match_initializer(initializer: &Expr, semantic: &SemanticModel) -> bool {
Self::is_type_var_like_call(initializer, semantic)
}
}
/// Test whether the given binding can be considered a list.
///
/// For this, we check what value might be associated with it through it's initialization and
@ -867,6 +901,11 @@ pub fn is_pathlib_path(binding: &Binding, semantic: &SemanticModel) -> bool {
check_type::<PathlibPathChecker>(binding, semantic)
}
/// Test whether the given binding is for an old-style `TypeVar`, `TypeVarTuple` or a `ParamSpec`.
pub fn is_type_var_like(binding: &Binding, semantic: &SemanticModel) -> bool {
check_type::<TypeVarLikeChecker>(binding, semantic)
}
/// Find the [`ParameterWithDefault`] corresponding to the given [`Binding`].
#[inline]
fn find_parameter<'a>(