diff --git a/mypy/build.py b/mypy/build.py index 931cac47dccb..0eca45d6ea62 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -391,7 +391,8 @@ def __init__(self, data_dir: str, check_untyped_defs = CHECK_UNTYPED_DEFS in self.flags self.semantic_analyzer = SemanticAnalyzer(lib_path, self.errors, pyversion=pyversion, - check_untyped_defs=check_untyped_defs) + check_untyped_defs=check_untyped_defs, + lightweight_type_check=(target >= TYPE_CHECK)) self.modules = self.semantic_analyzer.modules self.semantic_analyzer_pass3 = ThirdPass(self.modules, self.errors) self.type_checker = TypeChecker(self.errors, diff --git a/mypy/semanal.py b/mypy/semanal.py index 75e32184d068..b3ae3bb4553e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -63,6 +63,7 @@ YieldFromExpr, NamedTupleExpr, NonlocalDecl, SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr, YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, COVARIANT, CONTRAVARIANT, + IntExpr, FloatExpr, UnicodeExpr, INVARIANT, UNBOUND_IMPORTED ) from mypy.visitor import NodeVisitor @@ -169,6 +170,10 @@ class SemanticAnalyzer(NodeVisitor): bound_tvars = None # type: List[SymbolTableNode] # Stack of type variables that were bound by outer classess tvar_stack = None # type: List[List[SymbolTableNode]] + # Do weak type checking in this file + weak_opts = set() # type: Set[str] + # Do lightweight type checking + lightweight_type_check = False # type: bool # Stack of functions being analyzed function_stack = None # type: List[FuncItem] @@ -193,7 +198,8 @@ def __init__(self, lib_path: List[str], errors: Errors, pyversion: Tuple[int, int], - check_untyped_defs: bool) -> None: + check_untyped_defs: bool, + lightweight_type_check: bool = False) -> None: """Construct semantic analyzer. Use lib_path to search for modules, and report analysis errors @@ -214,6 +220,7 @@ def __init__(self, self.modules = {} self.pyversion = pyversion self.check_untyped_defs = check_untyped_defs + self.lightweight_type_check = lightweight_type_check self.postpone_nested_functions_stack = [FUNCTION_BOTH_PHASES] self.postponed_functions_stack = [] self.all_exports = set() # type: Set[str] @@ -224,6 +231,7 @@ def visit_file(self, file_node: MypyFile, fnam: str) -> None: self.cur_mod_id = file_node.fullname() self.is_stub_file = fnam.lower().endswith('.pyi') self.globals = file_node.names + self.weak_opts = file_node.weak_opts if 'builtins' in self.modules: self.globals['__builtins__'] = SymbolTableNode( @@ -1043,8 +1051,11 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: s.type = self.anal_type(s.type, allow_tuple_literal) else: # For simple assignments, allow binding type aliases. + # Also set the type if the rvalue is a simple literal. if (s.type is None and len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr)): + if s.lvalues[0].is_def: + s.type = self.analyze_simple_literal_type(s.rvalue) res = analyze_type_alias(s.rvalue, self.lookup_qualified, self.lookup_fully_qualified, @@ -1070,6 +1081,29 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: isinstance(s.rvalue, (ListExpr, TupleExpr))): self.add_exports(*s.rvalue.items) + def analyze_simple_literal_type(self, rvalue: Node) -> Optional[Type]: + """Return builtins.int if rvalue is an int literal, etc.""" + if self.weak_opts or not self.lightweight_type_check or self.function_stack: + # Skip this if any weak options are set. + # Also skip if lightweight type check not requested. + # This is mostly to avoid breaking unit tests. + # Also skip inside a function; this is to avoid confusing + # the code that handles dead code due to isinstance() + # inside type variables with value restrictions (like + # AnyStr). + return None + if isinstance(rvalue, IntExpr): + return self.named_type_or_none('builtins.int') + if isinstance(rvalue, FloatExpr): + return self.named_type_or_none('builtins.float') + if isinstance(rvalue, StrExpr): + return self.named_type_or_none('builtins.str') + if isinstance(rvalue, BytesExpr): + return self.named_type_or_none('builtins.bytes') + if isinstance(rvalue, UnicodeExpr): + return self.named_type_or_none('builtins.unicode') + return None + def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: """Check if assignment creates a type alias and set it up as needed.""" # For now, type aliases only work at the top level of a module. diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index bf501819a3c6..176c808f8e90 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -575,8 +575,9 @@ A.B = None # E: Cannot assign to a type [case testAccessingClassAttributeWithTypeInferenceIssue] x = C.x # E: Cannot determine type of 'x' +def f() -> int: return 1 class C: - x = 1 + x = f() [builtins fixtures/list.py] [case testAccessingClassAttributeWithTypeInferenceIssue2] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 1e2e78062dff..96faa414e6c0 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -1492,4 +1492,4 @@ reveal_type("foo") # E: Argument 1 to "reveal_type" has incompatible type "str"; [case testRevealTypeVar] reveal_type = 1 -1 + "foo" # E: Unsupported left operand type for + ("int") +1 + "foo" # E: Unsupported operand types for + ("int" and "str") diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 0f3f90bd833a..9106232841b9 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -1043,7 +1043,7 @@ f('x') # fail [out] main: note: In function "f": main:5: error: Incompatible types in assignment (expression has type "str", variable has type "int") -main:9: error: Unsupported left operand type for + ("int") +main:9: error: Unsupported operand types for + ("int" and "str") main: note: At top level: main:12: error: Argument 1 to "f" has incompatible type "str"; expected "int" @@ -1064,7 +1064,7 @@ def top() -> None: [out] main: note: In function "f": main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") -main:10: error: Unsupported left operand type for + ("int") +main:10: error: Unsupported operand types for + ("int" and "str") main: note: In function "top": main:13: error: Argument 1 to "f" has incompatible type "str"; expected "int" @@ -1227,7 +1227,7 @@ A().f('x') # fail [out] main: note: In member "f" of class "A": main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") -main:10: error: Unsupported left operand type for + ("int") +main:10: error: Unsupported operand types for + ("int" and "str") main: note: At top level: main:13: error: Argument 1 to "f" of "A" has incompatible type "str"; expected "int" @@ -1272,7 +1272,7 @@ def f(x: Callable[..., int]) -> None: x() x(1) x(z=1) - x() + '' # E: Unsupported left operand type for + ("int") + x() + '' # E: Unsupported operand types for + ("int" and "str") [out] main: note: In function "f": @@ -1285,7 +1285,7 @@ def f(x: Callable[..., int]) -> None: [case testCastWithCallableAndArbitraryArgs] from typing import Callable, cast f = cast(Callable[..., int], None) -f(x=4) + '' # E: Unsupported left operand type for + ("int") +f(x=4) + '' # E: Unsupported operand types for + ("int" and "str") [case testCallableWithArbitraryArgsInErrorMessage] from typing import Callable diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 5dad20e5fb98..63342c9f27a7 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -1523,7 +1523,7 @@ main: note: In member "f" of class "A": [case testMultipassAndTopLevelVariable] y = x # E: Cannot determine type of 'x' y() -x = 1 +x = 1+0 [out] [case testMultipassAndDecoratedMethod] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index c5cf467397d4..26736b2f92a1 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1118,11 +1118,10 @@ def f(x: Union[str, int]): from typing import Any def f(x: Any): assert isinstance(x, int) # this should narrow x to type int - x + "foo" + x + "foo" # E: Unsupported operand types for + ("int" and "str") [builtins fixtures/isinstance.py] [out] main: note: In function "f": -main:4: error: Unsupported left operand type for + ("int") [case testIsinstanceOfGenericClassRetainsParameters] from typing import List, Union diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 6674f91ee0e0..f8f35d255d83 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -486,13 +486,12 @@ x = 1 x = 1 [case testAssignToFuncDefViaImport] -from m import * -# TODO: This is bad, we don't see what's coming from m. +from m import * # E: Incompatible import of "x" (imported name has type "int", local name has type "str") f = None # E: Need type annotation for variable x = '' [file m.py] def f(): pass -x = 1 +x = 1+0 [out] @@ -816,9 +815,11 @@ x = 0 -- Test stability under import cycles -- ---------------------------------- --- The two tests are identical except one main has 'import x' and the other 'import y'. --- Previously (before build.order_ascc() was added) one of these would fail because the --- imports were processed in the (reverse) order in which the files were encountered. +-- The first two tests are identical except one main has 'import x' +-- and the other 'import y'. Previously (before build.order_ascc() +-- was added) one of these would fail because the imports were +-- processed in the (reverse) order in which the files were +-- encountered. [case testImportCycleStability1] import x @@ -847,3 +848,114 @@ import x class Sub(x.Base): attr = x.Base.attr [out] + +-- This case isn't fixed by order_ascc(), but is fixed by the +-- lightweight type inference added to semanal.py +-- (analyze_simple_literal_type()). + +[case testImportCycleStability3] +import y +[file x.py] +class Base: + pass +def foo() -> int: + import y + reveal_type(y.Sub.attr) + return y.Sub.attr +[file y.py] +import x +class Sub(x.Base): + attr = 0 +[out] +tmp/y.py:1: note: In module imported here, +main:1: note: ... from here: +tmp/x.py: note: In function "foo": +tmp/x.py:5: error: Revealed type is 'builtins.int' + +-- This case has a symmetrical cycle, so it doesn't matter in what +-- order the files are processed. It depends on the lightweight type +-- interference. + +[case testImportCycleStability4] +import x +[file x.py] +import y +class C: + attr = '' +def foo() -> int: + return y.D.attr +[file y.py] +import x +class D: + attr = 0 +def bar() -> str: + return x.C.attr + +-- These cases test all supported literal types. + +[case testImportCycleStability5] +import y +[file x.py] +class Base: + pass +def foo() -> None: + import y + i = y.Sub.iattr # type: int + f = y.Sub.fattr # type: float + s = y.Sub.sattr # type: str + b = y.Sub.battr # type: bytes +[file y.py] +import x +class Sub(x.Base): + iattr = 0 + fattr = 0.0 + sattr = '' + battr = b'' +[out] + +[case testImportCycleStability6_python2] +import y +[file x.py] +class Base: + pass +def foo() -> None: + import y + i = y.Sub.iattr # type: int + f = y.Sub.fattr # type: float + s = y.Sub.sattr # type: str + u = y.Sub.uattr # type: unicode +[file y.py] +import x +class Sub(x.Base): + iattr = 0 + fattr = 0.0 + sattr = '' + uattr = u'' +[out] + +-- This case tests module-level variables. + +[case testImportCycleStability7] +import x +[file x.py] +def foo() -> int: + import y + reveal_type(y.value) + return y.value +[file y.py] +import x +value = 12 +[out] +main:1: note: In module imported here: +tmp/x.py: note: In function "foo": +tmp/x.py:3: error: Revealed type is 'builtins.int' + +-- This is not really cycle-related but still about the lightweight +-- type checker. + +[case testImportCycleStability8] +x = 1 # type: str +reveal_type(x) +[out] +main:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") +main:2: error: Revealed type is 'builtins.str' diff --git a/test-data/unit/check-weak-typing.test b/test-data/unit/check-weak-typing.test index 35b4b73dd382..8aa14db53b99 100644 --- a/test-data/unit/check-weak-typing.test +++ b/test-data/unit/check-weak-typing.test @@ -120,7 +120,7 @@ main: note: In function "f": [case testWeakFunction3] # mypy: weak def f(): - 1 + 'a' # E: Unsupported left operand type for + ("int") + 1 + 'a' # E: Unsupported operand types for + ("int" and "str") [out] main: note: In function "f": [case testWeakFunctionCall] diff --git a/test-data/unit/fixtures/isinstance.py b/test-data/unit/fixtures/isinstance.py index 18542713da83..c155a9724ad4 100644 --- a/test-data/unit/fixtures/isinstance.py +++ b/test-data/unit/fixtures/isinstance.py @@ -14,7 +14,9 @@ class function: pass def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass -class int: pass +class int: + def __add__(self, other: 'int') -> 'int': pass class float: pass class bool(int): pass -class str: pass +class str: + def __add__(self, other: 'str') -> 'str': pass diff --git a/test-data/unit/lib-stub/__builtin__.py b/test-data/unit/lib-stub/__builtin__.py index 11540f50d70e..47e518d14dd0 100644 --- a/test-data/unit/lib-stub/__builtin__.py +++ b/test-data/unit/lib-stub/__builtin__.py @@ -12,7 +12,10 @@ def __init__(self, x): # These are provided here for convenience. class int: pass +class float: pass + class str: pass +class unicode: pass class tuple: pass class function: pass diff --git a/test-data/unit/lib-stub/builtins.py b/test-data/unit/lib-stub/builtins.py index 29dc26d81091..9a636bf6ff92 100644 --- a/test-data/unit/lib-stub/builtins.py +++ b/test-data/unit/lib-stub/builtins.py @@ -7,8 +7,13 @@ class type: def __init__(self, x: Any) -> None: pass # These are provided here for convenience. -class int: pass -class str: pass +class int: + def __add__(self, other: 'int') -> 'int': pass +class float: pass + +class str: + def __add__(self, other: 'str') -> 'str': pass +class bytes: pass class tuple: pass class function: pass diff --git a/test-data/unit/typexport-basic.test b/test-data/unit/typexport-basic.test index 8a947b569c01..15c772c23663 100644 --- a/test-data/unit/typexport-basic.test +++ b/test-data/unit/typexport-basic.test @@ -268,11 +268,11 @@ NameExpr(4) : builtins.bool [case testInferSingleType] import typing -x = 1 +x = () [builtins fixtures/primitives.py] [out] -IntExpr(2) : builtins.int -NameExpr(2) : builtins.int +NameExpr(2) : Tuple[] +TupleExpr(2) : Tuple[] [case testInferTwoTypes] ## NameExpr @@ -287,11 +287,11 @@ NameExpr(4) : builtins.int [case testInferSingleLocalVarType] import typing def f() -> None: - x = 1 + x = () [builtins fixtures/primitives.py] [out] -IntExpr(3) : builtins.int -NameExpr(3) : builtins.int +NameExpr(3) : Tuple[] +TupleExpr(3) : Tuple[] -- Basic generics