Skip to content

Commit 9366dbe

Browse files
committed
Merge remote-tracking branch 'upstream/master' into chore/st-sync-fixture-variance
2 parents bb10017 + d40f63c commit 9366dbe

File tree

11 files changed

+207
-29
lines changed

11 files changed

+207
-29
lines changed

mypy/checker.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
AssertStmt,
8181
AssignmentExpr,
8282
AssignmentStmt,
83+
AwaitExpr,
8384
Block,
8485
BreakStmt,
8586
BytesExpr,
@@ -4924,7 +4925,11 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
49244925
allow_none_func_call = is_lambda or declared_none_return or declared_any_return
49254926

49264927
# Return with a value.
4927-
if isinstance(s.expr, (CallExpr, ListExpr, TupleExpr, DictExpr, SetExpr, OpExpr)):
4928+
if (
4929+
isinstance(s.expr, (CallExpr, ListExpr, TupleExpr, DictExpr, SetExpr, OpExpr))
4930+
or isinstance(s.expr, AwaitExpr)
4931+
and isinstance(s.expr.expr, CallExpr)
4932+
):
49284933
# For expressions that (strongly) depend on type context (i.e. those that
49294934
# are handled like a function call), we allow fallback to empty type context
49304935
# in case of errors, this improves user experience in some cases,

mypy/test/helpers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ def clean_up(a: list[str]) -> list[str]:
233233
for p in prefix, prefix.replace(os.sep, "/"):
234234
if p != "/" and p != "//" and p != "\\" and p != "\\\\":
235235
ss = ss.replace(p, "")
236+
# Replace memory address with zeros
237+
if "at 0x" in ss:
238+
ss = re.sub(r"(at 0x)\w+>", r"\g<1>000000000000>", ss)
236239
# Ignore spaces at end of line.
237240
ss = re.sub(" +$", "", ss)
238241
# Remove pwd from driver.py's path

mypy/typeanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ
779779
if self.api.type.has_base("builtins.type"):
780780
self.fail("Self type cannot be used in a metaclass", t)
781781
if self.api.type.self_type is not None:
782-
if self.api.type.is_final:
782+
if self.api.type.is_final or self.api.type.is_enum and self.api.type.enum_members:
783783
return fill_typevars(self.api.type)
784784
return self.api.type.self_type.copy_modified(line=t.line, column=t.column)
785785
# TODO: verify this is unreachable and replace with an assert?

