From 3cbf1b421b4e9ee490107a6e05834bf5cd1fbe60 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 26 Jul 2016 12:39:41 -0700 Subject: [PATCH 1/9] Add sys.version_info checks. With some basic tests. --- mypy/semanal.py | 116 ++++++++++++++++++++- test-data/unit/check-unreachable-code.test | 20 ++++ test-data/unit/lib-stub/sys.py | 2 + 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 test-data/unit/lib-stub/sys.py diff --git a/mypy/semanal.py b/mypy/semanal.py index 4401183dbdf51..0f2de4360bbb0 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -64,7 +64,7 @@ SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr, YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr, IntExpr, FloatExpr, UnicodeExpr, - COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, + COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, ) from mypy.visitor import NodeVisitor from mypy.traverser import TraverserVisitor @@ -2785,6 +2785,10 @@ def infer_if_condition_value(expr: Node, pyversion: Tuple[int, int]) -> int: name = expr.name elif isinstance(expr, MemberExpr): name = expr.name + else: + r = consider_sys_version_info(expr, pyversion) + if r != TRUTH_VALUE_UNKNOWN: + return r result = TRUTH_VALUE_UNKNOWN if name == 'PY2': result = ALWAYS_TRUE if pyversion[0] == 2 else ALWAYS_FALSE @@ -2800,6 +2804,116 @@ def infer_if_condition_value(expr: Node, pyversion: Tuple[int, int]) -> int: return result +def consider_sys_version_info(expr: Node, pyversion: Tuple[int, ...]) -> int: + """Consider whether expr is a comparison involving sys.version_info. + + Return ALWAYS_TRUE, ALWAYS_FALSE, or TRUTH_VALUE_UNKNOWN. + """ + # Cases supported: + # - sys.version_info[] + # - sys.version_info[:] + # - sys.version_info + # (in this case must be >, >=, <, <=, but cannot be ==, !=) + if not isinstance(expr, ComparisonExpr): + return TRUTH_VALUE_UNKNOWN + # Let's not yet support chained comparisons. + if len(expr.operators) > 1: + return TRUTH_VALUE_UNKNOWN + op = expr.operators[0] + if op not in ('==', '!=', '<=', '>=', '<', '>'): + return TRUTH_VALUE_UNKNOWN + thing = contains_int_or_tuple_of_ints(expr.operands[1]) + if thing is None: + return TRUTH_VALUE_UNKNOWN + index = contains_sys_version_info(expr.operands[0]) + if isinstance(index, int) and isinstance(thing, int): + # sys.version_info[i] k + if 0 <= index <= 1: + return fixed_comparison(pyversion[index], op, thing) + else: + return TRUTH_VALUE_UNKNOWN + elif isinstance(index, tuple) and isinstance(thing, tuple): + lo, hi = cast(Tuple[Optional[int], Optional[int]], index) + if lo is None: + lo = 0 + if hi is None: + hi = 2 + if 0 <= lo <= 2: + if 0 <= hi <= 2: + val = pyversion[lo:hi] + if len(val) == len(thing) or op not in ('==', '!='): + return fixed_comparison(val, op, thing) + return TRUTH_VALUE_UNKNOWN + + +Targ = TypeVar('Targ', int, Tuple[int, ...]) + +def fixed_comparison(left: Targ, op: str, right: Targ) -> int: + rmap = {False: ALWAYS_FALSE, True: ALWAYS_TRUE} + if op == '==': + return rmap[left == right] + if op == '!=': + return rmap[left != right] + if op == '<=': + return rmap[left <= right] + if op == '>=': + return rmap[left >= right] + if op == '<': + return rmap[left < right] + if op == '>': + return rmap[left > right] + return TRUTH_VALUE_UNKNOWN + + +def contains_int_or_tuple_of_ints(expr: Node) -> Union[None, int, Tuple[int], Tuple[int, int]]: + if isinstance(expr, IntExpr): + return expr.value + if isinstance(expr, TupleExpr): + if expr.literal == LITERAL_YES: + if len(expr.items) == 1: + e = expr.items[0] + if isinstance(e, IntExpr): + return (e.value,) + elif len(expr.items) == 2: + e0, e1 = expr.items[0], expr.items[1] + if isinstance(e0, IntExpr) and isinstance(e1, IntExpr): + return (e0.value, e1.value) + return None + + +def contains_sys_version_info(expr: Node) -> Union[None, int, Tuple[Optional[int], Optional[int]]]: + if is_exactly_sys_version_info(expr): + return (None, None) # Same as sys.version_info[:] + if isinstance(expr, IndexExpr) and is_exactly_sys_version_info(expr.base): + index = expr.index + if isinstance(index, IntExpr): + return index.value + if isinstance(index, SliceExpr): + if index.stride is not None: + if not isinstance(index.stride, IntExpr) or index.stride.value != 1: + return None + begin = end = None + if index.begin_index is not None: + if not isinstance(index.begin_index, IntExpr): + return None + begin = index.begin_index.value + if index.end_index is not None: + if not isinstance(index.end_index, IntExpr): + return None + end = index.end_index.value + return (begin, end) + return None + + +def is_exactly_sys_version_info(expr: Node) -> bool: + if isinstance(expr, MemberExpr) and expr.name == 'version_info': + if isinstance(expr.expr, NameExpr) and expr.expr.name == 'sys': + # TODO: Guard against a local named sys, etc. + # (Though later passes will still do most checking.) + return True + return False + + def mark_block_unreachable(block: Block) -> None: block.is_unreachable = True block.accept(MarkImportsUnreachableVisitor()) diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index c1b74835ebac5..3e8814bf1d48b 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -111,3 +111,23 @@ else: import bad_name 1 + '' [out] + +[case testSysVersionInfo_python2] +import sys +if sys.version_info[0] >= 3: + def foo() -> int: return 0 +else: + def foo() -> str: return '' +reveal_type(foo()) # E: Revealed type is 'builtins.str' +[builtins_py2 fixtures/ops.py] +[out] + +[case testSysVersionInfo] +import sys +if sys.version_info[0] >= 3: + def foo() -> int: return 0 +else: + def foo() -> str: return '' +reveal_type(foo()) # E: Revealed type is 'builtins.int' +[builtins fixtures/ops.py] +[out] diff --git a/test-data/unit/lib-stub/sys.py b/test-data/unit/lib-stub/sys.py new file mode 100644 index 0000000000000..3959cb035ca15 --- /dev/null +++ b/test-data/unit/lib-stub/sys.py @@ -0,0 +1,2 @@ +version_info = (0, 0, 0, '', 0) +platform = '' From cea303b0925e9bf1e885e7462f5d2e22fd4c0134 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 26 Jul 2016 12:59:40 -0700 Subject: [PATCH 2/9] Add tests for negated sys.version checks, and fix the code. --- mypy/semanal.py | 19 +++++++++---------- test-data/unit/check-unreachable-code.test | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 0f2de4360bbb0..f8ec93f2eb36a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2781,21 +2781,20 @@ def infer_if_condition_value(expr: Node, pyversion: Tuple[int, int]) -> int: if alias.op == 'not': expr = alias.expr negated = True + result = TRUTH_VALUE_UNKNOWN if isinstance(expr, NameExpr): name = expr.name elif isinstance(expr, MemberExpr): name = expr.name else: - r = consider_sys_version_info(expr, pyversion) - if r != TRUTH_VALUE_UNKNOWN: - return r - result = TRUTH_VALUE_UNKNOWN - if name == 'PY2': - result = ALWAYS_TRUE if pyversion[0] == 2 else ALWAYS_FALSE - elif name == 'PY3': - result = ALWAYS_TRUE if pyversion[0] == 3 else ALWAYS_FALSE - elif name == 'MYPY': - result = ALWAYS_TRUE + result = consider_sys_version_info(expr, pyversion) + if result == TRUTH_VALUE_UNKNOWN: + if name == 'PY2': + result = ALWAYS_TRUE if pyversion[0] == 2 else ALWAYS_FALSE + elif name == 'PY3': + result = ALWAYS_TRUE if pyversion[0] == 3 else ALWAYS_FALSE + elif name == 'MYPY': + result = ALWAYS_TRUE if negated: if result == ALWAYS_TRUE: result = ALWAYS_FALSE diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 3e8814bf1d48b..fe24aa63d0311 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -131,3 +131,23 @@ else: reveal_type(foo()) # E: Revealed type is 'builtins.int' [builtins fixtures/ops.py] [out] + +[case testSysVersionInfoNegated_python2] +import sys +if not (sys.version_info[0] < 3): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +reveal_type(foo()) # E: Revealed type is 'builtins.str' +[builtins_py2 fixtures/ops.py] +[out] + +[case testSysVersionInfoNegated] +import sys +if not (sys.version_info[0] < 3): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +reveal_type(foo()) # E: Revealed type is 'builtins.int' +[builtins fixtures/ops.py] +[out] From 8816915b588aff4447290bcdb154081382d3a1ef Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 26 Jul 2016 13:15:20 -0700 Subject: [PATCH 3/9] More tests for sys.version_info[]. Found no new bugs (except in fixtures). --- test-data/unit/check-unreachable-code.test | 60 ++++++++++++++++++++++ test-data/unit/fixtures/ops.py | 17 +++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index fe24aa63d0311..4824e2f99bdae 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -151,3 +151,63 @@ else: reveal_type(foo()) # E: Revealed type is 'builtins.int' [builtins fixtures/ops.py] [out] + +[case testSysVersionInfoSliced] +import sys +if sys.version_info[:1] >= (3,): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +foo() + 0 +[builtins fixtures/ops.py] +[out] + +[case testSysVersionInfoSliced2] +import sys +if sys.version_info[:2] >= (3, 0): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +foo() + 0 +[builtins fixtures/ops.py] +[out] + +[case testSysVersionInfoSliced3] +import sys +if sys.version_info[:] >= (3, 0): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +foo() + 0 +[builtins fixtures/ops.py] +[out] + +[case testSysVersionInfoSliced4] +import sys +if sys.version_info[0:2] >= (3, 0): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +foo() + 0 +[builtins fixtures/ops.py] +[out] + +[case testSysVersionInfoSliced5] +import sys +if sys.version_info[0:] >= (3,): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +foo() + 0 +[builtins fixtures/ops.py] +[out] + +[case testSysVersionInfoSliced6] +import sys +if sys.version_info[1:] >= (5,): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +foo() + 0 +[builtins fixtures/ops.py] +[out] diff --git a/test-data/unit/fixtures/ops.py b/test-data/unit/fixtures/ops.py index 7d99aa1ef71e9..b19c37be6cd96 100644 --- a/test-data/unit/fixtures/ops.py +++ b/test-data/unit/fixtures/ops.py @@ -1,4 +1,6 @@ -from typing import builtinclass +from typing import builtinclass, overload, Any, Generic, Sequence, TypeVar + +Tco = TypeVar('Tco', covariant=True) # This is an extension of transform builtins with additional operations. @@ -9,7 +11,18 @@ def __eq__(self, o: 'object') -> 'bool': pass def __ne__(self, o: 'object') -> 'bool': pass class type: pass -class tuple: pass + +class slice: pass + +class tuple(Sequence[Tco], Generic[Tco]): + def __getitem__(self, x: int) -> Tco: pass + def __eq__(self, x: object) -> bool: pass + def __ne__(self, x: object) -> bool: pass + def __lt__(self, x: 'tuple') -> bool: pass + def __le__(self, x: 'tuple') -> bool: pass + def __gt__(self, x: 'tuple') -> bool: pass + def __ge__(self, x: 'tuple') -> bool: pass + class function: pass class bool: pass From 4e8487466b545964f6a5a0188de0dedecbbcfbac Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 26 Jul 2016 13:45:00 -0700 Subject: [PATCH 4/9] Add more slicing tests. Fixed a bug one of these found. --- mypy/semanal.py | 28 ++++++++++------------ test-data/unit/check-unreachable-code.test | 28 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index f8ec93f2eb36a..f07054853ba99 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2832,16 +2832,16 @@ def consider_sys_version_info(expr: Node, pyversion: Tuple[int, ...]) -> int: else: return TRUTH_VALUE_UNKNOWN elif isinstance(index, tuple) and isinstance(thing, tuple): - lo, hi = cast(Tuple[Optional[int], Optional[int]], index) + # Why doesn't mypy see that index can't be None here? + lo, hi = cast(tuple, index) if lo is None: lo = 0 if hi is None: hi = 2 - if 0 <= lo <= 2: - if 0 <= hi <= 2: - val = pyversion[lo:hi] - if len(val) == len(thing) or op not in ('==', '!='): - return fixed_comparison(val, op, thing) + if 0 <= lo < hi <= 2: + val = pyversion[lo:hi] + if len(val) == len(thing) or op not in ('==', '!='): + return fixed_comparison(val, op, thing) return TRUTH_VALUE_UNKNOWN @@ -2864,19 +2864,17 @@ def fixed_comparison(left: Targ, op: str, right: Targ) -> int: return TRUTH_VALUE_UNKNOWN -def contains_int_or_tuple_of_ints(expr: Node) -> Union[None, int, Tuple[int], Tuple[int, int]]: +def contains_int_or_tuple_of_ints(expr: Node) -> Union[None, int, Tuple[int], Tuple[int, ...]]: if isinstance(expr, IntExpr): return expr.value if isinstance(expr, TupleExpr): if expr.literal == LITERAL_YES: - if len(expr.items) == 1: - e = expr.items[0] - if isinstance(e, IntExpr): - return (e.value,) - elif len(expr.items) == 2: - e0, e1 = expr.items[0], expr.items[1] - if isinstance(e0, IntExpr) and isinstance(e1, IntExpr): - return (e0.value, e1.value) + thing = [] + for x in expr.items: + if not isinstance(x, IntExpr): + return None + thing.append(x.value) + return tuple(thing) return None diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 4824e2f99bdae..734216c6ad874 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -211,3 +211,31 @@ else: foo() + 0 [builtins fixtures/ops.py] [out] + +[case testSysVersionInfoSliced7] +import sys +if sys.version_info >= (3, 5): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +foo() + 0 +[builtins fixtures/ops.py] +[out] + +[case testSysVersionInfoSliced8] +import sys +if sys.version_info >= (3, 5, 0): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +[builtins fixtures/ops.py] +[out] + +[case testSysVersionInfoSliced9] +import sys +if sys.version_info[1:] >= (5, 0): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +[builtins fixtures/ops.py] +[out] From d28d75b7148f84ecf1db79bd72b547062a72a19f Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 26 Jul 2016 13:59:35 -0700 Subject: [PATCH 5/9] Add sys.platform support (no test yet). --- mypy/semanal.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index f07054853ba99..b051be8de5e62 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -43,6 +43,7 @@ traverse the entire AST. """ +import sys from typing import ( List, Dict, Set, Tuple, cast, Any, overload, TypeVar, Union, Optional, Callable ) @@ -2788,6 +2789,8 @@ def infer_if_condition_value(expr: Node, pyversion: Tuple[int, int]) -> int: name = expr.name else: result = consider_sys_version_info(expr, pyversion) + if result == TRUTH_VALUE_UNKNOWN: + result = consider_sys_platform(expr, sys.platform) if result == TRUTH_VALUE_UNKNOWN: if name == 'PY2': result = ALWAYS_TRUE if pyversion[0] == 2 else ALWAYS_FALSE @@ -2845,8 +2848,33 @@ def consider_sys_version_info(expr: Node, pyversion: Tuple[int, ...]) -> int: return TRUTH_VALUE_UNKNOWN +def consider_sys_platform(expr: Node, platform: str) -> int: + """Consider whether expr is a comparison involving sys.platform. + + Return ALWAYS_TRUE, ALWAYS_FALSE, or TRUTH_VALUE_UNKNOWN. + """ + # Cases supported: + # - sys.platform == 'posix' + # - sys.platform != 'win32' + if not isinstance(expr, ComparisonExpr): + return TRUTH_VALUE_UNKNOWN + # Let's not yet support chained comparisons. + if len(expr.operators) > 1: + return TRUTH_VALUE_UNKNOWN + op = expr.operators[0] + if op not in ('==', '!='): + return TRUTH_VALUE_UNKNOWN + if not is_sys_attr(expr.operands[0], 'platform'): + return TRUTH_VALUE_UNKNOWN + right = expr.operands[1] + if not isinstance(right, (StrExpr, UnicodeExpr)): + return TRUTH_VALUE_UNKNOWN + return fixed_comparison(platform, op, right.value) + + Targ = TypeVar('Targ', int, Tuple[int, ...]) + def fixed_comparison(left: Targ, op: str, right: Targ) -> int: rmap = {False: ALWAYS_FALSE, True: ALWAYS_TRUE} if op == '==': @@ -2879,9 +2907,9 @@ def contains_int_or_tuple_of_ints(expr: Node) -> Union[None, int, Tuple[int], Tu def contains_sys_version_info(expr: Node) -> Union[None, int, Tuple[Optional[int], Optional[int]]]: - if is_exactly_sys_version_info(expr): + if is_sys_attr(expr, 'version_info'): return (None, None) # Same as sys.version_info[:] - if isinstance(expr, IndexExpr) and is_exactly_sys_version_info(expr.base): + if isinstance(expr, IndexExpr) and is_sys_attr(expr.base, 'version_info'): index = expr.index if isinstance(index, IntExpr): return index.value @@ -2902,8 +2930,8 @@ def contains_sys_version_info(expr: Node) -> Union[None, int, Tuple[Optional[int return None -def is_exactly_sys_version_info(expr: Node) -> bool: - if isinstance(expr, MemberExpr) and expr.name == 'version_info': +def is_sys_attr(expr: Node, name: str) -> bool: + if isinstance(expr, MemberExpr) and expr.name == name: if isinstance(expr.expr, NameExpr) and expr.expr.name == 'sys': # TODO: Guard against a local named sys, etc. # (Though later passes will still do most checking.) From c70a97d34107a4d5e5eb0999b5e05894c582ce57 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 26 Jul 2016 14:57:33 -0700 Subject: [PATCH 6/9] Add tests for sys.platform. Fix type error. Change rules for sys.version >= (3, 5, 0). --- mypy/semanal.py | 4 +-- test-data/unit/check-unreachable-code.test | 34 ++++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index b051be8de5e62..2e43f28426a5a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2843,7 +2843,7 @@ def consider_sys_version_info(expr: Node, pyversion: Tuple[int, ...]) -> int: hi = 2 if 0 <= lo < hi <= 2: val = pyversion[lo:hi] - if len(val) == len(thing) or op not in ('==', '!='): + if len(val) == len(thing) or len(val) > len(thing) and op not in ('==', '!='): return fixed_comparison(val, op, thing) return TRUTH_VALUE_UNKNOWN @@ -2872,7 +2872,7 @@ def consider_sys_platform(expr: Node, platform: str) -> int: return fixed_comparison(platform, op, right.value) -Targ = TypeVar('Targ', int, Tuple[int, ...]) +Targ = TypeVar('Targ', int, str, Tuple[int, ...]) def fixed_comparison(left: Targ, op: str, right: Targ) -> int: diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 734216c6ad874..cbab5ab24692b 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -152,7 +152,7 @@ reveal_type(foo()) # E: Revealed type is 'builtins.int' [builtins fixtures/ops.py] [out] -[case testSysVersionInfoSliced] +[case testSysVersionInfoSliced1] import sys if sys.version_info[:1] >= (3,): def foo() -> int: return 0 @@ -227,7 +227,7 @@ import sys if sys.version_info >= (3, 5, 0): def foo() -> int: return 0 else: - def foo() -> str: return '' + def foo() -> str: return '' # E: All conditional function variants must have identical signatures [builtins fixtures/ops.py] [out] @@ -235,7 +235,37 @@ else: import sys if sys.version_info[1:] >= (5, 0): def foo() -> int: return 0 +else: + def foo() -> str: return '' # E: All conditional function variants must have identical signatures +[builtins fixtures/ops.py] +[out] + +[case testSysPlatform1] +import sys +if sys.platform == 'win32': + def foo() -> int: return 0 else: def foo() -> str: return '' +foo() + '' +[builtins fixtures/ops.py] +[out] + +[case testSysPlatform2] +import sys +if sys.platform != 'win32': + def foo() -> int: return 0 +else: + def foo() -> str: return '' +foo() + 0 +[builtins fixtures/ops.py] +[out] + +[case testSysPlatformNegated] +import sys +if not (sys.platform == 'win32'): + def foo() -> int: return 0 +else: + def foo() -> str: return '' +foo() + 0 [builtins fixtures/ops.py] [out] From 5eaae5a4659e544998268c178eb01005e409389a Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 26 Jul 2016 15:09:54 -0700 Subject: [PATCH 7/9] Be more careful with win32 platform in crawl.py. --- test-data/samples/crawl.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test-data/samples/crawl.py b/test-data/samples/crawl.py index 19c327f40979b..c7e8ab5497460 100644 --- a/test-data/samples/crawl.py +++ b/test-data/samples/crawl.py @@ -20,9 +20,10 @@ ARGS = argparse.ArgumentParser(description="Web crawler") -ARGS.add_argument( - '--iocp', action='store_true', dest='iocp', - default=False, help='Use IOCP event loop (Windows only)') +if sys.platform == 'win32': + ARGS.add_argument( + '--iocp', action='store_true', dest='iocp', + default=False, help='Use IOCP event loop (Windows only)') ARGS.add_argument( '--select', action='store_true', dest='select', default=False, help='Use Select event loop instead of default') @@ -824,9 +825,12 @@ def main() -> None: log = Logger(args.level) if args.iocp: - from asyncio import ProactorEventLoop - loop = ProactorEventLoop() # type: ignore - asyncio.set_event_loop(loop) + if sys.platform == 'win32': + from asyncio import ProactorEventLoop + loop = ProactorEventLoop() # type: ignore + asyncio.set_event_loop(loop) + else: + assert False elif args.select: loop = asyncio.SelectorEventLoop() # type: ignore asyncio.set_event_loop(loop) From 70bcaea48ca4ee0cfc35b14b7aed7c556afb926d Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 27 Jul 2016 09:22:56 -0700 Subject: [PATCH 8/9] Add TODOs in response to code review. --- mypy/semanal.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index 2e43f28426a5a..42d0f1c857547 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2856,6 +2856,8 @@ def consider_sys_platform(expr: Node, platform: str) -> int: # Cases supported: # - sys.platform == 'posix' # - sys.platform != 'win32' + # TODO: Maybe support e.g.: + # - sys.platform.startswith('win') if not isinstance(expr, ComparisonExpr): return TRUTH_VALUE_UNKNOWN # Let's not yet support chained comparisons. @@ -2931,6 +2933,9 @@ def contains_sys_version_info(expr: Node) -> Union[None, int, Tuple[Optional[int def is_sys_attr(expr: Node, name: str) -> bool: + # TODO: This currently doesn't work with code like this: + # - import sys as _sys + # - from sys import version_info if isinstance(expr, MemberExpr) and expr.name == name: if isinstance(expr.expr, NameExpr) and expr.expr.name == 'sys': # TODO: Guard against a local named sys, etc. From 65cf9acf00d8faae86916c79295e218562bb7124 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 27 Jul 2016 09:23:18 -0700 Subject: [PATCH 9/9] Add more tests for sys.version_info/platform. --- test-data/unit/check-unreachable-code.test | 78 +++++++++++++++++++++- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index cbab5ab24692b..1668aeae87134 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -223,6 +223,8 @@ foo() + 0 [out] [case testSysVersionInfoSliced8] +# Our pyversion only has (major, minor), +# so testing for (major, minor, bugfix) is unsupported. import sys if sys.version_info >= (3, 5, 0): def foo() -> int: return 0 @@ -232,6 +234,8 @@ else: [out] [case testSysVersionInfoSliced9] +# Our pyversion only has (major, minor), +# so testing for (minor, bugfix) is unsupported (also it's silly :-). import sys if sys.version_info[1:] >= (5, 0): def foo() -> int: return 0 @@ -242,7 +246,7 @@ else: [case testSysPlatform1] import sys -if sys.platform == 'win32': +if sys.platform == 'fictional': def foo() -> int: return 0 else: def foo() -> str: return '' @@ -252,7 +256,7 @@ foo() + '' [case testSysPlatform2] import sys -if sys.platform != 'win32': +if sys.platform != 'fictional': def foo() -> int: return 0 else: def foo() -> str: return '' @@ -262,10 +266,78 @@ foo() + 0 [case testSysPlatformNegated] import sys -if not (sys.platform == 'win32'): +if not (sys.platform == 'fictional'): def foo() -> int: return 0 else: def foo() -> str: return '' foo() + 0 [builtins fixtures/ops.py] [out] + +[case testSysVersionInfoClass] +import sys +if sys.version_info < (3, 5): + class C: + pass +else: + class C: + def foo(self) -> int: return 0 +C().foo() + 0 +[builtins fixtures/ops.py] +[out] + +[case testSysVersionInfoImport] +import sys +if sys.version_info >= (3, 5): + import collections +else: + collections = None +Pt = collections.namedtuple('Pt', 'x y z') +[builtins fixtures/ops.py] +[out] + +[case testSysVersionInfoVariable] +import sys +if sys.version_info >= (3, 5): + x = '' +else: + x = 0 +x + '' +[builtins fixtures/ops.py] +[out] + +[case testSysVersionInfoInClass] +import sys +class C: + if sys.version_info >= (3, 5): + def foo(self) -> int: return 0 + else: + def foo(self) -> str: return '' +reveal_type(C().foo()) # E: Revealed type is 'builtins.int' +[builtins fixtures/ops.py] +[out] + +[case testSysVersionInfoInFunction] +import sys +def foo() -> None: + if sys.version_info >= (3, 5): + x = '' + else: + x = 0 + reveal_type(x) # E: Revealed type is 'builtins.str' +[builtins fixtures/ops.py] +[out] +main: note: In function "foo": + +[case testSysVersionInfoInMethod] +import sys +class C: + def foo(self) -> None: + if sys.platform != 'fictional': + x = '' + else: + x = 0 + reveal_type(x) # E: Revealed type is 'builtins.str' +[builtins fixtures/ops.py] +[out] +main: note: In member "foo" of class "C":