Skip to content

fix Odd warning on shutil.rmtree and ExitStack.callback #2105#2340

Draft
asukaminato0721 wants to merge 1 commit intofacebook:mainfrom
asukaminato0721:2105
Draft

fix Odd warning on shutil.rmtree and ExitStack.callback #2105#2340
asukaminato0721 wants to merge 1 commit intofacebook:mainfrom
asukaminato0721:2105

Conversation

@asukaminato0721
Copy link
Contributor

@asukaminato0721 asukaminato0721 commented Feb 6, 2026

Summary

Fixes #2105

Fixed ParamSpec inference for forwarded callbacks to select the overload that matches the forwarded arguments, even when the callback is a bound __call__ (protocol/class instance).

Test Plan

Added a regression test for ExitStack.callback(shutil.rmtree, ..., ignore_errors=True).

@meta-cla meta-cla bot added the cla signed label Feb 6, 2026
@github-actions

This comment has been minimized.

@asukaminato0721 asukaminato0721 marked this pull request as ready for review February 7, 2026 00:29
Copilot AI review requested due to automatic review settings February 7, 2026 00:29
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes pyrefly’s ParamSpec inference when forwarding arguments through callback-style helpers (e.g., ExitStack.callback), ensuring the selected callable overload matches the forwarded arguments (including for bound __call__ callables), addressing issue #2105.

