Skip to content

Commit 59740e2

Browse files
Merge branch 'master' into patch-9
2 parents 048a50e + 6feecce commit 59740e2

File tree

12 files changed

+247
-107
lines changed

12 files changed

+247
-107
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/irbuild/for_helpers.py

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
TypeAlias,
3030
Var,
3131
)
32+
from mypy.types import LiteralType, TupleType, get_proper_type, get_proper_types
3233
from mypyc.ir.ops import (
3334
ERR_NEVER,
3435
BasicBlock,
@@ -37,6 +38,7 @@
3738
IntOp,
3839
LoadAddress,
3940
LoadErrorValue,
41+
LoadLiteral,
4042
LoadMem,
4143
MethodCall,
4244
RaiseStandardError,
@@ -171,7 +173,7 @@ def for_loop_helper_with_index(
171173
body_insts: a function that generates the body of the loop.
172174
It needs a index as parameter.
173175
"""
174-
assert is_sequence_rprimitive(expr_reg.type)
176+
assert is_sequence_rprimitive(expr_reg.type), (expr_reg, expr_reg.type)
175177
target_type = builder.get_sequence_type(expr)
176178

177179
body_block = BasicBlock()
@@ -218,10 +220,9 @@ def sequence_from_generator_preallocate_helper(
218220
there is no condition list in the generator and only one original sequence with
219221
one index is allowed.
220222
221-
e.g. (1) tuple(f(x) for x in a_list/a_tuple/a_str/a_bytes)
222-
(2) list(f(x) for x in a_list/a_tuple/a_str/a_bytes)
223-
(3) [f(x) for x in a_list/a_tuple/a_str/a_bytes]
224-
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]
225226
226227
Args:
227228
empty_op_llbuilder: A function that can generate an empty sequence op when
@@ -236,23 +237,41 @@ def sequence_from_generator_preallocate_helper(
236237
implementation.
237238
"""
238239
if len(gen.sequences) == 1 and len(gen.indices) == 1 and len(gen.condlists[0]) == 0:
239-
rtype = builder.node_type(gen.sequences[0])
240-
if is_sequence_rprimitive(rtype):
241-
sequence = builder.accept(gen.sequences[0])
242-
length = get_expr_length_value(
243-
builder, gen.sequences[0], sequence, gen.line, use_pyssize_t=True
244-
)
245-
target_op = empty_op_llbuilder(length, gen.line)
246-
247-
def set_item(item_index: Value) -> None:
248-
e = builder.accept(gen.left_expr)
249-
builder.call_c(set_item_op, [target_op, item_index, e], gen.line)
250-
251-
for_loop_helper_with_index(
252-
builder, gen.indices[0], gen.sequences[0], sequence, set_item, gen.line, length
253-
)
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+
)
254273

255-
return target_op
274+
return target_op
256275
return None
257276

258277

@@ -805,7 +824,7 @@ class ForSequence(ForGenerator):
805824
def init(
806825
self, expr_reg: Value, target_type: RType, reverse: bool, length: Value | None = None
807826
) -> None:
808-
assert is_sequence_rprimitive(expr_reg.type), expr_reg
827+
assert is_sequence_rprimitive(expr_reg.type), (expr_reg, expr_reg.type)
809828
builder = self.builder
810829
# Record a Value indicating the length of the sequence, if known at compile time.
811830
self.length = length
@@ -835,7 +854,6 @@ def init(
835854
def gen_condition(self) -> None:
836855
builder = self.builder
837856
line = self.line
838-
# TODO: Don't reload the length each time when iterating an immutable sequence?
839857
if self.reverse:
840858
# If we are iterating in reverse order, we obviously need
841859
# to check that the index is still positive. Somewhat less
@@ -1248,7 +1266,7 @@ def get_expr_length_value(
12481266
builder: IRBuilder, expr: Expression, expr_reg: Value, line: int, use_pyssize_t: bool
12491267
) -> Value:
12501268
rtype = builder.node_type(expr)
1251-
assert is_sequence_rprimitive(rtype), rtype
1269+
assert is_sequence_rprimitive(rtype) or isinstance(rtype, RTuple), rtype
12521270
length = get_expr_length(expr)
12531271
if length is None:
12541272
# We cannot compute the length at compile time, so we will fetch it.

mypyc/irbuild/specialize.py

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from __future__ import annotations
1616

17-
from typing import Callable, Final, Optional
17+
from typing import Callable, Final, Optional, cast
1818

1919
from mypy.nodes import (
2020
ARG_NAMED,
@@ -40,6 +40,7 @@
4040
Call,
4141
Extend,
4242
Integer,
43+
PrimitiveDescription,
4344
RaiseStandardError,
4445
Register,
4546
SetAttr,
@@ -589,26 +590,81 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) ->
589590
if not (len(expr.args) == 2 and expr.arg_kinds == [ARG_POS, ARG_POS]):
590591
return None
591592

592-
if isinstance(expr.args[1], (RefExpr, TupleExpr)):
593-
builder.types[expr.args[0]] = AnyType(TypeOfAny.from_error)
593+
obj_expr = expr.args[0]
594+
type_expr = expr.args[1]
594595

595-
irs = builder.flatten_classes(expr.args[1])
596+
if isinstance(type_expr, TupleExpr) and not type_expr.items:
597+
# we can compile this case to a noop
598+
return builder.false()
599+
600+
if isinstance(type_expr, (RefExpr, TupleExpr)):
601+
builder.types[obj_expr] = AnyType(TypeOfAny.from_error)
602+
603+
irs = builder.flatten_classes(type_expr)
596604
if irs is not None:
597605
can_borrow = all(
598606
ir.is_ext_class and not ir.inherits_python and not ir.allow_interpreted_subclasses
599607
for ir in irs
600608
)
601-
obj = builder.accept(expr.args[0], can_borrow=can_borrow)
609+
obj = builder.accept(obj_expr, can_borrow=can_borrow)
602610
return builder.builder.isinstance_helper(obj, irs, expr.line)
603611

604-
if isinstance(expr.args[1], RefExpr):
605-
node = expr.args[1].node
612+
if isinstance(type_expr, RefExpr):
613+
node = type_expr.node
606614
if node:
607615
desc = isinstance_primitives.get(node.fullname)
608616
if desc:
609-
obj = builder.accept(expr.args[0])
617+
obj = builder.accept(obj_expr)
610618
return builder.primitive_op(desc, [obj], expr.line)
611619

620+
elif isinstance(type_expr, TupleExpr):
621+
node_names: list[str] = []
622+
for item in type_expr.items:
623+
if not isinstance(item, RefExpr):
624+
return None
625+
if item.node is None:
626+
return None
627+
if item.node.fullname not in node_names:
628+
node_names.append(item.node.fullname)
629+
630+
descs = [isinstance_primitives.get(fullname) for fullname in node_names]
631+
if None in descs:
632+
# not all types are primitive types, abort
633+
return None
634+
635+
obj = builder.accept(obj_expr)
636+
637+
retval = Register(bool_rprimitive)
638+
pass_block = BasicBlock()
639+
fail_block = BasicBlock()
640+
exit_block = BasicBlock()
641+
642+
# Chain the checks: if any succeed, jump to pass_block; else, continue
643+
for i, desc in enumerate(descs):
644+
is_last = i == len(descs) - 1
645+
next_block = fail_block if is_last else BasicBlock()
646+
builder.add_bool_branch(
647+
builder.primitive_op(cast(PrimitiveDescription, desc), [obj], expr.line),
648+
pass_block,
649+
next_block,
650+
)
651+
if not is_last:
652+
builder.activate_block(next_block)
653+
654+
# If any check passed
655+
builder.activate_block(pass_block)
656+
builder.assign(retval, builder.true(), expr.line)
657+
builder.goto(exit_block)
658+
659+
# If all checks failed
660+
builder.activate_block(fail_block)
661+
builder.assign(retval, builder.false(), expr.line)
662+
builder.goto(exit_block)
663+
664+
# Return the result
665+
builder.activate_block(exit_block)
666+
return retval
667+
612668
return None
613669

614670

mypyc/lib-rt/setup.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@ def run(self) -> None:
7373
# TODO: we need a way to share our preferred C flags and get_extension() logic with
7474
# mypyc/build.py without code duplication.
7575
setup(
76-
name="mypy-native",
77-
version="0.0.1",
7876
ext_modules=[
7977
Extension(
8078
"native_internal",
@@ -88,5 +86,5 @@ def run(self) -> None:
8886
],
8987
include_dirs=["."],
9088
)
91-
],
89+
]
9290
)

mypyc/test-data/irbuild-isinstance.test

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,31 @@ def is_tuple(x):
189189
L0:
190190
r0 = PyTuple_Check(x)
191191
return r0
192+
193+
[case testTupleOfPrimitives]
194+
from typing import Any
195+
196+
def is_instance(x: Any) -> bool:
197+
return isinstance(x, (str, int, bytes))
198+
199+
[out]
200+
def is_instance(x):
201+
x :: object
202+
r0, r1, r2 :: bit
203+
r3 :: bool
204+
L0:
205+
r0 = PyUnicode_Check(x)
206+
if r0 goto L3 else goto L1 :: bool
207+
L1:
208+
r1 = PyLong_Check(x)
209+
if r1 goto L3 else goto L2 :: bool
210+
L2:
211+
r2 = PyBytes_Check(x)
212+
if r2 goto L3 else goto L4 :: bool
213+
L3:
214+
r3 = 1
215+
goto L5
216+
L4:
217+
r3 = 0
218+
L5:
219+
return r3

0 commit comments

Comments
 (0)