Skip to content

New option --python-version. #883

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 1 commit into from
Oct 12, 2015
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
13 changes: 7 additions & 6 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from mypy.errors import Errors, CompileError
from mypy import parse
from mypy import stats
from mypy import defaults


debug = False
Expand Down Expand Up @@ -86,7 +87,7 @@ def build(program_path: str,
program_text: Union[str, bytes] = None,
alt_lib_path: str = None,
bin_dir: str = None,
pyversion: int = 3,
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION,
custom_typing_module: str = None,
html_report_dir: str = None,
flags: List[str] = None,
Expand All @@ -108,7 +109,7 @@ def build(program_path: str,
(takes precedence over other directories)
bin_dir: directory containing the mypy script, used for finding data
directories; if omitted, use '.' as the data directory
pyversion: Python version (2 for 2.x or 3 for 3.x)
pyversion: Python version (major, minor)
custom_typing_module: if not None, use this module id as an alias for typing
flags: list of build options (e.g. COMPILE_ONLY)
"""
Expand Down Expand Up @@ -187,7 +188,7 @@ def default_data_dir(bin_dir: str) -> str:
raise RuntimeError("Broken installation: can't determine base dir")


def default_lib_path(data_dir: str, target: int, pyversion: int,
def default_lib_path(data_dir: str, target: int, pyversion: Tuple[int, int],
python_path: bool) -> List[str]:
"""Return default standard library search paths."""
# IDEA: Make this more portable.
Expand All @@ -202,7 +203,7 @@ def default_lib_path(data_dir: str, target: int, pyversion: int,
# stubs/x.y directory of the mypy installation.
version_dir = '3.2'
third_party_dir = 'third-party-3.2'
if pyversion < 3:
if pyversion[0] < 3:
version_dir = '2.7'
third_party_dir = 'third-party-2.7'
path.append(os.path.join(data_dir, 'stubs', version_dir))
Expand Down Expand Up @@ -268,7 +269,7 @@ class BuildManager:
Semantic analyzer, pass 3
type_checker: Type checker
errors: Used for reporting all errors
pyversion: Python version (2 or 3)
pyversion: Python version (major, minor)
flags: Build options
states: States of all individual files that are being
processed. Each file in a build is always represented
Expand All @@ -286,7 +287,7 @@ class BuildManager:
def __init__(self, data_dir: str,
lib_path: List[str],
target: int,
pyversion: int,
pyversion: Tuple[int, int],
flags: List[str],
ignore_prefix: str,
custom_typing_module: str,
Expand Down
11 changes: 6 additions & 5 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from mypy.sametypes import is_same_type
from mypy.messages import MessageBuilder
import mypy.checkexpr
from mypy import defaults
from mypy import messages
from mypy.subtypes import (
is_subtype, is_equivalent, is_proper_subtype,
Expand Down Expand Up @@ -302,8 +303,8 @@ class TypeChecker(NodeVisitor[Type]):
Type check mypy source files that have been semantically analyzed.
"""

# Target Python major version
pyversion = 3
# Target Python version
pyversion = defaults.PYTHON3_VERSION
# Are we type checking a stub?
is_stub = False
# Error message reporter
Expand Down Expand Up @@ -338,7 +339,7 @@ class TypeChecker(NodeVisitor[Type]):
modules = None # type: Dict[str, MypyFile]

def __init__(self, errors: Errors, modules: Dict[str, MypyFile],
pyversion: int = 3) -> None:
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION) -> None:
"""Construct a type checker.

Use errors to report type check errors. Assume symtable has been
Expand Down Expand Up @@ -1530,7 +1531,7 @@ def type_check_raise(self, e: Node, s: RaiseStmt) -> None:
# Good!
return None
# Else fall back to the checks below (which will fail).
if isinstance(typ, TupleType) and self.pyversion == 2:
if isinstance(typ, TupleType) and self.pyversion[0] == 2:
# allow `raise type, value, traceback`
# https://docs.python.org/2/reference/simple_stmts.html#the-raise-statement
# TODO: Also check tuple item types.
Expand Down Expand Up @@ -1642,7 +1643,7 @@ def analyze_iterable_item_type(self, expr: Node) -> Type:
method = echk.analyze_external_member_access('__iter__', iterable,
expr)
iterator = echk.check_call(method, [], [], expr)[0]
if self.pyversion >= 3:
if self.pyversion[0] >= 3:
nextmethod = '__next__'
else:
nextmethod = 'next'
Expand Down
6 changes: 3 additions & 3 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ def visit_complex_expr(self, e: ComplexExpr) -> Type:

def visit_ellipsis(self, e: EllipsisExpr) -> Type:
"""Type check '...'."""
if self.chk.pyversion >= 3:
if self.chk.pyversion[0] >= 3:
return self.named_type('builtins.ellipsis')
else:
# '...' is not valid in normal Python 2 code, but it can
Expand Down Expand Up @@ -821,7 +821,7 @@ def visit_comparison_expr(self, e: ComparisonExpr) -> Type:
return result

def get_operator_method(self, op: str) -> str:
if op == '/' and self.chk.pyversion == 2:
if op == '/' and self.chk.pyversion[0] == 2:
# TODO also check for "from __future__ import division"
return '__div__'
else:
Expand Down Expand Up @@ -893,7 +893,7 @@ def check_op(self, method: str, base_type: Type, arg: Node,
self.msg)

def get_reverse_op_method(self, method: str) -> str:
if method == '__div__' and self.chk.pyversion == 2:
if method == '__div__' and self.chk.pyversion[0] == 2:
return '__rdiv__'
else:
return nodes.reverse_op_methods[method]
Expand Down
2 changes: 2 additions & 0 deletions mypy/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PYTHON2_VERSION = (2, 7)
PYTHON3_VERSION = (3, 5)
17 changes: 10 additions & 7 deletions mypy/lex.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import re

from mypy.util import short_type
from mypy import defaults
from typing import List, Callable, Dict, Any, Match, Pattern, Set, Union, Tuple


Expand Down Expand Up @@ -158,7 +159,8 @@ def __str__(self):


def lex(string: Union[str, bytes], first_line: int = 1,
pyversion: int = 3, is_stub_file: bool = False) -> Tuple[List[Token], Set[int]]:
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION,
is_stub_file: bool = False) -> Tuple[List[Token], Set[int]]:
"""Analyze string, and return an array of token objects and the lines to ignore.

The last token is always Eof. The intention is to ignore any
Expand Down Expand Up @@ -291,12 +293,13 @@ class Lexer:
# newlines within parentheses/brackets.
open_brackets = None # type: List[str]

pyversion = 3
pyversion = defaults.PYTHON3_VERSION

# Ignore errors on these lines (defined using '# type: ignore').
ignored_lines = None # type: Set[int]

def __init__(self, pyversion: int = 3, is_stub_file: bool = False) -> None:
def __init__(self, pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION,
is_stub_file: bool = False) -> None:
self.map = [self.unknown_character] * 256
self.tok = []
self.indents = [0]
Expand All @@ -322,12 +325,12 @@ def __init__(self, pyversion: int = 3, is_stub_file: bool = False) -> None:
('-+*/<>%&|^~=!,@', self.lex_misc)]:
for c in seq:
self.map[ord(c)] = method
if pyversion == 2:
if pyversion[0] == 2:
self.keywords = keywords_common | keywords2
# Decimal/hex/octal/binary literal or integer complex literal
self.number_exp1 = re.compile('(0[xXoObB][0-9a-fA-F]+|[0-9]+)[lL]?')

