Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[ty] Respect ParamSpec binding contexts#25993

Merged
charliermarsh merged 5 commits into
mainfrom
charlie/respect-paramspec-binding-contexts
Jun 15, 2026
Merged

[ty] Respect ParamSpec binding contexts#25993
charliermarsh merged 5 commits into
mainfrom
charlie/respect-paramspec-binding-contexts

Conversation

@charliermarsh

@charliermarsh charliermarsh commented Jun 14, 2026

Copy link
Copy Markdown
Member

Summary

find_legacy_typevars accepts an optional binding context so that it only collects variables introduced by the current definition. Prior to this change, we applied that filter to legacy TypeVars but ignored it for ParamSpec, allowing an enclosing class or function's ParamSpec to be collected and inferred again.

This applies the same binding-context check to ParamSpec while continuing to normalize P.args and P.kwargs back to P.

@astral-sh-bot astral-sh-bot Bot added the ty Multi-file analysis & type inference label Jun 14, 2026
@astral-sh-bot

astral-sh-bot Bot commented Jun 14, 2026

Copy link
Copy Markdown

Typing conformance results

The 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.

Summary

How 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 (E) are true positives when ty flags the expected location and false negatives when it does not. Optional annotations (E?) are true positives when flagged but true negatives (not false negatives) when not. Tagged annotations (E[tag]) require ty to flag exactly one of the tagged lines; tagged multi-annotations (E[tag+]) allow any number up to the tag count. Flagging unexpected locations counts as a false positive.

Metric Old New Diff Outcome
True Positives 954 954 +0
False Positives 57 57 +0
False Negatives 119 119 +0
Total Diagnostics 1059 1059 +0
Precision 94.36% 94.36% +0.00%
Recall 88.91% 88.91% +0.00%
Passing Files 93/134 93/134 +0

True positives changed (1)

1 diagnostic
Test case Diff

generics_paramspec_components.py:83

-error[invalid-argument-type] Argument to function `foo` is incorrect: Expected `int`, found `(...)`
-error[parameter-already-assigned] Multiple values provided for parameter 1 (`x`) of function `foo`
+error[invalid-argument-type] Argument to function `foo` is incorrect: Expected `int`, found `[email protected]`
+error[parameter-already-assigned] Multiple values provided for parameter 1 (`x`) of function `foo`

@astral-sh-bot

astral-sh-bot Bot commented Jun 14, 2026

Copy link
Copy Markdown

Memory usage report

Summary

Project Old New Diff Outcome
flake8 33.77MB 33.77MB -
sphinx 193.31MB 193.30MB -0.00% (3.23kB) ⬇️
trio 82.16MB 82.16MB -0.01% (7.93kB) ⬇️
prefect 522.45MB 522.42MB -0.01% (36.86kB) ⬇️

Significant changes

Click to expand detailed breakdown

sphinx

Name Old New Diff Outcome
GenericContext 149.86kB 147.44kB -1.62% (2.42kB) ⬇️
FunctionType<'db>::last_definition_signature_ 2.86kB 2.56kB -10.52% (308.00B) ⬇️
inferable_typevars_inner 38.69kB 38.42kB -0.69% (272.00B) ⬇️
FunctionType<'db>::signature_ 1.74MB 1.74MB -0.01% (240.00B) ⬇️
infer_scope_types_impl 10.53MB 10.53MB -0.00% (12.00B) ⬇️

trio

