mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
[ty] Silence false positives for PEP-695 ParamSpec annotations (#18001)
## Summary Suppress false positives for uses of PEP-695 `ParamSpec` in `Callable` annotations: ```py from typing_extensions import Callable def f[**P](c: Callable[P, int]): pass ``` addresses a comment here: https://github.com/astral-sh/ty/issues/157#issuecomment-2859284721 ## Test Plan Adapted Markdown tests
This commit is contained in:
parent
235b74a310
commit
cd1d906ffa
5 changed files with 40 additions and 10 deletions
|
@ -249,10 +249,12 @@ Using a `ParamSpec` in a `Callable` annotation:
|
|||
```py
|
||||
from typing_extensions import Callable
|
||||
|
||||
# TODO: Not an error; remove once `ParamSpec` is supported
|
||||
# error: [invalid-type-form]
|
||||
def _[**P1](c: Callable[P1, int]):
|
||||
reveal_type(c) # revealed: (...) -> Unknown
|
||||
reveal_type(P1.args) # revealed: @Todo(ParamSpec)
|
||||
reveal_type(P1.kwargs) # revealed: @Todo(ParamSpec)
|
||||
|
||||
# TODO: Signature should be (**P1) -> int
|
||||
reveal_type(c) # revealed: (...) -> int
|
||||
```
|
||||
|
||||
And, using the legacy syntax:
|
||||
|
|
|
@ -662,7 +662,7 @@ impl<'db> Type<'db> {
|
|||
|
||||
pub fn contains_todo(&self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
Self::Dynamic(DynamicType::Todo(_)) => true,
|
||||
Self::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec) => true,
|
||||
|
||||
Self::AlwaysFalsy
|
||||
| Self::AlwaysTruthy
|
||||
|
@ -703,7 +703,9 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
Self::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
|
||||
SubclassOfInner::Dynamic(DynamicType::Todo(_)) => true,
|
||||
SubclassOfInner::Dynamic(
|
||||
DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec,
|
||||
) => true,
|
||||
SubclassOfInner::Dynamic(DynamicType::Unknown | DynamicType::Any) => false,
|
||||
SubclassOfInner::Class(_) => false,
|
||||
},
|
||||
|
@ -5502,6 +5504,9 @@ pub enum DynamicType {
|
|||
///
|
||||
/// This variant should be created with the `todo_type!` macro.
|
||||
Todo(TodoType),
|
||||
/// A special Todo-variant for PEP-695 `ParamSpec` types. A temporary variant to detect and special-
|
||||
/// case the handling of these types in `Callable` annotations.
|
||||
TodoPEP695ParamSpec,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DynamicType {
|
||||
|
@ -5512,6 +5517,13 @@ impl std::fmt::Display for DynamicType {
|
|||
// `DynamicType::Todo`'s display should be explicit that is not a valid display of
|
||||
// any other type
|
||||
DynamicType::Todo(todo) => write!(f, "@Todo{todo}"),
|
||||
DynamicType::TodoPEP695ParamSpec => {
|
||||
if cfg!(debug_assertions) {
|
||||
f.write_str("@Todo(ParamSpec)")
|
||||
} else {
|
||||
f.write_str("@Todo")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ impl<'db> ClassBase<'db> {
|
|||
ClassBase::Class(class) => class.name(db),
|
||||
ClassBase::Dynamic(DynamicType::Any) => "Any",
|
||||
ClassBase::Dynamic(DynamicType::Unknown) => "Unknown",
|
||||
ClassBase::Dynamic(DynamicType::Todo(_)) => "@Todo",
|
||||
ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec) => "@Todo",
|
||||
ClassBase::Protocol(_) => "Protocol",
|
||||
ClassBase::Generic(_) => "Generic",
|
||||
}
|
||||
|
|
|
@ -2612,7 +2612,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
default,
|
||||
} = node;
|
||||
self.infer_optional_expression(default.as_deref());
|
||||
let pep_695_todo = todo_type!("PEP-695 ParamSpec definition types");
|
||||
let pep_695_todo = Type::Dynamic(DynamicType::TodoPEP695ParamSpec);
|
||||
self.add_declaration_with_binding(
|
||||
node.into(),
|
||||
definition,
|
||||
|
@ -5797,8 +5797,16 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
| (_, any @ Type::Dynamic(DynamicType::Any), _) => Some(any),
|
||||
(unknown @ Type::Dynamic(DynamicType::Unknown), _, _)
|
||||
| (_, unknown @ Type::Dynamic(DynamicType::Unknown), _) => Some(unknown),
|
||||
(todo @ Type::Dynamic(DynamicType::Todo(_)), _, _)
|
||||
| (_, todo @ Type::Dynamic(DynamicType::Todo(_)), _) => Some(todo),
|
||||
(
|
||||
todo @ Type::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec),
|
||||
_,
|
||||
_,
|
||||
)
|
||||
| (
|
||||
_,
|
||||
todo @ Type::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec),
|
||||
_,
|
||||
) => Some(todo),
|
||||
(Type::Never, _, _) | (_, Type::Never, _) => Some(Type::Never),
|
||||
|
||||
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Add) => Some(
|
||||
|
@ -8651,9 +8659,14 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
// `Callable[]`.
|
||||
return None;
|
||||
}
|
||||
ast::Expr::Name(name)
|
||||
if self.infer_name_load(name)
|
||||
== Type::Dynamic(DynamicType::TodoPEP695ParamSpec) =>
|
||||
{
|
||||
return Some(Parameters::todo());
|
||||
}
|
||||
_ => {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, parameters) {
|
||||
// TODO: Check whether `Expr::Name` is a ParamSpec
|
||||
builder.into_diagnostic(format_args!(
|
||||
"The first argument to `Callable` \
|
||||
must be either a list of types, \
|
||||
|
|
|
@ -386,5 +386,8 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering
|
|||
|
||||
#[cfg(not(debug_assertions))]
|
||||
(DynamicType::Todo(TodoType), DynamicType::Todo(TodoType)) => Ordering::Equal,
|
||||
|
||||
(DynamicType::TodoPEP695ParamSpec, _) => Ordering::Less,
|
||||
(_, DynamicType::TodoPEP695ParamSpec) => Ordering::Greater,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue