Skip to content

[ty] fix query cycles in decorated function with parameter defaults#23014

Merged
carljm merged 2 commits intomainfrom
claude/minimize-handler-metrics-issue-rLJYm
Feb 2, 2026
Merged

[ty] fix query cycles in decorated function with parameter defaults#23014
carljm merged 2 commits intomainfrom
claude/minimize-handler-metrics-issue-rLJYm

Conversation

@carljm
Copy link
Contributor

@carljm carljm commented Feb 2, 2026

Fixes astral-sh/ty#2692

Summary

This PR fixes query cycles that occur when processing decorated functions with default parameter values. The issue arises because computing a function's full signature requires evaluating default parameter expressions, which can trigger deferred type inference that may cycle back to the original query.

For FunctionLiteral types, directly check decorator attributes (is_classmethod, is_staticmethod) instead of computing the full callable signature. This avoids unnecessary query cycles while still propagating the callable kind through decorators.

When computing parameter default types, use infer_deferred_types directly instead of definition_expression_type. Since defaults are always deferred (as per the function definition logic), this approach is more efficient and avoids potential cycles (because definition_expression_type re-queries infer_definition_types for the function definition

Test Plan

Added protocol subtyping test cases in protocols.md covering decorated methods with various configurations. Existing test suite passes without regressions.

We also see some significant memory-use reductions on some projects, presumably due to reduced query cycles.

…meters

When a protocol method is decorated (e.g., with `@contextmanager`) and has
default parameter values, the type checker would incorrectly report an
`invalid-argument-type` error when passing a conforming implementation.

The root cause was a query cycle during signature computation. When computing
the signature for a function with defaults, `definition_expression_type` was
called for each default, which first tries `infer_definition_types`. If we're
already in the middle of inferring that definition (e.g., during decorator
application), this creates a cycle.

The fix uses `infer_deferred_types` directly for default value types, following
the same pattern already used for parameter annotations in
`function_signature_expression_type`. Since defaults are always deferred (as
noted in `infer_function_definition`), we can skip the `infer_definition_types`
check and go directly to deferred inference, avoiding the cycle.

Additionally, when getting the `propagatable_kind` for a `FunctionLiteral`
during decorator application, we now get the kind directly from the function
without computing its full signature, which is a related optimization.

Fixes: astral-sh/ty#2692

https://claude.ai/code/session_01GZpEtiXNH5JVFUK4ntvUF7
@carljm carljm added the ty Multi-file analysis & type inference label Feb 2, 2026
@astral-sh-bot
Copy link

astral-sh-bot bot commented Feb 2, 2026

Typing conformance results

No changes detected ✅

@astral-sh-bot
Copy link

astral-sh-bot bot commented Feb 2, 2026

mypy_primer results

Changes were detected when running on open source projects
prefect (https://github.com/PrefectHQ/prefect)
- src/integrations/prefect-dbt/prefect_dbt/cloud/jobs.py:1248:22: error[unresolved-attribute] Object of type `(trigger_job_run_options: TriggerJobRunOptions | None = ...) -> DbtCloudJobRun | Coroutine[Any, Any, DbtCloudJobRun]` has no attribute `aio`
+ src/integrations/prefect-dbt/prefect_dbt/cloud/jobs.py:1248:22: error[unresolved-attribute] Object of type `(trigger_job_run_options: TriggerJobRunOptions | None = None) -> DbtCloudJobRun | Coroutine[Any, Any, DbtCloudJobRun]` has no attribute `aio`
+ src/integrations/prefect-dbt/prefect_dbt/core/settings.py:94:28: error[invalid-assignment] Object of type `dict[str, Any] | int | dict[Any, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
+ src/integrations/prefect-dbt/prefect_dbt/core/settings.py:99:28: error[invalid-assignment] Object of type `int | dict[Any, Any] | float | ... omitted 3 union elements` is not assignable to `dict[str, Any]`
+ src/prefect/cli/deploy/_core.py:86:21: error[invalid-assignment] Object of type `dict[str, Any] | int | dict[Any, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
+ src/prefect/cli/deploy/_core.py:87:21: error[invalid-assignment] Object of type `int | dict[Any, Any] | float | ... omitted 3 union elements` is not assignable to `dict[str, Any]`
+ src/prefect/deployments/steps/core.py:137:38: error[invalid-argument-type] Argument is incorrect: Argument type `dict[str, Any] | int | dict[Any, Any] | ... omitted 4 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
- src/prefect/utilities/templating.py:320:13: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `Unknown | dict[str, Any]` on object of type `dict[str, Any]`
+ src/prefect/utilities/templating.py:320:13: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `dict[str, Any] | int | Unknown | ... omitted 4 union elements` on object of type `dict[str, Any]`
- src/prefect/utilities/templating.py:323:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_block_document_references | dict[str, Any]`, found `list[Unknown | dict[str, Any]]`
+ src/prefect/utilities/templating.py:323:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_block_document_references | dict[str, Any]`, found `list[Unknown | dict[str, Any] | int | ... omitted 4 union elements]`
- src/prefect/utilities/templating.py:437:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `dict[object, Unknown]`
+ src/prefect/utilities/templating.py:437:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `dict[object, Unknown | int | float | ... omitted 4 union elements]`
- src/prefect/utilities/templating.py:442:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `list[Unknown]`
+ src/prefect/utilities/templating.py:442:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `list[Unknown | int | float | ... omitted 4 union elements]`
- src/prefect/workers/base.py:232:13: error[invalid-argument-type] Argument is incorrect: Argument type `str | dict[str, Any]` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
+ src/prefect/workers/base.py:232:13: error[invalid-argument-type] Argument is incorrect: Argument type `dict[str, Any] | int | str | ... omitted 3 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
+ src/prefect/workers/base.py:234:22: error[invalid-argument-type] Argument expression after ** must be a mapping type: Found `int | Unknown | float | ... omitted 4 union elements`
- Found 5365 diagnostics
+ Found 5371 diagnostics

pandas-stubs (https://github.com/pandas-dev/pandas-stubs)
- tests/frame/test_groupby.py:229:15: error[type-assertion-failure] Type `Series[Any]` does not match asserted type `Series[str | bytes | int | ... omitted 12 union elements]`
- tests/frame/test_groupby.py:625:15: error[type-assertion-failure] Type `Series[Any]` does not match asserted type `Series[str | bytes | int | ... omitted 12 union elements]`
- Found 4415 diagnostics
+ Found 4413 diagnostics

Memory usage changes were detected when running on open source projects
trio (https://github.com/python-trio/trio)
-     struct fields = ~12MB
+     struct fields = ~11MB
-     memo metadata = ~33MB
+     memo metadata = ~31MB

prefect (https://github.com/PrefectHQ/prefect)
-     struct fields = ~54MB
+     struct fields = ~52MB
-     memo metadata = ~194MB
+     memo metadata = ~185MB

@carljm carljm merged commit 879ea02 into main Feb 2, 2026
49 checks passed
@carljm carljm deleted the claude/minimize-handler-metrics-issue-rLJYm branch February 2, 2026 01:49
carljm added a commit that referenced this pull request Feb 2, 2026
* main: (48 commits)
  add info for non_octal permissions (#22972)
  Fix empty body rule rendering (#23039)
  [ty] Infer `ParamSpec` from class constructors for callable protocols (#22853)
  Update NPM Development dependencies (#23030)
  Update CodSpeedHQ/action action to v4.8.2 (#23029)
  [ty] remove special handling for `Any()` in match class patterns (#23011)
  Update Rust crate get-size2 to v0.7.4 (#23022)
  Update Rust crate insta to v1.46.1 (#23023)
  Update taiki-e/install-action action to v2.67.11 (#23033)
  Update Rust crate colored to v3.1.1 (#23031)
  Update cargo-bins/cargo-binstall action to v1.17.3 (#23028)
  Update Rust crate uuid to v1.20.0 (#23032)
  [ty] Avoid using `.node()` for detecting `Self` (#23000)
  Update Rust crate proc-macro2 to v1.0.106 (#23024)
  Update actions/setup-python action to v6.2.0 (#23027)
  [ty] fix query cycles in decorated function with parameter defaults (#23014)
  Update Rust crate quote to v1.0.44 (#23025)
  Update Rust crate thiserror to v2.0.18 (#23026)
  Update Rust crate filetime to v0.2.27 (#23021)
  Update Rust crate clearscreen to v4.0.3 (#23020)
  ...
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.

Invalid-assignment when using custom setter with a protocol passing an union type

3 participants