From 67a7742979a5d7279ab5e1d2504f7b830c0cbc8c Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Mon, 15 Jun 2020 17:07:46 -0300 Subject: [PATCH 01/15] [mypyc] Speed up in operations for list/tuple When right hand side of a in/not in operation is a literal list/tuple, simplify it into simpler direct equality comparision expressions and use binary and/or to join them. Yields speedup of up to 46% in micro benchmarks. --- mypyc/irbuild/expression.py | 25 +++++++- mypyc/test-data/run.test | 119 ++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 9daf2b3028750..80a2b95b9d595 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -4,6 +4,7 @@ and mypyc.irbuild.builder. """ +import functools from typing import List, Optional, Union from mypy.nodes import ( @@ -352,8 +353,30 @@ def transform_conditional_expr(builder: IRBuilder, expr: ConditionalExpr) -> Val def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: - # TODO: Don't produce an expression when used in conditional context + # x in (...)/[...] + if e.operators[0] in ['in', 'not in'] and isinstance(e.operands[1], (TupleExpr, ListExpr)): + cmp_op = '==' if e.operators[0] == 'in' else '!=' + items = e.operands[1].items + n_items = len(items) + # x in []/() -> False + if n_items == 0: + name = 'builtins.False' if e.operators[0] == 'in' else 'builtins.True' + return builder.add(PrimitiveOp([], name_ref_ops[name], e.line)) + # # x in [y]/(y) -> x == y + # # x not in [y]/(y) -> x != y + elif n_items == 1: + e.operators = [cmp_op] + e.operands[1] = items[0] + # x in y -> x == y[0] or ... or x == y[n] + # 16 is arbitrarily chosen to limit code size + elif n_items < 16: + bin_op = 'or' if e.operators[0] == 'in' else 'and' + lhs = e.operands[0] + exprs = (ComparisonExpr([cmp_op], [lhs, item]) for item in items) + or_expr = functools.reduce(lambda left, right: OpExpr(bin_op, left, right), exprs) + return builder.accept(or_expr) + # TODO: Don't produce an expression when used in conditional context # All of the trickiness here is due to support for chained conditionals # (`e1 < e2 > e3`, etc). `e1 < e2 > e3` is approximately equivalent to # `e1 < e2 and e2 > e3` except that `e2` is only evaluated once. diff --git a/mypyc/test-data/run.test b/mypyc/test-data/run.test index 8c03352d22d69..f81aebee73041 100644 --- a/mypyc/test-data/run.test +++ b/mypyc/test-data/run.test @@ -4918,3 +4918,122 @@ assert f(NT(3, 2)) == 3 class Sub(NT): pass assert f(Sub(3, 2)) == 3 + +[case testOperatorInExpression] + +def tuple_in_int0(i: int) -> bool: + return i in [] + +def tuple_in_int1(i: int) -> bool: + return i in (1,) + +def tuple_in_int3(i: int) -> bool: + return i in (1, 2, 3) + +def tuple_not_in_int0(i: int) -> bool: + return i not in [] + +def tuple_not_in_int1(i: int) -> bool: + return i not in (1,) + +def tuple_not_in_int3(i: int) -> bool: + return i not in (1, 2, 3) + +def tuple_in_str(s: "str") -> bool: + return s in ("foo", "bar", "baz") + +def tuple_not_in_str(s: "str") -> bool: + return s not in ("foo", "bar", "baz") + +def list_in_int0(i: int) -> bool: + return i in [] + +def list_in_int1(i: int) -> bool: + return i in (1,) + +def list_in_int3(i: int) -> bool: + return i in (1, 2, 3) + +def list_not_in_int0(i: int) -> bool: + return i not in [] + +def list_not_in_int1(i: int) -> bool: + return i not in (1,) + +def list_not_in_int3(i: int) -> bool: + return i not in (1, 2, 3) + +def list_in_str(s: "str") -> bool: + return s in ("foo", "bar", "baz") + +def list_not_in_str(s: "str") -> bool: + return s not in ("foo", "bar", "baz") + +def list_in_mixed(i: object): + return i in [[], (), "", 0, 0.0, False, 0j, {}, set(), type] + +[file driver.py] + +from native import * + +assert not tuple_in_int0(0) +assert not tuple_in_int1(0) +assert tuple_in_int1(1) +assert not tuple_in_int3(0) +assert tuple_in_int3(1) +assert tuple_in_int3(2) +assert tuple_in_int3(3) +assert not tuple_in_int3(4) + +assert tuple_not_in_int0(0) +assert tuple_not_in_int1(0) +assert not tuple_not_in_int1(1) +assert tuple_not_in_int3(0) +assert not tuple_not_in_int3(1) +assert not tuple_not_in_int3(2) +assert not tuple_not_in_int3(3) +assert tuple_not_in_int3(4) + +assert tuple_in_str("foo") +assert tuple_in_str("bar") +assert tuple_in_str("baz") +assert not tuple_in_str("apple") +assert not tuple_in_str("pie") +assert not tuple_in_str("\0") +assert not tuple_in_str("") + +assert not list_in_int0(0) +assert not list_in_int1(0) +assert list_in_int1(1) +assert not list_in_int3(0) +assert list_in_int3(1) +assert list_in_int3(2) +assert list_in_int3(3) +assert not list_in_int3(4) + +assert list_not_in_int0(0) +assert list_not_in_int1(0) +assert not list_not_in_int1(1) +assert list_not_in_int3(0) +assert not list_not_in_int3(1) +assert not list_not_in_int3(2) +assert not list_not_in_int3(3) +assert list_not_in_int3(4) + +assert list_in_str("foo") +assert list_in_str("bar") +assert list_in_str("baz") +assert not list_in_str("apple") +assert not list_in_str("pie") +assert not list_in_str("\0") +assert not list_in_str("") + +assert list_in_mixed(0) +assert list_in_mixed([]) +assert list_in_mixed({}) +assert list_in_mixed(()) +assert list_in_mixed(False) +assert list_in_mixed(0.0) +assert not list_in_mixed([1]) +assert not list_in_mixed(object) +assert list_in_mixed(type) \ No newline at end of file From 4c5dd786c54eaae86698af46567cd37b2f14b7a5 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Mon, 15 Jun 2020 17:37:48 -0300 Subject: [PATCH 02/15] Inline reduce This makes it easier to add type annotations for subtypes. --- mypyc/irbuild/expression.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 80a2b95b9d595..0f61e64391607 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -4,7 +4,6 @@ and mypyc.irbuild.builder. """ -import functools from typing import List, Optional, Union from mypy.nodes import ( @@ -373,7 +372,9 @@ def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: bin_op = 'or' if e.operators[0] == 'in' else 'and' lhs = e.operands[0] exprs = (ComparisonExpr([cmp_op], [lhs, item]) for item in items) - or_expr = functools.reduce(lambda left, right: OpExpr(bin_op, left, right), exprs) + or_expr: Expression = next(exprs) + for expr in exprs: + or_expr = OpExpr(bin_op, or_expr, expr) return builder.accept(or_expr) # TODO: Don't produce an expression when used in conditional context From 186d50d99b2ff6bda35589e5228ed1b31215dd35 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Mon, 15 Jun 2020 17:45:23 -0300 Subject: [PATCH 03/15] Use type comment for Python 3.5 compatibility --- mypyc/irbuild/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 0f61e64391607..be93a94d4d558 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -372,7 +372,7 @@ def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: bin_op = 'or' if e.operators[0] == 'in' else 'and' lhs = e.operands[0] exprs = (ComparisonExpr([cmp_op], [lhs, item]) for item in items) - or_expr: Expression = next(exprs) + or_expr = next(exprs) # type: Expression for expr in exprs: or_expr = OpExpr(bin_op, or_expr, expr) return builder.accept(or_expr) From 1572ee0fe035d9173930142fe0005b830e723b9e Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Fri, 19 Jun 2020 17:28:18 -0300 Subject: [PATCH 04/15] Shortcut typeless Comparision/OpExpr as bool --- mypyc/irbuild/builder.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 17559b90bcdfa..cd1feadaede8c 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -19,7 +19,8 @@ from mypy.nodes import ( MypyFile, SymbolNode, Statement, OpExpr, IntExpr, NameExpr, LDEF, Var, UnaryExpr, CallExpr, IndexExpr, Expression, MemberExpr, RefExpr, Lvalue, TupleExpr, - TypeInfo, Decorator, OverloadedFuncDef, StarExpr, GDEF, ARG_POS, ARG_NAMED + TypeInfo, Decorator, OverloadedFuncDef, StarExpr, GDEF, ARG_POS, ARG_NAMED, + ComparisonExpr ) from mypy.types import ( Type, Instance, TupleType, UninhabitedType, get_proper_type @@ -39,7 +40,7 @@ from mypyc.ir.rtypes import ( RType, RTuple, RInstance, int_rprimitive, dict_rprimitive, none_rprimitive, is_none_rprimitive, object_rprimitive, is_object_rprimitive, - str_rprimitive, + str_rprimitive, bool_rprimitive, ) from mypyc.ir.func_ir import FuncIR, INVALID_FUNC_DEF from mypyc.ir.class_ir import ClassIR, NonExtClassInfo @@ -834,6 +835,8 @@ def node_type(self, node: Expression) -> RType: # TODO: Don't special case IntExpr return int_rprimitive if node not in self.types: + if isinstance(node, (ComparisonExpr, OpExpr)): + return bool_rprimitive return object_rprimitive mypy_type = self.types[node] return self.type_to_rtype(mypy_type) From c4ee283932ef984d10d7fda731e5db5d663fb670 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Fri, 19 Jun 2020 17:29:16 -0300 Subject: [PATCH 05/15] Order of comparisions by most likely --- mypyc/irbuild/expression.py | 38 +++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index be93a94d4d558..da7bcfc2ddd9c 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -353,29 +353,43 @@ def transform_conditional_expr(builder: IRBuilder, expr: ConditionalExpr) -> Val def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: # x in (...)/[...] + # x not in (...)/[...] if e.operators[0] in ['in', 'not in'] and isinstance(e.operands[1], (TupleExpr, ListExpr)): - cmp_op = '==' if e.operators[0] == 'in' else '!=' items = e.operands[1].items n_items = len(items) - # x in []/() -> False - if n_items == 0: - name = 'builtins.False' if e.operators[0] == 'in' else 'builtins.True' - return builder.add(PrimitiveOp([], name_ref_ops[name], e.line)) - # # x in [y]/(y) -> x == y - # # x not in [y]/(y) -> x != y - elif n_items == 1: - e.operators = [cmp_op] - e.operands[1] = items[0] # x in y -> x == y[0] or ... or x == y[n] + # x not in y -> x != y[0] and ... and x != y[n] # 16 is arbitrarily chosen to limit code size - elif n_items < 16: - bin_op = 'or' if e.operators[0] == 'in' else 'and' + if 1 < n_items < 16: + if e.operators[0] == 'in': + bin_op = 'or' + cmp_op = '==' + else: + bin_op = 'and' + cmp_op = '!=' lhs = e.operands[0] exprs = (ComparisonExpr([cmp_op], [lhs, item]) for item in items) or_expr = next(exprs) # type: Expression for expr in exprs: or_expr = OpExpr(bin_op, or_expr, expr) return builder.accept(or_expr) + # x in [y]/(y) -> x == y + # x not in [y]/(y) -> x != y + elif n_items == 1: + if e.operators[0] == 'in': + cmp_op = '==' + else: + cmp_op = '!=' + e.operators = [cmp_op] + e.operands[1] = items[0] + # x in []/() -> False + # x not in []/() -> True + elif n_items == 0: + if e.operators[0] == 'in': + name = 'builtins.False' + else: + name = 'builtins.True' + return builder.add(PrimitiveOp([], name_ref_ops[name], e.line)) # TODO: Don't produce an expression when used in conditional context # All of the trickiness here is due to support for chained conditionals From 70c8cd5d4a4941cf70731c76812f51d08bd51474 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Fri, 19 Jun 2020 17:29:20 -0300 Subject: [PATCH 06/15] Add an IR test for in tuple/list literal --- mypyc/test-data/irbuild-tuple.test | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index e7a8d09d73c38..99e2d633fb8a5 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -193,3 +193,38 @@ L2: r4 = CPySequenceTuple_GetItem(nt, r3) r5 = unbox(int, r4) return r5 +[case testTupleOperatorIn] +def f(i: int) -> bool: + return i in [1, 2, 3] +[out] +def f(i): + i :: int + r0, r1 :: bool + r2 :: short_int + r3 :: bool + r4 :: short_int + r5 :: bool + r6 :: short_int + r7 :: bool +L0: + r2 = 1 + r3 = i == r2 :: int + if r3 goto L1 else goto L2 :: bool +L1: + r1 = r3 + goto L3 +L2: + r4 = 2 + r5 = i == r4 :: int + r1 = r5 +L3: + if r1 goto L4 else goto L5 :: bool +L4: + r0 = r1 + goto L6 +L5: + r6 = 3 + r7 = i == r6 :: int + r0 = r7 +L6: + return r0 \ No newline at end of file From 21b419eacf3eeeccde24669bdf58a6cf10ac3918 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Fri, 19 Jun 2020 17:38:09 -0300 Subject: [PATCH 07/15] Regenerate test against HEAD --- mypyc/test-data/irbuild-tuple.test | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 99e2d633fb8a5..dd97973975eee 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -193,6 +193,8 @@ L2: r4 = CPySequenceTuple_GetItem(nt, r3) r5 = unbox(int, r4) return r5 + + [case testTupleOperatorIn] def f(i: int) -> bool: return i in [1, 2, 3] @@ -208,14 +210,14 @@ def f(i): r7 :: bool L0: r2 = 1 - r3 = i == r2 :: int + r3 = CPyTagged_IsEq(i, r2) if r3 goto L1 else goto L2 :: bool L1: r1 = r3 goto L3 L2: r4 = 2 - r5 = i == r4 :: int + r5 = CPyTagged_IsEq(i, r4) r1 = r5 L3: if r1 goto L4 else goto L5 :: bool @@ -224,7 +226,7 @@ L4: goto L6 L5: r6 = 3 - r7 = i == r6 :: int + r7 = CPyTagged_IsEq(i, r6) r0 = r7 L6: - return r0 \ No newline at end of file + return r0 From 1623477b80f7a24a4579f9fab2ff07533bc163cc Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Thu, 25 Jun 2020 20:08:08 -0300 Subject: [PATCH 08/15] Specify types directly when creating AST nodes. --- mypyc/irbuild/builder.py | 7 ++----- mypyc/irbuild/expression.py | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index cd1feadaede8c..17559b90bcdfa 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -19,8 +19,7 @@ from mypy.nodes import ( MypyFile, SymbolNode, Statement, OpExpr, IntExpr, NameExpr, LDEF, Var, UnaryExpr, CallExpr, IndexExpr, Expression, MemberExpr, RefExpr, Lvalue, TupleExpr, - TypeInfo, Decorator, OverloadedFuncDef, StarExpr, GDEF, ARG_POS, ARG_NAMED, - ComparisonExpr + TypeInfo, Decorator, OverloadedFuncDef, StarExpr, GDEF, ARG_POS, ARG_NAMED ) from mypy.types import ( Type, Instance, TupleType, UninhabitedType, get_proper_type @@ -40,7 +39,7 @@ from mypyc.ir.rtypes import ( RType, RTuple, RInstance, int_rprimitive, dict_rprimitive, none_rprimitive, is_none_rprimitive, object_rprimitive, is_object_rprimitive, - str_rprimitive, bool_rprimitive, + str_rprimitive, ) from mypyc.ir.func_ir import FuncIR, INVALID_FUNC_DEF from mypyc.ir.class_ir import ClassIR, NonExtClassInfo @@ -835,8 +834,6 @@ def node_type(self, node: Expression) -> RType: # TODO: Don't special case IntExpr return int_rprimitive if node not in self.types: - if isinstance(node, (ComparisonExpr, OpExpr)): - return bool_rprimitive return object_rprimitive mypy_type = self.types[node] return self.type_to_rtype(mypy_type) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index da7bcfc2ddd9c..d26ab7291891c 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -4,7 +4,7 @@ and mypyc.irbuild.builder. """ -from typing import List, Optional, Union +from typing import List, Optional, Union, cast from mypy.nodes import ( Expression, NameExpr, MemberExpr, SuperExpr, CallExpr, UnaryExpr, OpExpr, IndexExpr, @@ -13,12 +13,13 @@ SetComprehension, DictionaryComprehension, SliceExpr, GeneratorExpr, CastExpr, StarExpr, Var, RefExpr, MypyFile, TypeInfo, TypeApplication, LDEF, ARG_POS ) -from mypy.types import TupleType, get_proper_type +from mypy.types import TupleType, get_proper_type, Instance from mypyc.ir.ops import ( Value, TupleGet, TupleSet, PrimitiveOp, BasicBlock, OpDescription, Assign ) -from mypyc.ir.rtypes import RTuple, object_rprimitive, is_none_rprimitive +from mypyc.ir.rtypes import RTuple, object_rprimitive, is_none_rprimitive, \ + bool_rprimitive from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD from mypyc.primitives.registry import name_ref_ops from mypyc.primitives.generic_ops import iter_op @@ -368,10 +369,19 @@ def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: bin_op = 'and' cmp_op = '!=' lhs = e.operands[0] - exprs = (ComparisonExpr([cmp_op], [lhs, item]) for item in items) - or_expr = next(exprs) # type: Expression + mypy_file = builder.graph['builtins'].tree + assert mypy_file is not None + bool_type = Instance(cast(TypeInfo, mypy_file.names['bool'].node), []) + exprs = [] + for item in items: + expr = ComparisonExpr([cmp_op], [lhs, item]) + builder.types[expr] = bool_type + exprs.append(expr) + + or_expr = exprs.pop(0) # type: Expression for expr in exprs: or_expr = OpExpr(bin_op, or_expr, expr) + builder.types[or_expr] = bool_type return builder.accept(or_expr) # x in [y]/(y) -> x == y # x not in [y]/(y) -> x != y From 6ed0964ab4dc1d54927425307cb381d05a176601 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Fri, 26 Jun 2020 08:39:59 -0300 Subject: [PATCH 09/15] Remove unused bool_rprimitive to pass pyflakes --- mypyc/irbuild/expression.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index d26ab7291891c..1a6bea19da2b9 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -18,8 +18,7 @@ from mypyc.ir.ops import ( Value, TupleGet, TupleSet, PrimitiveOp, BasicBlock, OpDescription, Assign ) -from mypyc.ir.rtypes import RTuple, object_rprimitive, is_none_rprimitive, \ - bool_rprimitive +from mypyc.ir.rtypes import RTuple, object_rprimitive, is_none_rprimitive from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD from mypyc.primitives.registry import name_ref_ops from mypyc.primitives.generic_ops import iter_op From abb4555136cef17a15259d544b6f5577c6761267 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Thu, 9 Jul 2020 13:26:43 -0300 Subject: [PATCH 10/15] Update mypyc/irbuild/expression.py Ensure we only operate on ComparisonExpr with at most one operator. Co-authored-by: Tomer Chachamu --- mypyc/irbuild/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 1a6bea19da2b9..f6f740653c45a 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -354,7 +354,7 @@ def transform_conditional_expr(builder: IRBuilder, expr: ConditionalExpr) -> Val def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: # x in (...)/[...] # x not in (...)/[...] - if e.operators[0] in ['in', 'not in'] and isinstance(e.operands[1], (TupleExpr, ListExpr)): + if e.operators[0] in ['in', 'not in'] and len(e.operators) == 1 and isinstance(e.operands[1], (TupleExpr, ListExpr)): items = e.operands[1].items n_items = len(items) # x in y -> x == y[0] or ... or x == y[n] From b917da0b7c44f1a0c12ffbdf4e8e3e1124e75eda Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Sun, 27 Sep 2020 22:40:29 -0300 Subject: [PATCH 11/15] Merge remote-tracking branch 'upstream/master' into mypyc-in-operation Add back operator tests, use builder.false/true --- mypyc/irbuild/expression.py | 5 +- mypyc/test-data/run-lists.test | 119 +++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 3 deletions(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index cc6af23a09de1..4c3850b78e839 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -449,10 +449,9 @@ def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: # x not in []/() -> True elif n_items == 0: if e.operators[0] == 'in': - name = 'builtins.False' + return builder.false() else: - name = 'builtins.True' - return builder.add(PrimitiveOp([], name_ref_ops[name], e.line)) + return builder.true() # TODO: Don't produce an expression when used in conditional context # All of the trickiness here is due to support for chained conditionals diff --git a/mypyc/test-data/run-lists.test b/mypyc/test-data/run-lists.test index ed9cfa72c5864..2f02be67d358a 100644 --- a/mypyc/test-data/run-lists.test +++ b/mypyc/test-data/run-lists.test @@ -149,3 +149,122 @@ def test_slicing() -> None: assert s[1:long_int] == ["o", "o", "b", "a", "r"] assert s[long_int:] == [] assert s[-long_int:-1] == ["f", "o", "o", "b", "a"] + +[case testOperatorInExpression] + +def tuple_in_int0(i: int) -> bool: + return i in [] + +def tuple_in_int1(i: int) -> bool: + return i in (1,) + +def tuple_in_int3(i: int) -> bool: + return i in (1, 2, 3) + +def tuple_not_in_int0(i: int) -> bool: + return i not in [] + +def tuple_not_in_int1(i: int) -> bool: + return i not in (1,) + +def tuple_not_in_int3(i: int) -> bool: + return i not in (1, 2, 3) + +def tuple_in_str(s: "str") -> bool: + return s in ("foo", "bar", "baz") + +def tuple_not_in_str(s: "str") -> bool: + return s not in ("foo", "bar", "baz") + +def list_in_int0(i: int) -> bool: + return i in [] + +def list_in_int1(i: int) -> bool: + return i in (1,) + +def list_in_int3(i: int) -> bool: + return i in (1, 2, 3) + +def list_not_in_int0(i: int) -> bool: + return i not in [] + +def list_not_in_int1(i: int) -> bool: + return i not in (1,) + +def list_not_in_int3(i: int) -> bool: + return i not in (1, 2, 3) + +def list_in_str(s: "str") -> bool: + return s in ("foo", "bar", "baz") + +def list_not_in_str(s: "str") -> bool: + return s not in ("foo", "bar", "baz") + +def list_in_mixed(i: object): + return i in [[], (), "", 0, 0.0, False, 0j, {}, set(), type] + +[file driver.py] + +from native import * + +assert not tuple_in_int0(0) +assert not tuple_in_int1(0) +assert tuple_in_int1(1) +assert not tuple_in_int3(0) +assert tuple_in_int3(1) +assert tuple_in_int3(2) +assert tuple_in_int3(3) +assert not tuple_in_int3(4) + +assert tuple_not_in_int0(0) +assert tuple_not_in_int1(0) +assert not tuple_not_in_int1(1) +assert tuple_not_in_int3(0) +assert not tuple_not_in_int3(1) +assert not tuple_not_in_int3(2) +assert not tuple_not_in_int3(3) +assert tuple_not_in_int3(4) + +assert tuple_in_str("foo") +assert tuple_in_str("bar") +assert tuple_in_str("baz") +assert not tuple_in_str("apple") +assert not tuple_in_str("pie") +assert not tuple_in_str("\0") +assert not tuple_in_str("") + +assert not list_in_int0(0) +assert not list_in_int1(0) +assert list_in_int1(1) +assert not list_in_int3(0) +assert list_in_int3(1) +assert list_in_int3(2) +assert list_in_int3(3) +assert not list_in_int3(4) + +assert list_not_in_int0(0) +assert list_not_in_int1(0) +assert not list_not_in_int1(1) +assert list_not_in_int3(0) +assert not list_not_in_int3(1) +assert not list_not_in_int3(2) +assert not list_not_in_int3(3) +assert list_not_in_int3(4) + +assert list_in_str("foo") +assert list_in_str("bar") +assert list_in_str("baz") +assert not list_in_str("apple") +assert not list_in_str("pie") +assert not list_in_str("\0") +assert not list_in_str("") + +assert list_in_mixed(0) +assert list_in_mixed([]) +assert list_in_mixed({}) +assert list_in_mixed(()) +assert list_in_mixed(False) +assert list_in_mixed(0.0) +assert not list_in_mixed([1]) +assert not list_in_mixed(object) +assert list_in_mixed(type) From 7c599c0701c234e00fda765b7e44b01416002c3a Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Sun, 27 Sep 2020 23:07:52 -0300 Subject: [PATCH 12/15] Regenerate IR --- mypyc/test-data/irbuild-tuple.test | 69 +++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index cb2157f69d383..521cf0ba92cde 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -189,32 +189,59 @@ def f(i: int) -> bool: [out] def f(i): i :: int - r0, r1 :: bool - r2 :: short_int - r3 :: bool - r4 :: short_int - r5 :: bool - r6 :: short_int - r7 :: bool + r0, r1, r2 :: bool + r3 :: int64 + r4, r5, r6, r7 :: bool + r8 :: int64 + r9, r10, r11, r12 :: bool + r13 :: int64 + r14, r15, r16 :: bool L0: - r2 = 1 - r3 = CPyTagged_IsEq(i, r2) - if r3 goto L1 else goto L2 :: bool + r3 = i & 1 + r4 = r3 == 0 + if r4 goto L1 else goto L2 :: bool L1: - r1 = r3 + r5 = i == 2 + r2 = r5 goto L3 L2: - r4 = 2 - r5 = CPyTagged_IsEq(i, r4) - r1 = r5 + r6 = CPyTagged_IsEq_(i, 2) + r2 = r6 L3: - if r1 goto L4 else goto L5 :: bool + if r2 goto L4 else goto L5 :: bool L4: - r0 = r1 - goto L6 + r1 = r2 + goto L9 L5: - r6 = 3 - r7 = CPyTagged_IsEq(i, r6) - r0 = r7 + r8 = i & 1 + r9 = r8 == 0 + if r9 goto L6 else goto L7 :: bool L6: - return r0 + r10 = i == 4 + r7 = r10 + goto L8 +L7: + r11 = CPyTagged_IsEq_(i, 4) + r7 = r11 +L8: + r1 = r7 +L9: + if r1 goto L10 else goto L11 :: bool +L10: + r0 = r1 + goto L15 +L11: + r13 = i & 1 + r14 = r13 == 0 + if r14 goto L12 else goto L13 :: bool +L12: + r15 = i == 6 + r12 = r15 + goto L14 +L13: + r16 = CPyTagged_IsEq_(i, 6) + r12 = r16 +L14: + r0 = r12 +L15: + return r0 \ No newline at end of file From b92d467c298698dc2143e731d1d6fb4710ea5924 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Mon, 28 Sep 2020 07:07:18 -0300 Subject: [PATCH 13/15] irbuild-tuple: int64 -> native_int --- mypyc/test-data/irbuild-tuple.test | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 521cf0ba92cde..ec582317f9a2b 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -190,11 +190,11 @@ def f(i: int) -> bool: def f(i): i :: int r0, r1, r2 :: bool - r3 :: int64 + r3 :: native_int r4, r5, r6, r7 :: bool - r8 :: int64 + r8 :: native_int r9, r10, r11, r12 :: bool - r13 :: int64 + r13 :: native_int r14, r15, r16 :: bool L0: r3 = i & 1 From 62c6ddd279c1657336265846da7d824bed7d4aa9 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Mon, 28 Sep 2020 07:43:17 -0300 Subject: [PATCH 14/15] Wrap line to make flake8 happy --- mypyc/irbuild/expression.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 620b1be575d92..7801bf4cf4b64 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -408,7 +408,9 @@ def transform_conditional_expr(builder: IRBuilder, expr: ConditionalExpr) -> Val def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: # x in (...)/[...] # x not in (...)/[...] - if e.operators[0] in ['in', 'not in'] and len(e.operators) == 1 and isinstance(e.operands[1], (TupleExpr, ListExpr)): + if (e.operators[0] in ['in', 'not in'] + and len(e.operators) == 1 + and isinstance(e.operands[1], (TupleExpr, ListExpr))): items = e.operands[1].items n_items = len(items) # x in y -> x == y[0] or ... or x == y[n] From c9675cf937f1080d877045e8b02a48372ca2fd5b Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Mon, 28 Sep 2020 11:28:15 -0300 Subject: [PATCH 15/15] Update mypyc/test-data/irbuild-tuple.test Co-authored-by: Xuanda Yang --- mypyc/test-data/irbuild-tuple.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index ec582317f9a2b..4e94441cf7482 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -244,4 +244,4 @@ L13: L14: r0 = r12 L15: - return r0 \ No newline at end of file + return r0