mirror of https://github.com/astral-sh/ruff
[ty] Avoid diagnostic when `typing_extensions.ParamSpec` uses `default` parameter (#21839)
## Summary fixes: https://github.com/astral-sh/ty/issues/1798 ## Test Plan Add mdtest.
This commit is contained in:
parent
dfd6ed0524
commit
a364195335
|
|
@ -102,6 +102,38 @@ Other values are invalid.
|
||||||
P4 = ParamSpec("P4", default=int)
|
P4 = ParamSpec("P4", default=int)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `default` parameter in `typing_extensions.ParamSpec`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `default` parameter to `ParamSpec` is available from `typing_extensions` in Python 3.12 and
|
||||||
|
earlier.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import ParamSpec
|
||||||
|
from typing_extensions import ParamSpec as ExtParamSpec
|
||||||
|
|
||||||
|
# This shouldn't emit a diagnostic
|
||||||
|
P1 = ExtParamSpec("P1", default=[int, str])
|
||||||
|
|
||||||
|
# But, this should
|
||||||
|
# error: [invalid-paramspec] "The `default` parameter of `typing.ParamSpec` was added in Python 3.13"
|
||||||
|
P2 = ParamSpec("P2", default=[int, str])
|
||||||
|
```
|
||||||
|
|
||||||
|
And, it allows the same set of values as `typing.ParamSpec`.
|
||||||
|
|
||||||
|
```py
|
||||||
|
P3 = ExtParamSpec("P3", default=...)
|
||||||
|
P4 = ExtParamSpec("P4", default=P3)
|
||||||
|
|
||||||
|
# error: [invalid-paramspec]
|
||||||
|
P5 = ExtParamSpec("P5", default=int)
|
||||||
|
```
|
||||||
|
|
||||||
### Forward references in stub files
|
### Forward references in stub files
|
||||||
|
|
||||||
Stubs natively support forward references, so patterns that would raise `NameError` at runtime are
|
Stubs natively support forward references, so patterns that would raise `NameError` at runtime are
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# `ParamSpec` regression on 3.9
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.9"
|
||||||
|
```
|
||||||
|
|
||||||
|
This used to panic when run on Python 3.9 because `ParamSpec` was introduced in Python 3.10 and the
|
||||||
|
diagnostic message for `invalid-exception-caught` expects to construct `typing.ParamSpec`.
|
||||||
|
|
||||||
|
```py
|
||||||
|
# error: [invalid-syntax]
|
||||||
|
def foo[**P]() -> None:
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
# error: [invalid-exception-caught] "Invalid object caught in an exception handler: Object has type `typing.ParamSpec`"
|
||||||
|
except P:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
@ -4168,6 +4168,8 @@ pub enum KnownClass {
|
||||||
SpecialForm,
|
SpecialForm,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
ParamSpec,
|
ParamSpec,
|
||||||
|
// typing_extensions.ParamSpec
|
||||||
|
ExtensionsParamSpec, // must be distinct from typing.ParamSpec, backports new features
|
||||||
ParamSpecArgs,
|
ParamSpecArgs,
|
||||||
ParamSpecKwargs,
|
ParamSpecKwargs,
|
||||||
ProtocolMeta,
|
ProtocolMeta,
|
||||||
|
|
@ -4239,6 +4241,7 @@ impl KnownClass {
|
||||||
| Self::TypeVar
|
| Self::TypeVar
|
||||||
| Self::ExtensionsTypeVar
|
| Self::ExtensionsTypeVar
|
||||||
| Self::ParamSpec
|
| Self::ParamSpec
|
||||||
|
| Self::ExtensionsParamSpec
|
||||||
| Self::ParamSpecArgs
|
| Self::ParamSpecArgs
|
||||||
| Self::ParamSpecKwargs
|
| Self::ParamSpecKwargs
|
||||||
| Self::TypeVarTuple
|
| Self::TypeVarTuple
|
||||||
|
|
@ -4371,6 +4374,7 @@ impl KnownClass {
|
||||||
| KnownClass::TypeVar
|
| KnownClass::TypeVar
|
||||||
| KnownClass::ExtensionsTypeVar
|
| KnownClass::ExtensionsTypeVar
|
||||||
| KnownClass::ParamSpec
|
| KnownClass::ParamSpec
|
||||||
|
| KnownClass::ExtensionsParamSpec
|
||||||
| KnownClass::ParamSpecArgs
|
| KnownClass::ParamSpecArgs
|
||||||
| KnownClass::ParamSpecKwargs
|
| KnownClass::ParamSpecKwargs
|
||||||
| KnownClass::TypeVarTuple
|
| KnownClass::TypeVarTuple
|
||||||
|
|
@ -4457,6 +4461,7 @@ impl KnownClass {
|
||||||
| KnownClass::TypeVar
|
| KnownClass::TypeVar
|
||||||
| KnownClass::ExtensionsTypeVar
|
| KnownClass::ExtensionsTypeVar
|
||||||
| KnownClass::ParamSpec
|
| KnownClass::ParamSpec
|
||||||
|
| KnownClass::ExtensionsParamSpec
|
||||||
| KnownClass::ParamSpecArgs
|
| KnownClass::ParamSpecArgs
|
||||||
| KnownClass::ParamSpecKwargs
|
| KnownClass::ParamSpecKwargs
|
||||||
| KnownClass::TypeVarTuple
|
| KnownClass::TypeVarTuple
|
||||||
|
|
@ -4543,6 +4548,7 @@ impl KnownClass {
|
||||||
| KnownClass::TypeVar
|
| KnownClass::TypeVar
|
||||||
| KnownClass::ExtensionsTypeVar
|
| KnownClass::ExtensionsTypeVar
|
||||||
| KnownClass::ParamSpec
|
| KnownClass::ParamSpec
|
||||||
|
| KnownClass::ExtensionsParamSpec
|
||||||
| KnownClass::ParamSpecArgs
|
| KnownClass::ParamSpecArgs
|
||||||
| KnownClass::ParamSpecKwargs
|
| KnownClass::ParamSpecKwargs
|
||||||
| KnownClass::TypeVarTuple
|
| KnownClass::TypeVarTuple
|
||||||
|
|
@ -4634,6 +4640,7 @@ impl KnownClass {
|
||||||
| Self::TypeVar
|
| Self::TypeVar
|
||||||
| Self::ExtensionsTypeVar
|
| Self::ExtensionsTypeVar
|
||||||
| Self::ParamSpec
|
| Self::ParamSpec
|
||||||
|
| Self::ExtensionsParamSpec
|
||||||
| Self::ParamSpecArgs
|
| Self::ParamSpecArgs
|
||||||
| Self::ParamSpecKwargs
|
| Self::ParamSpecKwargs
|
||||||
| Self::TypeVarTuple
|
| Self::TypeVarTuple
|
||||||
|
|
@ -4733,6 +4740,7 @@ impl KnownClass {
|
||||||
| KnownClass::TypeVar
|
| KnownClass::TypeVar
|
||||||
| KnownClass::ExtensionsTypeVar
|
| KnownClass::ExtensionsTypeVar
|
||||||
| KnownClass::ParamSpec
|
| KnownClass::ParamSpec
|
||||||
|
| KnownClass::ExtensionsParamSpec
|
||||||
| KnownClass::ParamSpecArgs
|
| KnownClass::ParamSpecArgs
|
||||||
| KnownClass::ParamSpecKwargs
|
| KnownClass::ParamSpecKwargs
|
||||||
| KnownClass::ProtocolMeta
|
| KnownClass::ProtocolMeta
|
||||||
|
|
@ -4806,6 +4814,7 @@ impl KnownClass {
|
||||||
Self::TypeVar => "TypeVar",
|
Self::TypeVar => "TypeVar",
|
||||||
Self::ExtensionsTypeVar => "TypeVar",
|
Self::ExtensionsTypeVar => "TypeVar",
|
||||||
Self::ParamSpec => "ParamSpec",
|
Self::ParamSpec => "ParamSpec",
|
||||||
|
Self::ExtensionsParamSpec => "ParamSpec",
|
||||||
Self::ParamSpecArgs => "ParamSpecArgs",
|
Self::ParamSpecArgs => "ParamSpecArgs",
|
||||||
Self::ParamSpecKwargs => "ParamSpecKwargs",
|
Self::ParamSpecKwargs => "ParamSpecKwargs",
|
||||||
Self::TypeVarTuple => "TypeVarTuple",
|
Self::TypeVarTuple => "TypeVarTuple",
|
||||||
|
|
@ -5139,11 +5148,18 @@ impl KnownClass {
|
||||||
Self::TypeAliasType
|
Self::TypeAliasType
|
||||||
| Self::ExtensionsTypeVar
|
| Self::ExtensionsTypeVar
|
||||||
| Self::TypeVarTuple
|
| Self::TypeVarTuple
|
||||||
| Self::ParamSpec
|
| Self::ExtensionsParamSpec
|
||||||
| Self::ParamSpecArgs
|
| Self::ParamSpecArgs
|
||||||
| Self::ParamSpecKwargs
|
| Self::ParamSpecKwargs
|
||||||
| Self::Deprecated
|
| Self::Deprecated
|
||||||
| Self::NewType => KnownModule::TypingExtensions,
|
| Self::NewType => KnownModule::TypingExtensions,
|
||||||
|
Self::ParamSpec => {
|
||||||
|
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||||
|
KnownModule::Typing
|
||||||
|
} else {
|
||||||
|
KnownModule::TypingExtensions
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::NoDefaultType => {
|
Self::NoDefaultType => {
|
||||||
let python_version = Program::get(db).python_version(db);
|
let python_version = Program::get(db).python_version(db);
|
||||||
|
|
||||||
|
|
@ -5247,6 +5263,7 @@ impl KnownClass {
|
||||||
| Self::TypeVar
|
| Self::TypeVar
|
||||||
| Self::ExtensionsTypeVar
|
| Self::ExtensionsTypeVar
|
||||||
| Self::ParamSpec
|
| Self::ParamSpec
|
||||||
|
| Self::ExtensionsParamSpec
|
||||||
| Self::ParamSpecArgs
|
| Self::ParamSpecArgs
|
||||||
| Self::ParamSpecKwargs
|
| Self::ParamSpecKwargs
|
||||||
| Self::TypeVarTuple
|
| Self::TypeVarTuple
|
||||||
|
|
@ -5337,6 +5354,7 @@ impl KnownClass {
|
||||||
| Self::TypeVar
|
| Self::TypeVar
|
||||||
| Self::ExtensionsTypeVar
|
| Self::ExtensionsTypeVar
|
||||||
| Self::ParamSpec
|
| Self::ParamSpec
|
||||||
|
| Self::ExtensionsParamSpec
|
||||||
| Self::ParamSpecArgs
|
| Self::ParamSpecArgs
|
||||||
| Self::ParamSpecKwargs
|
| Self::ParamSpecKwargs
|
||||||
| Self::TypeVarTuple
|
| Self::TypeVarTuple
|
||||||
|
|
@ -5420,7 +5438,7 @@ impl KnownClass {
|
||||||
"Iterable" => &[Self::Iterable],
|
"Iterable" => &[Self::Iterable],
|
||||||
"Iterator" => &[Self::Iterator],
|
"Iterator" => &[Self::Iterator],
|
||||||
"Mapping" => &[Self::Mapping],
|
"Mapping" => &[Self::Mapping],
|
||||||
"ParamSpec" => &[Self::ParamSpec],
|
"ParamSpec" => &[Self::ParamSpec, Self::ExtensionsParamSpec],
|
||||||
"ParamSpecArgs" => &[Self::ParamSpecArgs],
|
"ParamSpecArgs" => &[Self::ParamSpecArgs],
|
||||||
"ParamSpecKwargs" => &[Self::ParamSpecKwargs],
|
"ParamSpecKwargs" => &[Self::ParamSpecKwargs],
|
||||||
"TypeVarTuple" => &[Self::TypeVarTuple],
|
"TypeVarTuple" => &[Self::TypeVarTuple],
|
||||||
|
|
@ -5542,6 +5560,8 @@ impl KnownClass {
|
||||||
| Self::TypedDictFallback
|
| Self::TypedDictFallback
|
||||||
| Self::TypeVar
|
| Self::TypeVar
|
||||||
| Self::ExtensionsTypeVar
|
| Self::ExtensionsTypeVar
|
||||||
|
| Self::ParamSpec
|
||||||
|
| Self::ExtensionsParamSpec
|
||||||
| Self::NamedTupleLike
|
| Self::NamedTupleLike
|
||||||
| Self::ConstraintSet
|
| Self::ConstraintSet
|
||||||
| Self::GenericContext
|
| Self::GenericContext
|
||||||
|
|
@ -5555,7 +5575,6 @@ impl KnownClass {
|
||||||
| Self::TypeAliasType
|
| Self::TypeAliasType
|
||||||
| Self::NoDefaultType
|
| Self::NoDefaultType
|
||||||
| Self::SupportsIndex
|
| Self::SupportsIndex
|
||||||
| Self::ParamSpec
|
|
||||||
| Self::ParamSpecArgs
|
| Self::ParamSpecArgs
|
||||||
| Self::ParamSpecKwargs
|
| Self::ParamSpecKwargs
|
||||||
| Self::TypeVarTuple
|
| Self::TypeVarTuple
|
||||||
|
|
@ -5970,6 +5989,7 @@ mod tests {
|
||||||
KnownClass::Member | KnownClass::Nonmember | KnownClass::StrEnum => {
|
KnownClass::Member | KnownClass::Nonmember | KnownClass::StrEnum => {
|
||||||
PythonVersion::PY311
|
PythonVersion::PY311
|
||||||
}
|
}
|
||||||
|
KnownClass::ParamSpec => PythonVersion::PY310,
|
||||||
_ => PythonVersion::PY37,
|
_ => PythonVersion::PY37,
|
||||||
};
|
};
|
||||||
(class, version_added)
|
(class, version_added)
|
||||||
|
|
|
||||||
|
|
@ -5033,9 +5033,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
) => {
|
) => {
|
||||||
self.infer_legacy_typevar(target, call_expr, definition, typevar_class)
|
self.infer_legacy_typevar(target, call_expr, definition, typevar_class)
|
||||||
}
|
}
|
||||||
Some(KnownClass::ParamSpec) => {
|
Some(
|
||||||
self.infer_paramspec(target, call_expr, definition)
|
paramspec_class @ (KnownClass::ParamSpec
|
||||||
}
|
| KnownClass::ExtensionsParamSpec),
|
||||||
|
) => self.infer_legacy_paramspec(
|
||||||
|
target,
|
||||||
|
call_expr,
|
||||||
|
definition,
|
||||||
|
paramspec_class,
|
||||||
|
),
|
||||||
Some(KnownClass::NewType) => {
|
Some(KnownClass::NewType) => {
|
||||||
self.infer_newtype_expression(target, call_expr, definition)
|
self.infer_newtype_expression(target, call_expr, definition)
|
||||||
}
|
}
|
||||||
|
|
@ -5080,11 +5086,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
target_ty
|
target_ty
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_paramspec(
|
fn infer_legacy_paramspec(
|
||||||
&mut self,
|
&mut self,
|
||||||
target: &ast::Expr,
|
target: &ast::Expr,
|
||||||
call_expr: &ast::ExprCall,
|
call_expr: &ast::ExprCall,
|
||||||
definition: Definition<'db>,
|
definition: Definition<'db>,
|
||||||
|
known_class: KnownClass,
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
fn error<'db>(
|
fn error<'db>(
|
||||||
context: &InferContext<'db, '_>,
|
context: &InferContext<'db, '_>,
|
||||||
|
|
@ -5101,7 +5108,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
let db = self.db();
|
let db = self.db();
|
||||||
let arguments = &call_expr.arguments;
|
let arguments = &call_expr.arguments;
|
||||||
let assume_all_features = self.in_stub();
|
let is_typing_extensions = known_class == KnownClass::ExtensionsParamSpec;
|
||||||
|
let assume_all_features = self.in_stub() || is_typing_extensions;
|
||||||
let python_version = Program::get(db).python_version(db);
|
let python_version = Program::get(db).python_version(db);
|
||||||
let have_features_from =
|
let have_features_from =
|
||||||
|version: PythonVersion| assume_all_features || python_version >= version;
|
|version: PythonVersion| assume_all_features || python_version >= version;
|
||||||
|
|
@ -5594,7 +5602,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
self.infer_type_expression(&bound.value);
|
self.infer_type_expression(&bound.value);
|
||||||
}
|
}
|
||||||
if let Some(default) = arguments.find_keyword("default") {
|
if let Some(default) = arguments.find_keyword("default") {
|
||||||
if let Some(KnownClass::ParamSpec) = known_class {
|
if matches!(
|
||||||
|
known_class,
|
||||||
|
Some(KnownClass::ParamSpec | KnownClass::ExtensionsParamSpec)
|
||||||
|
) {
|
||||||
self.infer_paramspec_default(&default.value);
|
self.infer_paramspec_default(&default.value);
|
||||||
} else {
|
} else {
|
||||||
self.infer_type_expression(&default.value);
|
self.infer_type_expression(&default.value);
|
||||||
|
|
@ -8440,7 +8451,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(KnownClass::ParamSpec) => {
|
Some(KnownClass::ParamSpec | KnownClass::ExtensionsParamSpec) => {
|
||||||
if let Some(builder) = self
|
if let Some(builder) = self
|
||||||
.context
|
.context
|
||||||
.report_lint(&INVALID_PARAMSPEC, call_expression)
|
.report_lint(&INVALID_PARAMSPEC, call_expression)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue