Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 14 additions & 0 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26126,6 +26126,20 @@ export function createTypeEvaluator(
return boolVal === (intVal === 1);
}

// `bytes` and `bytearray` are distinct built-in classes
// (neither subclasses the other), but `bytes.__eq__` and
// `bytearray.__eq__` cross-compare by content, so an
// expression like `b"abc" == bytearray(b"abc")` is True
// at runtime. Treat them as comparable so we don't emit
// a spurious `reportUnnecessaryComparison` diagnostic.
// The same applies when one side is a `Literal[bytes]`,
// because the literal is still a `bytes` instance.
const isBytesLike = (t: ClassType) =>
ClassType.isBuiltIn(t, 'bytes') || ClassType.isBuiltIn(t, 'bytearray');
if (isBytesLike(leftType) && isBytesLike(rightType)) {
return true;
}

return false;
}
}
Expand Down
37 changes: 37 additions & 0 deletions packages/pyright-internal/src/tests/samples/comparison3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This sample tests that `bytes` and `bytearray` are treated as
# comparable types for `==` / `!=`. Although they are distinct built-in
# classes (neither subclasses the other), their `__eq__` implementations
# cross-compare by content, so a comparison such as
# `b"abc" == bytearray(b"abc")` is `True` at runtime. See issue #11433.


def func1(b1: bytes, b2: bytearray) -> None:
if b1 == b2:
pass

if b1 != b2:
pass

if b2 == b1:
pass

if b2 != b1:
pass


def func2(b: bytearray) -> None:
# Literal[bytes] on the RHS is still a `bytes` instance, so the same
# cross-class comparison rule applies.
if b == b"\x00\x00\x00\x00":
pass

if b"\x00\x00\x00\x00" == b:
pass


def func3(b: bytes, s: str) -> None:
# bytes vs str remains a real "no overlap" — the special case must
# not over-relax. This should still generate an error when
# reportUnnecessaryComparison is enabled.
if b == s:
pass
14 changes: 14 additions & 0 deletions packages/pyright-internal/src/tests/typeEvaluator6.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,20 @@ test('Comparison2', () => {
TestUtils.validateResults(analysisResults2, 18);
});

test('Comparison3', () => {
const configOptions = new ConfigOptions(Uri.empty());

const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['comparison3.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0);

// bytes/bytearray comparisons in func1 and func2 must NOT emit the
// reportUnnecessaryComparison diagnostic; only the bytes-vs-str case
// in func3 should — see issue #11433.
configOptions.diagnosticRuleSet.reportUnnecessaryComparison = 'error';
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['comparison3.py'], configOptions);
TestUtils.validateResults(analysisResults2, 1);
});

test('EmptyContainers1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['emptyContainers1.py']);
TestUtils.validateResults(analysisResults, 5);
Expand Down
Loading