Skip to content

Fix #11483: Incorrect type inference from context manager __exit__ return type with Generic#11502

Open
rchiodo wants to merge 3 commits into
microsoft:mainfrom
rchiodo:fix11483
Open

Fix #11483: Incorrect type inference from context manager __exit__ return type with Generic#11502
rchiodo wants to merge 3 commits into
microsoft:mainfrom
rchiodo:fix11483

Conversation

@rchiodo

@rchiodo rchiodo commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Description

When a generic context manager annotates its __exit__ / __aexit__ return type with a TypeVar (e.g. class ContextManager(Generic[T]) where __exit__ returns T bound to bool | None), Pyright determined exception-suppression behavior from the unspecialized declared return type instead of the solved type argument. This made code-flow narrowing after the with block wrong — reveal_type(foo) was Literal[1] instead of Literal["str", 1] for a bool-specialized manager.

How you figured out what to do

Reproduced the issue with a generic context manager, then used the debugger to inspect isExceptionContextManager in codeFlowEngine.ts. At the bool case:

  • exitType.shared.declaredReturnType.shared.name = "T" (the raw TypeVar — the bug)
  • exitType.priv.specializedTypes.returnType.shared.name = "bool" (the solved type)

Because the declared type was the TypeVar T, the ClassType.isBuiltIn(returnType, 'bool') check never matched and cmSwallowsExceptions stayed false.

Implementation

In isExceptionContextManager (codeFlowEngine.ts), use FunctionType.getEffectiveReturnType(exitType, /* includeInferred */ false) instead of reading shared.declaredReturnType directly. The effective return type prefers priv.specializedTypes.returnType, so generic managers are evaluated using the solved type argument. The existing __aexit__ Coroutine-unwrapping (third type arg) and literal-bool handling are preserved.

Testing

  • Added sample packages/pyright-internal/src/tests/samples/with7.py covering generic sync and async context managers specialized to None and bool, asserting the narrowed type after the block via assert_type.
  • Added With7 test case to checker.test.ts.
  • Red → green: With7 failed before the fix (4 assert_type mismatches), passes after.
  • checker.test (72) and typeEvaluator1-8 (1163) suites pass.
  • Prettier, ESLint, and tsc --noEmit clean.

Addresses

Fixes #11483


Generated by fix_all_my_issues pipeline

rchiodo and others added 2 commits June 15, 2026 09:58
…ypes

isExceptionContextManager read the unspecialized declared __exit__/__aexit__ return type, so a generic context manager whose exit return type is a TypeVar (e.g. ContextManager[bool]) was treated as non-suppressing. Use FunctionType.getEffectiveReturnType so the solved type argument is used instead.

Fixes microsoft#11483

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

Comment thread packages/pyright-internal/src/tests/samples/with7.py Outdated
Comment thread .github/plans/archived/FIX_11483_CONTEXT_MANAGER_GENERIC_EXIT_PLAN.md Outdated
@rchiodo

rchiodo commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Fix looks correct and is well-tested. A couple of minor repo-convention/test-placement notes below; none are blocking.

@heejaechang

Copy link
Copy Markdown
Collaborator

This change appears to be based on an out-of-date tree. The current working tree already resolves #11483 in codeFlowEngine.ts using applySolvedTypeVars, and both the With7 test and a richer with7.py (with SplitContextManager multi-TypeVar coverage) already exist. Please re-baseline onto the current subrepo state, confirm With7 is already green, and drop the redundant changes. Note also that packages/pyright is an upstream-owned subrepo per AGENTS.md.

@heejaechang

Copy link
Copy Markdown
Collaborator

This PR appears stale relative to the current subrepo: the working tree already fixes #11483 in codeFlowEngine.ts (via applySolvedTypeVars), already has a With7 test in checker.test.ts, and already has a richer with7.py (including a multi-TypeVar SplitContextManager case). As-is, this would revert the better fix, create a duplicate test, and delete regression coverage. Please re-baseline onto current main and confirm With7 is already green before re-submitting.

@heejaechang

Copy link
Copy Markdown
Collaborator

This change appears to be based on a stale baseline. The current subrepo already fixes #11483 in codeFlowEngine.ts (via applySolvedTypeVars on the declared return type), and with7.py / checker.test.ts already contain With7 coverage on disk. As written this diff would revert the existing fix, drop a return-type guard, delete the SplitContextManager multi-TypeVar coverage, and add a duplicate With7 test. Please re-baseline onto the current branch, confirm With7 is already green, and drop the redundant edits before re-submitting.

