diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index b382bb57cb22..6e020d55f946 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -42,7 +42,7 @@ for full details, see :ref:`running-mypy`. ``-c PROGRAM_TEXT``, ``--command PROGRAM_TEXT`` Asks mypy to type check the provided string as a program. - + .. _config-file-flag: @@ -51,12 +51,12 @@ Config file ``--config-file CONFIG_FILE`` This flag makes mypy read configuration settings from the given file. - + By default settings are read from ``mypy.ini`` or ``setup.cfg`` in the current directory, or ``.mypy.ini`` in the user's home directory. Settings override mypy's built-in defaults and command line flags - can override settings. - + can override settings. + See :ref:`config-file` for the syntax of configuration files. ``--warn-unused-configs`` @@ -104,19 +104,19 @@ imports. all modules. For more information on what the other options do, see :ref:`Following imports `. -``--python-executable EXECUTABLE`` +``--python-executable EXECUTABLE`` This flag will have mypy collect type information from `PEP 561`_ - compliant packages installed for the Python executable ``EXECUTABLE``. + compliant packages installed for the Python executable ``EXECUTABLE``. If not provided, mypy will use PEP 561 compliant packages installed for - the Python executable running mypy. - + the Python executable running mypy. + See :ref:`installed-packages` for more on making PEP 561 compliant packages. This flag will attempt to set ``--python-version`` if not already set. ``--no-site-packages`` This flag will disable searching for `PEP 561`_ compliant packages. This will also disable searching for a usable Python executable. - + Use this flag if mypy cannot find a Python executable for the version of Python being checked, and you don't need to use PEP 561 typed packages. Otherwise, use ``--python-executable``. @@ -137,15 +137,15 @@ following flags let you modify this behavior. For more information on how to use these flags, see :ref:`version_and_platform_checks`. -``--python-version X.Y`` +``--python-version X.Y`` This flag will make mypy type check your code as if it were run under Python version X.Y. Without this option, mypy will default to using whatever version of Python is running mypy. Note that the ``-2`` and - ``--py2`` flags are aliases for ``--python-version 2.7``. - - This flag will attempt to find a Python executable of the corresponding + ``--py2`` flags are aliases for ``--python-version 2.7``. + + This flag will attempt to find a Python executable of the corresponding version to search for `PEP 561`_ compliant packages. If you'd like to - disable this, use the ``--no-site-packages`` flag (see + disable this, use the ``--no-site-packages`` flag (see :ref:`import-discovery` for more details). ``-2``, ``--py2`` @@ -154,19 +154,19 @@ For more information on how to use these flags, see :ref:`version_and_platform_c ``--platform PLATFORM`` This flag will make mypy type check your code as if it were run under the given operating system. Without this option, mypy will - default to using whatever operating system you are currently using. - + default to using whatever operating system you are currently using. + The ``PLATFORM`` parameter may be any string supported by `sys.platform `_. .. _always-true: -``--always-true NAME`` +``--always-true NAME`` This flag will treat all variables named ``NAME`` as compile-time constants that are always true. This flag may be repeated. -``--always-false NAME`` +``--always-false NAME`` This flag will treat all variables named ``NAME`` as compile-time constants that are always false. This flag may be repeated. @@ -194,7 +194,7 @@ The following options are available: mypy will output an error unless the expression is immediately used as an argument to ``cast`` or assigned to a variable with an explicit type annotation. - + In addition, declaring a variable of type ``Any`` or casting to type ``Any`` is not allowed. Note that calling functions that take parameters of type ``Any`` is still allowed. @@ -213,14 +213,14 @@ The following options are available: ``dict``) become disallowed as you should use their aliases from the typing module (such as ``List[int]`` and ``Dict[str, str]``). -``--disallow-subclassing-any`` - This flag reports an error whenever a class subclasses a value of - type ``Any``. This may occur when the base class is imported from +``--disallow-subclassing-any`` + This flag reports an error whenever a class subclasses a value of + type ``Any``. This may occur when the base class is imported from a module that doesn't exist (when using :ref:`--ignore-missing-imports `) or is ignored due to :ref:`--follow-imports=skip ` or a - ``# type: ignore`` comment on the ``import`` statement. - + ``# type: ignore`` comment on the ``import`` statement. + Since the module is silenced, the imported class is given a type of ``Any``. By default mypy will assume that the subclass correctly inherited the base class even though that may not actually be the case. This @@ -238,20 +238,20 @@ definitions or calls. This flag reports an error whenever a function with type annotations calls a function defined without annotations. -``--disallow-untyped-defs`` +``--disallow-untyped-defs`` This flag reports an error whenever it encounters a function definition without type annotations. -``--disallow-incomplete-defs`` +``--disallow-incomplete-defs`` This flag reports an error whenever it encounters a partly annotated function definition. -``--check-untyped-defs`` +``--check-untyped-defs`` This flag is less severe than the previous two options -- it type checks the body of every function, regardless of whether it has type annotations. (By default the bodies of functions without annotations are not type checked.) - + It will assume all arguments have type ``Any`` and always infer ``Any`` as the return type. @@ -349,6 +349,10 @@ Miscellaneous strictness flags This section documents any other flags that do not neatly fall under any of the above sections. +``--allow-untyped-globals`` + This flag causes mypy to suppress errors caused by not being able to fully + infer the types of global and class variables. + ``--strict`` This flag mode enables all optional error checking flags. You can see the list of flags enabled by strict mode in the full ``mypy --help`` output. @@ -422,8 +426,8 @@ beyond what incremental mode can offer, try running mypy in Mypy will also always write to the cache even when incremental mode is disabled so it can "warm up" the cache. To disable - writing to the cache, use ``--cache-dir=/dev/null`` (UNIX) - or ``--cache-dir=nul`` (Windows). + writing to the cache, use ``--cache-dir=/dev/null`` (UNIX) + or ``--cache-dir=nul`` (Windows). ``--skip-version-check`` By default, mypy will ignore cache data generated by a different @@ -431,13 +435,13 @@ beyond what incremental mode can offer, try running mypy in .. _quick-mode: -``--quick-and-dirty`` +``--quick-and-dirty`` This flag enables an experimental, unsafe variant of incremental mode. Quick mode is faster than regular incremental mode because it only re-checks modules that were modified since their cache file was last written: regular incremental mode also re-checks all modules that depend on one or more modules that were re-checked. - + Quick mode is unsafe because it may miss problems caused by a change in a dependency. Quick mode updates the cache, but regular incremental mode ignores cache files written by quick mode. @@ -475,7 +479,7 @@ in developing or debugging mypy internals. .. _warn-incomplete-stub: ``--warn-incomplete-stub`` - This flag modifies both the ``--disallow-untyped-defs`` and + This flag modifies both the ``--disallow-untyped-defs`` and ``--disallow-incomplete-defs`` flags so they also report errors if stubs in typeshed are missing type annotations or has incomplete annotations. If both flags are missing, ``--warn-incomplete-stub`` @@ -483,9 +487,9 @@ in developing or debugging mypy internals. This flag is mainly intended to be used by people who want contribute to typeshed and would like a convenient way to find gaps and omissions. - + If you want mypy to report an error when your codebase *uses* an untyped - function, whether that function is defined in typeshed or not, use the + function, whether that function is defined in typeshed or not, use the ``--disallow-untyped-call`` flag. See :ref:`untyped-definitions-and-calls` for more details. @@ -495,7 +499,7 @@ in developing or debugging mypy internals. When mypy is asked to type check ``SOURCE_FILE``, this flag makes mypy read from and type check the contents of ``SHADOW_FILE`` instead. However, diagnostics will continue to refer to ``SOURCE_FILE``. - + Specifying this argument multiple times (``--shadow-file X1 Y1 --shadow-file X2 Y2``) will allow mypy to perform multiple substitutions. @@ -507,7 +511,7 @@ in developing or debugging mypy internals. cause mypy to type check the contents of ``temp.py`` instead of ``original.py``, but error messages will still reference ``original.py``. -Report generation +Report generation ***************** If these flags are set, mypy will generate a report in the specified @@ -541,25 +545,25 @@ format into the specified directory. You must install the `lxml`_ library to generate this report. -``--junit-xml JUNIT_XML`` +``--junit-xml JUNIT_XML`` Causes mypy to generate a JUnit XML test result document with type checking results. This can make it easier to integrate mypy with continuous integration (CI) tools. -Miscellaneous +Miscellaneous ************* -``--find-occurrences CLASS.MEMBER`` +``--find-occurrences CLASS.MEMBER`` This flag will make mypy print out all usages of a class member based on static type information. This feature is experimental. -``--scripts-are-modules`` +``--scripts-are-modules`` This flag will give command line arguments that appear to be scripts (i.e. files whose name does not end in ``.py``) a module name derived from the script name rather than the fixed - name ``__main__``. - + name ``__main__``. + This lets you check more than one script in a single mypy invocation. (The default ``__main__`` is technically more correct, but if you have many scripts that import a large package, the behavior enabled @@ -568,4 +572,3 @@ Miscellaneous .. _PEP 561: https://www.python.org/dev/peps/pep-0561/ .. _lxml: https://pypi.org/project/lxml/ - diff --git a/mypy/checker.py b/mypy/checker.py index db38747bdf88..5efdd8221827 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -128,7 +128,9 @@ # mode partial types initially defined at the top level cannot be completed in # a function, and we use the 'is_function' attribute to enforce this. PartialTypeScope = NamedTuple('PartialTypeScope', [('map', Dict[Var, Context]), - ('is_function', bool)]) + ('is_function', bool), + ('is_local', bool), + ]) class TypeChecker(NodeVisitor[None], CheckerPluginInterface): @@ -3565,10 +3567,13 @@ def enter_partial_types(self, *, is_function: bool = False, Also report errors for (some) variables which still have partial types, i.e. we couldn't infer a complete type. """ - self.partial_types.append(PartialTypeScope({}, is_function)) + is_local = (self.partial_types and self.partial_types[-1].is_local) or is_function + self.partial_types.append(PartialTypeScope({}, is_function, is_local)) yield - partial_types, _ = self.partial_types.pop() + permissive = (self.options.allow_untyped_globals and not is_local) + + partial_types, _, _ = self.partial_types.pop() if not self.current_node_deferred: for var, context in partial_types.items(): # If we require local partial types, there are a few exceptions where @@ -3589,15 +3594,55 @@ def enter_partial_types(self, *, is_function: bool = False, or (is_class and self.is_defined_in_base_class(var))) if (allow_none and isinstance(var.type, PartialType) - and var.type.type is None): + and var.type.type is None + and not permissive): var.type = NoneTyp() else: - if var not in self.partial_reported: + if var not in self.partial_reported and not permissive: self.msg.need_annotation_for_var(var, context) self.partial_reported.add(var) - # Give the variable an 'Any' type to avoid generating multiple errors - # from a single missing annotation. - var.type = AnyType(TypeOfAny.from_error) + if var.type: + var.type = self.fixup_partial_type(var.type) + + def handle_partial_var_type( + self, typ: PartialType, is_lvalue: bool, node: Var, context: Context) -> Type: + """Handle a reference to a partial type through a var. + + (Used by checkexpr and checkmember.) + """ + in_scope, is_local, partial_types = self.find_partial_types_in_all_scopes(node) + if typ.type is None and in_scope: + # 'None' partial type. It has a well-defined type. In an lvalue context + # we want to preserve the knowledge of it being a partial type. + if not is_lvalue: + return NoneTyp() + else: + return typ + else: + if partial_types is not None and not self.current_node_deferred: + if in_scope: + context = partial_types[node] + if is_local or not self.options.allow_untyped_globals: + self.msg.need_annotation_for_var(node, context) + else: + # Defer the node -- we might get a better type in the outer scope + self.handle_cannot_determine_type(node.name(), context) + return self.fixup_partial_type(typ) + + def fixup_partial_type(self, typ: Type) -> Type: + """Convert a partial type that we couldn't resolve into something concrete. + + This means, for None we make it Optional[Any], and for anything else we + fill in all of the type arguments with Any. + """ + if not isinstance(typ, PartialType): + return typ + if typ.type is None: + return UnionType.make_union([AnyType(TypeOfAny.unannotated), NoneTyp()]) + else: + return Instance( + typ.type, + [AnyType(TypeOfAny.unannotated) for _ in typ.inner_types]) def is_defined_in_base_class(self, var: Var) -> bool: if var.info: @@ -3615,41 +3660,26 @@ def find_partial_types(self, var: Var) -> Optional[Dict[Var, Context]]: type originally defined in the scope. This is affected by the local_partial_types configuration option. """ - in_scope, partial_types = self.find_partial_types_in_all_scopes(var) + in_scope, _, partial_types = self.find_partial_types_in_all_scopes(var) if in_scope: return partial_types return None - def find_partial_types_in_all_scopes(self, var: Var) -> Tuple[bool, - Optional[Dict[Var, Context]]]: + def find_partial_types_in_all_scopes( + self, var: Var) -> Tuple[bool, bool, Optional[Dict[Var, Context]]]: """Look for partial type scope containing variable. - Return tuple (is the scope active, scope). + Return tuple (is the scope active, is the scope a local scope, scope). """ - active = self.partial_types - inactive = [] # type: List[PartialTypeScope] - if self.options.local_partial_types: - # All scopes within the outermost function are active. Scopes out of - # the outermost function are inactive to allow local reasoning (important - # for fine-grained incremental mode). - for i, t in enumerate(self.partial_types): - if t.is_function: - active = self.partial_types[i:] - inactive = self.partial_types[:i] - break - else: - # Not within a function -- only the innermost scope is in scope. - active = self.partial_types[-1:] - inactive = self.partial_types[:-1] - # First look within in-scope partial types. - for scope in reversed(active): - if var in scope.map: - return True, scope.map - # Then for out-of-scope partial types. - for scope in reversed(inactive): + for scope in reversed(self.partial_types): if var in scope.map: - return False, scope.map - return False, None + # All scopes within the outermost function are active. Scopes out of + # the outermost function are inactive to allow local reasoning (important + # for fine-grained incremental mode). + scope_active = (not self.options.local_partial_types + or scope.is_local == self.partial_types[-1].is_local) + return scope_active, scope.is_local, scope.map + return False, False, None def temp_node(self, t: Type, context: Optional[Context] = None) -> TempNode: """Create a temporary node with the given, fixed type.""" diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 5d9e5b841f17..ecbb356f7b4d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -165,21 +165,7 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: # Variable reference. result = self.analyze_var_ref(node, e) if isinstance(result, PartialType): - in_scope, partial_types = self.chk.find_partial_types_in_all_scopes(node) - if result.type is None and in_scope: - # 'None' partial type. It has a well-defined type. In an lvalue context - # we want to preserve the knowledge of it being a partial type. - if not lvalue: - result = NoneTyp() - else: - if partial_types is not None and not self.chk.current_node_deferred: - if in_scope: - context = partial_types[node] - self.msg.need_annotation_for_var(node, context) - else: - # Defer the node -- we might get a better type in the outer scope - self.chk.handle_cannot_determine_type(node.name(), e) - result = AnyType(TypeOfAny.special_form) + result = self.chk.handle_partial_var_type(result, lvalue, node, e) elif isinstance(node, FuncDef): # Reference to a global function. result = function_type(node, self.named_type('builtins.function')) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 3045f042cff9..2fa016e165fb 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -415,7 +415,7 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont typ = var.type if typ: if isinstance(typ, PartialType): - return handle_partial_attribute_type(typ, is_lvalue, msg, var) + return chk.handle_partial_var_type(typ, is_lvalue, var, node) t = expand_type_by_instance(typ, itype) if is_lvalue and var.is_property and not var.is_settable_property: # TODO allow setting attributes in subclass (although it is probably an error) @@ -476,20 +476,6 @@ def freeze_type_vars(member_type: Type) -> None: v.id.meta_level = 0 -def handle_partial_attribute_type(typ: PartialType, is_lvalue: bool, msg: MessageBuilder, - node: SymbolNode) -> Type: - if typ.type is None: - # 'None' partial type. It has a well-defined type -- 'None'. - # In an lvalue context we want to preserver the knowledge of - # it being a partial type. - if not is_lvalue: - return NoneTyp() - return typ - else: - msg.need_annotation_for_var(node, node) - return AnyType(TypeOfAny.from_error) - - def lookup_member_var_or_accessor(info: TypeInfo, name: str, is_lvalue: bool) -> Optional[SymbolNode]: """Find the attribute/accessor node that refers to a member of a type.""" @@ -567,8 +553,8 @@ def analyze_class_attribute_access(itype: Instance, if t: if isinstance(t, PartialType): symnode = node.node - assert symnode is not None - return handle_partial_attribute_type(t, is_lvalue, msg, symnode) + assert isinstance(symnode, Var) + return chk.handle_partial_var_type(t, is_lvalue, symnode, context) if not is_method and (isinstance(t, TypeVarType) or get_type_vars(t)): msg.fail(messages.GENERIC_INSTANCE_VAR_CLASS_ACCESS, context) is_classmethod = ((is_decorated and cast(Decorator, node.node).func.is_class) diff --git a/mypy/main.py b/mypy/main.py index 00cb1987a761..72f9c29d0d76 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -574,6 +574,10 @@ def add_invertible_flag(flag: str, strictness_group = parser.add_argument_group( title='Other strictness checks') + add_invertible_flag('--allow-untyped-globals', default=False, strict_flag=False, + help="Suppress toplevel errors caused by missing annotations", + group=strictness_group) + incremental_group = parser.add_argument_group( title='Incremental mode', description="Adjust how mypy incrementally type checks and caches modules. " diff --git a/mypy/options.py b/mypy/options.py index 066e60e73584..3f54b50fe9c8 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -20,6 +20,7 @@ class BuildType: PER_MODULE_OPTIONS = { # Please keep this list sorted + "allow_untyped_globals", "always_false", "always_true", "check_untyped_defs", @@ -147,6 +148,9 @@ def __init__(self) -> None: # Don't assume arguments with default values of None are Optional self.no_implicit_optional = False + # Suppress toplevel errors caused by missing annotations + self.allow_untyped_globals = False + # Variable names considered True self.always_true = [] # type: List[str] diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 1ace4b249fe8..890d0be2dc18 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -1397,8 +1397,8 @@ if g(C()): f = [] # E: Need type annotation for 'f' if object(): def f(): pass # E: Incompatible redefinition -f() -f(1) +f() # E: "List[Any]" not callable +f(1) # E: "List[Any]" not callable [builtins fixtures/list.pyi] [case testDefineConditionallyAsImportedAndDecorated] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 67ebfa917f97..cc631699aa04 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -19,12 +19,12 @@ main:6: error: Incompatible types in assignment (expression has type "B", variab [case testInferSimpleLvarType] import typing def f() -> None: - x = A() - y = B() - x = B() # Fail - x = A() - x = y # Fail - x = x + x = A() + y = B() + x = B() # Fail + x = A() + x = y # Fail + x = x class A: pass class B: pass [out] @@ -1291,7 +1291,7 @@ a = [] # E: Need type annotation for 'a' [case testInferListInitializedToEmptyAndReadBeforeAppend] a = [] # E: Need type annotation for 'a' if a: pass -a.xyz +a.xyz # E: "List[Any]" has no attribute "xyz" a.append('') [builtins fixtures/list.pyi] [out] @@ -1299,7 +1299,7 @@ a.append('') [case testInferListInitializedToEmptyAndIncompleteTypeInAppend] a = [] # E: Need type annotation for 'a' a.append([]) -a() +a() # E: "List[Any]" not callable [builtins fixtures/list.pyi] [out] @@ -1335,7 +1335,7 @@ a.append(1) def f() -> None: a = [] # E: Need type annotation for 'a' if a: pass - a.xyz + a.xyz # E: "List[Any]" has no attribute "xyz" a.append('') [builtins fixtures/list.pyi] [out] @@ -1392,8 +1392,9 @@ class A: self.x = [] # E: Need type annotation for 'x' class B(A): + # TODO?: This error is kind of a false positive, unfortunately @property - def x(self) -> List[int]: + def x(self) -> List[int]: # E: Signature of "x" incompatible with supertype "A" return [123] [builtins fixtures/list.pyi] [out] @@ -1435,8 +1436,8 @@ a() # E: "Dict[str, int]" not callable [case testInferDictInitializedToEmptyUsingUpdateError] a = {} # E: Need type annotation for 'a' -a.update([1, 2]) -a() +a.update([1, 2]) # E: Argument 1 to "update" of "dict" has incompatible type "List[int]"; expected "Mapping[Any, Any]" +a() # E: "Dict[Any, Any]" not callable [builtins fixtures/dict.pyi] [out] @@ -2157,8 +2158,8 @@ class A: def f(self) -> None: self.x[0] = '' -reveal_type(A().x) # E: Revealed type is 'Any' -reveal_type(A.x) # E: Revealed type is 'Any' +reveal_type(A().x) # E: Revealed type is 'builtins.dict[Any, Any]' +reveal_type(A.x) # E: Revealed type is 'builtins.dict[Any, Any]' [builtins fixtures/dict.pyi] [case testLocalPartialTypesWithGlobalInitializedToEmptyList] @@ -2179,9 +2180,9 @@ a = [] # E: Need type annotation for 'a' def f() -> None: a.append(1) - reveal_type(a) # E: Revealed type is 'Any' + reveal_type(a) # E: Revealed type is 'builtins.list[Any]' -reveal_type(a) # E: Revealed type is 'Any' +reveal_type(a) # E: Revealed type is 'builtins.list[Any]' [builtins fixtures/list.pyi] [case testLocalPartialTypesWithGlobalInitializedToEmptyList3] @@ -2191,7 +2192,7 @@ a = [] # E: Need type annotation for 'a' def f(): a.append(1) -reveal_type(a) # E: Revealed type is 'Any' +reveal_type(a) # E: Revealed type is 'builtins.list[Any]' [builtins fixtures/list.pyi] [case testLocalPartialTypesWithGlobalInitializedToEmptyDict] @@ -2212,9 +2213,9 @@ a = {} # E: Need type annotation for 'a' def f() -> None: a[0] = '' - reveal_type(a) # E: Revealed type is 'Any' + reveal_type(a) # E: Revealed type is 'builtins.dict[Any, Any]' -reveal_type(a) # E: Revealed type is 'Any' +reveal_type(a) # E: Revealed type is 'builtins.dict[Any, Any]' [builtins fixtures/dict.pyi] [case testLocalPartialTypesWithGlobalInitializedToEmptyDict3] @@ -2224,7 +2225,7 @@ a = {} # E: Need type annotation for 'a' def f(): a[0] = '' -reveal_type(a) # E: Revealed type is 'Any' +reveal_type(a) # E: Revealed type is 'builtins.dict[Any, Any]' [builtins fixtures/dict.pyi] [case testLocalPartialTypesWithNestedFunction] @@ -2341,8 +2342,7 @@ class C: a = None # E: Need type annotation for 'a' def f(self, x) -> None: - # TODO: It would be better for the type to be Any here - C.a.y # E: "None" has no attribute "y" + C.a.y # E: Item "None" of "Optional[Any]" has no attribute "y" [case testLocalPartialTypesAccessPartialNoneAttribute] # flags: --local-partial-types @@ -2350,8 +2350,7 @@ class C: a = None # E: Need type annotation for 'a' def f(self, x) -> None: - # TODO: It would be better for the type to be Any here - self.a.y # E: "None" has no attribute "y" + self.a.y # E: Item "None" of "Optional[Any]" has no attribute "y" -- Special case for assignment to '_' -- ---------------------------------- @@ -2467,3 +2466,125 @@ def f() -> None: _ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "BaseException") _ = '' # E: Incompatible types in assignment (expression has type "str", variable has type "BaseException") [builtins fixtures/exception.pyi] + +-- Tests for permissive toplevel checking +-- -------------- + +[case testPermissiveAttributeOverride1] +# flags: --allow-untyped-globals + +class A: + x = None + +class B(A): + x = 12 + +class C(A): + x = '12' + +reveal_type(A.x) # E: Revealed type is 'Union[Any, None]' +reveal_type(B.x) # E: Revealed type is 'builtins.int' +reveal_type(C.x) # E: Revealed type is 'builtins.str' + +[case testPermissiveAttributeOverride2] +# flags: --allow-untyped-globals + +class A: + x = [] + +class B(A): + x = [12] + +class C(A): + x = ['12'] + +reveal_type(A.x) # E: Revealed type is 'builtins.list[Any]' +reveal_type(B.x) # E: Revealed type is 'builtins.list[builtins.int*]' +reveal_type(C.x) # E: Revealed type is 'builtins.list[builtins.str*]' + +[builtins fixtures/list.pyi] + +[case testPermissiveAttribute] +# flags: --allow-untyped-globals + +class A: + x = [] + def f(self) -> None: + reveal_type(self.x) # E: Revealed type is 'builtins.list[Any]' + +[builtins fixtures/list.pyi] + +[case testPermissiveGlobalContainer1] +# flags: --allow-untyped-globals --local-partial-types + +import a + +[file b.py] +x = [] +y = {} + +def foo() -> None: + reveal_type(x) # E: Revealed type is 'builtins.list[Any]' + reveal_type(y) # E: Revealed type is 'builtins.dict[Any, Any]' + +[file a.py] +from b import x, y +reveal_type(x) # E: Revealed type is 'builtins.list[Any]' +reveal_type(y) # E: Revealed type is 'builtins.dict[Any, Any]' + +[builtins fixtures/dict.pyi] + +[case testPermissiveGlobalContainer2] +# flags: --allow-untyped-globals + +import a + +[file b.py] +x = [] +y = {} + +def foo() -> None: + reveal_type(x) # E: Revealed type is 'builtins.list[Any]' + reveal_type(y) # E: Revealed type is 'builtins.dict[Any, Any]' + +[file a.py] +from b import x, y +reveal_type(x) # E: Revealed type is 'builtins.list[Any]' +reveal_type(y) # E: Revealed type is 'builtins.dict[Any, Any]' + +[builtins fixtures/dict.pyi] + +[case testPermissiveGlobalContainer3] +# flags: --allow-untyped-globals --local-partial-types + +import a + +[file b.py] +x = [] +y = {} +z = y + + +[file a.py] +from b import x, y +reveal_type(x) # E: Revealed type is 'builtins.list[Any]' +reveal_type(y) # E: Revealed type is 'builtins.dict[Any, Any]' + +[builtins fixtures/dict.pyi] +[case testPermissiveGlobalContainer4] +# flags: --allow-untyped-globals + +import a + +[file b.py] +x = [] +y = {} +z = y + + +[file a.py] +from b import x, y +reveal_type(x) # E: Revealed type is 'builtins.list[Any]' +reveal_type(y) # E: Revealed type is 'builtins.dict[Any, Any]' + +[builtins fixtures/dict.pyi]