Fix negative type narrowing for tuple membership checks with class objects - #11623
Fix negative type narrowing for tuple membership checks with class objects#11623Henry Su (hsusul) wants to merge 2 commits into
Conversation
|
GitHub cannot anchor PR review comments to unchanged lines in the diff. Falling back to a general PR comment for packages/pyright-internal/src/analyzer/typeGuards.ts:L2219.
This unconditionally adds class objects to the negative-branch elimination set, but [verified] |
|
GitHub cannot anchor PR review comments to unchanged lines in the diff. Falling back to a general PR comment for packages/pyright-internal/src/analyzer/typeGuards.ts:L2211.
The comment now contradicts the expanded handling. Update it to describe instantiable-class handling and its exactness/subclass constraint so it continues documenting the soundness invariant. [verified] |
| if x not in (ClassA, ClassB): | ||
| assert_type(x, type[ClassC]) | ||
| else: | ||
| assert_type(x, type[ClassA] | type[ClassB]) |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
This sample covers only leaf classes, so it misses the unsound subclass case. Add SubA(ClassA) and assert that the negative branch retains the type[ClassA] possibility when ClassA is not final.
[verified]
…s to final classes
|
Thanks for the thorough review! I've updated the implementation and tests accordingly:
|
|
The minimal reproduction and solution text should use |
|
🔒 Automated review in progress — Rich Chiodo (@rchiodo) is auto-reviewing this PR. |
| containerType.priv.tupleTypeArgs.forEach((tupleEntry) => { | ||
| if (!tupleEntry.isUnbounded) { | ||
| if (isNoneInstance(tupleEntry.type)) { | ||
| typesToEliminate.push(tupleEntry.type); |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
📍 packages/pyright-internal/src/analyzer/typeGuards.ts:2149
The implementation deliberately narrows only final class objects, but the PR title, summary, and reproduction describe non-final ClassA and ClassB as narrowing away. Update the PR text and example to use @final classes so the documented behavior matches the verified safety boundary.
[verified]
Rich Chiodo (rchiodo)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
Summary
Fixes negative type narrowing for tuple membership checks (
in/not in) when the tuple contains instantiable class objects (type[T]).Problem
Given a union of class types
x: type[A] | type[B] | type[C], evaluatingif x not in (A, B):(orif x in (A, B): ... else:) failed to eliminatetype[A]andtype[B]in the negative branch, leavingximproperly astype[A] | type[B] | type[C].Minimal Reproduction
Cause
narrowTypeForContainerintypeGuards.tspreviously only collectedNoneand literal class instances (isClassInstance(...) && isLiteralType(...)) intotypesToEliminate. Instantiable class types (isInstantiableClass(...), e.g., class objectsClassA,ClassB) were omitted. As a result,typesToEliminateremained empty, returningreferenceTypeunchanged in negative tests.Solution
Include
isInstantiableClass(tupleEntry.type)intypesToEliminatesomapSubtypeseliminates matching class types during negative container narrowing.Validation
npx jest src/tests/checker.test.ts -t "TypeNarrowingContainer1"(PASSED)npm run typecheck(PASSED with 0 errors)npm run check(PASSED for eslint, prettier, syncpack)git diff --check(PASSED)typeNarrowingContainer1.py.