Name Old New Diff Outcome
InferableTypeVarsInner 73.84kB 71.22kB -3.54% (2.62kB) ⬇️
GenericContext 133.28kB 131.06kB -1.66% (2.22kB) ⬇️
InferableTypeVars<'db>::merge_::interned_arguments 14.27kB 13.22kB -7.39% (1.05kB) ⬇️
InferableTypeVars<'db>::merge_ 11.66kB 10.74kB -7.94% (948.00B) ⬇️
inferable_typevars_inner 29.06kB 28.66kB -1.37% (408.00B) ⬇️
infer_scope_types_impl 3.19MB 3.19MB -0.01% (228.00B) ⬇️
is_redundant_with_impl 172.71kB 172.55kB -0.09% (168.00B) ⬇️
FunctionType<'db>::signature_ 724.23kB 724.10kB -0.02% (132.00B) ⬇️
infer_definition_types 5.56MB 5.56MB -0.00% (120.00B) ⬇️
Type<'db>::apply_specialization_inner_ 508.41kB 508.34kB -0.01% (72.00B) ⬇️
infer_expression_types_impl 6.45MB 6.45MB -0.00% (12.00B) ⬇️

prefect

Name Old New Diff Outcome
GenericContext 334.12kB 323.80kB -3.09% (10.33kB) ⬇️
InferableTypeVarsInner 230.11kB 224.12kB -2.60% (5.98kB) ⬇️
member_lookup_with_policy_inner 14.14MB 14.14MB -0.01% (2.05kB) ⬇️
InferableTypeVars<'db>::merge_::interned_arguments 41.91kB 39.94kB -4.70% (1.97kB) ⬇️
infer_scope_types_impl 38.92MB 38.92MB -0.00% (1.78kB) ⬇️
InferableTypeVars<'db>::merge_ 34.60kB 32.94kB -4.80% (1.66kB) ⬇️
inferable_typevars_inner 83.11kB 81.51kB -1.92% (1.59kB) ⬇️
FunctionType<'db>::signature_ 2.85MB 2.85MB -0.05% (1.54kB) ⬇️
Type<'db>::apply_specialization_inner_::interned_arguments 2.78MB 2.78MB -0.05% (1.41kB) ⬇️
Type<'db>::apply_specialization_inner_ 2.85MB 2.85MB -0.05% (1.38kB) ⬇️
Specialization 2.49MB 2.49MB -0.05% (1.28kB) ⬇️
check_file_impl 17.83MB 17.83MB +0.01% (1.08kB) ⬇️
Type<'db>::class_member_with_policy_ 10.87MB 10.87MB -0.01% (972.00B) ⬇️
infer_definition_types 69.65MB 69.65MB -0.00% (840.00B) ⬇️
member_lookup_with_policy_inner::interned_arguments 6.67MB 6.67MB -0.01% (840.00B) ⬇️
... 29 more

@astral-sh-bot

astral-sh-bot Bot commented Jun 14, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

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[()]`

Full report with detailed diff (timing results)

@charliermarsh charliermarsh force-pushed the charlie/respect-paramspec-binding-contexts branch 2 times, most recently from ce61b56 to 7f7a4aa Compare June 14, 2026 19:40
@charliermarsh charliermarsh force-pushed the charlie/respect-paramspec-binding-contexts branch from 7f7a4aa to 00aa8be Compare June 14, 2026 19:50
@charliermarsh charliermarsh marked this pull request as ready for review June 14, 2026 20:28
@astral-sh-bot astral-sh-bot Bot requested a review from dhruvmanila June 14, 2026 20:29
class C(Generic[P]):
def method(self, *args: P.args, **kwargs: P.kwargs): ...

# revealed: ty_extensions.GenericContext[Self@method]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P is used but not rebound.

@charliermarsh

Copy link
Copy Markdown
Member Author

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)

Comment thread crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md Outdated
@charliermarsh charliermarsh force-pushed the charlie/respect-paramspec-binding-contexts branch from 00ac486 to f990b15 Compare June 14, 2026 23:09

@AlexWaygood AlexWaygood left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely

kind: &str,
name: &ast::name::Name,
range: TextRange,
is_paramspec: bool,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, &param_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,
                             &param_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,
                         );
                     }

@charliermarsh charliermarsh enabled auto-merge (squash) June 15, 2026 13:28
@charliermarsh charliermarsh merged commit cfa4d72 into main Jun 15, 2026
59 checks passed
@charliermarsh charliermarsh deleted the charlie/respect-paramspec-binding-contexts branch June 15, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants