Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into 2023-01-25-attrs-ev…
Browse files Browse the repository at this point in the history
…olve
  • Loading branch information
ikonst committed Jan 30, 2023
2 parents 0976f86 + b2cf9d1 commit babf91f
Show file tree
Hide file tree
Showing 58 changed files with 413 additions and 87 deletions.
3 changes: 2 additions & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,8 @@ def try_infer_partial_type(self, e: CallExpr) -> None:
return
var, partial_types = ret
typ = self.try_infer_partial_value_type_from_call(e, callee.name, var)
if typ is not None:
# Var may be deleted from partial_types in try_infer_partial_value_type_from_call
if typ is not None and var in partial_types:
var.type = typ
del partial_types[var]
elif isinstance(callee.expr, IndexExpr) and isinstance(callee.expr.base, RefExpr):
Expand Down
11 changes: 11 additions & 0 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,17 @@ def visit_instance(self, template: Instance) -> list[Constraint]:
actual.item, template, subtype, template, class_obj=True
)
)
if self.direction == SUPERTYPE_OF:
# Infer constraints for Type[T] via metaclass of T when it makes sense.
a_item = actual.item
if isinstance(a_item, TypeVarType):
a_item = get_proper_type(a_item.upper_bound)
if isinstance(a_item, Instance) and a_item.type.metaclass_type:
res.extend(
self.infer_constraints_from_protocol_members(
a_item.type.metaclass_type, template, actual, template
)
)

if isinstance(actual, Overloaded) and actual.fallback is not None:
actual = actual.fallback
Expand Down
2 changes: 0 additions & 2 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,6 @@
# test-data/unit/fixtures/) that provides the definition. This is used for
# generating better error messages when running mypy tests only.
SUGGESTED_TEST_FIXTURES: Final = {
"builtins.list": "list.pyi",
"builtins.dict": "dict.pyi",
"builtins.set": "set.pyi",
"builtins.tuple": "tuple.pyi",
"builtins.bool": "bool.pyi",
Expand Down
2 changes: 1 addition & 1 deletion mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ def _add_attrs_magic_attribute(
ctx.cls,
MAGIC_ATTR_NAME,
TupleType(attributes_types, fallback=attributes_type),
fullname=f"{ctx.cls.fullname}.{attr_name}",
fullname=f"{ctx.cls.fullname}.{MAGIC_ATTR_NAME}",
override_allow_incompatible=True,
is_classvar=True,
)
Expand Down
53 changes: 39 additions & 14 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,23 +625,23 @@ def add_implicit_module_attrs(self, file_node: MypyFile) -> None:
continue
# Need to construct the type ourselves, to avoid issues with __builtins__.list
# not being subscriptable or typing.List not getting bound
sym = self.lookup_qualified("__builtins__.list", Context())
if not sym:
continue
node = sym.node
if not isinstance(node, TypeInfo):
self.defer(node)
inst = self.named_type_or_none("builtins.list", [str_type])
if inst is None:
assert not self.final_iteration, "Cannot find builtins.list to add __path__"
self.defer()
return
typ = Instance(node, [str_type])
typ = inst
elif name == "__annotations__":
sym = self.lookup_qualified("__builtins__.dict", Context(), suppress_errors=True)
if not sym:
continue
node = sym.node
if not isinstance(node, TypeInfo):
self.defer(node)
inst = self.named_type_or_none(
"builtins.dict", [str_type, AnyType(TypeOfAny.special_form)]
)
if inst is None:
assert (
not self.final_iteration
), "Cannot find builtins.dict to add __annotations__"
self.defer()
return
typ = Instance(node, [str_type, AnyType(TypeOfAny.special_form)])
typ = inst
else:
assert t is not None, f"type should be specified for {name}"
typ = UnboundType(t)
Expand Down Expand Up @@ -2658,8 +2658,33 @@ def visit_import_all(self, i: ImportAll) -> None:

def visit_assignment_expr(self, s: AssignmentExpr) -> None:
s.value.accept(self)
if self.is_func_scope():
if not self.check_valid_comprehension(s):
return
self.analyze_lvalue(s.target, escape_comprehensions=True, has_explicit_value=True)

def check_valid_comprehension(self, s: AssignmentExpr) -> bool:
"""Check that assignment expression is not nested within comprehension at class scope.
class C:
[(j := i) for i in [1, 2, 3]]
is a syntax error that is not enforced by Python parser, but at later steps.
"""
for i, is_comprehension in enumerate(reversed(self.is_comprehension_stack)):
if not is_comprehension and i < len(self.locals) - 1:
if self.locals[-1 - i] is None:
self.fail(
"Assignment expression within a comprehension"
" cannot be used in a class body",
s,
code=codes.SYNTAX,
serious=True,
blocker=True,
)
return False
break
return True

def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
self.statement = s

Expand Down
8 changes: 2 additions & 6 deletions mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,13 +481,9 @@ def build_namedtuple_typeinfo(
strtype = self.api.named_type("builtins.str")
implicit_any = AnyType(TypeOfAny.special_form)
basetuple_type = self.api.named_type("builtins.tuple", [implicit_any])
dictype = self.api.named_type_or_none(
"builtins.dict", [strtype, implicit_any]
) or self.api.named_type("builtins.object")
dictype = self.api.named_type("builtins.dict", [strtype, implicit_any])
# Actual signature should return OrderedDict[str, Union[types]]
ordereddictype = self.api.named_type_or_none(
"builtins.dict", [strtype, implicit_any]
) or self.api.named_type("builtins.object")
ordereddictype = self.api.named_type("builtins.dict", [strtype, implicit_any])
fallback = self.api.named_type("builtins.tuple", [implicit_any])
# Note: actual signature should accept an invariant version of Iterable[UnionType[types]].
# but it can't be expressed. 'new' and 'len' should be callable types.
Expand Down
4 changes: 4 additions & 0 deletions mypy/stubgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
MemberExpr,
MypyFile,
NameExpr,
OpExpr,
OverloadedFuncDef,
Statement,
StrExpr,
Expand Down Expand Up @@ -402,6 +403,9 @@ def visit_list_expr(self, node: ListExpr) -> str:
def visit_ellipsis(self, node: EllipsisExpr) -> str:
return "..."

def visit_op_expr(self, o: OpExpr) -> str:
return f"{o.left.accept(self)} {o.op} {o.right.accept(self)}"


class ImportTracker:
"""Record necessary imports during stub generation."""
Expand Down
5 changes: 2 additions & 3 deletions mypy/test/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,12 @@ def parse_test_case(case: DataDrivenTestCase) -> None:
output_files.append((file_entry[0], re.compile(file_entry[1].rstrip(), re.S)))
else:
output_files.append(file_entry)
elif item.id in ("builtins", "builtins_py2"):
elif item.id == "builtins":
# Use an alternative stub file for the builtins module.
assert item.arg is not None
mpath = join(os.path.dirname(case.file), item.arg)
fnam = "builtins.pyi" if item.id == "builtins" else "__builtin__.pyi"
with open(mpath, encoding="utf8") as f:
files.append((join(base_path, fnam), f.read()))
files.append((join(base_path, "builtins.pyi"), f.read()))
elif item.id == "typing":
# Use an alternative stub file for the typing module.
assert item.arg is not None
Expand Down
54 changes: 38 additions & 16 deletions mypyc/irbuild/statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
Integer,
LoadAddress,
LoadErrorValue,
MethodCall,
RaiseStandardError,
Register,
Return,
Expand All @@ -61,6 +62,7 @@
RInstance,
exc_rtuple,
is_tagged,
none_rprimitive,
object_pointer_rprimitive,
object_rprimitive,
)
Expand Down Expand Up @@ -657,14 +659,45 @@ def transform_with(
al = "a" if is_async else ""

mgr_v = builder.accept(expr)
typ = builder.call_c(type_op, [mgr_v], line)
exit_ = builder.maybe_spill(builder.py_get_attr(typ, f"__{al}exit__", line))
value = builder.py_call(builder.py_get_attr(typ, f"__{al}enter__", line), [mgr_v], line)
is_native = isinstance(mgr_v.type, RInstance)
if is_native:
value = builder.add(MethodCall(mgr_v, f"__{al}enter__", args=[], line=line))
exit_ = None
else:
typ = builder.call_c(type_op, [mgr_v], line)
exit_ = builder.maybe_spill(builder.py_get_attr(typ, f"__{al}exit__", line))
value = builder.py_call(builder.py_get_attr(typ, f"__{al}enter__", line), [mgr_v], line)

mgr = builder.maybe_spill(mgr_v)
exc = builder.maybe_spill_assignable(builder.true())
if is_async:
value = emit_await(builder, value, line)

def maybe_natively_call_exit(exc_info: bool) -> Value:
if exc_info:
args = get_sys_exc_info(builder)
else:
none = builder.none_object()
args = [none, none, none]

if is_native:
assert isinstance(mgr_v.type, RInstance)
exit_val = builder.gen_method_call(
builder.read(mgr),
f"__{al}exit__",
arg_values=args,
line=line,
result_type=none_rprimitive,
)
else:
assert exit_ is not None
exit_val = builder.py_call(builder.read(exit_), [builder.read(mgr)] + args, line)

if is_async:
return emit_await(builder, exit_val, line)
else:
return exit_val

def try_body() -> None:
if target:
builder.assign(builder.get_assignment_target(target), value, line)
Expand All @@ -673,13 +706,7 @@ def try_body() -> None:
def except_body() -> None:
builder.assign(exc, builder.false(), line)
out_block, reraise_block = BasicBlock(), BasicBlock()
exit_val = builder.py_call(
builder.read(exit_), [builder.read(mgr)] + get_sys_exc_info(builder), line
)
if is_async:
exit_val = emit_await(builder, exit_val, line)

builder.add_bool_branch(exit_val, out_block, reraise_block)
builder.add_bool_branch(maybe_natively_call_exit(exc_info=True), out_block, reraise_block)
builder.activate_block(reraise_block)
builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO)
builder.add(Unreachable())
Expand All @@ -689,13 +716,8 @@ def finally_body() -> None:
out_block, exit_block = BasicBlock(), BasicBlock()
builder.add(Branch(builder.read(exc), exit_block, out_block, Branch.BOOL))
builder.activate_block(exit_block)
none = builder.none_object()
exit_val = builder.py_call(
builder.read(exit_), [builder.read(mgr), none, none, none], line
)
if is_async:
emit_await(builder, exit_val, line)

maybe_natively_call_exit(exc_info=False)
builder.goto_and_activate(out_block)

transform_try_finally_stmt(
Expand Down
105 changes: 105 additions & 0 deletions mypyc/test-data/irbuild-try.test
Original file line number Diff line number Diff line change
Expand Up @@ -416,3 +416,108 @@ L19:
L20:
return 1

[case testWithNativeSimple]
class DummyContext:
def __enter__(self) -> None:
pass
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
pass

def foo(x: DummyContext) -> None:
with x:
print('hello')
[out]
def DummyContext.__enter__(self):
self :: __main__.DummyContext
L0:
return 1
def DummyContext.__exit__(self, exc_type, exc_val, exc_tb):
self :: __main__.DummyContext
exc_type, exc_val, exc_tb :: object
L0:
return 1
def foo(x):
x :: __main__.DummyContext
r0 :: None
r1 :: bool
r2 :: str
r3 :: object
r4 :: str
r5, r6 :: object
r7, r8 :: tuple[object, object, object]
r9, r10, r11 :: object
r12 :: None
r13 :: object
r14 :: int32
r15 :: bit
r16 :: bool
r17 :: bit
r18, r19, r20 :: tuple[object, object, object]
r21 :: object
r22 :: None
r23 :: bit
L0:
r0 = x.__enter__()
r1 = 1
L1:
L2:
r2 = 'hello'
r3 = builtins :: module
r4 = 'print'
r5 = CPyObject_GetAttr(r3, r4)
r6 = PyObject_CallFunctionObjArgs(r5, r2, 0)
goto L8
L3: (handler for L2)
r7 = CPy_CatchError()
r1 = 0
r8 = CPy_GetExcInfo()
r9 = r8[0]
r10 = r8[1]
r11 = r8[2]
r12 = x.__exit__(r9, r10, r11)
r13 = box(None, r12)
r14 = PyObject_IsTrue(r13)
r15 = r14 >= 0 :: signed
r16 = truncate r14: int32 to builtins.bool
if r16 goto L5 else goto L4 :: bool
L4:
CPy_Reraise()
unreachable
L5:
L6:
CPy_RestoreExcInfo(r7)
goto L8
L7: (handler for L3, L4, L5)
CPy_RestoreExcInfo(r7)
r17 = CPy_KeepPropagating()
unreachable
L8:
L9:
L10:
r18 = <error> :: tuple[object, object, object]
r19 = r18
goto L12
L11: (handler for L1, L6, L7, L8)
r20 = CPy_CatchError()
r19 = r20
L12:
if r1 goto L13 else goto L14 :: bool
L13:
r21 = load_address _Py_NoneStruct
r22 = x.__exit__(r21, r21, r21)
L14:
if is_error(r19) goto L16 else goto L15
L15:
CPy_Reraise()
unreachable
L16:
goto L20
L17: (handler for L12, L13, L14, L15)
if is_error(r19) goto L19 else goto L18
L18:
CPy_RestoreExcInfo(r19)
L19:
r23 = CPy_KeepPropagating()
unreachable
L20:
return 1
17 changes: 17 additions & 0 deletions mypyc/test-data/run-generators.test
Original file line number Diff line number Diff line change
Expand Up @@ -662,3 +662,20 @@ def list_comp() -> List[int]:
[file driver.py]
from native import list_comp
assert list_comp() == [5]

[case testWithNative]
class DummyContext:
def __init__(self) -> None:
self.x = 0

def __enter__(self) -> None:
self.x += 1

def __exit__(self, exc_type, exc_value, exc_tb) -> None:
self.x -= 1

def test_basic() -> None:
context = DummyContext()
with context:
assert context.x == 1
assert context.x == 0
Loading

0 comments on commit babf91f

Please sign in to comment.