[`flake8-pyi`] Implement PYI036 (#5668)

## Summary

Implements PYI036 from `flake8-pyi`. See [original
code](https://github.com/PyCQA/flake8-pyi/blob/main/pyi.py#L1585)

## Test Plan

- Updated snapshots
- Checked against manual runs of flake8

ref: #848
This commit is contained in:
Justin Prieto 2023-07-12 22:50:00 -04:00 committed by GitHub
parent 2b03bd18f4
commit 19f475ae1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 690 additions and 0 deletions

View File

@ -0,0 +1,75 @@
import builtins
import types
import typing
from collections.abc import Awaitable
from types import TracebackType
from typing import Any, Type
import _typeshed
import typing_extensions
from _typeshed import Unused
class GoodOne:
def __exit__(self, *args: object) -> None: ...
async def __aexit__(self, *args) -> str: ...
class GoodTwo:
def __exit__(self, typ: type[builtins.BaseException] | None, *args: builtins.object) -> bool | None: ...
async def __aexit__(self, /, typ: Type[BaseException] | None, *args: object, **kwargs) -> bool: ...
class GoodThree:
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: object) -> None: ...
async def __aexit__(self, typ: typing_extensions.Type[BaseException] | None, __exc: BaseException | None, *args: object) -> None: ...
class GoodFour:
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: types.TracebackType | None, *args: list[None]) -> None: ...
class GoodFive:
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: int, **kwargs: str) -> None: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> Awaitable[None]: ...
class GoodSix:
def __exit__(self, typ: object, exc: builtins.object, tb: object) -> None: ...
async def __aexit__(self, typ: object, exc: object, tb: builtins.object) -> None: ...
class GoodSeven:
def __exit__(self, *args: Unused) -> bool: ...
async def __aexit__(self, typ: Type[BaseException] | None, *args: _typeshed.Unused) -> Awaitable[None]: ...
class GoodEight:
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class GoodNine:
def __exit__(self, __typ: typing.Union[typing.Type[BaseException] , None], exc: typing.Union[BaseException , None], *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: typing.Union[typing.Type[BaseException], None], exc: typing.Union[BaseException , None], tb: typing.Union[TracebackType , None], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class GoodTen:
def __exit__(self, __typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], tb: typing.Optional[TracebackType], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class BadOne:
def __exit__(self, *args: Any) -> None: ... # PYI036: Bad star-args annotation
async def __aexit__(self) -> None: ... # PYI036: Missing args
class BadTwo:
def __exit__(self, typ, exc, tb, weird_extra_arg) -> None: ... # PYI036: Extra arg must have default
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ...# PYI036: Extra arg must have default
class BadThree:
def __exit__(self, typ: type[BaseException], exc: BaseException | None, tb: TracebackType | None) -> None: ... # PYI036: First arg has bad annotation
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException, __tb: TracebackType) -> bool | None: ... # PYI036: Second arg has bad annotation
class BadFour:
def __exit__(self, typ: typing.Optional[type[BaseException]], exc: typing.Union[BaseException, None], tb: TracebackType) -> None: ... # PYI036: Third arg has bad annotation
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException | None, __tb: typing.Union[TracebackType, None, int]) -> bool | None: ... # PYI036: Third arg has bad annotation
class BadFive:
def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # PYI036: Bad star-args annotation
async def __aexit__(self, /, typ: type[BaseException] | None, *args: Any) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
class BadSix:
def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default

View File