Changes:

  • Update ParamSpec inference in call analysis to prefer the callable overload that matches forwarded *args/**kwargs.
  • Add a regression test covering ExitStack.callback(shutil.rmtree, ..., ignore_errors=True).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
pyrefly/lib/alt/callable.rs Adds logic to select an overload for a forwarded callable to constrain the ParamSpec based on forwarded arguments.
pyrefly/lib/test/paramspec.rs Adds a regression test reproducing the ExitStack.callback(shutil.rmtree, ..., ignore_errors=True) scenario.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +548 to +552
let call_errors_local = self.error_collector();
let _ = self.callable_infer(
sig.clone(),
None,
None,
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

Overload selection here uses a “first signature with zero call errors wins” approach by iterating sigs and breaking on the first match. This can diverge from the project’s overload call evaluation logic (see pyrefly/lib/alt/overload.rs, which applies additional disambiguation steps when multiple overloads match), and could pin the ParamSpec to the wrong parameter list when multiple signatures accept the forwarded args. Consider reusing the existing overload-selection machinery (or at least its disambiguation heuristic) to choose the same overload that a normal call would select, before constraining paramspec_var.

Copilot uses AI. Check for mistakes.
Comment on lines +544 to +548
if sigs.len() > 1 {
let forwarded_args = args.get(arg_index + 1..).unwrap_or(&[]);
let mut selected = None;
for sig in sigs {
let call_errors_local = self.error_collector();
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

This loop potentially calls callable_infer once per candidate signature, which re-infers forwarded_args/keywords each time. In hot paths with large overload sets this can become noticeably expensive. If possible, precompute argument types once (similar to CallWithTypes in call_overloads) and/or delegate to call_overloads to benefit from its argument-flattening and matching heuristics.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@rchen152 rchen152 left a comment

Choose a reason for hiding this comment

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

At least conceptually, this seems like another flavor of #105 - we're matching the paramspec to the first overload rather than considering all of them.

I'd be okay with putting in a workaround to address this specific bug, but this PR makes me a bit nervous because (1) it's doing some fiddly tracking of parameters and arguments by idx, and (2) it's doing its own overload evaluation, using different logic from call_overloads.

As a more general question, I wonder how pervasive/urgent this issue is. Maybe if we wait until we've addressed some of our problems with type parameter solving more holistically, a better solution will become more obvious.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

Diff from mypy_primer, showing the effect of this PR on open source code:

tornado (https://github.com/tornadoweb/tornado)
+ ERROR tornado/gen.py:255:62-69: Argument `Awaitable[Unknown] | Future[Unknown] | dict[Any, Awaitable[Unknown]] | list[Awaitable[Unknown]] | _T | None` is not assignable to parameter `first_yielded` with type `Awaitable[Unknown] | Future[Unknown] | dict[Any, Awaitable[Unknown]] | list[Awaitable[Unknown]] | None` in function `Runner.__init__` [bad-argument-type]

pip (https://github.com/pypa/pip)
-  WARN iterative_resolve_scc: SCC CalcId(pip._vendor.msgpack.fallback, /home/runner/work/pyrefly/pyrefly/primer_base/projects/pip/src/pip/_vendor/msgpack/fallback.py, KeyClassField(class1, _unpack)) exceeded 5 iterations; committing last answers

starlette (https://github.com/encode/starlette)
+ ERROR starlette/testclient.py:112:28-39: Argument `Overload[
+   [PosArgsT, T_Retval](self: BlockingPortal, func: (**tuple[*PosArgsT]) -> Awaitable[T_Retval], *args: *PosArgsT) -> T_Retval
+   [PosArgsT, T_Retval](self: BlockingPortal, func: (**tuple[*PosArgsT]) -> T_Retval, *args: *PosArgsT) -> T_Retval
+ ]` is not assignable to parameter with type `(func: (**tuple[*PosArgsT]) -> Awaitable[T_Retval], *args: *PosArgsT) -> @_` in function `contextlib._BaseExitStack.callback` [bad-argument-type]

cloud-init (https://github.com/canonical/cloud-init)
+  WARN iterative_resolve_scc: SCC CalcId(cloudinit.sources.helpers.aliyun, /home/runner/work/pyrefly/pyrefly/primer_base/projects/cloud-init/cloudinit/sources/helpers/aliyun.py, Key::Definition(_process_dict_values 85:9-29)) exceeded 5 iterations; committing last answers

pandera (https://github.com/pandera-dev/pandera)
+  WARN iterative_resolve_scc: SCC CalcId(typing_inspect, /home/runner/work/pyrefly/pyrefly/primer_base/projects/_pandera_venv/lib/python3.13/site-packages/typing_inspect.py, Key::Definition(_union_subs_tree 779:5-21)) exceeded 5 iterations; committing last answers

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

Primer Diff Classification

❌ 1 regression(s) | 1 project(s) total

1 regression(s) across tornado. error kinds: bad-argument-type. caused by callable_infer().

Project Verdict Changes Error Kinds Root Cause
tornado ❌ Regression +1 bad-argument-type callable_infer()
Detailed analysis

❌ Regression (1)

tornado (+1)

This is a regression. Pyrefly is incorrectly inferring that the yielded variable can have type _T (the generator's return type) when it should only have type _Yieldable (the generator's yield type). The ParamSpec inference changes have introduced a type inference bug where generator return types are being confused with yield types. The code is correct - generators yield _Yieldable objects, not their return type.
Attribution: The change to ParamSpec inference in pyrefly/lib/alt/callable.rs in the callable_infer() function appears to have affected type inference, causing pyrefly to incorrectly infer that yielded values can have the generator's return type _T in addition to _Yieldable types.

Suggested Fix

Summary: The ParamSpec inference changes in callable_infer() are causing incorrect type inference for generator yield types, confusing return types with yield types.

1. In callable_infer() in pyrefly/lib/alt/callable.rs, add a guard condition before the new ParamSpec forwarding logic (lines 531-639) to exclude generator types: if the callable being analyzed has generator semantics (yield types), skip the ParamSpec overload selection logic that's causing yield/return type confusion. This prevents the new inference from interfering with generator type analysis.

Files: pyrefly/lib/alt/callable.rs
Confidence: high
Affected projects: tornado
Fixes: bad-assignment
The regression is specifically attributed to the ParamSpec inference changes in callable_infer(), and the issue is that generator return types (_T) are being confused with yield types (_Yieldable). The new ParamSpec forwarding logic (lines 531-639) is likely interfering with generator type inference. Adding a guard to exclude generators from this new logic should restore correct yield type inference while preserving the ParamSpec improvements for non-generator callables. Expected outcome: eliminates the incorrect _T type inference for yielded values in tornado.


Was this helpful? React with 👍 or 👎

Classification by primer-classifier (1 LLM)

@asukaminato0721 asukaminato0721 marked this pull request as draft March 9, 2026 13:48
@github-actions github-actions bot removed the stale label Mar 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ParamSpec pins to the first overload incorrectly

3 participants