Skip to content

Commit 431f9e2

Browse files
Merge branch 'master' into any-all-sum-for-helper
2 parents 44fc9af + 6feecce commit 431f9e2

25 files changed

+1504
-125
lines changed

mypy-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ typing_extensions>=4.6.0
44
mypy_extensions>=1.0.0
55
pathspec>=0.9.0
66
tomli>=1.1.0; python_version<'3.11'
7+
librt>=0.1.0

mypy/build.py

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,7 +1546,7 @@ def write_cache(
15461546
source_hash: str,
15471547
ignore_all: bool,
15481548
manager: BuildManager,
1549-
) -> tuple[str, tuple[dict[str, Any], str, str] | None]:
1549+
) -> tuple[str, tuple[dict[str, Any], str] | None]:
15501550
"""Write cache files for a module.
15511551
15521552
Note that this mypy's behavior is still correct when any given
@@ -1568,7 +1568,7 @@ def write_cache(
15681568
15691569
Returns:
15701570
A tuple containing the interface hash and inner tuple with cache meta JSON
1571-
that should be written and paths to cache files (inner tuple may be None,
1571+
that should be written and path to cache file (inner tuple may be None,
15721572
if the cache data could not be written).
15731573
"""
15741574
metastore = manager.metastore
@@ -1662,12 +1662,10 @@ def write_cache(
16621662
"ignore_all": ignore_all,
16631663
"plugin_data": plugin_data,
16641664
}
1665-
return interface_hash, (meta, meta_json, data_json)
1665+
return interface_hash, (meta, meta_json)
16661666

16671667

1668-
def write_cache_meta(
1669-
meta: dict[str, Any], manager: BuildManager, meta_json: str, data_json: str
1670-
) -> CacheMeta:
1668+
def write_cache_meta(meta: dict[str, Any], manager: BuildManager, meta_json: str) -> None:
16711669
# Write meta cache file
16721670
metastore = manager.metastore
16731671
meta_str = json_dumps(meta, manager.options.debug_cache)
@@ -1677,8 +1675,6 @@ def write_cache_meta(
16771675
# The next run will simply find the cache entry out of date.
16781676
manager.log(f"Error writing meta JSON file {meta_json}")
16791677

1680-
return cache_meta_from_dict(meta, data_json)
1681-
16821678

16831679
"""Dependency manager.
16841680
@@ -1864,9 +1860,6 @@ class State:
18641860
# List of (path, line number) tuples giving context for import
18651861
import_context: list[tuple[str, int]]
18661862

1867-
# The State from which this module was imported, if any
1868-
caller_state: State | None = None
1869-
18701863
# If caller_state is set, the line number in the caller where the import occurred
18711864
caller_line = 0
18721865

@@ -1917,7 +1910,6 @@ def __init__(
19171910
self.manager = manager
19181911
State.order_counter += 1
19191912
self.order = State.order_counter
1920-
self.caller_state = caller_state
19211913
self.caller_line = caller_line
19221914
if caller_state:
19231915
self.import_context = caller_state.import_context.copy()
@@ -2008,11 +2000,6 @@ def __init__(
20082000
self.parse_file(temporary=temporary)
20092001
self.compute_dependencies()
20102002

2011-
@property
2012-
def xmeta(self) -> CacheMeta:
2013-
assert self.meta, "missing meta on allegedly fresh module"
2014-
return self.meta
2015-
20162003
def add_ancestors(self) -> None:
20172004
if self.path is not None:
20182005
_, name = os.path.split(self.path)
@@ -2479,7 +2466,7 @@ def valid_references(self) -> set[str]:
24792466

24802467
return valid_refs
24812468

2482-
def write_cache(self) -> tuple[dict[str, Any], str, str] | None:
2469+
def write_cache(self) -> tuple[dict[str, Any], str] | None:
24832470
assert self.tree is not None, "Internal error: method must be called on parsed file only"
24842471
# We don't support writing cache files in fine-grained incremental mode.
24852472
if (
@@ -3477,14 +3464,13 @@ def process_stale_scc(graph: Graph, scc: list[str], manager: BuildManager) -> No
34773464
for id in stale:
34783465
meta_tuple = meta_tuples[id]
34793466
if meta_tuple is None:
3480-
graph[id].meta = None
34813467
continue
3482-
meta, meta_json, data_json = meta_tuple
3468+
meta, meta_json = meta_tuple
34833469
meta["dep_hashes"] = {
34843470
dep: graph[dep].interface_hash for dep in graph[id].dependencies if dep in graph
34853471
}
34863472
meta["error_lines"] = errors_by_id.get(id, [])
3487-
graph[id].meta = write_cache_meta(meta, manager, meta_json, data_json)
3473+
write_cache_meta(meta, manager, meta_json)
34883474

34893475

34903476
def sorted_components(

mypy_self_check.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ enable_error_code = ignore-without-code,redundant-expr
1313
enable_incomplete_feature = PreciseTupleTypes
1414
show_error_code_links = True
1515
warn_unreachable = True
16+
fixed_format_cache = True

mypyc/codegen/emitclass.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def dunder_attr_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
6262
"__hash__": ("tp_hash", generate_hash_wrapper),
6363
"__get__": ("tp_descr_get", generate_get_wrapper),
6464
"__getattr__": ("tp_getattro", dunder_attr_slot),
65+
"__setattr__": ("tp_setattro", dunder_attr_slot),
6566
}
6667

6768
AS_MAPPING_SLOT_DEFS: SlotTable = {

mypyc/irbuild/builder.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
Integer,
7777
IntOp,
7878
LoadStatic,
79+
MethodCall,
7980
Op,
8081
PrimitiveDescription,
8182
RaiseStandardError,
@@ -700,7 +701,12 @@ def get_assignment_target(
700701
assert False, "Unsupported lvalue: %r" % lvalue
701702

702703
def read(
703-
self, target: Value | AssignmentTarget, line: int = -1, can_borrow: bool = False
704+
self,
705+
target: Value | AssignmentTarget,
706+
line: int = -1,
707+
*,
708+
can_borrow: bool = False,
709+
allow_error_value: bool = False,
704710
) -> Value:
705711
if isinstance(target, Value):
706712
return target
@@ -716,7 +722,15 @@ def read(
716722
if isinstance(target, AssignmentTargetAttr):
717723
if isinstance(target.obj.type, RInstance) and target.obj.type.class_ir.is_ext_class:
718724
borrow = can_borrow and target.can_borrow
719-
return self.add(GetAttr(target.obj, target.attr, line, borrow=borrow))
725+
return self.add(
726+
GetAttr(
727+
target.obj,
728+
target.attr,
729+
line,
730+
borrow=borrow,
731+
allow_error_value=allow_error_value,
732+
)
733+
)
720734
else:
721735
return self.py_get_attr(target.obj, target.attr, line)
722736

@@ -735,8 +749,15 @@ def assign(self, target: Register | AssignmentTarget, rvalue_reg: Value, line: i
735749
self.add(Assign(target.register, rvalue_reg))
736750
elif isinstance(target, AssignmentTargetAttr):
737751
if isinstance(target.obj_type, RInstance):
738-
rvalue_reg = self.coerce_rvalue(rvalue_reg, target.type, line)
739-
self.add(SetAttr(target.obj, target.attr, rvalue_reg, line))
752+
setattr = target.obj_type.class_ir.get_method("__setattr__")
753+
if setattr:
754+
key = self.load_str(target.attr)
755+
boxed_reg = self.builder.box(rvalue_reg)
756+
call = MethodCall(target.obj, setattr.name, [key, boxed_reg], line)
757+
self.add(call)
758+
else:
759+
rvalue_reg = self.coerce_rvalue(rvalue_reg, target.type, line)
760+
self.add(SetAttr(target.obj, target.attr, rvalue_reg, line))
740761
else:
741762
key = self.load_str(target.attr)
742763
boxed_reg = self.builder.box(rvalue_reg)

mypyc/irbuild/expression.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
apply_function_specialization,
102102
apply_method_specialization,
103103
translate_object_new,
104+
translate_object_setattr,
104105
)
105106
from mypyc.primitives.bytes_ops import bytes_slice_op
106107
from mypyc.primitives.dict_ops import dict_get_item_op, dict_new_op, exact_dict_set_item_op
@@ -480,6 +481,12 @@ def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: Supe
480481
result = translate_object_new(builder, expr, MemberExpr(callee.call, "__new__"))
481482
if result:
482483
return result
484+
elif callee.name == "__setattr__":
485+
result = translate_object_setattr(
486+
builder, expr, MemberExpr(callee.call, "__setattr__")
487+
)
488+
if result:
489+
return result
483490
if ir.is_ext_class and ir.builtin_base is None and not ir.inherits_python:
484491
if callee.name == "__init__" and len(expr.args) == 0:
485492
# Call translates to object.__init__(self), which is a

mypyc/irbuild/for_helpers.py

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
TypeAlias,
3030
Var,
3131
)
32-
from mypy.types import Type
32+
from mypy.types import LiteralType, TupleType, Type, get_proper_type, get_proper_types
3333
from mypyc.ir.ops import (
3434
ERR_NEVER,
3535
BasicBlock,
@@ -38,6 +38,7 @@
3838
IntOp,
3939
LoadAddress,
4040
LoadErrorValue,
41+
LoadLiteral,
4142
LoadMem,
4243
MethodCall,
4344
RaiseStandardError,
@@ -172,7 +173,7 @@ def for_loop_helper_with_index(
172173
body_insts: a function that generates the body of the loop.
173174
It needs a index as parameter.
174175
"""
175-
assert is_sequence_rprimitive(expr_reg.type)
176+
assert is_sequence_rprimitive(expr_reg.type), (expr_reg, expr_reg.type)
176177
target_type = builder.get_sequence_type(expr)
177178

178179
body_block = BasicBlock()
@@ -185,7 +186,12 @@ def for_loop_helper_with_index(
185186

186187
builder.push_loop_stack(step_block, exit_block)
187188

188-
builder.goto_and_activate(condition_block)
189+
if isinstance(length, Integer) and length.value > 0:
190+
builder.goto(body_block)
191+
builder.activate_block(condition_block)
192+
else:
193+
builder.goto_and_activate(condition_block)
194+
189195
for_gen.gen_condition()
190196

191197
builder.activate_block(body_block)
@@ -214,10 +220,9 @@ def sequence_from_generator_preallocate_helper(
214220
there is no condition list in the generator and only one original sequence with
215221
one index is allowed.
216222
217-
e.g. (1) tuple(f(x) for x in a_list/a_tuple/a_str/a_bytes)
218-
(2) list(f(x) for x in a_list/a_tuple/a_str/a_bytes)
219-
(3) [f(x) for x in a_list/a_tuple/a_str/a_bytes]
220-
RTuple as an original sequence is not supported yet.
223+
e.g. (1) tuple(f(x) for x in a_list/a_tuple/a_str/a_bytes/an_rtuple)
224+
(2) list(f(x) for x in a_list/a_tuple/a_str/a_bytes/an_rtuple)
225+
(3) [f(x) for x in a_list/a_tuple/a_str/a_bytes/an_rtuple]
221226
222227
Args:
223228
empty_op_llbuilder: A function that can generate an empty sequence op when
@@ -232,23 +237,41 @@ def sequence_from_generator_preallocate_helper(
232237
implementation.
233238
"""
234239
if len(gen.sequences) == 1 and len(gen.indices) == 1 and len(gen.condlists[0]) == 0:
235-
rtype = builder.node_type(gen.sequences[0])
236-
if is_sequence_rprimitive(rtype):
237-
sequence = builder.accept(gen.sequences[0])
238-
length = get_expr_length_value(
239-
builder, gen.sequences[0], sequence, gen.line, use_pyssize_t=True
240-
)
241-
target_op = empty_op_llbuilder(length, gen.line)
242-
243-
def set_item(item_index: Value) -> None:
244-
e = builder.accept(gen.left_expr)
245-
builder.call_c(set_item_op, [target_op, item_index, e], gen.line)
246-
247-
for_loop_helper_with_index(
248-
builder, gen.indices[0], gen.sequences[0], sequence, set_item, gen.line, length
249-
)
240+
line = gen.line
241+
sequence_expr = gen.sequences[0]
242+
rtype = builder.node_type(sequence_expr)
243+
if not (is_sequence_rprimitive(rtype) or isinstance(rtype, RTuple)):
244+
return None
245+
sequence = builder.accept(sequence_expr)
246+
length = get_expr_length_value(builder, sequence_expr, sequence, line, use_pyssize_t=True)
247+
if isinstance(rtype, RTuple):
248+
# If input is RTuple, box it to tuple_rprimitive for generic iteration
249+
# TODO: this can be optimized a bit better with an unrolled ForRTuple helper
250+
proper_type = get_proper_type(builder.types[sequence_expr])
251+
assert isinstance(proper_type, TupleType), proper_type
252+
253+
get_item_ops = [
254+
(
255+
LoadLiteral(typ.value, object_rprimitive)
256+
if isinstance(typ, LiteralType)
257+
else TupleGet(sequence, i, line)
258+
)
259+
for i, typ in enumerate(get_proper_types(proper_type.items))
260+
]
261+
items = list(map(builder.add, get_item_ops))
262+
sequence = builder.new_tuple(items, line)
263+
264+
target_op = empty_op_llbuilder(length, line)
265+
266+
def set_item(item_index: Value) -> None:
267+
e = builder.accept(gen.left_expr)
268+
builder.call_c(set_item_op, [target_op, item_index, e], line)
269+
270+
for_loop_helper_with_index(
271+
builder, gen.indices[0], sequence_expr, sequence, set_item, line, length
272+
)
250273

251-
return target_op
274+
return target_op
252275
return None
253276

254277

@@ -801,7 +824,7 @@ class ForSequence(ForGenerator):
801824
def init(
802825
self, expr_reg: Value, target_type: RType, reverse: bool, length: Value | None = None
803826
) -> None:
804-
assert is_sequence_rprimitive(expr_reg.type), expr_reg
827+
assert is_sequence_rprimitive(expr_reg.type), (expr_reg, expr_reg.type)
805828
builder = self.builder
806829
# Record a Value indicating the length of the sequence, if known at compile time.
807830
self.length = length
@@ -831,7 +854,6 @@ def init(
831854
def gen_condition(self) -> None:
832855
builder = self.builder
833856
line = self.line
834-
# TODO: Don't reload the length each time when iterating an immutable sequence?
835857
if self.reverse:
836858
# If we are iterating in reverse order, we obviously need
837859
# to check that the index is still positive. Somewhat less
@@ -1213,7 +1235,7 @@ def get_expr_length_value(
12131235
builder: IRBuilder, expr: Expression, expr_reg: Value, line: int, use_pyssize_t: bool
12141236
) -> Value:
12151237
rtype = builder.node_type(expr)
1216-
assert is_sequence_rprimitive(rtype), rtype
1238+
assert is_sequence_rprimitive(rtype) or isinstance(rtype, RTuple), rtype
12171239
length = get_expr_length(expr)
12181240
if length is None:
12191241
# We cannot compute the length at compile time, so we will fetch it.

mypyc/irbuild/function.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from mypyc.ir.rtypes import (
5858
RInstance,
5959
bool_rprimitive,
60+
c_int_rprimitive,
6061
dict_rprimitive,
6162
int_rprimitive,
6263
object_rprimitive,
@@ -415,6 +416,34 @@ def generate_getattr_wrapper(builder: IRBuilder, cdef: ClassDef, getattr: FuncDe
415416
builder.add(Return(getattr_result, line))
416417

417418

419+
def generate_setattr_wrapper(builder: IRBuilder, cdef: ClassDef, setattr: FuncDef) -> None:
420+
"""
421+
Generate a wrapper function for __setattr__ that can be put into the tp_setattro slot.
422+
The wrapper takes two arguments besides self - attribute name and the new value.
423+
Returns 0 on success and -1 on failure. Restrictions are similar to the __getattr__
424+
wrapper above.
425+
426+
This one is simpler because to match interpreted python semantics it's enough to always
427+
call the user-provided function, including for names matching regular attributes.
428+
"""
429+
name = setattr.name + "__wrapper"
430+
ir = builder.mapper.type_to_ir[cdef.info]
431+
line = setattr.line
432+
433+
error_base = f'"__setattr__" not supported in class "{cdef.name}" because '
434+
if ir.allow_interpreted_subclasses:
435+
builder.error(error_base + "it allows interpreted subclasses", line)
436+
if ir.inherits_python:
437+
builder.error(error_base + "it inherits from a non-native class", line)
438+
439+
with builder.enter_method(ir, name, c_int_rprimitive, internal=True):
440+
attr_arg = builder.add_argument("attr", object_rprimitive)
441+
value_arg = builder.add_argument("value", object_rprimitive)
442+
443+
builder.gen_method_call(builder.self(), setattr.name, [attr_arg, value_arg], None, line)
444+
builder.add(Return(Integer(0, c_int_rprimitive), line))
445+
446+
418447
def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None:
419448
# Perform the function of visit_method for methods inside extension classes.
420449
name = fdef.name
@@ -483,6 +512,8 @@ def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None
483512

484513
if fdef.name == "__getattr__":
485514
generate_getattr_wrapper(builder, cdef, fdef)
515+
elif fdef.name == "__setattr__":
516+
generate_setattr_wrapper(builder, cdef, fdef)
486517

487518

488519
def handle_non_ext_method(

0 commit comments

Comments
 (0)