@ -0,0 +1,75 @@
import builtins
import types
import typing
from collections.abc import Awaitable
from types import TracebackType
from typing import Any, Type
import _typeshed
import typing_extensions
from _typeshed import Unused
class GoodOne:
def __exit__(self, *args: object) -> None: ...
async def __aexit__(self, *args) -> str: ...
class GoodTwo:
def __exit__(self, typ: type[builtins.BaseException] | None, *args: builtins.object) -> bool | None: ...
async def __aexit__(self, /, typ: Type[BaseException] | None, *args: object, **kwargs) -> bool: ...
class GoodThree:
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: object) -> None: ...
async def __aexit__(self, typ: typing_extensions.Type[BaseException] | None, __exc: BaseException | None, *args: object) -> None: ...
class GoodFour:
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: types.TracebackType | None, *args: list[None]) -> None: ...
class GoodFive:
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: int, **kwargs: str) -> None: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> Awaitable[None]: ...
class GoodSix:
def __exit__(self, typ: object, exc: builtins.object, tb: object) -> None: ...
async def __aexit__(self, typ: object, exc: object, tb: builtins.object) -> None: ...
class GoodSeven:
def __exit__(self, *args: Unused) -> bool: ...
async def __aexit__(self, typ: Type[BaseException] | None, *args: _typeshed.Unused) -> Awaitable[None]: ...
class GoodEight:
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class GoodNine:
def __exit__(self, __typ: typing.Union[typing.Type[BaseException] , None], exc: typing.Union[BaseException , None], *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: typing.Union[typing.Type[BaseException], None], exc: typing.Union[BaseException , None], tb: typing.Union[TracebackType , None], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class GoodTen:
def __exit__(self, __typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], tb: typing.Optional[TracebackType], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class BadOne:
def __exit__(self, *args: Any) -> None: ... # PYI036: Bad star-args annotation
async def __aexit__(self) -> None: ... # PYI036: Missing args
class BadTwo:
def __exit__(self, typ, exc, tb, weird_extra_arg) -> None: ... # PYI036: Extra arg must have default
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg1, weird_extra_arg2) -> None: ...# PYI036: kwargs must have default
class BadThree:
def __exit__(self, typ: type[BaseException], exc: BaseException | None, tb: TracebackType | None) -> None: ... # PYI036: First arg has bad annotation
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException, __tb: TracebackType) -> bool | None: ... # PYI036: Second arg has bad annotation
class BadFour:
def __exit__(self, typ: typing.Optional[type[BaseException]], exc: typing.Union[BaseException, None], tb: TracebackType) -> None: ... # PYI036: Third arg has bad annotation
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException | None, __tb: typing.Union[TracebackType, None, int]) -> bool | None: ... # PYI036: Third arg has bad annotation
class BadFive:
def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # PYI036: Bad star-args annotation
async def __aexit__(self, /, typ: type[BaseException] | None, *args: Any) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
class BadSix:
def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default

View File

@ -435,6 +435,14 @@ where
if self.enabled(Rule::NoReturnArgumentAnnotationInStub) {
flake8_pyi::rules::no_return_argument_annotation(self, args);
}
if self.enabled(Rule::BadExitAnnotation) {
flake8_pyi::rules::bad_exit_annotation(
self,
stmt.is_async_function_def_stmt(),
name,
args,
);
}
}
if self.enabled(Rule::DunderFunctionName) {
if let Some(diagnostic) = pep8_naming::rules::dunder_function_name(

View File

@ -638,6 +638,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Pyi, "033") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TypeCommentInStub),
(Flake8Pyi, "034") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NonSelfReturnType),
(Flake8Pyi, "035") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnassignedSpecialVariableInStub),
(Flake8Pyi, "036") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::BadExitAnnotation),
(Flake8Pyi, "042") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::SnakeCaseTypeAlias),
(Flake8Pyi, "043") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TSuffixedTypeAlias),
(Flake8Pyi, "044") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::FutureAnnotationsInStub),

View File

