Skip to content

Commit b3bc75c

Browse files
committed
New option --python-version.
Store Python version as (major, minor) internally.
1 parent dcff596 commit b3bc75c

12 files changed

+87
-61
lines changed

mypy/build.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from mypy.errors import Errors, CompileError
2727
from mypy import parse
2828
from mypy import stats
29+
from mypy import defaults
2930

3031

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

189190

190-
def default_lib_path(data_dir: str, target: int, pyversion: int,
191+
def default_lib_path(data_dir: str, target: int, pyversion: Tuple[int, int],
191192
python_path: bool) -> List[str]:
192193
"""Return default standard library search paths."""
193194
# IDEA: Make this more portable.
@@ -202,7 +203,7 @@ def default_lib_path(data_dir: str, target: int, pyversion: int,
202203
# stubs/x.y directory of the mypy installation.
203204
version_dir = '3.2'
204205
third_party_dir = 'third-party-3.2'
205-
if pyversion < 3:
206+
if pyversion[0] < 3:
206207
version_dir = '2.7'
207208
third_party_dir = 'third-party-2.7'
208209
path.append(os.path.join(data_dir, 'stubs', version_dir))
@@ -268,7 +269,7 @@ class BuildManager:
268269
Semantic analyzer, pass 3
269270
type_checker: Type checker
270271
errors: Used for reporting all errors
271-
pyversion: Python version (2 or 3)
272+
pyversion: Python version (major, minor)
272273
flags: Build options
273274
states: States of all individual files that are being
274275
processed. Each file in a build is always represented
@@ -286,7 +287,7 @@ class BuildManager:
286287
def __init__(self, data_dir: str,
287288
lib_path: List[str],
288289
target: int,
289-
pyversion: int,
290+
pyversion: Tuple[int, int],
290291
flags: List[str],
291292
ignore_prefix: str,
292293
custom_typing_module: str,

mypy/checker.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from mypy.sametypes import is_same_type
3131
from mypy.messages import MessageBuilder
3232
import mypy.checkexpr
33+
from mypy import defaults
3334
from mypy import messages
3435
from mypy.subtypes import (
3536
is_subtype, is_equivalent, is_proper_subtype,
@@ -302,8 +303,8 @@ class TypeChecker(NodeVisitor[Type]):
302303
Type check mypy source files that have been semantically analyzed.
303304
"""
304305

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

340341
def __init__(self, errors: Errors, modules: Dict[str, MypyFile],
341-
pyversion: int = 3) -> None:
342+
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION) -> None:
342343
"""Construct a type checker.
343344
344345
Use errors to report type check errors. Assume symtable has been
@@ -1530,7 +1531,7 @@ def type_check_raise(self, e: Node, s: RaiseStmt) -> None:
15301531
# Good!
15311532
return None
15321533
# Else fall back to the checks below (which will fail).
1533-
if isinstance(typ, TupleType) and self.pyversion == 2:
1534+
if isinstance(typ, TupleType) and self.pyversion[0] == 2:
15341535
# allow `raise type, value, traceback`
15351536
# https://docs.python.org/2/reference/simple_stmts.html#the-raise-statement
15361537
# TODO: Also check tuple item types.
@@ -1642,7 +1643,7 @@ def analyze_iterable_item_type(self, expr: Node) -> Type:
16421643
method = echk.analyze_external_member_access('__iter__', iterable,
16431644
expr)
16441645
iterator = echk.check_call(method, [], [], expr)[0]
1645-
if self.pyversion >= 3:
1646+
if self.pyversion[0] >= 3:
16461647
nextmethod = '__next__'
16471648
else:
16481649
nextmethod = 'next'

mypy/checkexpr.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ def visit_complex_expr(self, e: ComplexExpr) -> Type:
727727

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

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

895895
def get_reverse_op_method(self, method: str) -> str:
896-
if method == '__div__' and self.chk.pyversion == 2:
896+
if method == '__div__' and self.chk.pyversion[0] == 2:
897897
return '__rdiv__'
898898
else:
899899
return nodes.reverse_op_methods[method]

mypy/defaults.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
PYTHON2_VERSION = (2, 7)
2+
PYTHON3_VERSION = (3, 5)

mypy/lex.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import re
1010

1111
from mypy.util import short_type
12+
from mypy import defaults
1213
from typing import List, Callable, Dict, Any, Match, Pattern, Set, Union, Tuple
1314

1415

@@ -158,7 +159,8 @@ def __str__(self):
158159

159160

160161
def lex(string: Union[str, bytes], first_line: int = 1,
161-
pyversion: int = 3, is_stub_file: bool = False) -> Tuple[List[Token], Set[int]]:
162+
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION,
163+
is_stub_file: bool = False) -> Tuple[List[Token], Set[int]]:
162164
"""Analyze string, and return an array of token objects and the lines to ignore.
163165
164166
The last token is always Eof. The intention is to ignore any
@@ -291,12 +293,13 @@ class Lexer:
291293
# newlines within parentheses/brackets.
292294
open_brackets = None # type: List[str]
293295

294-
pyversion = 3
296+
pyversion = defaults.PYTHON3_VERSION
295297

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

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

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

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

400403
def report_unicode_decode_error(self, exc: UnicodeDecodeError, text: bytes) -> None:
@@ -484,7 +487,7 @@ def lex_number(self) -> None:
484487
self.add_token(LexError(' ' * maxlen, NUMERIC_LITERAL_ERROR))
485488
elif len(s1) == maxlen:
486489
# Integer literal.
487-
if self.pyversion >= 3 and self.octal_int.match(s1):
490+
if self.pyversion[0] >= 3 and self.octal_int.match(s1):
488491
# Python 2 style octal literal such as 0377 not supported in Python 3.
489492
self.add_token(LexError(s1, NUMERIC_LITERAL_ERROR))
490493
else:

mypy/parse.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
StarExpr, YieldFromStmt, YieldFromExpr, NonlocalDecl, DictionaryComprehension,
2929
SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, ExecStmt
3030
)
31+
from mypy import defaults
3132
from mypy import nodes
3233
from mypy.errors import Errors, CompileError
3334
from mypy.types import Void, Type, CallableType, AnyType, UnboundType
@@ -67,14 +68,14 @@
6768

6869

6970
def parse(source: Union[str, bytes], fnam: str = None, errors: Errors = None,
70-
pyversion: int = 3, custom_typing_module: str = None) -> MypyFile:
71+
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION,
72+
custom_typing_module: str = None) -> MypyFile:
7173
"""Parse a source file, without doing any semantic analysis.
7274
7375
Return the parse tree. If errors is not provided, raise ParseError
7476
on failure. Otherwise, use the errors object to report parse errors.
7577
76-
The pyversion argument determines the Python syntax variant (2 for 2.x and
77-
3 for 3.x).
78+
The pyversion (major, minor) argument determines the Python syntax variant.
7879
"""
7980
is_stub_file = bool(fnam) and fnam.endswith('.pyi')
8081
parser = Parser(fnam, errors, pyversion, custom_typing_module, is_stub_file=is_stub_file)
@@ -108,7 +109,7 @@ class Parser:
108109
# Lines to ignore (using # type: ignore).
109110
ignored_lines = None # type: Set[int]
110111

111-
def __init__(self, fnam: str, errors: Errors, pyversion: int,
112+
def __init__(self, fnam: str, errors: Errors, pyversion: Tuple[int, int],
112113
custom_typing_module: str = None, is_stub_file: bool = False) -> None:
113114
self.raise_on_error = errors is None
114115
self.pyversion = pyversion
@@ -761,7 +762,7 @@ def parse_statement(self) -> Tuple[Node, bool]:
761762
is_simple = False
762763
elif ts == 'global':
763764
stmt = self.parse_global_decl()
764-
elif ts == 'nonlocal' and self.pyversion >= 3:
765+
elif ts == 'nonlocal' and self.pyversion[0] >= 3:
765766
stmt = self.parse_nonlocal_decl()
766767
elif ts == 'assert':
767768
stmt = self.parse_assert_stmt()
@@ -775,10 +776,10 @@ def parse_statement(self) -> Tuple[Node, bool]:
775776
elif ts == '@':
776777
stmt = self.parse_decorated_function_or_class()
777778
is_simple = False
778-
elif ts == 'print' and (self.pyversion == 2 and
779+
elif ts == 'print' and (self.pyversion[0] == 2 and
779780
'print_function' not in self.future_options):
780781
stmt = self.parse_print_stmt()
781-
elif ts == 'exec' and self.pyversion == 2:
782+
elif ts == 'exec' and self.pyversion[0] == 2:
782783
stmt = self.parse_exec_stmt()
783784
else:
784785
stmt = self.parse_expression_or_assignment()
@@ -1033,7 +1034,7 @@ def parse_try_stmt(self) -> Node:
10331034
self.expect('as')
10341035
vars.append(self.parse_name_expr())
10351036
else:
1036-
if (self.pyversion == 2 and
1037+
if (self.pyversion[0] == 2 and
10371038
isinstance(types[-1], TupleExpr) and
10381039
len(cast(TupleExpr, types[-1]).items) == 2 and
10391040
isinstance(cast(TupleExpr, types[-1]).items[1], NameExpr)):
@@ -1166,7 +1167,8 @@ def parse_expression(self, prec: int = 0, star_expr_allowed: bool = False) -> No
11661167
elif isinstance(current, Keyword) and s == "yield":
11671168
# The expression yield from and yield to assign
11681169
expr = self.parse_yield_or_yield_from_expr()
1169-
elif isinstance(current, EllipsisToken) and (self.pyversion >= 3 or self.is_stub_file):
1170+
elif isinstance(current, EllipsisToken) and (self.pyversion[0] >= 3
1171+
or self.is_stub_file):
11701172
expr = self.parse_ellipsis()
11711173
else:
11721174
# Invalid expression.
@@ -1417,7 +1419,7 @@ def parse_str_expr(self) -> Node:
14171419
tok.append(t)
14181420
value += t.parsed()
14191421
node = None # type: Node
1420-
if self.pyversion == 2 and 'unicode_literals' in self.future_options:
1422+
if self.pyversion[0] == 2 and 'unicode_literals' in self.future_options:
14211423
node = UnicodeExpr(value)
14221424
else:
14231425
node = StrExpr(value)
@@ -1430,7 +1432,7 @@ def parse_bytes_literal(self) -> Node:
14301432
while isinstance(self.current(), BytesLit):
14311433
t = cast(BytesLit, self.skip())
14321434
value += t.parsed()
1433-
if self.pyversion >= 3:
1435+
if self.pyversion[0] >= 3:
14341436
node = BytesExpr(value) # type: Node
14351437
else:
14361438
node = StrExpr(value)
@@ -1443,7 +1445,7 @@ def parse_unicode_literal(self) -> Node:
14431445
while isinstance(self.current(), UnicodeLit):
14441446
t = cast(UnicodeLit, self.skip())
14451447
value += t.parsed()
1446-
if self.pyversion >= 3:
1448+
if self.pyversion[0] >= 3:
14471449
# Python 3.3 supports u'...' as an alias of '...'.
14481450
node = StrExpr(value) # type: Node
14491451
else:
@@ -1833,11 +1835,11 @@ def usage():
18331835
sys.exit(2)
18341836

18351837
args = sys.argv[1:]
1836-
pyversion = 3
1838+
pyversion = defaults.PYTHON3_VERSION
18371839
quiet = False
18381840
while args and args[0].startswith('--'):
18391841
if args[0] == '--py2':
1840-
pyversion = 2
1842+
pyversion = defaults.PYTHON2_VERSION
18411843
elif args[0] == '--quiet':
18421844
quiet = True
18431845
else:

mypy/semanal.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
from mypy.lex import lex
7676
from mypy.parsetype import parse_type
7777
from mypy.sametypes import is_same_type
78+
from mypy import defaults
7879

7980

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

166-
def __init__(self, lib_path: List[str], errors: Errors, pyversion: int = 3) -> None:
167+
def __init__(self, lib_path: List[str], errors: Errors,
168+
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION) -> None:
167169
"""Construct semantic analyzer.
168170
169171
Use lib_path to search for modules, and report analysis errors
@@ -534,7 +536,7 @@ def setup_type_promotion(self, defn: ClassDef) -> None:
534536
# _promote class decorator (undocumented faeture).
535537
promote_target = analyzed.type
536538
if not promote_target:
537-
promotions = (TYPE_PROMOTIONS_PYTHON3 if self.pyversion >= 3
539+
promotions = (TYPE_PROMOTIONS_PYTHON3 if self.pyversion[0] >= 3
538540
else TYPE_PROMOTIONS_PYTHON2)
539541
if defn.fullname in promotions:
540542
promote_target = self.named_type_or_none(promotions[defn.fullname])
@@ -2227,7 +2229,8 @@ def remove_imported_names_from_symtable(names: SymbolTable,
22272229
del names[name]
22282230

22292231

2230-
def infer_reachability_of_if_statement(s: IfStmt, pyversion: int) -> None:
2232+
def infer_reachability_of_if_statement(s: IfStmt,
2233+
pyversion: Tuple[int, int]) -> None:
22312234
for i in range(len(s.expr)):
22322235
result = infer_if_condition_value(s.expr[i], pyversion)
22332236
if result == ALWAYS_FALSE:
@@ -2243,7 +2246,7 @@ def infer_reachability_of_if_statement(s: IfStmt, pyversion: int) -> None:
22432246
break
22442247

22452248

2246-
def infer_if_condition_value(expr: Node, pyversion: int) -> int:
2249+
def infer_if_condition_value(expr: Node, pyversion: Tuple[int, int]) -> int:
22472250
"""Infer whether if condition is always true/false.
22482251
22492252
Return ALWAYS_TRUE if always true, ALWAYS_FALSE if always false,
@@ -2262,9 +2265,9 @@ def infer_if_condition_value(expr: Node, pyversion: int) -> int:
22622265
name = expr.name
22632266
result = TRUTH_VALUE_UNKNOWN
22642267
if name == 'PY2':
2265-
result = ALWAYS_TRUE if pyversion == 2 else ALWAYS_FALSE
2268+
result = ALWAYS_TRUE if pyversion[0] == 2 else ALWAYS_FALSE
22662269
elif name == 'PY3':
2267-
result = ALWAYS_TRUE if pyversion == 3 else ALWAYS_FALSE
2270+
result = ALWAYS_TRUE if pyversion[0] == 3 else ALWAYS_FALSE
22682271
elif name == 'MYPY':
22692272
result = ALWAYS_TRUE
22702273
if negated:

0 commit comments

Comments
 (0)