[ty] Respect ParamSpec binding contexts#25993
Conversation
Typing conformance resultsThe percentage of diagnostics emitted that were expected errors held steady at 94.36%. The percentage of expected errors that received a diagnostic held steady at 88.91%. The number of fully passing files held steady at 93/134. SummaryHow are test cases classified?Each test case represents one expected error annotation or a group of annotations sharing a tag. Counts are per test case, not per diagnostic — multiple diagnostics on the same line count as one. Required annotations (
True positives changed (1)1 diagnostic
|
Memory usage reportSummary
Significant changesClick to expand detailed breakdownsphinx
trio
prefect
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-argument-type |
2 | 0 | 0 |
invalid-overload |
1 | 0 | 0 |
missing-argument |
1 | 0 | 0 |
no-matching-overload |
1 | 0 | 0 |
| Total | 5 | 0 | 0 |
Flaky changes detected. This PR summary excludes flaky changes; see the HTML report for details.
Raw diff:
prefect (https://github.com/PrefectHQ/prefect)
+ src/prefect/tasks.py:1222:9 error[invalid-overload] Implementation does not accept all arguments of this overload
pytest-autoprofile (https://gitlab.com/TTsangSC/pytest-autoprofile)
+ src/pytest_autoprofile/_test_utils.py:764:20 error[no-matching-overload] No overload of function `wrap_call` matches arguments
spack (https://github.com/spack/spack)
+ lib/spack/spack/vendor/jsonschema/_format.py:172:9 error[missing-argument] No argument provided for required parameter `**kwargs`
+ lib/spack/spack/vendor/jsonschema/_format.py:172:34 error[invalid-argument-type] Argument is incorrect: Expected `[email protected]`, found `Unknown | None`
+ lib/spack/spack/vendor/jsonschema/_format.py:172:72 error[invalid-argument-type] Argument is incorrect: Expected `[email protected]`, found `Unknown | tuple[()]`ce61b56 to
7f7a4aa
Compare
7f7a4aa to
00aa8be
Compare
| class C(Generic[P]): | ||
| def method(self, *args: P.args, **kwargs: P.kwargs): ... | ||
|
|
||
| # revealed: ty_extensions.GenericContext[Self@method] |
There was a problem hiding this comment.
P is used but not rebound.
|
Per Codex, the first two diagnostics are TPs, and the Spack diagnostics are FPs, but caused by a cascading failure since we now leave a method unspecialized rather than accidentally degrading to something forgiving: class FormatChecker:
def checks(self, format, raises=()):
...
cls_checks = classmethod(checks) |
00ac486 to
f990b15
Compare
| kind: &str, | ||
| name: &ast::name::Name, | ||
| range: TextRange, | ||
| is_paramspec: bool, |
There was a problem hiding this comment.
I'd be inclined to pass in a TypeVarKind rather than a bool:
- It's more strongly typed
- It's immediately clear at callsites what the passed-in argument represents
- It'll be robust for when we (soon) add support for
TypeVarTuple
Patch
diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs
index a0feda5620..9ae1efef7d 100644
--- a/crates/ty_python_semantic/src/types/diagnostic.rs
+++ b/crates/ty_python_semantic/src/types/diagnostic.rs
@@ -30,7 +30,7 @@ use crate::types::{
ProtocolInstanceType, SpecialFormType, SubclassOfInner, Type, TypeContext, TypeVarVariance,
binding_type, protocol_class::ProtocolClass,
};
-use crate::types::{KnownInstanceType, MemberLookupPolicy, TypedDictType, UnionType};
+use crate::types::{KnownInstanceType, MemberLookupPolicy, TypeVarKind, TypedDictType, UnionType};
use crate::{Db, DisplaySettings, FxIndexMap, Program, declare_lint};
use itertools::Itertools;
use ruff_db::source::source_text;
@@ -5922,17 +5922,19 @@ pub(crate) fn report_shadowed_type_variable<'db>(
kind: &str,
name: &ast::name::Name,
range: TextRange,
- is_paramspec: bool,
+ type_var_kind: TypeVarKind,
other_typevar: BoundTypeVarInstance<'db>,
) {
let db = context.db();
let Some(builder) = context.report_lint(&SHADOWED_TYPE_VARIABLE, range) else {
return;
};
- let typevar_kind = if is_paramspec {
- "ParamSpec"
- } else {
- "type variable"
+ let typevar_kind = match type_var_kind {
+ TypeVarKind::Legacy
+ | TypeVarKind::Pep695
+ | TypeVarKind::TypingSelf
+ | TypeVarKind::Pep613Alias => "type variable",
+ TypeVarKind::ParamSpec | TypeVarKind::Pep695ParamSpec => "ParamSpec",
};
let mut diagnostic = builder.into_diagnostic(format_args!(
"Generic {kind} `{name}` uses {typevar_kind} `{typevar_name}` already bound by an enclosing scope",
diff --git a/crates/ty_python_semantic/src/types/infer/builder/function.rs b/crates/ty_python_semantic/src/types/infer/builder/function.rs
index ebfed70fde..dc8e167d1a 100644
--- a/crates/ty_python_semantic/src/types/infer/builder/function.rs
+++ b/crates/ty_python_semantic/src/types/infer/builder/function.rs
@@ -3,7 +3,7 @@ use crate::{
reachability::ReachabilityConstraintsExtension,
types::{
KnownClass, KnownInstanceType, ParamSpecAttrKind, SubclassOfInner, SubclassOfType, Type,
- TypeContext, UnionType,
+ TypeContext, TypeVarKind, UnionType,
diagnostic::{
FINAL_ON_NON_METHOD, INVALID_PARAMETER_DEFAULT, INVALID_PARAMSPEC, INVALID_TYPE_FORM,
USELESS_OVERLOAD_BODY, add_type_expression_reference_link,
@@ -434,13 +434,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let param_name = type_param.name();
for enclosing in enclosing_generic_contexts(db, self.index, current_scope) {
if let Some(other_typevar) = enclosing.binds_named_typevar(db, ¶m_name.id) {
+ let kind = match type_param {
+ ast::TypeParam::TypeVar(_) => TypeVarKind::Pep695,
+ ast::TypeParam::ParamSpec(_) => TypeVarKind::Pep695ParamSpec,
+ // TODO: should be `TypeVarKind::Pep695TypeVarTuple`
+ ast::TypeParam::TypeVarTuple(_) => TypeVarKind::Pep695,
+ };
report_shadowed_type_variable(
&self.context,
¶m_name.id,
"function",
&function.name.id,
function.name.range(),
- matches!(type_param, ast::TypeParam::ParamSpec(_)),
+ kind,
other_typevar,
);
}
diff --git a/crates/ty_python_semantic/src/types/infer/builder/post_inference/static_class.rs b/crates/ty_python_semantic/src/types/infer/builder/post_inference/stat
ic_class.rs
index 8876a9d4c1..331db86836 100644
--- a/crates/ty_python_semantic/src/types/infer/builder/post_inference/static_class.rs
+++ b/crates/ty_python_semantic/src/types/infer/builder/post_inference/static_class.rs
@@ -861,7 +861,7 @@ pub(crate) fn check_static_class_definitions<'db>(
"class",
&class_node.name.id,
class.header_range(db),
- self_typevar.is_paramspec(db),
+ self_typevar.kind(db),
other_typevar,
);
}
@@ -881,7 +881,7 @@ pub(crate) fn check_static_class_definitions<'db>(
"class",
&class_node.name.id,
class.header_range(db),
- base_typevar.is_paramspec(db),
+ base_typevar.kind(db),
other_typevar,
);
}
Summary
find_legacy_typevarsaccepts an optional binding context so that it only collects variables introduced by the current definition. Prior to this change, we applied that filter to legacyTypeVars but ignored it forParamSpec, allowing an enclosing class or function'sParamSpecto be collected and inferred again.This applies the same binding-context check to
ParamSpecwhile continuing to normalizeP.argsandP.kwargsback toP.