@ -19,6 +19,8 @@ mod tests {
#[test_case(Rule::ArgumentDefaultInStub, Path::new("PYI014.pyi"))]
#[test_case(Rule::AssignmentDefaultInStub, Path::new("PYI015.py"))]
#[test_case(Rule::AssignmentDefaultInStub, Path::new("PYI015.pyi"))]
#[test_case(Rule::BadExitAnnotation, Path::new("PYI036.py"))]
#[test_case(Rule::BadExitAnnotation, Path::new("PYI036.pyi"))]
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.py"))]
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.pyi"))]
#[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.py"))]

View File

@ -0,0 +1,351 @@
use std::fmt::{Display, Formatter};
use rustpython_parser::ast::{
ArgWithDefault, Arguments, Expr, ExprBinOp, ExprSubscript, ExprTuple, Identifier, Operator,
Ranged,
};
use smallvec::SmallVec;
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_const_none;
use ruff_python_semantic::SemanticModel;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does
/// Checks for incorrect function signatures on `__exit__` and `__aexit__`
/// methods.
///
/// ## Why is this bad?
/// Improperly-annotated `__exit__` and `__aexit__` methods can cause
/// unexpected behavior when interacting with type checkers.
///
/// ## Example
/// ```python
/// class Foo:
/// def __exit__(self, typ, exc, tb, extra_arg) -> None:
/// ...
/// ```
///
/// Use instead:
/// ```python
/// class Foo:
/// def __exit__(
/// self,
/// typ: type[BaseException] | None,
/// exc: BaseException | None,
/// tb: TracebackType | None,
/// extra_arg: int = 0,
/// ) -> None:
/// ...
/// ```
#[violation]
pub struct BadExitAnnotation {
func_kind: FuncKind,
error_kind: ErrorKind,
}
impl Violation for BadExitAnnotation {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let method_name = self.func_kind.to_string();
match self.error_kind {
ErrorKind::StarArgsNotAnnotated => format!("Star-args in `{method_name}` should be annotated with `object`"),
ErrorKind::MissingArgs => format!("If there are no star-args, `{method_name}` should have at least 3 non-keyword-only args (excluding `self`)"),
ErrorKind::ArgsAfterFirstFourMustHaveDefault => format!("All arguments after the first four in `{method_name}` must have a default value"),
ErrorKind::AllKwargsMustHaveDefault => format!("All keyword-only arguments in `{method_name}` must have a default value"),
ErrorKind::FirstArgBadAnnotation => format!("The first argument in `{method_name}` should be annotated with `object` or `type[BaseException] | None`"),
ErrorKind::SecondArgBadAnnotation => format!("The second argument in `{method_name}` should be annotated with `object` or `BaseException | None`"),
ErrorKind::ThirdArgBadAnnotation => format!("The third argument in `{method_name}` should be annotated with `object` or `types.TracebackType | None`"),
}
}
fn autofix_title(&self) -> Option<String> {
if matches!(self.error_kind, ErrorKind::StarArgsNotAnnotated) {
Some("Annotate star-args with `object`".to_string())
} else {
None
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum FuncKind {
Sync,
Async,
}
impl Display for FuncKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FuncKind::Sync => write!(f, "__exit__"),
FuncKind::Async => write!(f, "__aexit__"),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum ErrorKind {
StarArgsNotAnnotated,
MissingArgs,
FirstArgBadAnnotation,
SecondArgBadAnnotation,
ThirdArgBadAnnotation,
ArgsAfterFirstFourMustHaveDefault,
AllKwargsMustHaveDefault,
}
/// PYI036
pub(crate) fn bad_exit_annotation(
checker: &mut Checker,
is_async: bool,
name: &Identifier,
args: &Arguments,
) {
let func_kind = match name.as_str() {
"__exit__" if !is_async => FuncKind::Sync,
"__aexit__" if is_async => FuncKind::Async,
_ => return,
};
let positional_args = args
.args
.iter()
.chain(args.posonlyargs.iter())
.collect::<SmallVec<[&ArgWithDefault; 4]>>();
// If there are less than three positional arguments, at least one of them must be a star-arg,
// and it must be annotated with `object`.
if positional_args.len() < 4 {
check_short_args_list(checker, args, func_kind);
}
// Every positional argument (beyond the first four) must have a default.
for arg_with_default in positional_args
.iter()
.skip(4)
.filter(|arg_with_default| arg_with_default.default.is_none())
{
checker.diagnostics.push(Diagnostic::new(
BadExitAnnotation {
func_kind,
error_kind: ErrorKind::ArgsAfterFirstFourMustHaveDefault,
},
arg_with_default.range(),
));
}
// ...as should all keyword-only arguments.
for arg_with_default in args.kwonlyargs.iter().filter(|arg| arg.default.is_none()) {
checker.diagnostics.push(Diagnostic::new(
BadExitAnnotation {
func_kind,
error_kind: ErrorKind::AllKwargsMustHaveDefault,
},
arg_with_default.range(),
));
}
check_positional_args(checker, &positional_args, func_kind);
}
/// Determine whether a "short" argument list (i.e., an argument list with less than four elements)
/// contains a star-args argument annotated with `object`. If not, report an error.
fn check_short_args_list(checker: &mut Checker, args: &Arguments, func_kind: FuncKind) {
if let Some(varargs) = &args.vararg {
if let Some(annotation) = varargs
.annotation
.as_ref()
.filter(|ann| !is_object_or_unused(ann, checker.semantic()))
{
let mut diagnostic = Diagnostic::new(
BadExitAnnotation {
func_kind,
error_kind: ErrorKind::StarArgsNotAnnotated,
},
annotation.range(),
);
if checker.patch(diagnostic.kind.rule()) {
if checker.semantic().is_builtin("object") {
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
"object".to_string(),
annotation.range(),
)));
}
}
checker.diagnostics.push(diagnostic);
}
} else {
checker.diagnostics.push(Diagnostic::new(
BadExitAnnotation {
func_kind,
error_kind: ErrorKind::MissingArgs,
},
args.range(),
));
}
}
/// Determines whether the positional arguments of an `__exit__` or `__aexit__` method are
/// annotated correctly.
fn check_positional_args(
checker: &mut Checker,
positional_args: &[&ArgWithDefault],
kind: FuncKind,
) {
// For each argument, define the predicate against which to check the annotation.
type AnnotationValidator = fn(&Expr, &SemanticModel) -> bool;
let validations: [(ErrorKind, AnnotationValidator); 3] = [
(ErrorKind::FirstArgBadAnnotation, is_base_exception_type),
(ErrorKind::SecondArgBadAnnotation, is_base_exception),
(ErrorKind::ThirdArgBadAnnotation, is_traceback_type),
];
for (arg, (error_info, predicate)) in positional_args
.iter()
.skip(1)
.take(3)
.zip(validations.into_iter())
{
let Some(annotation) = arg.def.annotation.as_ref() else {
continue;
};
if is_object_or_unused(annotation, checker.semantic()) {
continue;
}
// If there's an annotation that's not `object` or `Unused`, check that the annotated type
// matches the predicate.
if non_none_annotation_element(annotation, checker.semantic())
.map_or(false, |elem| predicate(elem, checker.semantic()))
{
continue;
}
checker.diagnostics.push(Diagnostic::new(
BadExitAnnotation {
func_kind: kind,
error_kind: error_info,
},
annotation.range(),
));
}
}
/// Return the non-`None` annotation element of a PEP 604-style union or `Optional` annotation.
fn non_none_annotation_element<'a>(
annotation: &'a Expr,
model: &SemanticModel,
) -> Option<&'a Expr> {
// E.g., `typing.Union` or `typing.Optional`
if let Expr::Subscript(ExprSubscript { value, slice, .. }) = annotation {
if model.match_typing_expr(value, "Optional") {
return if is_const_none(slice) {
None
} else {
Some(slice)
};
}
if !model.match_typing_expr(value, "Union") {
return None;
}
let Expr::Tuple(ExprTuple { elts, .. }) = slice.as_ref() else {
return None;
};
let [left, right] = elts.as_slice() else {
return None;
};
return match (is_const_none(left), is_const_none(right)) {
(false, true) => Some(left),
(true, false) => Some(right),
(true, true) => None,
(false, false) => None,
};
}
// PEP 604-style union (e.g., `int | None`)
if let Expr::BinOp(ExprBinOp {
op: Operator::BitOr,
left,
right,
..
}) = annotation
{
if !is_const_none(left) {
return Some(left);
}
if !is_const_none(right) {
return Some(right);
}
return None;
}
None
}
/// Return `true` if the [`Expr`] is the `object` builtin or the `_typeshed.Unused` type.
fn is_object_or_unused(expr: &Expr, model: &SemanticModel) -> bool {
model
.resolve_call_path(expr)
.as_ref()
.map_or(false, |call_path| {
matches!(
call_path.as_slice(),
["" | "builtins", "object"] | ["_typeshed", "Unused"]
)
})
}
/// Return `true` if the [`Expr`] is `BaseException`.
fn is_base_exception(expr: &Expr, model: &SemanticModel) -> bool {
model
.resolve_call_path(expr)
.as_ref()
.map_or(false, |call_path| {
matches!(call_path.as_slice(), ["" | "builtins", "BaseException"])
})
}
/// Return `true` if the [`Expr`] is the `types.TracebackType` type.
fn is_traceback_type(expr: &Expr, model: &SemanticModel) -> bool {
model
.resolve_call_path(expr)
.as_ref()
.map_or(false, |call_path| {
matches!(call_path.as_slice(), ["types", "TracebackType"])
})
}
/// Return `true` if the [`Expr`] is, e.g., `Type[BaseException]`.
fn is_base_exception_type(expr: &Expr, model: &SemanticModel) -> bool {
let Expr::Subscript(ExprSubscript { value, slice, .. }) = expr else {
return false;
};
if model.match_typing_expr(value, "Type")
|| model
.resolve_call_path(value)
.as_ref()
.map_or(false, |call_path| {
matches!(call_path.as_slice(), ["" | "builtins", "type"])
})
{
is_base_exception(slice, model)
} else {
false
}
}

