diff --git a/docs/source/cheat_sheet_py3.rst b/docs/source/cheat_sheet_py3.rst index 31242d0ad0bc..297427e72aca 100644 --- a/docs/source/cheat_sheet_py3.rst +++ b/docs/source/cheat_sheet_py3.rst @@ -178,8 +178,6 @@ Classes class AuditedBankAccount(BankAccount): # You can optionally declare instance variables in the class body audit_log: list[str] - # This is an instance variable with a default value - auditor_name: str = "The Spanish Inquisition" def __init__(self, account_name: str, initial_balance: int = 0) -> None: super().__init__(account_name, initial_balance) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index c2c6b3555805..7af61a532b7b 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -388,7 +388,7 @@ def analyze_type_callable_member_access(name: str, typ: FunctionLike, mx: Member # See https://github.com/python/mypy/pull/1787 for more info. # TODO: do not rely on same type variables being present in all constructor overloads. result = analyze_class_attribute_access( - ret_type, name, mx, original_vars=typ.items[0].variables + ret_type, name, mx, original_vars=typ.items[0].variables, mcs_fallback=typ.fallback ) if result: return result @@ -434,17 +434,21 @@ def analyze_type_type_member_access( if isinstance(typ.item.item, Instance): item = typ.item.item.type.metaclass_type ignore_messages = False + + if item is not None: + fallback = item.type.metaclass_type or fallback + if item and not mx.is_operator: # See comment above for why operators are skipped - result = analyze_class_attribute_access(item, name, mx, override_info) + result = analyze_class_attribute_access( + item, name, mx, mcs_fallback=fallback, override_info=override_info + ) if result: if not (isinstance(get_proper_type(result), AnyType) and item.type.fallback_to_any): return result else: # We don't want errors on metaclass lookup for classes with Any fallback ignore_messages = True - if item is not None: - fallback = item.type.metaclass_type or fallback with mx.msg.filter_errors(filter_errors=ignore_messages): return _analyze_member_access(name, fallback, mx, override_info) @@ -893,6 +897,8 @@ def analyze_class_attribute_access( itype: Instance, name: str, mx: MemberContext, + *, + mcs_fallback: Instance, override_info: TypeInfo | None = None, original_vars: Sequence[TypeVarLikeType] | None = None, ) -> Type | None: @@ -919,6 +925,22 @@ def analyze_class_attribute_access( return apply_class_attr_hook(mx, hook, AnyType(TypeOfAny.special_form)) return None + if ( + isinstance(node.node, Var) + and not node.node.is_classvar + and not hook + and mcs_fallback.type.get(name) + ): + # If the same attribute is declared on the metaclass and the class but with different types, + # and the attribute on the class is not a ClassVar, + # the type of the attribute on the metaclass should take priority + # over the type of the attribute on the class, + # when the attribute is being accessed from the class object itself. + # + # Return `None` here to signify that the name should be looked up + # on the class object itself rather than the instance. + return None + is_decorated = isinstance(node.node, Decorator) is_method = is_decorated or isinstance(node.node, FuncBase) if mx.is_lvalue: diff --git a/mypy/test/testpep561.py b/mypy/test/testpep561.py index ed8674e8d5bb..b8a11d7fc8af 100644 --- a/mypy/test/testpep561.py +++ b/mypy/test/testpep561.py @@ -47,22 +47,16 @@ def virtualenv(python_executable: str = sys.executable) -> Iterator[tuple[str, s def install_package( - pkg: str, python_executable: str = sys.executable, use_pip: bool = True, editable: bool = False + pkg: str, python_executable: str = sys.executable, editable: bool = False ) -> None: """Install a package from test-data/packages/pkg/""" working_dir = os.path.join(package_path, pkg) with tempfile.TemporaryDirectory() as dir: - if use_pip: - install_cmd = [python_executable, "-m", "pip", "install"] - if editable: - install_cmd.append("-e") - install_cmd.append(".") - else: - install_cmd = [python_executable, "setup.py"] - if editable: - install_cmd.append("develop") - else: - install_cmd.append("install") + install_cmd = [python_executable, "-m", "pip", "install"] + if editable: + install_cmd.append("-e") + install_cmd.append(".") + # Note that newer versions of pip (21.3+) don't # follow this env variable, but this is for compatibility env = {"PIP_BUILD": dir} @@ -82,21 +76,25 @@ def test_pep561(testcase: DataDrivenTestCase) -> None: assert testcase.old_cwd is not None, "test was not properly set up" python = sys.executable + if sys.version_info < (3, 8) and testcase.location[-1] == "testTypedPkgSimpleEditable": + # Python 3.7 doesn't ship with new enough pip to support PEP 660 + # This is a quick hack to skip the test; we'll drop Python 3.7 support soon enough + return + assert python is not None, "Should be impossible" pkgs, pip_args = parse_pkgs(testcase.input[0]) mypy_args = parse_mypy_args(testcase.input[1]) - use_pip = True editable = False for arg in pip_args: - if arg == "no-pip": - use_pip = False - elif arg == "editable": + if arg == "editable": editable = True + else: + raise ValueError(f"Unknown pip argument: {arg}") assert pkgs, "No packages to install for PEP 561 test?" with virtualenv(python) as venv: venv_dir, python_executable = venv for pkg in pkgs: - install_package(pkg, python_executable, use_pip, editable) + install_package(pkg, python_executable, editable) cmd_line = list(mypy_args) has_program = not ("-p" in cmd_line or "--package" in cmd_line) diff --git a/mypyc/codegen/emit.py b/mypyc/codegen/emit.py index a2e3d9849dca..8114e0517219 100644 --- a/mypyc/codegen/emit.py +++ b/mypyc/codegen/emit.py @@ -345,12 +345,6 @@ def tuple_c_declaration(self, rtuple: RTuple) -> list[str]: result.append(f"{self.ctype_spaced(typ)}f{i};") i += 1 result.append(f"}} {rtuple.struct_name};") - values = self.tuple_undefined_value_helper(rtuple) - result.append( - "static {} {} = {{ {} }};".format( - self.ctype(rtuple), self.tuple_undefined_value(rtuple), "".join(values) - ) - ) result.append("#endif") result.append("") @@ -470,23 +464,20 @@ def tuple_undefined_check_cond( return check def tuple_undefined_value(self, rtuple: RTuple) -> str: - return "tuple_undefined_" + rtuple.unique_id + """Undefined tuple value suitable in an expression.""" + return f"({rtuple.struct_name}) {self.c_initializer_undefined_value(rtuple)}" - def tuple_undefined_value_helper(self, rtuple: RTuple) -> list[str]: - res = [] - # see tuple_c_declaration() - if len(rtuple.types) == 0: - return [self.c_undefined_value(int_rprimitive)] - for item in rtuple.types: - if not isinstance(item, RTuple): - res.append(self.c_undefined_value(item)) - else: - sub_list = self.tuple_undefined_value_helper(item) - res.append("{ ") - res.extend(sub_list) - res.append(" }") - res.append(", ") - return res[:-1] + def c_initializer_undefined_value(self, rtype: RType) -> str: + """Undefined value represented in a form suitable for variable initialization.""" + if isinstance(rtype, RTuple): + if not rtype.types: + # Empty tuples contain a flag so that they can still indicate + # error values. + return f"{{ {int_rprimitive.c_undefined} }}" + items = ", ".join([self.c_initializer_undefined_value(t) for t in rtype.types]) + return f"{{ {items} }}" + else: + return self.c_undefined_value(rtype) # Higher-level operations diff --git a/mypyc/codegen/emitmodule.py b/mypyc/codegen/emitmodule.py index 0e80ff6da1f2..f360fabbe8f6 100644 --- a/mypyc/codegen/emitmodule.py +++ b/mypyc/codegen/emitmodule.py @@ -51,7 +51,7 @@ from mypyc.ir.func_ir import FuncIR from mypyc.ir.module_ir import ModuleIR, ModuleIRs, deserialize_modules from mypyc.ir.ops import DeserMaps, LoadLiteral -from mypyc.ir.rtypes import RTuple, RType +from mypyc.ir.rtypes import RType from mypyc.irbuild.main import build_ir from mypyc.irbuild.mapper import Mapper from mypyc.irbuild.prepare import load_type_map @@ -1052,11 +1052,7 @@ def declare_finals( def final_definition(self, module: str, name: str, typ: RType, emitter: Emitter) -> str: static_name = emitter.static_name(name, module) # Here we rely on the fact that undefined value and error value are always the same - if isinstance(typ, RTuple): - # We need to inline because initializer must be static - undefined = "{{ {} }}".format("".join(emitter.tuple_undefined_value_helper(typ))) - else: - undefined = emitter.c_undefined_value(typ) + undefined = emitter.c_initializer_undefined_value(typ) return f"{emitter.ctype_spaced(typ)}{static_name} = {undefined};" def declare_static_pyobject(self, identifier: str, emitter: Emitter) -> None: diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index 7a3e16fe9d65..30d93be60989 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -41,7 +41,6 @@ typedef struct tuple_T3OOO { PyObject *f1; PyObject *f2; } tuple_T3OOO; -static tuple_T3OOO tuple_undefined_T3OOO = { NULL, NULL, NULL }; #endif // Our return tuple wrapper for dictionary iteration helper. @@ -52,7 +51,6 @@ typedef struct tuple_T3CIO { CPyTagged f1; // Last dict offset PyObject *f2; // Next dictionary key or value } tuple_T3CIO; -static tuple_T3CIO tuple_undefined_T3CIO = { 2, CPY_INT_TAG, NULL }; #endif // Same as above but for both key and value. @@ -64,7 +62,6 @@ typedef struct tuple_T4CIOO { PyObject *f2; // Next dictionary key PyObject *f3; // Next dictionary value } tuple_T4CIOO; -static tuple_T4CIOO tuple_undefined_T4CIOO = { 2, CPY_INT_TAG, NULL, NULL }; #endif diff --git a/mypyc/test/test_emit.py b/mypyc/test/test_emit.py index 54bf4eef3c74..e4ace3ec01f0 100644 --- a/mypyc/test/test_emit.py +++ b/mypyc/test/test_emit.py @@ -4,7 +4,7 @@ from mypyc.codegen.emit import Emitter, EmitterContext from mypyc.ir.ops import BasicBlock, Register, Value -from mypyc.ir.rtypes import int_rprimitive +from mypyc.ir.rtypes import RTuple, bool_rprimitive, int_rprimitive, str_rprimitive from mypyc.namegen import NameGenerator @@ -49,3 +49,21 @@ def test_emit_line(self) -> None: CPyStatics[1]; /* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] */\n""" ) + + def test_emit_undefined_value_for_simple_type(self) -> None: + emitter = Emitter(self.context, {}) + assert emitter.c_undefined_value(int_rprimitive) == "CPY_INT_TAG" + assert emitter.c_undefined_value(str_rprimitive) == "NULL" + assert emitter.c_undefined_value(bool_rprimitive) == "2" + + def test_emit_undefined_value_for_tuple(self) -> None: + emitter = Emitter(self.context, {}) + assert ( + emitter.c_undefined_value(RTuple([str_rprimitive, int_rprimitive, bool_rprimitive])) + == "(tuple_T3OIC) { NULL, CPY_INT_TAG, 2 }" + ) + assert emitter.c_undefined_value(RTuple([str_rprimitive])) == "(tuple_T1O) { NULL }" + assert ( + emitter.c_undefined_value(RTuple([RTuple([str_rprimitive]), bool_rprimitive])) + == "(tuple_T2T1OC) { { NULL }, 2 }" + ) diff --git a/test-data/packages/typedpkg-stubs/pyproject.toml b/test-data/packages/typedpkg-stubs/pyproject.toml new file mode 100644 index 000000000000..125816151ef8 --- /dev/null +++ b/test-data/packages/typedpkg-stubs/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = 'typedpkg-stubs' +version = '0.1' +description = 'test' + +[tool.hatch.build] +include = ["**/*.pyi"] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/test-data/packages/typedpkg-stubs/setup.py b/test-data/packages/typedpkg-stubs/setup.py deleted file mode 100644 index 4948dc6a01df..000000000000 --- a/test-data/packages/typedpkg-stubs/setup.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -This setup file installs packages to test mypy's PEP 561 implementation -""" - -from setuptools import setup - -setup( - name='typedpkg-stubs', - author="The mypy team", - version='0.1', - package_data={'typedpkg-stubs': ['sample.pyi', '__init__.pyi', 'py.typed']}, - packages=['typedpkg-stubs'], -) diff --git a/test-data/packages/typedpkg/pyproject.toml b/test-data/packages/typedpkg/pyproject.toml new file mode 100644 index 000000000000..5269c94320e1 --- /dev/null +++ b/test-data/packages/typedpkg/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = 'typedpkg' +version = '0.1' +description = 'test' + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/test-data/packages/typedpkg/setup.py b/test-data/packages/typedpkg/setup.py deleted file mode 100644 index 11bcfb11a104..000000000000 --- a/test-data/packages/typedpkg/setup.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -This setup file installs packages to test mypy's PEP 561 implementation -""" - -from setuptools import setup - -setup( - name='typedpkg', - author="The mypy team", - version='0.1', - package_data={'typedpkg': ['py.typed']}, - packages=['typedpkg', 'typedpkg.pkg'], - include_package_data=True, - zip_safe=False, -) diff --git a/test-data/packages/typedpkg_ns_a/pyproject.toml b/test-data/packages/typedpkg_ns_a/pyproject.toml new file mode 100644 index 000000000000..cc464af75b17 --- /dev/null +++ b/test-data/packages/typedpkg_ns_a/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = 'typedpkg_namespace.alpha' +version = '0.1' +description = 'test' + +[tool.hatch.build] +include = ["**/*.py", "**/*.pyi", "**/py.typed"] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/test-data/packages/typedpkg_ns_a/setup.py b/test-data/packages/typedpkg_ns_a/setup.py deleted file mode 100644 index 3dab731cada9..000000000000 --- a/test-data/packages/typedpkg_ns_a/setup.py +++ /dev/null @@ -1,10 +0,0 @@ -from setuptools import setup - -setup( - name='typedpkg_namespace.alpha', - version='1.0.0', - namespace_packages=['typedpkg_ns'], - zip_safe=False, - package_data={'typedpkg_ns.a': ['py.typed']}, - packages=['typedpkg_ns.a'], -) diff --git a/test-data/packages/typedpkg_ns_b-stubs/pyproject.toml b/test-data/packages/typedpkg_ns_b-stubs/pyproject.toml new file mode 100644 index 000000000000..d5275d1ed8b3 --- /dev/null +++ b/test-data/packages/typedpkg_ns_b-stubs/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = 'typedpkg_ns-stubs' +version = '0.1' +description = 'test' + +[tool.hatch.build] +include = ["**/*.pyi"] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/test-data/packages/typedpkg_ns_b-stubs/setup.py b/test-data/packages/typedpkg_ns_b-stubs/setup.py deleted file mode 100644 index a5d7df83eeea..000000000000 --- a/test-data/packages/typedpkg_ns_b-stubs/setup.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -This setup file installs packages to test mypy's PEP 561 implementation -""" - -from distutils.core import setup - -setup( - name='typedpkg_ns_b-stubs', - author="The mypy team", - version='0.1', - namespace_packages=['typedpkg_ns-stubs'], - package_data={'typedpkg_ns-stubs.b': ['__init__.pyi', 'bbb.pyi']}, - packages=['typedpkg_ns-stubs.b'], -) diff --git a/test-data/packages/typedpkg_ns_b/pyproject.toml b/test-data/packages/typedpkg_ns_b/pyproject.toml new file mode 100644 index 000000000000..8567af11152e --- /dev/null +++ b/test-data/packages/typedpkg_ns_b/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = 'typedpkg_namespace.beta' +version = '0.1' +description = 'test' + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/test-data/packages/typedpkg_ns_b/setup.py b/test-data/packages/typedpkg_ns_b/setup.py deleted file mode 100644 index 4f0d0d954a73..000000000000 --- a/test-data/packages/typedpkg_ns_b/setup.py +++ /dev/null @@ -1,10 +0,0 @@ -from setuptools import setup - -setup( - name='typedpkg_namespace.beta', - version='1.0.0', - namespace_packages=['typedpkg_ns'], - zip_safe=False, - package_data={'typedpkg_ns.b': []}, - packages=['typedpkg_ns.b'], -) diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index 8ae7f6555f9d..ab2f5f3f6b48 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -325,7 +325,10 @@ class X(NamedTuple): reveal_type(X._fields) # N: Revealed type is "Tuple[builtins.str, builtins.str]" reveal_type(X._field_types) # N: Revealed type is "builtins.dict[builtins.str, Any]" reveal_type(X._field_defaults) # N: Revealed type is "builtins.dict[builtins.str, Any]" -reveal_type(X.__annotations__) # N: Revealed type is "builtins.dict[builtins.str, Any]" + +# In typeshed's stub for builtins.pyi, __annotations__ is `dict[str, Any]`, +# but it's inferred as `Mapping[str, object]` here due to the fixture we're using +reveal_type(X.__annotations__) # N: Revealed type is "typing.Mapping[builtins.str, builtins.object]" [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index cfd0cc3a4ec6..2c80eb7b49bc 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4540,6 +4540,39 @@ def f(TA: Type[A]): reveal_type(TA) # N: Revealed type is "Type[__main__.A]" reveal_type(TA.x) # N: Revealed type is "builtins.int" +[case testMetaclassConflictingInstanceVars] +from typing import ClassVar + +class Meta(type): + foo: int + bar: int + eggs: ClassVar[int] = 42 + spam: ClassVar[int] = 42 + +class Foo(metaclass=Meta): + foo: str + bar: ClassVar[str] = 'bar' + eggs: str + spam: ClassVar[str] = 'spam' + +reveal_type(Foo.foo) # N: Revealed type is "builtins.int" +reveal_type(Foo.bar) # N: Revealed type is "builtins.str" +reveal_type(Foo.eggs) # N: Revealed type is "builtins.int" +reveal_type(Foo.spam) # N: Revealed type is "builtins.str" + +class MetaSub(Meta): ... + +class Bar(metaclass=MetaSub): + foo: str + bar: ClassVar[str] = 'bar' + eggs: str + spam: ClassVar[str] = 'spam' + +reveal_type(Bar.foo) # N: Revealed type is "builtins.int" +reveal_type(Bar.bar) # N: Revealed type is "builtins.str" +reveal_type(Bar.eggs) # N: Revealed type is "builtins.int" +reveal_type(Bar.spam) # N: Revealed type is "builtins.str" + [case testSubclassMetaclass] class M1(type): x = 0 diff --git a/test-data/unit/pep561.test b/test-data/unit/pep561.test index 8c401cfc3c51..e8ebbd03dca7 100644 --- a/test-data/unit/pep561.test +++ b/test-data/unit/pep561.test @@ -72,15 +72,6 @@ reveal_type(a) [out] testStubPrecedence.py:5: note: Revealed type is "builtins.list[builtins.str]" -[case testTypedPkgSimpleEgg] -# pkgs: typedpkg; no-pip -from typedpkg.sample import ex -from typedpkg import dne -a = ex(['']) -reveal_type(a) -[out] -testTypedPkgSimpleEgg.py:5: note: Revealed type is "builtins.tuple[builtins.str, ...]" - [case testTypedPkgSimpleEditable] # pkgs: typedpkg; editable from typedpkg.sample import ex @@ -90,15 +81,6 @@ reveal_type(a) [out] testTypedPkgSimpleEditable.py:5: note: Revealed type is "builtins.tuple[builtins.str, ...]" -[case testTypedPkgSimpleEditableEgg] -# pkgs: typedpkg; editable; no-pip -from typedpkg.sample import ex -from typedpkg import dne -a = ex(['']) -reveal_type(a) -[out] -testTypedPkgSimpleEditableEgg.py:5: note: Revealed type is "builtins.tuple[builtins.str, ...]" - [case testTypedPkgNamespaceImportFrom] # pkgs: typedpkg, typedpkg_ns_a from typedpkg.pkg.aaa import af diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 7108734102d1..93ff2da9f7b9 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1987,6 +1987,19 @@ def good9(foo1: Foo[Concatenate[int, P]], foo2: Foo[[int, str, bytes]], *args: P [out] _testStrictEqualitywithParamSpec.py:11: error: Non-overlapping equality check (left operand type: "Foo[[int]]", right operand type: "Bar[[int]]") +[case testInferenceOfDunderDictOnClassObjects] +class Foo: ... +reveal_type(Foo.__dict__) +reveal_type(Foo().__dict__) +Foo.__dict__ = {} +Foo().__dict__ = {} + +[out] +_testInferenceOfDunderDictOnClassObjects.py:2: note: Revealed type is "types.MappingProxyType[builtins.str, Any]" +_testInferenceOfDunderDictOnClassObjects.py:3: note: Revealed type is "builtins.dict[builtins.str, Any]" +_testInferenceOfDunderDictOnClassObjects.py:4: error: Property "__dict__" defined in "type" is read-only +_testInferenceOfDunderDictOnClassObjects.py:4: error: Incompatible types in assignment (expression has type "Dict[, ]", variable has type "MappingProxyType[str, Any]") + [case testTypeVarTuple] # flags: --enable-incomplete-feature=TypeVarTuple --enable-incomplete-feature=Unpack --python-version=3.11 from typing import Any, Callable, Unpack, TypeVarTuple