Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into add-error-links
Browse files Browse the repository at this point in the history
  • Loading branch information
ilevkivskyi committed Jun 16, 2023
2 parents 179b553 + 21cc1c7 commit fb5d089
Show file tree
Hide file tree
Showing 21 changed files with 174 additions and 136 deletions.
2 changes: 0 additions & 2 deletions docs/source/cheat_sheet_py3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
30 changes: 26 additions & 4 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
32 changes: 15 additions & 17 deletions mypy/test/testpep561.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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)
Expand Down
35 changes: 13 additions & 22 deletions mypyc/codegen/emit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("")

Expand Down Expand Up @@ -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

Expand Down
8 changes: 2 additions & 6 deletions mypyc/codegen/emitmodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 0 additions & 3 deletions mypyc/lib-rt/CPy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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


Expand Down
20 changes: 19 additions & 1 deletion mypyc/test/test_emit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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 }"
)
11 changes: 11 additions & 0 deletions test-data/packages/typedpkg-stubs/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
13 changes: 0 additions & 13 deletions test-data/packages/typedpkg-stubs/setup.py

This file was deleted.

8 changes: 8 additions & 0 deletions test-data/packages/typedpkg/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
name = 'typedpkg'
version = '0.1'
description = 'test'

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
15 changes: 0 additions & 15 deletions test-data/packages/typedpkg/setup.py

This file was deleted.

11 changes: 11 additions & 0 deletions test-data/packages/typedpkg_ns_a/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
10 changes: 0 additions & 10 deletions test-data/packages/typedpkg_ns_a/setup.py

This file was deleted.

11 changes: 11 additions & 0 deletions test-data/packages/typedpkg_ns_b-stubs/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
14 changes: 0 additions & 14 deletions test-data/packages/typedpkg_ns_b-stubs/setup.py

This file was deleted.

8 changes: 8 additions & 0 deletions test-data/packages/typedpkg_ns_b/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
name = 'typedpkg_namespace.beta'
version = '0.1'
description = 'test'

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
10 changes: 0 additions & 10 deletions test-data/packages/typedpkg_ns_b/setup.py

This file was deleted.

5 changes: 4 additions & 1 deletion test-data/unit/check-class-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
Loading

0 comments on commit fb5d089

Please sign in to comment.