Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/pyright-internal/src/analyzer/typeGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2146,6 +2146,8 @@ function narrowTypeForContainerType(
typesToEliminate.push(tupleEntry.type);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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]

} else if (isClassInstance(tupleEntry.type) && isLiteralType(tupleEntry.type)) {
typesToEliminate.push(tupleEntry.type);
} else if (isInstantiableClass(tupleEntry.type)) {
typesToEliminate.push(tupleEntry.type);
}
}
});
Expand Down
5 changes: 5 additions & 0 deletions packages/pyright-internal/src/tests/checker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -721,3 +721,8 @@ test('Deprecated8', () => {
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['deprecated8.py'], configOptions);
TestUtils.validateResults(analysisResults2, 4);
});

test('TypeNarrowingContainer1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowingContainer1.py']);
TestUtils.validateResults(analysisResults, 0);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# This sample tests negative type narrowing for tuple membership checks (in / not in)
# containing instantiable class objects (type[T]).

from typing_extensions import assert_type

class ClassA: pass
class ClassB: pass
class ClassC: pass

def test_in_class_tuple(x: type[ClassA] | type[ClassB] | type[ClassC]):
if x in (ClassA, ClassB):
assert_type(x, type[ClassA] | type[ClassB])
else:
assert_type(x, type[ClassC])

def test_not_in_class_tuple(x: type[ClassA] | type[ClassB] | type[ClassC]):
if x not in (ClassA, ClassB):
assert_type(x, type[ClassC])
else:
assert_type(x, type[ClassA] | type[ClassB])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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]