Skip to content

Add new --platform flag #2045

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 23, 2016
Merged
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
7 changes: 4 additions & 3 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ summary of command line flags can always be printed using the ``-h``
flag (or its long form ``--help``)::

$ mypy -h
usage: mypy [-h] [-v] [-V] [--python-version x.y] [--py2] [-s] [--silent]
[--almost-silent] [--disallow-untyped-calls]
[--disallow-untyped-defs] [--check-untyped-defs]
usage: mypy [-h] [-v] [-V] [--python-version x.y] [--platform PLATFORM]
[--py2] [-s] [--silent] [--almost-silent]
[--disallow-untyped-calls] [--disallow-untyped-defs]
[--check-untyped-defs]
[--warn-incomplete-stub] [--warn-redundant-casts]
[--warn-unused-ignores] [--fast-parser] [-i] [--cache-dir DIR]
[--strict-optional] [-f] [--pdb] [--use-python-path] [--stats]
Expand Down
20 changes: 12 additions & 8 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ def __getattr__(self, name: str) -> Any:
return getattr(self._standard_namespace, name)


def parse_version(v: str) -> Tuple[int, int]:
m = re.match(r'\A(\d)\.(\d+)\Z', v)
if m:
return int(m.group(1)), int(m.group(2))
else:
raise argparse.ArgumentTypeError(
"Invalid python version '{}' (expected format: 'x.y')".format(v))


def process_options(args: List[str]) -> Tuple[List[BuildSource], Options]:
"""Process command line arguments.

Expand All @@ -121,14 +130,6 @@ def process_options(args: List[str]) -> Tuple[List[BuildSource], Options]:
parser = argparse.ArgumentParser(prog='mypy', epilog=FOOTER,
formatter_class=help_factory)

def parse_version(v: str) -> Tuple[int, int]:
m = re.match(r'\A(\d)\.(\d+)\Z', v)
if m:
return int(m.group(1)), int(m.group(2))
else:
raise argparse.ArgumentTypeError(
"Invalid python version '{}' (expected format: 'x.y')".format(v))

# Unless otherwise specified, arguments will be parsed directly onto an
# Options object. Options that require further processing should have
# their `dest` prefixed with `special-opts:`, which will cause them to be
Expand All @@ -139,6 +140,9 @@ def parse_version(v: str) -> Tuple[int, int]:
version='%(prog)s ' + __version__)
parser.add_argument('--python-version', type=parse_version, metavar='x.y',
help='use Python x.y')
parser.add_argument('--platform', action='store', metavar='PLATFORM',
help="typecheck special-cased code for the given OS platform "
"(defaults to sys.platform).")
parser.add_argument('-2', '--py2', dest='python_version', action='store_const',
const=defaults.PYTHON2_VERSION, help="use Python 2 mode")
parser.add_argument('-s', '--silent-imports', action='store_true',
Expand Down
2 changes: 2 additions & 0 deletions mypy/options.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from mypy import defaults
import pprint
import sys
from typing import Any


Expand All @@ -16,6 +17,7 @@ def __init__(self) -> None:
# -- build options --
self.build_type = BuildType.STANDARD
self.python_version = defaults.PYTHON3_VERSION
self.platform = sys.platform
self.custom_typing_module = None # type: str
self.report_dirs = {} # type: Dict[str, str]
self.silent_imports = False
Expand Down
57 changes: 37 additions & 20 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1819,7 +1819,9 @@ def visit_continue_stmt(self, s: ContinueStmt) -> None:
self.fail("'continue' outside loop", s, True, blocker=True)

def visit_if_stmt(self, s: IfStmt) -> None:
infer_reachability_of_if_statement(s, pyversion=self.options.python_version)
infer_reachability_of_if_statement(s,
pyversion=self.options.python_version,
platform=self.options.platform)
for i in range(len(s.expr)):
s.expr[i].accept(self)
self.visit_block(s.body[i])
Expand Down Expand Up @@ -2473,6 +2475,7 @@ class FirstPass(NodeVisitor):
def __init__(self, sem: SemanticAnalyzer) -> None:
self.sem = sem
self.pyversion = sem.options.python_version
self.platform = sem.options.platform

def analyze(self, file: MypyFile, fnam: str, mod_id: str) -> None:
"""Perform the first analysis pass.
Expand Down Expand Up @@ -2641,7 +2644,7 @@ def visit_decorator(self, d: Decorator) -> None:
self.sem.add_symbol(d.var.name(), SymbolTableNode(GDEF, d.var), d)

def visit_if_stmt(self, s: IfStmt) -> None:
infer_reachability_of_if_statement(s, pyversion=self.pyversion)
infer_reachability_of_if_statement(s, pyversion=self.pyversion, platform=self.platform)
for node in s.body:
node.accept(self)
if s.else_body:
Expand Down Expand Up @@ -2878,9 +2881,10 @@ def remove_imported_names_from_symtable(names: SymbolTable,


def infer_reachability_of_if_statement(s: IfStmt,
pyversion: Tuple[int, int]) -> None:
pyversion: Tuple[int, int],
platform: str) -> None:
for i in range(len(s.expr)):
result = infer_if_condition_value(s.expr[i], pyversion)
result = infer_if_condition_value(s.expr[i], pyversion, platform)
if result == ALWAYS_FALSE:
# The condition is always false, so we skip the if/elif body.
mark_block_unreachable(s.body[i])
Expand All @@ -2894,7 +2898,7 @@ def infer_reachability_of_if_statement(s: IfStmt,
break


def infer_if_condition_value(expr: Node, pyversion: Tuple[int, int]) -> int:
def infer_if_condition_value(expr: Node, pyversion: Tuple[int, int], platform: str) -> int:
"""Infer whether if condition is always true/false.

Return ALWAYS_TRUE if always true, ALWAYS_FALSE if always false,
Expand All @@ -2915,7 +2919,7 @@ def infer_if_condition_value(expr: Node, pyversion: Tuple[int, int]) -> int:
else:
result = consider_sys_version_info(expr, pyversion)
if result == TRUTH_VALUE_UNKNOWN:
result = consider_sys_platform(expr, sys.platform)
result = consider_sys_platform(expr, platform)
if result == TRUTH_VALUE_UNKNOWN:
if name == 'PY2':
result = ALWAYS_TRUE if pyversion[0] == 2 else ALWAYS_FALSE
Expand Down Expand Up @@ -2981,22 +2985,35 @@ 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.
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)):
if isinstance(expr, ComparisonExpr):
# 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)
elif isinstance(expr, CallExpr):
if not isinstance(expr.callee, MemberExpr):
return TRUTH_VALUE_UNKNOWN
if len(expr.args) != 1 or not isinstance(expr.args[0], (StrExpr, UnicodeExpr)):
return TRUTH_VALUE_UNKNOWN
if not is_sys_attr(expr.callee.expr, 'platform'):
return TRUTH_VALUE_UNKNOWN
if expr.callee.name != 'startswith':
return TRUTH_VALUE_UNKNOWN
if platform.startswith(expr.args[0].value):
return ALWAYS_TRUE
else:
return ALWAYS_FALSE
else:
return TRUTH_VALUE_UNKNOWN
return fixed_comparison(platform, op, right.value)


Targ = TypeVar('Targ', int, str, Tuple[int, ...])
Expand Down
25 changes: 19 additions & 6 deletions mypy/test/testcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Tuple, List, Dict, Set

from mypy import build, defaults
from mypy.main import parse_version
from mypy.build import BuildSource, find_module_clear_caches
from mypy.myunit import AssertionFailure
from mypy.test.config import test_temp_dir, test_data_prefix
Expand Down Expand Up @@ -109,9 +110,8 @@ def run_case_once(self, testcase: DataDrivenTestCase, incremental=0) -> None:
original_program_text = '\n'.join(testcase.input)
module_data = self.parse_module(original_program_text, incremental)

options = self.parse_options(original_program_text)
options = self.parse_options(original_program_text, testcase)
options.use_builtins_fixtures = True
options.python_version = testcase_pyversion(testcase.file, testcase.name)
set_show_tb(True) # Show traceback on crash.

output = testcase.output
Expand Down Expand Up @@ -276,11 +276,24 @@ def parse_module(self, program_text: str, incremental: int = 0) -> List[Tuple[st
else:
return [('__main__', 'main', program_text)]

def parse_options(self, program_text: str) -> Options:
def parse_options(self, program_text: str, testcase: DataDrivenTestCase) -> Options:
options = Options()
m = re.search('# options: (.*)$', program_text, flags=re.MULTILINE)
if m:
options_to_enable = m.group(1).split()
flags = re.search('# options: (.*)$', program_text, flags=re.MULTILINE)
version_flag = re.search('# pyversion: (.*)$', program_text, flags=re.MULTILINE)
platform_flag = re.search('# platform: (.*)$', program_text, flags=re.MULTILINE)

if flags:
options_to_enable = flags.group(1).split()
for opt in options_to_enable:
setattr(options, opt, True)

# Allow custom pyversion comment to override testcase_pyversion
if version_flag:
options.python_version = parse_version(version_flag.group(1))
else:
options.python_version = testcase_pyversion(testcase.file, testcase.name)

if platform_flag:
options.platform = platform_flag.group(1)

return options
55 changes: 55 additions & 0 deletions test-data/unit/check-unreachable-code.test
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,58 @@ class C:
[builtins fixtures/ops.pyi]
[out]
main: note: In member "foo" of class "C":

[case testCustomSysVersionInfo]
# pyversion: 3.2
import sys
if sys.version_info == (3, 2):
x = "foo"
else:
x = 3
reveal_type(x) # E: Revealed type is 'builtins.str'
[builtins fixtures/ops.pyi]
[out]

[case testCustomSysVersionInfo2]
# pyversion: 3.1
import sys
if sys.version_info == (3, 2):
x = "foo"
else:
x = 3
reveal_type(x) # E: Revealed type is 'builtins.int'
[builtins fixtures/ops.pyi]
[out]

[case testCustomSysPlatform]
# platform: linux
import sys
if sys.platform == 'linux':
x = "foo"
else:
x = 3
reveal_type(x) # E: Revealed type is 'builtins.str'
[builtins fixtures/ops.pyi]
[out]

[case testCustomSysPlatform2]
# platform: win32
import sys
if sys.platform == 'linux':
x = "foo"
else:
x = 3
reveal_type(x) # E: Revealed type is 'builtins.int'
[builtins fixtures/ops.pyi]
[out]

[case testCustomSysPlatformStartsWith]
# platform: win32
import sys
if sys.platform.startswith('win'):
x = "foo"
else:
x = 3
reveal_type(x) # E: Revealed type is 'builtins.str'
[builtins fixtures/ops.pyi]
[out]
5 changes: 4 additions & 1 deletion test-data/unit/fixtures/ops.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import builtinclass, overload, Any, Generic, Sequence, TypeVar
from typing import builtinclass, overload, Any, Generic, Sequence, Tuple, TypeVar

Tco = TypeVar('Tco', covariant=True)

Expand Down Expand Up @@ -30,6 +30,7 @@ class bool: pass
class str:
def __init__(self, x: 'int') -> None: pass
def __add__(self, x: 'str') -> 'str': pass
def startswith(self, x: 'str') -> bool: pass

class int:
def __add__(self, x: 'int') -> 'int': pass
Expand All @@ -54,3 +55,5 @@ True = None # type: bool
False = None # type: bool

def __print(a1=None, a2=None, a3=None, a4=None): pass

class module: pass