Skip to content

Commit deda499

Browse files
Support in/not in tests on sys.platform (#21913)
This is proposed for the spec in python/typing#2173. It is trivial to support in mypy and seems useful, so let's add it.
1 parent 6934a9d commit deda499

2 files changed

Lines changed: 110 additions & 4 deletions

File tree

mypy/reachability.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
ImportFrom,
2020
IndexExpr,
2121
IntExpr,
22+
ListExpr,
2223
MatchStmt,
2324
MemberExpr,
2425
NameExpr,
2526
OpExpr,
27+
SetExpr,
2628
SliceExpr,
2729
StrExpr,
2830
TupleExpr,
@@ -232,19 +234,27 @@ def consider_sys_platform(expr: Expression, platform: str) -> int:
232234
# - sys.platform == 'linux'
233235
# - sys.platform != 'win32'
234236
# - sys.platform.startswith('win')
237+
# - sys.platform in {'linux', 'darwin'}
238+
# - sys.platform not in ('win32', 'cygwin')
235239
if isinstance(expr, ComparisonExpr):
236240
# Let's not yet support chained comparisons.
237241
if len(expr.operators) > 1:
238242
return TRUTH_VALUE_UNKNOWN
239243
op = expr.operators[0]
240-
if op not in ("==", "!="):
241-
return TRUTH_VALUE_UNKNOWN
242244
if not is_sys_attr(expr.operands[0], "platform"):
243245
return TRUTH_VALUE_UNKNOWN
244246
right = expr.operands[1]
245-
if not isinstance(right, StrExpr):
247+
if op in ("==", "!=") and isinstance(right, StrExpr):
248+
return fixed_comparison(platform, op, right.value)
249+
if op not in ("in", "not in"):
250+
return TRUTH_VALUE_UNKNOWN
251+
items = contains_tuple_or_set_of_strings(right)
252+
if items is None:
246253
return TRUTH_VALUE_UNKNOWN
247-
return fixed_comparison(platform, op, right.value)
254+
result = platform in items
255+
if op == "not in":
256+
result = not result
257+
return ALWAYS_TRUE if result else ALWAYS_FALSE
248258
elif isinstance(expr, CallExpr):
249259
if not isinstance(expr.callee, MemberExpr):
250260
return TRUTH_VALUE_UNKNOWN
@@ -296,6 +306,14 @@ def contains_int_or_tuple_of_ints(expr: Expression) -> None | int | tuple[int, .
296306
return None
297307

298308

309+
def contains_tuple_or_set_of_strings(expr: Expression) -> tuple[str, ...] | None:
310+
if isinstance(expr, (TupleExpr, SetExpr, ListExpr)) and all(
311+
isinstance(item, StrExpr) for item in expr.items
312+
):
313+
return tuple(item.value for item in expr.items if isinstance(item, StrExpr))
314+
return None
315+
316+
299317
def contains_sys_version_info(expr: Expression) -> None | int | tuple[int | None, int | None]:
300318
if is_sys_attr(expr, "version_info"):
301319
return (None, None) # Same as sys.version_info[:]

test-data/unit/check-unreachable-code.test

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,94 @@ reveal_type(x) # N: Revealed type is "builtins.str"
474474
[builtins fixtures/ops.pyi]
475475
[out]
476476

477+
[case testCustomSysPlatformMembershipTuple]
478+
# flags: --platform linux
479+
import sys
480+
if sys.platform in ('linux', 'darwin'):
481+
x = "foo"
482+
else:
483+
x = 3
484+
reveal_type(x) # N: Revealed type is "builtins.str"
485+
486+
if sys.platform not in ('win32', 'cygwin'):
487+
y = "foo"
488+
else:
489+
y = 3
490+
reveal_type(y) # N: Revealed type is "builtins.str"
491+
492+
if sys.platform in ('win32', 'cygwin'):
493+
z = "foo"
494+
else:
495+
z = 3
496+
reveal_type(z) # N: Revealed type is "builtins.int"
497+
498+
if sys.platform not in ('linux', 'darwin'):
499+
w = "foo"
500+
else:
501+
w = 3
502+
reveal_type(w) # N: Revealed type is "builtins.int"
503+
[builtins fixtures/ops.pyi]
504+
[out]
505+
506+
[case testCustomSysPlatformMembershipSet]
507+
# flags: --platform linux
508+
import sys
509+
if sys.platform in {'linux', 'darwin'}:
510+
x = "foo"
511+
else:
512+
x = 3
513+
reveal_type(x) # N: Revealed type is "builtins.str"
514+
515+
if sys.platform not in {'linux', 'darwin'}:
516+
y = "foo"
517+
else:
518+
y = 3
519+
reveal_type(y) # N: Revealed type is "builtins.int"
520+
[builtins fixtures/set.pyi]
521+
[out]
522+
523+
[case testCustomSysPlatformMembershipList]
524+
# flags: --platform linux
525+
import sys
526+
if sys.platform in ['linux', 'darwin']:
527+
x = "foo"
528+
else:
529+
x = 3
530+
reveal_type(x) # N: Revealed type is "builtins.str"
531+
532+
if sys.platform not in ['linux', 'darwin']:
533+
y = "foo"
534+
else:
535+
y = 3
536+
reveal_type(y) # N: Revealed type is "builtins.int"
537+
[builtins fixtures/list.pyi]
538+
[out]
539+
540+
[case testSysPlatformMembershipUnknown]
541+
# flags: --platform linux
542+
import sys
543+
544+
platform = sys.platform
545+
if sys.platform in ('linux', platform):
546+
y = "foo"
547+
else:
548+
y = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "str")
549+
reveal_type(y) # N: Revealed type is "builtins.str"
550+
[builtins fixtures/ops.pyi]
551+
[out]
552+
553+
[case testSysPlatformMembershipDictIsUnknown]
554+
# flags: --platform linux
555+
import sys
556+
557+
if sys.platform in {'linux': 'hi', 'darwin': 'bye'}:
558+
y = "foo"
559+
else:
560+
y = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "str")
561+
reveal_type(y) # N: Revealed type is "builtins.str"
562+
[builtins fixtures/dict.pyi]
563+
[out]
564+
477565
[case testShortCircuitInExpression]
478566
import typing
479567
def make() -> bool: pass

0 commit comments

Comments
 (0)