mypyc/codegen/emitclass.py

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,6 @@ def generate_class_reuse(
205205
TODO: Generalize to support a free list with up to N objects.
206206
"""
207207
assert cl.reuse_freed_instance
208-
209-
# The free list implementation doesn't support class hierarchies
210-
assert cl.is_final_class or cl.children == []
211-
212208
context = c_emitter.context
213209
name = cl.name_prefix(c_emitter.names) + "_free_instance"
214210
struct_name = cl.struct_name(c_emitter.names)
@@ -889,8 +885,21 @@ def generate_dealloc_for_class(
889885
emitter.emit_line(f"{dealloc_func_name}({cl.struct_name(emitter.names)} *self)")
890886
emitter.emit_line("{")
891887
if has_tp_finalize:
892-
emitter.emit_line("if (!PyObject_GC_IsFinalized((PyObject *)self)) {")
893-
emitter.emit_line("Py_TYPE(self)->tp_finalize((PyObject *)self);")
888+
emitter.emit_line("PyObject *type, *value, *traceback;")
889+
emitter.emit_line("PyErr_Fetch(&type, &value, &traceback);")
890+
emitter.emit_line("int res = PyObject_CallFinalizerFromDealloc((PyObject *)self);")
891+
# CPython interpreter uses PyErr_WriteUnraisable: https://docs.python.org/3/c-api/exceptions.html#c.PyErr_WriteUnraisable
892+
# However, the message is slightly different due to the way mypyc compiles classes.
893+
# CPython interpreter prints: Exception ignored in: <function F.__del__ at 0x100aed940>
894+
# mypyc prints: Exception ignored in: <slot wrapper '__del__' of 'F' objects>
895+
emitter.emit_line("if (PyErr_Occurred() != NULL) {")
896+
# Don't untrack instance if error occurred
897+
emitter.emit_line("PyErr_WriteUnraisable((PyObject *)self);")
898+
emitter.emit_line("res = -1;")
899+
emitter.emit_line("}")
900+
emitter.emit_line("PyErr_Restore(type, value, traceback);")
901+
emitter.emit_line("if (res < 0) {")
902+
emitter.emit_line("goto done;")
894903
emitter.emit_line("}")
895904
emitter.emit_line("PyObject_GC_UnTrack(self);")
896905
if cl.reuse_freed_instance:
@@ -900,6 +909,7 @@ def generate_dealloc_for_class(
900909
emitter.emit_line(f"{clear_func_name}(self);")
901910
emitter.emit_line("Py_TYPE(self)->tp_free((PyObject *)self);")
902911
emitter.emit_line("CPy_TRASHCAN_END(self)")
912+
emitter.emit_line("done: ;")
903913
emitter.emit_line("}")
904914

905915

@@ -930,30 +940,13 @@ def generate_finalize_for_class(
930940
emitter.emit_line("static void")
931941
emitter.emit_line(f"{finalize_func_name}(PyObject *self)")
932942
emitter.emit_line("{")
933-
emitter.emit_line("PyObject *type, *value, *traceback;")
934-
emitter.emit_line("PyErr_Fetch(&type, &value, &traceback);")
935943
emitter.emit_line(
936944
"{}{}{}(self);".format(
937945
emitter.get_group_prefix(del_method.decl),
938946
NATIVE_PREFIX,
939947
del_method.cname(emitter.names),
940948
)
941949
)
942-
emitter.emit_line("if (PyErr_Occurred() != NULL) {")
943-
emitter.emit_line('PyObject *del_str = PyUnicode_FromString("__del__");')
944-
emitter.emit_line(
945-
"PyObject *del_method = (del_str == NULL) ? NULL : _PyType_Lookup(Py_TYPE(self), del_str);"
946-
)
947-
# CPython interpreter uses PyErr_WriteUnraisable: https://docs.python.org/3/c-api/exceptions.html#c.PyErr_WriteUnraisable
948-
# However, the message is slightly different due to the way mypyc compiles classes.
949-
# CPython interpreter prints: Exception ignored in: <function F.__del__ at 0x100aed940>
950-
# mypyc prints: Exception ignored in: <slot wrapper '__del__' of 'F' objects>
951-
emitter.emit_line("PyErr_WriteUnraisable(del_method);")
952-
emitter.emit_line("Py_XDECREF(del_method);")
953-
emitter.emit_line("Py_XDECREF(del_str);")
954-
emitter.emit_line("}")
955-
# PyErr_Restore also clears exception raised in __del__.
956-
emitter.emit_line("PyErr_Restore(type, value, traceback);")
957950
emitter.emit_line("}")
958951

959952

mypyc/irbuild/prepare.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,13 +351,29 @@ def prepare_class_def(
351351
ir = mapper.type_to_ir[cdef.info]
352352
info = cdef.info
353353

354-
attrs = get_mypyc_attrs(cdef)
354+
attrs, attrs_lines = get_mypyc_attrs(cdef)
355355
if attrs.get("allow_interpreted_subclasses") is True:
356356
ir.allow_interpreted_subclasses = True
357357
if attrs.get("serializable") is True:
358358
# Supports copy.copy and pickle (including subclasses)
359359
ir._serializable = True
360360

361+
free_list_len = attrs.get("free_list_len")
362+
if free_list_len is not None:
363+
line = attrs_lines["free_list_len"]
364+
if ir.is_trait:
365+
errors.error('"free_list_len" can\'t be used with traits', path, line)
366+
if ir.allow_interpreted_subclasses:
367+
errors.error(
368+
'"free_list_len" can\'t be used in a class that allows interpreted subclasses',
369+
path,
370+
line,
371+
)
372+
if free_list_len == 1:
373+
ir.reuse_freed_instance = True
374+
else:
375+
errors.error(f'Unsupported value for "free_list_len": {free_list_len}', path, line)
376+
361377
# Check for subclassing from builtin types
362378
for cls in info.mro:
363379
# Special case exceptions and dicts

mypyc/irbuild/util.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ def get_mypyc_attr_literal(e: Expression) -> Any:
9696
return False
9797
elif isinstance(e, RefExpr) and e.fullname == "builtins.None":
9898
return None
99+
elif isinstance(e, IntExpr):
100+
return e.value
99101
return NotImplemented
100102

101103

@@ -110,20 +112,23 @@ def get_mypyc_attr_call(d: Expression) -> CallExpr | None:
110112
return None
111113

112114

113-
def get_mypyc_attrs(stmt: ClassDef | Decorator) -> dict[str, Any]:
115+
def get_mypyc_attrs(stmt: ClassDef | Decorator) -> tuple[dict[str, Any], dict[str, int]]:
114116
"""Collect all the mypyc_attr attributes on a class definition or a function."""
115117
attrs: dict[str, Any] = {}
118+
lines: dict[str, int] = {}
116119
for dec in stmt.decorators:
117120
d = get_mypyc_attr_call(dec)
118121
if d:
119122
for name, arg in zip(d.arg_names, d.args):
120123
if name is None:
121124
if isinstance(arg, StrExpr):
122125
attrs[arg.value] = True
126+
lines[arg.value] = d.line
123127
else:
124128
attrs[name] = get_mypyc_attr_literal(arg)
129+
lines[name] = d.line
125130

126-
return attrs
131+
return attrs, lines
127132

128133

129134
def is_extension_class(path: str, cdef: ClassDef, errors: Errors) -> bool:

mypyc/test-data/irbuild-classes.test

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,3 +1734,27 @@ class NonNative:
17341734
class InheritsPython(dict):
17351735
def __new__(cls) -> InheritsPython:
17361736
return super().__new__(cls) # E: super().__new__() not supported for classes inheriting from non-native classes
1737+
1738+
[case testClassWithFreeList]
1739+
from mypy_extensions import mypyc_attr, trait
1740+
1741+
@mypyc_attr(free_list_len=1)
1742+
class UsesFreeList:
1743+
pass
1744+
1745+
@mypyc_attr(free_list_len=None)
1746+
class NoFreeList:
1747+
pass
1748+
1749+
@mypyc_attr(free_list_len=2) # E: Unsupported value for "free_list_len": 2
1750+
class FreeListError:
1751+
pass
1752+
1753+
@trait
1754+
@mypyc_attr(free_list_len=1) # E: "free_list_len" can't be used with traits
1755+
class NonNative:
1756+
pass
1757+
1758+
@mypyc_attr(free_list_len=1, allow_interpreted_subclasses=True) # E: "free_list_len" can't be used in a class that allows interpreted subclasses
1759+
class InterpSub:
1760+
pass

mypyc/test-data/run-classes.test

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3110,7 +3110,7 @@ f = native.F()
31103110
del f
31113111

31123112
[out]
3113-
Exception ignored in: <slot wrapper '__del__' of 'F' objects>
3113+
Exception ignored in: <native.F object at 0x000000000000>
31143114
Traceback (most recent call last):
31153115
File "native.py", line 5, in __del__
31163116
raise Exception("e2")
@@ -3794,3 +3794,100 @@ assert t.native == 43
37943794
assert t.generic == "{}"
37953795
assert t.bitfield == 0x0C
37963796
assert t.default == 10
3797+
3798+
[case testPerTypeFreeList]
3799+
from __future__ import annotations
3800+
3801+
from mypy_extensions import mypyc_attr
3802+
3803+
a = []
3804+
3805+
@mypyc_attr(free_list_len=1)
3806+
class Foo:
3807+
def __init__(self, x: int) -> None:
3808+
self.x = x
3809+
a.append(x)
3810+
3811+
def test_alloc() -> None:
3812+
x: Foo | None
3813+
y: Foo | None
3814+
3815+
x = Foo(1)
3816+
assert x.x == 1
3817+
x = None
3818+
3819+
x = Foo(2)
3820+
assert x.x == 2
3821+
y = Foo(3)
3822+
assert x.x == 2
3823+
assert y.x == 3
3824+
x = None
3825+
y = None
3826+
assert a == [1, 2, 3]
3827+
3828+
x = Foo(4)
3829+
assert x.x == 4
3830+
y = Foo(5)
3831+
assert x.x == 4
3832+
assert y.x == 5
3833+
3834+
@mypyc_attr(free_list_len=1)
3835+
class Base:
3836+
def __init__(self, x: str) -> None:
3837+
self.x = x
3838+
3839+
class Deriv(Base):
3840+
def __init__(self, x: str, y: str) -> None:
3841+
super().__init__(x)
3842+
self.y = y
3843+
3844+
@mypyc_attr(free_list_len=1)
3845+
class Deriv2(Base):
3846+
def __init__(self, x: str, y: str) -> None:
3847+
super().__init__(x)
3848+
self.y = y
3849+
3850+
def test_inheritance() -> None:
3851+
x: Base | None
3852+
y: Base | None
3853+
x = Base('x' + str())
3854+
y = Base('y' + str())
3855+
y = None
3856+
d = Deriv('a' + str(), 'b' + str())
3857+
assert type(d) is Deriv
3858+
assert d.x == 'a'
3859+
assert d.y == 'b'
3860+
assert x.x == 'x'
3861+
y = Base('z' + str())
3862+
assert d.x == 'a'
3863+
assert d.y == 'b'
3864+
assert y.x == 'z'
3865+
x = None
3866+
y = None
3867+
3868+
def test_inheritance_2() -> None:
3869+
x: Base | None
3870+
y: Base | None
3871+
d: Deriv2 | None
3872+
x = Base('x' + str())
3873+
y = Base('y' + str())
3874+
y = None
3875+
d = Deriv2('a' + str(), 'b' + str())
3876+
assert type(d) is Deriv2
3877+
assert d.x == 'a'
3878+
assert d.y == 'b'
3879+
assert x.x == 'x'
3880+
d = None
3881+
d = Deriv2('c' + str(), 'd' + str())
3882+
assert type(d) is Deriv2
3883+
assert d.x == 'c'
3884+
assert d.y == 'd'
3885+
assert x.x == 'x'
3886+
y = Base('z' + str())
3887+
assert type(y) is Base
3888+
assert d.x == 'c'
3889+
assert d.y == 'd'
3890+
assert y.x == 'z'
3891+
x = None
3892+
y = None
3893+
d = None

test-data/unit/check-inference-context.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,3 +1582,5 @@ async def inner(c: Cls[T]) -> Optional[T]:
15821582

15831583
async def outer(c: Cls[T]) -> Optional[T]:
15841584
return await inner(c)
1585+
[builtins fixtures/async_await.pyi]
1586+
[typing fixtures/typing-async.pyi]

test-data/unit/check-selftype.test

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2346,3 +2346,35 @@ gc: G[D2]
23462346
reveal_type(gb.test()) # N: Revealed type is "typing.Sequence[__main__.D1]"
23472347
reveal_type(gc.test()) # N: Revealed type is "builtins.list[__main__.D2]"
23482348
[builtins fixtures/list.pyi]
2349+
2350+
[case testEnumImplicitlyFinalForSelfType]
2351+
from enum import Enum
2352+
from typing import Self
2353+
2354+
# This enum has members and so is implicitly final.
2355+
# Foo and Self are interchangeable within the class.
2356+
class Foo(Enum):
2357+
A = 1
2358+
2359+
@classmethod
2360+
def foo(cls) -> Self:
2361+
return Foo.A
2362+
2363+
@classmethod
2364+
def foo2(cls) -> Self:
2365+
return cls.bar()
2366+
2367+
@classmethod
2368+
def bar(cls) -> Foo:
2369+
...
2370+
2371+
# This enum is empty and should not be assignable to Self
2372+
class Bar(Enum):
2373+
@classmethod
2374+
def foo(cls) -> Self:
2375+
return cls.bar() # E: Incompatible return value type (got "Bar", expected "Self")
2376+
2377+
@classmethod
2378+
def bar(cls) -> Bar:
2379+
...
2380+
[builtins fixtures/classmethod.pyi]

0 commit comments

Comments
 (0)