View File

@ -5,6 +5,7 @@ pub(crate) use complex_if_statement_in_stub::*;
pub(crate) use docstring_in_stubs::*;
pub(crate) use duplicate_union_member::*;
pub(crate) use ellipsis_in_non_empty_class_body::*;
pub(crate) use exit_annotations::*;
pub(crate) use future_annotations_in_stub::*;
pub(crate) use iter_method_return_iterable::*;
pub(crate) use no_return_argument_annotation::*;
@ -33,6 +34,7 @@ mod complex_if_statement_in_stub;
mod docstring_in_stubs;
mod duplicate_union_member;
mod ellipsis_in_non_empty_class_body;
mod exit_annotations;
mod future_annotations_in_stub;
mod iter_method_return_iterable;
mod no_return_argument_annotation;

View File

@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---

View File

@ -0,0 +1,171 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---
PYI036.pyi:54:31: PYI036 [*] Star-args in `__exit__` should be annotated with `object`
|
53 | class BadOne:
54 | def __exit__(self, *args: Any) -> None: ... # PYI036: Bad star-args annotation
| ^^^ PYI036
55 | async def __aexit__(self) -> None: ... # PYI036: Missing args
|
= help: Annotate star-args with `object`
Fix
51 51 |
52 52 |
53 53 | class BadOne:
54 |- def __exit__(self, *args: Any) -> None: ... # PYI036: Bad star-args annotation
54 |+ def __exit__(self, *args: object) -> None: ... # PYI036: Bad star-args annotation
55 55 | async def __aexit__(self) -> None: ... # PYI036: Missing args
56 56 |
57 57 | class BadTwo:
PYI036.pyi:55:24: PYI036 If there are no star-args, `__aexit__` should have at least 3 non-keyword-only args (excluding `self`)
|
53 | class BadOne:
54 | def __exit__(self, *args: Any) -> None: ... # PYI036: Bad star-args annotation
55 | async def __aexit__(self) -> None: ... # PYI036: Missing args
| ^^^^^^ PYI036
56 |
57 | class BadTwo:
|
PYI036.pyi:58:38: PYI036 All arguments after the first four in `__exit__` must have a default value
|
57 | class BadTwo:
58 | def __exit__(self, typ, exc, tb, weird_extra_arg) -> None: ... # PYI036: Extra arg must have default
| ^^^^^^^^^^^^^^^ PYI036
59 | async def __aexit__(self, typ, exc, tb, *, weird_extra_arg1, weird_extra_arg2) -> None: ...# PYI036: kwargs must have default
|
PYI036.pyi:59:48: PYI036 All keyword-only arguments in `__aexit__` must have a default value
|
57 | class BadTwo:
58 | def __exit__(self, typ, exc, tb, weird_extra_arg) -> None: ... # PYI036: Extra arg must have default
59 | async def __aexit__(self, typ, exc, tb, *, weird_extra_arg1, weird_extra_arg2) -> None: ...# PYI036: kwargs must have default
| ^^^^^^^^^^^^^^^^ PYI036
60 |
61 | class BadThree:
|
PYI036.pyi:59:66: PYI036 All keyword-only arguments in `__aexit__` must have a default value
|
57 | class BadTwo:
58 | def __exit__(self, typ, exc, tb, weird_extra_arg) -> None: ... # PYI036: Extra arg must have default
59 | async def __aexit__(self, typ, exc, tb, *, weird_extra_arg1, weird_extra_arg2) -> None: ...# PYI036: kwargs must have default
| ^^^^^^^^^^^^^^^^ PYI036
60 |
61 | class BadThree:
|
PYI036.pyi:62:29: PYI036 The first argument in `__exit__` should be annotated with `object` or `type[BaseException] | None`
|
61 | class BadThree:
62 | def __exit__(self, typ: type[BaseException], exc: BaseException | None, tb: TracebackType | None) -> None: ... # PYI036: First arg has bad annotation
| ^^^^^^^^^^^^^^^^^^^ PYI036
63 | async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException, __tb: TracebackType) -> bool | None: ... # PYI036: Second arg has bad annotation
|
PYI036.pyi:63:73: PYI036 The second argument in `__aexit__` should be annotated with `object` or `BaseException | None`
|
61 | class BadThree:
62 | def __exit__(self, typ: type[BaseException], exc: BaseException | None, tb: TracebackType | None) -> None: ... # PYI036: First arg has bad annotation
63 | async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException, __tb: TracebackType) -> bool | None: ... # PYI036: Second arg has bad annotation
| ^^^^^^^^^^^^^ PYI036
64 |
65 | class BadFour:
|
PYI036.pyi:63:94: PYI036 The third argument in `__aexit__` should be annotated with `object` or `types.TracebackType | None`
|
61 | class BadThree:
62 | def __exit__(self, typ: type[BaseException], exc: BaseException | None, tb: TracebackType | None) -> None: ... # PYI036: First arg has bad annotation
63 | async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException, __tb: TracebackType) -> bool | None: ... # PYI036: Second arg has bad annotation
| ^^^^^^^^^^^^^ PYI036
64 |
65 | class BadFour:
|
PYI036.pyi:66:111: PYI036 The third argument in `__exit__` should be annotated with `object` or `types.TracebackType | None`
|
65 | class BadFour:
66 | def __exit__(self, typ: typing.Optional[type[BaseException]], exc: typing.Union[BaseException, None], tb: TracebackType) -> None: ... # PYI036: Third arg has bad annotation
| ^^^^^^^^^^^^^ PYI036
67 | async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException | None, __tb: typing.Union[TracebackType, None, int]) -> bool | None: ... # PYI036: Third arg has bad annotation
|
PYI036.pyi:67:101: PYI036 The third argument in `__aexit__` should be annotated with `object` or `types.TracebackType | None`
|
65 | class BadFour:
66 | def __exit__(self, typ: typing.Optional[type[BaseException]], exc: typing.Union[BaseException, None], tb: TracebackType) -> None: ... # PYI036: Third arg has bad annotation
67 | async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException | None, __tb: typing.Union[TracebackType, None, int]) -> bool | None: ... # PYI036: Third arg has bad annotation
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI036
68 |
69 | class BadFive:
|
PYI036.pyi:70:29: PYI036 The first argument in `__exit__` should be annotated with `object` or `type[BaseException] | None`
|
69 | class BadFive:
70 | def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # PYI036: Bad star-args annotation
| ^^^^^^^^^^^^^^^^^^^^ PYI036
71 | async def __aexit__(self, /, typ: type[BaseException] | None, *args: Any) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
|
PYI036.pyi:70:58: PYI036 [*] Star-args in `__exit__` should be annotated with `object`
|
69 | class BadFive:
70 | def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # PYI036: Bad star-args annotation
| ^^^^^^^^^ PYI036
71 | async def __aexit__(self, /, typ: type[BaseException] | None, *args: Any) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
|
= help: Annotate star-args with `object`
Fix
67 67 | async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException | None, __tb: typing.Union[TracebackType, None, int]) -> bool | None: ... # PYI036: Third arg has bad annotation
68 68 |
69 69 | class BadFive:
70 |- def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # PYI036: Bad star-args annotation
70 |+ def __exit__(self, typ: BaseException | None, *args: object) -> bool: ... # PYI036: Bad star-args annotation
71 71 | async def __aexit__(self, /, typ: type[BaseException] | None, *args: Any) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
72 72 |
73 73 | class BadSix:
PYI036.pyi:71:74: PYI036 [*] Star-args in `__aexit__` should be annotated with `object`
|
69 | class BadFive:
70 | def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # PYI036: Bad star-args annotation
71 | async def __aexit__(self, /, typ: type[BaseException] | None, *args: Any) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
| ^^^ PYI036
72 |
73 | class BadSix:
|
= help: Annotate star-args with `object`
Fix
68 68 |
69 69 | class BadFive:
70 70 | def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # PYI036: Bad star-args annotation
71 |- async def __aexit__(self, /, typ: type[BaseException] | None, *args: Any) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
71 |+ async def __aexit__(self, /, typ: type[BaseException] | None, *args: object) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
72 72 |
73 73 | class BadSix:
74 74 | def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
PYI036.pyi:74:38: PYI036 All arguments after the first four in `__exit__` must have a default value
|
73 | class BadSix:
74 | def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
| ^^^^^^^^^^^^^^^ PYI036
75 | async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default
|
PYI036.pyi:75:48: PYI036 All keyword-only arguments in `__aexit__` must have a default value
|
73 | class BadSix:
74 | def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
75 | async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default
| ^^^^^^^^^^^^^^^ PYI036
|

1
ruff.schema.json generated
View File

@ -2355,6 +2355,7 @@
"PYI033",
"PYI034",
"PYI035",
"PYI036",
"PYI04",
"PYI042",
"PYI043",