if pyversion == 3:
if pyversion[0] == 3:
self.keywords = keywords_common | keywords3
self.number_exp1 = re.compile('0[xXoObB][0-9a-fA-F]+|[0-9]+')

Expand Down Expand Up @@ -394,7 +397,7 @@ def find_encoding(self, text: bytes) -> Tuple[str, int]:
line = 2 if result.group(1) else 1
return result.group(3).decode('ascii'), line
else:
default_encoding = 'utf8' if self.pyversion >= 3 else 'ascii'
default_encoding = 'utf8' if self.pyversion[0] >= 3 else 'ascii'
return default_encoding, -1

def report_unicode_decode_error(self, exc: UnicodeDecodeError, text: bytes) -> None:
Expand Down Expand Up @@ -484,7 +487,7 @@ def lex_number(self) -> None:
self.add_token(LexError(' ' * maxlen, NUMERIC_LITERAL_ERROR))
elif len(s1) == maxlen:
# Integer literal.
if self.pyversion >= 3 and self.octal_int.match(s1):
if self.pyversion[0] >= 3 and self.octal_int.match(s1):
# Python 2 style octal literal such as 0377 not supported in Python 3.
self.add_token(LexError(s1, NUMERIC_LITERAL_ERROR))
else:
Expand Down
30 changes: 16 additions & 14 deletions mypy/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
StarExpr, YieldFromStmt, YieldFromExpr, NonlocalDecl, DictionaryComprehension,
SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, ExecStmt
)
from mypy import defaults
from mypy import nodes
from mypy.errors import Errors, CompileError
from mypy.types import Void, Type, CallableType, AnyType, UnboundType
Expand Down Expand Up @@ -67,14 +68,14 @@


def parse(source: Union[str, bytes], fnam: str = None, errors: Errors = None,
pyversion: int = 3, custom_typing_module: str = None) -> MypyFile:
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION,
custom_typing_module: str = None) -> MypyFile:
"""Parse a source file, without doing any semantic analysis.

Return the parse tree. If errors is not provided, raise ParseError
on failure. Otherwise, use the errors object to report parse errors.

The pyversion argument determines the Python syntax variant (2 for 2.x and
3 for 3.x).
The pyversion (major, minor) argument determines the Python syntax variant.
"""
is_stub_file = bool(fnam) and fnam.endswith('.pyi')
parser = Parser(fnam, errors, pyversion, custom_typing_module, is_stub_file=is_stub_file)
Expand Down Expand Up @@ -108,7 +109,7 @@ class Parser:
# Lines to ignore (using # type: ignore).
ignored_lines = None # type: Set[int]

def __init__(self, fnam: str, errors: Errors, pyversion: int,
def __init__(self, fnam: str, errors: Errors, pyversion: Tuple[int, int],
custom_typing_module: str = None, is_stub_file: bool = False) -> None:
self.raise_on_error = errors is None
self.pyversion = pyversion
Expand Down Expand Up @@ -761,7 +762,7 @@ def parse_statement(self) -> Tuple[Node, bool]:
is_simple = False
elif ts == 'global':
stmt = self.parse_global_decl()
elif ts == 'nonlocal' and self.pyversion >= 3:
elif ts == 'nonlocal' and self.pyversion[0] >= 3:
stmt = self.parse_nonlocal_decl()
elif ts == 'assert':
stmt = self.parse_assert_stmt()
Expand All @@ -775,10 +776,10 @@ def parse_statement(self) -> Tuple[Node, bool]:
elif ts == '@':
stmt = self.parse_decorated_function_or_class()
is_simple = False
elif ts == 'print' and (self.pyversion == 2 and
elif ts == 'print' and (self.pyversion[0] == 2 and
'print_function' not in self.future_options):
stmt = self.parse_print_stmt()
elif ts == 'exec' and self.pyversion == 2:
elif ts == 'exec' and self.pyversion[0] == 2:
stmt = self.parse_exec_stmt()
else:
stmt = self.parse_expression_or_assignment()
Expand Down Expand Up @@ -1033,7 +1034,7 @@ def parse_try_stmt(self) -> Node:
self.expect('as')
vars.append(self.parse_name_expr())
else:
if (self.pyversion == 2 and
if (self.pyversion[0] == 2 and
isinstance(types[-1], TupleExpr) and
len(cast(TupleExpr, types[-1]).items) == 2 and
isinstance(cast(TupleExpr, types[-1]).items[1], NameExpr)):
Expand Down Expand Up @@ -1166,7 +1167,8 @@ def parse_expression(self, prec: int = 0, star_expr_allowed: bool = False) -> No
elif isinstance(current, Keyword) and s == "yield":
# The expression yield from and yield to assign
expr = self.parse_yield_or_yield_from_expr()
elif isinstance(current, EllipsisToken) and (self.pyversion >= 3 or self.is_stub_file):
elif isinstance(current, EllipsisToken) and (self.pyversion[0] >= 3
or self.is_stub_file):
expr = self.parse_ellipsis()
else:
# Invalid expression.
Expand Down Expand Up @@ -1417,7 +1419,7 @@ def parse_str_expr(self) -> Node:
tok.append(t)
value += t.parsed()
node = None # type: Node
if self.pyversion == 2 and 'unicode_literals' in self.future_options:
if self.pyversion[0] == 2 and 'unicode_literals' in self.future_options:
node = UnicodeExpr(value)
else:
node = StrExpr(value)
Expand All @@ -1430,7 +1432,7 @@ def parse_bytes_literal(self) -> Node:
while isinstance(self.current(), BytesLit):
t = cast(BytesLit, self.skip())
value += t.parsed()
if self.pyversion >= 3:
if self.pyversion[0] >= 3:
node = BytesExpr(value) # type: Node
else:
node = StrExpr(value)
Expand All @@ -1443,7 +1445,7 @@ def parse_unicode_literal(self) -> Node:
while isinstance(self.current(), UnicodeLit):
t = cast(UnicodeLit, self.skip())
value += t.parsed()
if self.pyversion >= 3:
if self.pyversion[0] >= 3:
# Python 3.3 supports u'...' as an alias of '...'.
node = StrExpr(value) # type: Node
else:
Expand Down Expand Up @@ -1833,11 +1835,11 @@ def usage():
sys.exit(2)

args = sys.argv[1:]
pyversion = 3
pyversion = defaults.PYTHON3_VERSION
quiet = False
while args and args[0].startswith('--'):
if args[0] == '--py2':
pyversion = 2
pyversion = defaults.PYTHON2_VERSION
elif args[0] == '--quiet':
quiet = True
else:
Expand Down
15 changes: 9 additions & 6 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
from mypy.lex import lex
from mypy.parsetype import parse_type
from mypy.sametypes import is_same_type
from mypy import defaults


T = TypeVar('T')
Expand Down Expand Up @@ -163,7 +164,8 @@ class SemanticAnalyzer(NodeVisitor):
imports = None # type: Set[str] # Imported modules (during phase 2 analysis)
errors = None # type: Errors # Keeps track of generated errors

def __init__(self, lib_path: List[str], errors: Errors, pyversion: int = 3) -> None:
def __init__(self, lib_path: List[str], errors: Errors,
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION) -> None:
"""Construct semantic analyzer.

Use lib_path to search for modules, and report analysis errors
Expand Down Expand Up @@ -534,7 +536,7 @@ def setup_type_promotion(self, defn: ClassDef) -> None:
# _promote class decorator (undocumented faeture).
promote_target = analyzed.type
if not promote_target:
promotions = (TYPE_PROMOTIONS_PYTHON3 if self.pyversion >= 3
promotions = (TYPE_PROMOTIONS_PYTHON3 if self.pyversion[0] >= 3
else TYPE_PROMOTIONS_PYTHON2)
if defn.fullname in promotions:
promote_target = self.named_type_or_none(promotions[defn.fullname])
Expand Down Expand Up @@ -2227,7 +2229,8 @@ def remove_imported_names_from_symtable(names: SymbolTable,
del names[name]


def infer_reachability_of_if_statement(s: IfStmt, pyversion: int) -> None:
def infer_reachability_of_if_statement(s: IfStmt,
pyversion: Tuple[int, int]) -> None:
for i in range(len(s.expr)):
result = infer_if_condition_value(s.expr[i], pyversion)
if result == ALWAYS_FALSE:
Expand All @@ -2243,7 +2246,7 @@ def infer_reachability_of_if_statement(s: IfStmt, pyversion: int) -> None:
break


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

Return ALWAYS_TRUE if always true, ALWAYS_FALSE if always false,
Expand All @@ -2262,9 +2265,9 @@ def infer_if_condition_value(expr: Node, pyversion: int) -> int:
name = expr.name
result = TRUTH_VALUE_UNKNOWN
if name == 'PY2':
result = ALWAYS_TRUE if pyversion == 2 else ALWAYS_FALSE
result = ALWAYS_TRUE if pyversion[0] == 2 else ALWAYS_FALSE
elif name == 'PY3':
result = ALWAYS_TRUE if pyversion == 3 else ALWAYS_FALSE
result = ALWAYS_TRUE if pyversion[0] == 3 else ALWAYS_FALSE
elif name == 'MYPY':
result = ALWAYS_TRUE
if negated:
Expand Down
Loading