Skip to content

Commit

Permalink
[mypyc] Translate attribute access to index access for named tuples (#…
Browse files Browse the repository at this point in the history
…8665)

Fixes mypyc/mypyc#638

Previously `PyObject_GetAttr` was used for named tuples, which is slower.
  • Loading branch information
ilevkivskyi authored Apr 13, 2020
1 parent 448c447 commit 2bbe22a
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 4 deletions.
2 changes: 2 additions & 0 deletions mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,8 @@ def build_namedtuple_typeinfo(self,
tuple_base = TupleType(types, fallback)
info.tuple_type = tuple_base
info.line = line
# For use by mypyc.
info.metadata['namedtuple'] = {'fields': items.copy()}

# We can't calculate the complete fallback type until after semantic
# analysis, since otherwise base classes might be incomplete. Postpone a
Expand Down
13 changes: 10 additions & 3 deletions mypyc/irbuild/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
SetComprehension, DictionaryComprehension, SliceExpr, GeneratorExpr, CastExpr, StarExpr,
Var, RefExpr, MypyFile, TypeInfo, TypeApplication, LDEF, ARG_POS
)
from mypy.types import TupleType, get_proper_type

from mypyc.ir.ops import (
Value, TupleGet, TupleSet, PrimitiveOp, BasicBlock, OpDescription, Assign
Expand Down Expand Up @@ -93,9 +94,15 @@ def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value:
return builder.load_module(expr.node.fullname)

obj = builder.accept(expr.expr)
return builder.builder.get_attr(
obj, expr.name, builder.node_type(expr), expr.line
)
rtype = builder.node_type(expr)
# Special case: for named tuples transform attribute access to faster index access.
typ = get_proper_type(builder.types.get(expr.expr))
if isinstance(typ, TupleType) and typ.partial_fallback.type.is_named_tuple:
fields = typ.partial_fallback.type.metadata['namedtuple']['fields']
if expr.name in fields:
index = builder.builder.load_static_int(fields.index(expr.name))
return builder.gen_method_call(obj, '__getitem__', [index], rtype, expr.line)
return builder.builder.get_attr(obj, expr.name, rtype, expr.line)


def transform_super_expr(builder: IRBuilder, o: SuperExpr) -> Value:
Expand Down
31 changes: 31 additions & 0 deletions mypyc/test-data/irbuild-tuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,34 @@ L4:
r8 = None
return r8

[case testNamedTupleAttribute]
from typing import NamedTuple

NT = NamedTuple('NT', [('x', int), ('y', int)])

def f(nt: NT, b: bool) -> int:
if b:
return nt.x
return nt.y
[out]
def f(nt, b):
nt :: tuple
b :: bool
r0 :: short_int
r1 :: object
r2 :: int
r3 :: short_int
r4 :: object
r5 :: int
L0:
if b goto L1 else goto L2 :: bool
L1:
r0 = 0
r1 = nt[r0] :: tuple
r2 = unbox(int, r1)
return r2
L2:
r3 = 1
r4 = nt[r3] :: tuple
r5 = unbox(int, r4)
return r5
21 changes: 21 additions & 0 deletions mypyc/test-data/run.test
Original file line number Diff line number Diff line change
Expand Up @@ -4741,3 +4741,24 @@ assert f("a", 16) == 10
assert f("1a", 16) == 26
with assertRaises(ValueError, "invalid literal for int() with base 10: 'xyz'"):
f("xyz")

[case testNamedTupleAttributeRun]
from typing import NamedTuple

NT = NamedTuple('NT', [('x', int), ('y', int)])

def f(nt: NT) -> int:
if nt.x > nt.y:
return nt.x
return nt.y

nt = NT(1, 2)
[file driver.py]
from native import NT, nt, f

assert f(nt) == 2
assert f(NT(3, 2)) == 3

class Sub(NT):
pass
assert f(Sub(3, 2)) == 3
6 changes: 5 additions & 1 deletion mypyc/test/testutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import shutil
from typing import List, Callable, Iterator, Optional, Tuple

import pytest # type: ignore[import]

from mypy import build
from mypy.errors import CompileError
from mypy.options import Options
Expand Down Expand Up @@ -110,7 +112,9 @@ def build_ir_for_single_file(input_lines: List[str],
[result.files['__main__']], result.graph, result.types,
Mapper({'__main__': None}),
compiler_options, errors)
assert errors.num_errors == 0
if errors.num_errors:
errors.flush_errors()
pytest.fail('Errors while building IR')

module = list(modules.values())[0]
return module.functions
Expand Down

0 comments on commit 2bbe22a

Please sign in to comment.