# Conflicts:
#	packages/pyright-internal/src/analyzer/codeFlowEngine.ts
#	packages/pyright-internal/src/tests/samples/with7.py
@rchiodo

rchiodo commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

@heejaechang you're right — thanks for catching this. I verified your claim against current upstream/main and it holds:

I re-baselined fix11483 onto current main and dropped all the redundant/conflicting edits:

  • codeFlowEngine.ts and checker.test.ts now match main exactly (no revert, no duplicate With7 test, SplitContextManager coverage preserved).
  • Removed the .github/plans/archived/*.md artifact — it's a pyrx pipeline file that doesn't belong in upstream-owned pyright.

The only remaining diff vs main is a small additive extension to with7.py: a generic AsyncContextManager[T] regression case covering __aexit__ specialized to both bool (exception possibly suppressed) and None (never suppressed). Upstream's with7.py only covers the sync path, so this just strengthens the existing fix's coverage without touching any product code.

git diff main HEAD is now a single file, +22 lines in with7.py. If you'd prefer this PR be closed instead (since the fix already landed via #11527), happy to do that too.

@github-actions

Copy link
Copy Markdown
Contributor

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

sympy (https://github.com/sympy/sympy)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:176:44 - error: Cannot access attribute "expand" for class "Basic"
+     Attribute "expand" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:380:15 - error: Operator "*" not supported for "None" (reportOptionalOperand)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:13 - error: No overloads for "__init__" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:18 - error: Argument of type "list[Iterator[Unknown]]" cannot be assigned to parameter "iterable" of type "Iterable[list[bytes]]" in function "__init__" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:78 - error: "args" is not a known attribute of "None" (reportOptionalMemberAccess)
+   .../projects/sympy/sympy/solvers/ode/lie_group.py:619:61 - error: Operator "-" not supported for type "Unknown | Basic" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:225:45 - error: Cannot access attribute "has" for class "tuple[Expr, int]"
+     Attribute "has" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:223:22 - error: No overloads for "__new__" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:223:25 - error: Argument of type "CRootOf | tuple[Expr, int]" cannot be assigned to parameter "arg" of type "Expr" in function "__new__"
-     Type "CRootOf | tuple[Expr, int]" is not assignable to type "Expr"
-       "tuple[Expr, int]" is not assignable to "Expr" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:238:40 - error: No overloads for "__new__" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:238:50 - error: Argument of type "CRootOf | tuple[Expr, int]" cannot be assigned to parameter "arg" of type "Expr" in function "__new__"
-     Type "CRootOf | tuple[Expr, int]" is not assignable to type "Expr"
-       "tuple[Expr, int]" is not assignable to "Expr" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/single.py:867:9 - error: Expression with type "tuple[ComplexInfinity | Unknown | Any, list[tuple[Unknown, Unknown]] | list[Unknown]] | tuple[ComplexInfinity | Unknown | Any, list[tuple[Unknown, Unknown]] | list[Unknown], list[tuple[Unknown, Unknown]] | list[Unknown]]" cannot be assigned to target tuple
-     Type "tuple[ComplexInfinity | Unknown | Any, list[tuple[Unknown, Unknown]] | list[Unknown], list[tuple[Unknown, Unknown]] | list[Unknown]]" is incompatible with target tuple
-       Tuple size mismatch; expected 2 but received 3 (reportAssignmentType)
+   .../projects/sympy/sympy/solvers/ode/single.py:2646:27 - error: Argument of type "One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | Expr | Unknown | int" cannot be assigned to parameter "stop" of type "SupportsIndex" in function "__new__"
+     Type "One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | Expr | Unknown | int" is not assignable to type "SupportsIndex"
+       "Expr" is incompatible with protocol "SupportsIndex"
+         "__index__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/single.py:2652:9 - error: Operator "+=" not supported for types "Unknown | Any | Zero | One | NegativeOne | Integer | NaN | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | Expr" and "Basic | Any | Unknown"
+   .../projects/sympy/sympy/solvers/ode/single.py:2652:9 - error: Operator "+=" not supported for types "One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | Expr | Any | Unknown" and "Basic | Any | Unknown"
-   .../projects/sympy/sympy/solvers/ode/systems.py:1214:26 - error: Argument of type "dict[str, str | Unknown | Expr | bool | Symbol]" cannot be assigned to parameter "m" of type "Iterable[tuple[str, str]]" in function "update"
+   .../projects/sympy/sympy/solvers/ode/systems.py:1214:26 - error: Argument of type "dict[str, str | Unknown | Pow | bool | Symbol]" cannot be assigned to parameter "m" of type "Iterable[tuple[str, str]]" in function "update"
-   .../projects/sympy/sympy/stats/crv_types.py:2544:9 - error: Method "_cdf" overrides class "SingleContinuousDistribution" in an incompatible manner
-     Return type mismatch: base method returns type "None", override returns type "ComplexInfinity | Unknown"
-       Type "ComplexInfinity | Unknown" is not assignable to type "None"
-         "ComplexInfinity" is not assignable to "None" (reportIncompatibleMethodOverride)
-   .../projects/sympy/sympy/stats/crv_types.py:2723:9 - error: Method "_cdf" overrides class "SingleContinuousDistribution" in an incompatible manner
-     Return type mismatch: base method returns type "None", override returns type "Expr | Unknown"
-       Type "Expr | Unknown" is not assignable to type "None"
-         "Expr" is not assignable to "None" (reportIncompatibleMethodOverride)
-   .../projects/sympy/sympy/stats/drv.py:269:22 - error: Argument of type "Generator[tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic | int | None, None, None]" cannot be assigned to parameter "iterable" of type "Iterable[_SupportsSumNoDefaultT@sum]" in function "sum"
+   .../projects/sympy/sympy/stats/drv.py:269:22 - error: Argument of type "Generator[tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | int | None, None, None]" cannot be assigned to parameter "iterable" of type "Iterable[_SupportsSumNoDefaultT@sum]" in function "sum"
-     "Generator[tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic | int | None, None, None]" is not assignable to "Iterable[_SupportsSumNoDefaultT@sum]"
+     "Generator[tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | int | None, None, None]" is not assignable to "Iterable[_SupportsSumNoDefaultT@sum]"
-       Type parameter "_T_co@Iterable" is covariant, but "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic | int | None" is not a subtype of "_SupportsSumNoDefaultT@sum"
+       Type parameter "_T_co@Iterable" is covariant, but "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | int | None" is not a subtype of "_SupportsSumNoDefaultT@sum"
-         Type "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic | int | None" is not assignable to type "_SupportsSumWithNoDefaultGiven"
+         Type "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | int | None" is not assignable to type "_SupportsSumWithNoDefaultGiven"
-           Type "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic | int | None" is not assignable to type "_SupportsSumWithNoDefaultGiven"
+           Type "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | int | None" is not assignable to type "_SupportsSumWithNoDefaultGiven"
-   .../projects/sympy/sympy/stats/drv_types.py:293:16 - error: Operator "*" not supported for types "Expr" and "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic"
+   .../projects/sympy/sympy/stats/drv_types.py:293:16 - error: Operator "*" not supported for types "Expr" and "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic"
-   .../projects/sympy/sympy/stats/random_matrix_models.py:111:36 - error: Operator "*" not supported for types "Expr" and "tuple[Unknown, ...] | Unknown | Product | Basic | BooleanFalse | BooleanTrue | Equality | Expr | NaN | One | Zero"
+   .../projects/sympy/sympy/stats/random_matrix_models.py:111:36 - error: Operator "*" not supported for types "Expr" and "tuple[Unknown, ...] | Unknown | Product | Basic | BooleanFalse | BooleanTrue | ComplexInfinity | Equality | Expr | Float | Infinity | Integer | NaN | NegativeInfinity | NegativeOne | Number | One | Rational | Zero"
-   .../projects/sympy/sympy/stats/rv.py:473:25 - error: Argument of type "Expr | Lambda | Zero | One | Integral | Unknown | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | NaN | Piecewise | Basic | NegativeOne | Integer | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | int" cannot be assigned to parameter "args" of type "Expr | complex" in function "__new__"
+   .../projects/sympy/sympy/stats/rv.py:473:25 - error: Argument of type "Expr | Lambda | Zero | One | Integral | Unknown | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | Add | NaN | Piecewise | Basic | NegativeOne | Integer | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | int" cannot be assigned to parameter "args" of type "Expr | complex" in function "__new__"
-     Type "Expr | Lambda | Zero | One | Integral | Unknown | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | NaN | Piecewise | Basic | NegativeOne | Integer | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | int" is not assignable to type "Expr | complex"
+     Type "Expr | Lambda | Zero | One | Integral | Unknown | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | Add | NaN | Piecewise | Basic | NegativeOne | Integer | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | int" is not assignable to type "Expr | complex"

... (truncated 1304 lines) ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect type inference from context manager __exit__ return type with Generic

2 participants