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)
|
||||
```
|
||||
|
||||
### `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
|
||||
|
||||
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,
|
||||
TypeVar,
|
||||
ParamSpec,
|
||||
// typing_extensions.ParamSpec
|
||||
ExtensionsParamSpec, // must be distinct from typing.ParamSpec, backports new features
|
||||
ParamSpecArgs,
|
||||
ParamSpecKwargs,
|
||||
ProtocolMeta,
|
||||
|
|
@ -4239,6 +4241,7 @@ impl KnownClass {
|
|||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::TypeVarTuple
|
||||
|
|
@ -4371,6 +4374,7 @@ impl KnownClass {
|
|||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ExtensionsParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
| KnownClass::TypeVarTuple
|
||||
|
|
@ -4457,6 +4461,7 @@ impl KnownClass {
|
|||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ExtensionsParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
| KnownClass::TypeVarTuple
|
||||
|
|
@ -4543,6 +4548,7 @@ impl KnownClass {
|
|||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ExtensionsParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
| KnownClass::TypeVarTuple
|
||||
|
|
@ -4634,6 +4640,7 @@ impl KnownClass {
|
|||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::TypeVarTuple
|
||||
|
|
@ -4733,6 +4740,7 @@ impl KnownClass {
|
|||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ExtensionsParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
| KnownClass::ProtocolMeta
|
||||
|
|
@ -4806,6 +4814,7 @@ impl KnownClass {
|
|||
Self::TypeVar => "TypeVar",
|
||||
Self::ExtensionsTypeVar => "TypeVar",
|
||||
Self::ParamSpec => "ParamSpec",
|
||||
Self::ExtensionsParamSpec => "ParamSpec",
|
||||
Self::ParamSpecArgs => "ParamSpecArgs",
|
||||
Self::ParamSpecKwargs => "ParamSpecKwargs",
|
||||
Self::TypeVarTuple => "TypeVarTuple",
|
||||
|
|
@ -5139,11 +5148,18 @@ impl KnownClass {
|
|||
Self::TypeAliasType
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::TypeVarTuple
|
||||
| Self::ParamSpec
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::Deprecated
|
||||
| Self::NewType => KnownModule::TypingExtensions,
|
||||
Self::ParamSpec => {
|
||||
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||
KnownModule::Typing
|
||||
} else {
|
||||
KnownModule::TypingExtensions
|
||||
}
|
||||
}
|
||||
Self::NoDefaultType => {
|
||||
let python_version = Program::get(db).python_version(db);
|
||||
|
||||
|
|
@ -5247,6 +5263,7 @@ impl KnownClass {
|
|||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::TypeVarTuple
|
||||
|
|
@ -5337,6 +5354,7 @@ impl KnownClass {
|
|||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::TypeVarTuple
|
||||
|
|
@ -5420,7 +5438,7 @@ impl KnownClass {
|
|||
"Iterable" => &[Self::Iterable],
|
||||
"Iterator" => &[Self::Iterator],
|
||||
"Mapping" => &[Self::Mapping],
|
||||
"ParamSpec" => &[Self::ParamSpec],
|
||||
"ParamSpec" => &[Self::ParamSpec, Self::ExtensionsParamSpec],
|
||||
"ParamSpecArgs" => &[Self::ParamSpecArgs],
|
||||
"ParamSpecKwargs" => &[Self::ParamSpecKwargs],
|
||||
"TypeVarTuple" => &[Self::TypeVarTuple],
|
||||
|
|
@ -5542,6 +5560,8 @@ impl KnownClass {
|
|||
| Self::TypedDictFallback
|
||||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::NamedTupleLike
|
||||
| Self::ConstraintSet
|
||||
| Self::GenericContext
|
||||
|
|
@ -5555,7 +5575,6 @@ impl KnownClass {
|
|||
| Self::TypeAliasType
|
||||
| Self::NoDefaultType
|
||||
| Self::SupportsIndex
|
||||
| Self::ParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::TypeVarTuple
|
||||
|
|
@ -5970,6 +5989,7 @@ mod tests {
|
|||
KnownClass::Member | KnownClass::Nonmember | KnownClass::StrEnum => {
|
||||
PythonVersion::PY311
|
||||
}
|
||||
KnownClass::ParamSpec => PythonVersion::PY310,
|
||||
_ => PythonVersion::PY37,
|
||||
};
|
||||
(class, version_added)
|
||||
|
|
|
|||
|
|
@ -5033,9 +5033,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
) => {
|
||||
self.infer_legacy_typevar(target, call_expr, definition, typevar_class)
|
||||
}
|
||||
Some(KnownClass::ParamSpec) => {
|
||||
self.infer_paramspec(target, call_expr, definition)
|
||||
}
|
||||
Some(
|
||||
paramspec_class @ (KnownClass::ParamSpec
|
||||
| KnownClass::ExtensionsParamSpec),
|
||||
) => self.infer_legacy_paramspec(
|
||||
target,
|
||||
call_expr,
|
||||
definition,
|
||||
paramspec_class,
|
||||
),
|
||||
Some(KnownClass::NewType) => {
|
||||
self.infer_newtype_expression(target, call_expr, definition)
|
||||
}
|
||||
|
|
@ -5080,11 +5086,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
target_ty
|
||||
}
|
||||
|
||||
fn infer_paramspec(
|
||||
fn infer_legacy_paramspec(
|
||||
&mut self,
|
||||
target: &ast::Expr,
|
||||
call_expr: &ast::ExprCall,
|
||||
definition: Definition<'db>,
|
||||
known_class: KnownClass,
|
||||
) -> Type<'db> {
|
||||
fn error<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
|
|
@ -5101,7 +5108,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
|
||||
let db = self.db();
|
||||
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 have_features_from =
|
||||
|version: PythonVersion| assume_all_features || python_version >= version;
|
||||
|
|
@ -5594,7 +5602,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
self.infer_type_expression(&bound.value);
|
||||
}
|
||||
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);
|
||||
} else {
|
||||
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
|
||||
.context
|
||||
.report_lint(&INVALID_PARAMSPEC, call_expression)
|
||||
|
|
|
|||
Loading…
Reference in New Issue