From ad565ecb77edfcc64d445dc807fc95b527203f94 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Wed, 23 Jun 2021 11:22:43 +0800 Subject: [PATCH 01/19] Add a specializer for str.format --- mypyc/irbuild/specialize.py | 29 ++++++++++++++++++++++++++++- mypyc/lib-rt/CPy.h | 1 + mypyc/lib-rt/str_ops.c | 15 +++++++++++++++ mypyc/primitives/str_ops.py | 10 +++++++++- mypyc/test-data/run-strings.test | 18 ++++++++++++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index cf524fe96fd8..36343d2e1202 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -11,12 +11,13 @@ See comment below for more documentation. """ +import re from typing import Callable, Optional, Dict, Tuple, List from mypy.nodes import ( CallExpr, RefExpr, MemberExpr, NameExpr, TupleExpr, GeneratorExpr, - ListExpr, DictExpr, ARG_POS + ListExpr, DictExpr, StrExpr, ARG_POS ) from mypy.types import AnyType, TypeOfAny @@ -32,6 +33,7 @@ ) from mypyc.primitives.list_ops import new_list_set_item_op from mypyc.primitives.tuple_ops import new_tuple_set_item_op +from mypyc.primitives.str_ops import str_op, str_build_op from mypyc.irbuild.builder import IRBuilder from mypyc.irbuild.for_helpers import ( translate_list_comprehension, translate_set_comprehension, @@ -356,3 +358,28 @@ def translate_dict_setdefault( [callee_dict, key_val, data_type], expr.line) return None + + +@specialize_function('format', str_rprimitive) +def translate_str_format( + builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: + if isinstance(callee, MemberExpr) and isinstance(callee.expr, StrExpr): + # TODO + # Only consider simplest situation here + format_str = callee.expr.value + if format_str.count("{") != format_str.count("{}"): + return + + literals = [builder.load_str(x) for x in format_str.split("{}")] + variables = [builder.call_c(str_op, [builder.accept(x)], expr.line) for x in expr.args] + + result_len = len(literals) + len(variables) + result_list = [None] * result_len + + result_list[::2] = literals + result_list[1::2] = variables + + return builder.call_c(str_build_op, + [Integer(result_len, c_int_rprimitive)] + result_list, + expr.line) + return None diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index b50802f2d6af..e5f2c7460dc2 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -384,6 +384,7 @@ static inline char CPyDict_CheckSize(PyObject *dict, CPyTagged size) { // Str operations +PyObject *CPyStr_Build(int len, ...); PyObject *CPyStr_GetItem(PyObject *str, CPyTagged index); PyObject *CPyStr_Split(PyObject *str, PyObject *sep, CPyTagged max_split); PyObject *CPyStr_Replace(PyObject *str, PyObject *old_substr, PyObject *new_substr, CPyTagged max_replace); diff --git a/mypyc/lib-rt/str_ops.c b/mypyc/lib-rt/str_ops.c index 7208a9b7e206..c9d81a7a57ef 100644 --- a/mypyc/lib-rt/str_ops.c +++ b/mypyc/lib-rt/str_ops.c @@ -43,6 +43,21 @@ PyObject *CPyStr_GetItem(PyObject *str, CPyTagged index) { } } +PyObject *CPyStr_Build(int len, ...) { + int i; + va_list args; + va_start(args, len); + + PyObject *res = PyUnicode_FromObject(va_arg(args, PyObject *)); + for (i = 1; i < len; i++) { + PyObject *str = va_arg(args, PyObject *); + PyUnicode_Append(&res, str); + } + + va_end(args); + return res; +} + PyObject *CPyStr_Split(PyObject *str, PyObject *sep, CPyTagged max_split) { Py_ssize_t temp_max_split = CPyTagged_AsSsize_t(max_split); diff --git a/mypyc/primitives/str_ops.py b/mypyc/primitives/str_ops.py index 4e56d885528b..383228b30b56 100644 --- a/mypyc/primitives/str_ops.py +++ b/mypyc/primitives/str_ops.py @@ -20,7 +20,7 @@ src='PyUnicode_Type') # str(obj) -function_op( +str_op = function_op( name='builtins.str', arg_types=[object_rprimitive], return_type=str_rprimitive, @@ -43,6 +43,14 @@ error_kind=ERR_MAGIC ) +str_build_op = custom_op( + arg_types=[c_int_rprimitive], + return_type=str_rprimitive, + c_function_name='CPyStr_Build', + error_kind=ERR_MAGIC, + var_arg_type=str_rprimitive +) + # str.startswith(str) method_op( name='startswith', diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index 83186d5b033c..3c575b1e4229 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -178,3 +178,21 @@ def test_str_to_bool() -> None: for x in 'a', 'foo', 'bar', 'some string': assert is_true(x) assert not is_false(x) + +[case testStringFormatting] + +def test_format_method() -> None: + name = "Eric" + age = 14 + s1 = "Hello, {}!".format(name) + assert s1 == "Hello, Eric!" + s2 = "{}".format(name) + assert s2 == "Eric" + s3 = "{}! Hi!".format(name) + assert s3 == "Eric! Hi!" + s4 = "{}, Hi, {}".format(name, name) + assert s4 == "Eric, Hi, Eric" + s5 = "Hi! {}".format(name} + assert s5 == "Hi! Eric" + s6 = "Hi, I'm {}. I'm {}.".format(name, age) + assert s6 == "Hi, I'm Eric. I'm 14." From 3bf3f81a07f0bf09660ab7ae1b7765cb827f9193 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Wed, 23 Jun 2021 11:27:42 +0800 Subject: [PATCH 02/19] Add irbuild test --- mypyc/test-data/irbuild-str.test | 20 ++++++++++++++++++++ mypyc/test-data/run-strings.test | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-str.test b/mypyc/test-data/irbuild-str.test index 3de91be40ea2..ad299f2c635a 100644 --- a/mypyc/test-data/irbuild-str.test +++ b/mypyc/test-data/irbuild-str.test @@ -159,3 +159,23 @@ L2: L3: unreachable +[case testStrFormatMethod] +def f(s: str, num: int) -> str: + return "Hi! I'm {}, and I'm {} years old.".format(s, num) +[out] +def f(s, num): + s :: str + num :: int + r0, r1, r2, r3 :: str + r4 :: object + r5, r6 :: str +L0: + r0 = "Hi! I'm " + r1 = ", and I'm " + r2 = ' years old.' + r3 = PyObject_Str(s) + r4 = box(int, num) + r5 = PyObject_Str(r4) + r6 = CPyStr_Build(5, r0, r3, r1, r5, r2) + return r6 + diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index 3c575b1e4229..78aeec492425 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -192,7 +192,7 @@ def test_format_method() -> None: assert s3 == "Eric! Hi!" s4 = "{}, Hi, {}".format(name, name) assert s4 == "Eric, Hi, Eric" - s5 = "Hi! {}".format(name} + s5 = "Hi! {}".format(name) assert s5 == "Hi! Eric" s6 = "Hi, I'm {}. I'm {}.".format(name, age) assert s6 == "Hi, I'm Eric. I'm 14." From 66e3f7491db1b19384993fba95ab94fd1421973c Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Wed, 23 Jun 2021 11:41:58 +0800 Subject: [PATCH 03/19] Fix --- mypyc/irbuild/specialize.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 36343d2e1202..971a5763268a 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -11,7 +11,6 @@ See comment below for more documentation. """ -import re from typing import Callable, Optional, Dict, Tuple, List From bac70bfff23f2a4342fe90dfa533776319701045 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Wed, 23 Jun 2021 15:15:40 +0800 Subject: [PATCH 04/19] Fix type error --- mypyc/irbuild/specialize.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 971a5763268a..49465ec3f487 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -367,18 +367,16 @@ def translate_str_format( # Only consider simplest situation here format_str = callee.expr.value if format_str.count("{") != format_str.count("{}"): - return + return None literals = [builder.load_str(x) for x in format_str.split("{}")] variables = [builder.call_c(str_op, [builder.accept(x)], expr.line) for x in expr.args] result_len = len(literals) + len(variables) - result_list = [None] * result_len - - result_list[::2] = literals - result_list[1::2] = variables + result_list: List[Value] = [] * (result_len + 1) - return builder.call_c(str_build_op, - [Integer(result_len, c_int_rprimitive)] + result_list, - expr.line) + result_list[0] = Integer(result_len, c_int_rprimitive, expr.line) + result_list[1::2] = literals + result_list[2::2] = variables + return builder.call_c(str_build_op, result_list, expr.line) return None From 9ca103e21a1f46331111374cfb2c5cd56da764cd Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Wed, 23 Jun 2021 16:16:56 +0800 Subject: [PATCH 05/19] Support brackets literals --- mypyc/irbuild/specialize.py | 25 ++++++++++++++++++++++--- mypyc/test-data/run-strings.test | 22 ++++++++++------------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 49465ec3f487..12a3479ba0b7 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -25,7 +25,7 @@ ) from mypyc.ir.rtypes import ( RType, RTuple, str_rprimitive, list_rprimitive, dict_rprimitive, set_rprimitive, - bool_rprimitive, is_dict_rprimitive, c_int_rprimitive + bool_rprimitive, is_dict_rprimitive, c_int_rprimitive, object_rprimitive ) from mypyc.primitives.dict_ops import ( dict_keys_op, dict_values_op, dict_items_op, dict_setdefault_spec_init_op @@ -366,17 +366,36 @@ def translate_str_format( # TODO # Only consider simplest situation here format_str = callee.expr.value - if format_str.count("{") != format_str.count("{}"): + if not str_format_check_helper(format_str): return None literals = [builder.load_str(x) for x in format_str.split("{}")] variables = [builder.call_c(str_op, [builder.accept(x)], expr.line) for x in expr.args] + # Allocate space for the parameter list of CPyStr_Build(). result_len = len(literals) + len(variables) - result_list: List[Value] = [] * (result_len + 1) + result_list: List[Value] = [Integer(0, c_int_rprimitive)] * (result_len + 1) + # The first parameter is the total size of the following PyObject* merged from + # two lists alternatively. result_list[0] = Integer(result_len, c_int_rprimitive, expr.line) result_list[1::2] = literals result_list[2::2] = variables return builder.call_c(str_build_op, result_list, expr.line) return None + + +# A helper function for checking whether the format_str can be optimized by +# translate_str_format(). +def str_format_check_helper(format_str: str) -> bool: + prev = '' + for c in format_str: + if (c == '{' and prev == '{' + or c == '}' and prev == '}'): + prev = '' + continue + if (c == '}' and prev != '{' + or prev == '{' and c != '}'): + return False + prev = c + return True diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index 78aeec492425..a7bac2628b19 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -184,15 +184,13 @@ def test_str_to_bool() -> None: def test_format_method() -> None: name = "Eric" age = 14 - s1 = "Hello, {}!".format(name) - assert s1 == "Hello, Eric!" - s2 = "{}".format(name) - assert s2 == "Eric" - s3 = "{}! Hi!".format(name) - assert s3 == "Eric! Hi!" - s4 = "{}, Hi, {}".format(name, name) - assert s4 == "Eric, Hi, Eric" - s5 = "Hi! {}".format(name) - assert s5 == "Hi! Eric" - s6 = "Hi, I'm {}. I'm {}.".format(name, age) - assert s6 == "Hi, I'm Eric. I'm 14." + + assert "Hello, {}!".format(name) == "Hello, Eric!" + assert "{}".format(name) == "Eric" + assert "{}! Hi!".format(name) == "Eric! Hi!" + assert "{}, Hi, {}".format(name, name) == "Eric, Hi, Eric" + assert "Hi! {}".format(name) == "Hi! Eric" + assert "Hi, I'm {}. I'm {}.".format(name, age) == "Hi, I'm Eric. I'm 14." + + assert "{{}}{}".format(name) == "{}Eric" + assert "Hi! {{{}}}".format(name) == "Hi! {Eric}" From 2072c99231b29bc9419136e2f63f98e2aa949559 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Wed, 23 Jun 2021 16:18:34 +0800 Subject: [PATCH 06/19] Remove useless value --- mypyc/irbuild/specialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 12a3479ba0b7..6348b2f8fd01 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -25,7 +25,7 @@ ) from mypyc.ir.rtypes import ( RType, RTuple, str_rprimitive, list_rprimitive, dict_rprimitive, set_rprimitive, - bool_rprimitive, is_dict_rprimitive, c_int_rprimitive, object_rprimitive + bool_rprimitive, is_dict_rprimitive, c_int_rprimitive ) from mypyc.primitives.dict_ops import ( dict_keys_op, dict_values_op, dict_items_op, dict_setdefault_spec_init_op From 8c08364ac9cbe47fcab1b7afbe54f917a6ac10ab Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Thu, 24 Jun 2021 19:04:23 +0800 Subject: [PATCH 07/19] Optimize --- mypyc/irbuild/specialize.py | 48 ++++++++++++++++++-------------- mypyc/test-data/irbuild-str.test | 21 ++++++-------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 6348b2f8fd01..e745d1fb20a8 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -25,7 +25,7 @@ ) from mypyc.ir.rtypes import ( RType, RTuple, str_rprimitive, list_rprimitive, dict_rprimitive, set_rprimitive, - bool_rprimitive, is_dict_rprimitive, c_int_rprimitive + bool_rprimitive, is_dict_rprimitive, c_int_rprimitive, is_str_rprimitive ) from mypyc.primitives.dict_ops import ( dict_keys_op, dict_values_op, dict_items_op, dict_setdefault_spec_init_op @@ -39,7 +39,6 @@ comprehension_helper, sequence_from_generator_preallocate_helper ) - # Specializers are attempted before compiling the arguments to the # function. Specializers can return None to indicate that they failed # and the call should be compiled normally. Otherwise they should emit @@ -63,9 +62,11 @@ def specialize_function( There may exist multiple specializers for one function. When translating method calls, the earlier appended specializer has higher priority. """ + def wrapper(f: Specializer) -> Specializer: specializers.setdefault((name, typ), []).append(f) return f + return wrapper @@ -190,13 +191,13 @@ def translate_safe_generator_call( return builder.gen_method_call( builder.accept(callee.expr), callee.name, ([translate_list_comprehension(builder, expr.args[0])] - + [builder.accept(arg) for arg in expr.args[1:]]), + + [builder.accept(arg) for arg in expr.args[1:]]), builder.node_type(expr), expr.line, expr.arg_kinds, expr.arg_names) else: return builder.call_refexpr_with_args( expr, callee, ([translate_list_comprehension(builder, expr.args[0])] - + [builder.accept(arg) for arg in expr.args[1:]])) + + [builder.accept(arg) for arg in expr.args[1:]])) return None @@ -344,7 +345,7 @@ def translate_dict_setdefault( return None data_type = Integer(2, c_int_rprimitive, expr.line) elif (isinstance(arg, CallExpr) and isinstance(arg.callee, NameExpr) - and arg.callee.fullname == 'builtins.set'): + and arg.callee.fullname == 'builtins.set'): if len(arg.args): return None data_type = Integer(3, c_int_rprimitive, expr.line) @@ -362,32 +363,37 @@ def translate_dict_setdefault( @specialize_function('format', str_rprimitive) def translate_str_format( builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: - if isinstance(callee, MemberExpr) and isinstance(callee.expr, StrExpr): - # TODO - # Only consider simplest situation here + if (isinstance(callee, MemberExpr) and isinstance(callee.expr, StrExpr) + and expr.arg_kinds.count(ARG_POS) == len(expr.arg_kinds)): + format_str = callee.expr.value - if not str_format_check_helper(format_str): + if not can_optimize_format(format_str): return None - literals = [builder.load_str(x) for x in format_str.split("{}")] - variables = [builder.call_c(str_op, [builder.accept(x)], expr.line) for x in expr.args] - - # Allocate space for the parameter list of CPyStr_Build(). - result_len = len(literals) + len(variables) - result_list: List[Value] = [Integer(0, c_int_rprimitive)] * (result_len + 1) + literals = format_str.split("{}") + variables = [builder.accept(x) if is_str_rprimitive(builder.node_type(x)) + else builder.call_c(str_op, [builder.accept(x)], expr.line) + for x in expr.args] # The first parameter is the total size of the following PyObject* merged from # two lists alternatively. - result_list[0] = Integer(result_len, c_int_rprimitive, expr.line) - result_list[1::2] = literals - result_list[2::2] = variables + result_list: List[Value] = [Integer(0, c_int_rprimitive)] + for a, b in zip(literals, variables): + if a: + result_list.append(builder.load_str(a)) + result_list.append(b) + # The str.split() always generates one more element + if literals[-1]: + result_list.append(builder.load_str(literals[-1])) + + result_list[0] = Integer(len(result_list) - 1, c_int_rprimitive) return builder.call_c(str_build_op, result_list, expr.line) return None -# A helper function for checking whether the format_str can be optimized by -# translate_str_format(). -def str_format_check_helper(format_str: str) -> bool: +def can_optimize_format(format_str: str) -> bool: + # TODO + # Only empty brackets can be optimized prev = '' for c in format_str: if (c == '{' and prev == '{' diff --git a/mypyc/test-data/irbuild-str.test b/mypyc/test-data/irbuild-str.test index ad299f2c635a..799b547ce03b 100644 --- a/mypyc/test-data/irbuild-str.test +++ b/mypyc/test-data/irbuild-str.test @@ -166,16 +166,13 @@ def f(s: str, num: int) -> str: def f(s, num): s :: str num :: int - r0, r1, r2, r3 :: str - r4 :: object - r5, r6 :: str + r0 :: object + r1, r2, r3, r4, r5 :: str L0: - r0 = "Hi! I'm " - r1 = ", and I'm " - r2 = ' years old.' - r3 = PyObject_Str(s) - r4 = box(int, num) - r5 = PyObject_Str(r4) - r6 = CPyStr_Build(5, r0, r3, r1, r5, r2) - return r6 - + r0 = box(int, num) + r1 = PyObject_Str(r0) + r2 = "Hi! I'm " + r3 = ", and I'm " + r4 = ' years old.' + r5 = CPyStr_Build(5, r2, s, r3, r1, r4) + return r5 From e649c3828a08ace63e6d6a63f4d20a12afc363b5 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Thu, 24 Jun 2021 20:18:55 +0800 Subject: [PATCH 08/19] Support brackets literals --- mypyc/irbuild/specialize.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index e745d1fb20a8..6971dbcc462d 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -370,6 +370,9 @@ def translate_str_format( if not can_optimize_format(format_str): return None + format_str = format_str.replace("{{", "{") + format_str = format_str.replace("}}", "}") + literals = format_str.split("{}") variables = [builder.accept(x) if is_str_rprimitive(builder.node_type(x)) else builder.call_c(str_op, [builder.accept(x)], expr.line) From 04ea05434d7df15c61abc01b6349b41d805d3fc9 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Thu, 24 Jun 2021 20:22:42 +0800 Subject: [PATCH 09/19] Add more running tests --- mypyc/test-data/run-strings.test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index a7bac2628b19..7ba73e71fad1 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -194,3 +194,6 @@ def test_format_method() -> None: assert "{{}}{}".format(name) == "{}Eric" assert "Hi! {{{}}}".format(name) == "Hi! {Eric}" + assert "Hi! {{ {}".format(name) == "Hi! { Eric" + assert "Hi! {{ {} }}}}".format(name) == "Hi! { Eric }}" + From 1249d89fa9615199f32ddc4cdadbc252cf2e84f7 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Fri, 25 Jun 2021 15:40:46 +0800 Subject: [PATCH 10/19] Add test --- mypyc/test-data/run-strings.test | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index 7ba73e71fad1..3119b4c64751 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -182,6 +182,10 @@ def test_str_to_bool() -> None: [case testStringFormatting] def test_format_method() -> None: + + assert "".format() == "" + assert "abc".format() == "abc" + name = "Eric" age = 14 @@ -196,4 +200,3 @@ def test_format_method() -> None: assert "Hi! {{{}}}".format(name) == "Hi! {Eric}" assert "Hi! {{ {}".format(name) == "Hi! { Eric" assert "Hi! {{ {} }}}}".format(name) == "Hi! { Eric }}" - From bbd5df5b2dc08d8f684e0628bfbf49e3c110cd52 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Fri, 25 Jun 2021 17:49:13 +0800 Subject: [PATCH 11/19] Add placeholder cases --- mypyc/test-data/run-strings.test | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index 3119b4c64751..210f57cdf579 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -200,3 +200,11 @@ def test_format_method() -> None: assert "Hi! {{{}}}".format(name) == "Hi! {Eric}" assert "Hi! {{ {}".format(name) == "Hi! { Eric" assert "Hi! {{ {} }}}}".format(name) == "Hi! { Eric }}" + + assert "My name is {name}, I'm {age}.".format(name=name, age=age) == "My name is Eric, I'm 14." + assert "My name is {A}, I'm {B}.".format(A=name, B=age) == "My name is Eric, I'm 14." + assert "My name is {}, I'm {B}.".format(name, B=age) == "My name is Eric, I'm 14." + try: + "My name is {A}, I'm {}.".format(A=name, age) + except SyntaxError: + pass From 64d8147e34a3739d603032680e4c1bdf17e74a0c Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Fri, 25 Jun 2021 18:28:51 +0800 Subject: [PATCH 12/19] Skip empty strings and literal strings --- mypyc/irbuild/specialize.py | 8 +++++++- mypyc/test-data/run-strings.test | 5 +---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 6971dbcc462d..5515aefd5530 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -372,8 +372,8 @@ def translate_str_format( format_str = format_str.replace("{{", "{") format_str = format_str.replace("}}", "}") - literals = format_str.split("{}") + variables = [builder.accept(x) if is_str_rprimitive(builder.node_type(x)) else builder.call_c(str_op, [builder.accept(x)], expr.line) for x in expr.args] @@ -389,6 +389,12 @@ def translate_str_format( if literals[-1]: result_list.append(builder.load_str(literals[-1])) + # Special case for empty string and literal string + if len(result_list) == 1: + return builder.load_str("") + if not variables and len(result_list) == 2: + return result_list[1] + result_list[0] = Integer(len(result_list) - 1, c_int_rprimitive) return builder.call_c(str_build_op, result_list, expr.line) return None diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index 210f57cdf579..ab7b80bece75 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -201,10 +201,7 @@ def test_format_method() -> None: assert "Hi! {{ {}".format(name) == "Hi! { Eric" assert "Hi! {{ {} }}}}".format(name) == "Hi! { Eric }}" + # placeholder assert "My name is {name}, I'm {age}.".format(name=name, age=age) == "My name is Eric, I'm 14." assert "My name is {A}, I'm {B}.".format(A=name, B=age) == "My name is Eric, I'm 14." assert "My name is {}, I'm {B}.".format(name, B=age) == "My name is Eric, I'm 14." - try: - "My name is {A}, I'm {}.".format(A=name, age) - except SyntaxError: - pass From fb4ecc1e64b4da606ff2c2be1f1f5bb3581938fe Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Mon, 26 Jul 2021 18:18:40 +0800 Subject: [PATCH 13/19] Use conversion speicifiers --- mypy/checkstrformat.py | 24 +++++++++-------- mypyc/irbuild/expression.py | 14 +++++----- mypyc/irbuild/format_str_tokenizer.py | 37 +++++++++++---------------- 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index 6c66e48afe39..b0a4729e0ac7 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -50,14 +50,14 @@ def compile_format_re() -> Pattern[str]: See https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting The regexp is intentionally a bit wider to report better errors. """ - key_re = r'(\(([^()]*)\))?' # (optional) parenthesised sequence of characters. - flags_re = r'([#0\-+ ]*)' # (optional) sequence of flags. - width_re = r'(\*|[1-9][0-9]*)?' # (optional) minimum field width (* or numbers). - precision_re = r'(?:\.(\*|[0-9]+)?)?' # (optional) . followed by * of numbers. - length_mod_re = r'[hlL]?' # (optional) length modifier (unused). - type_re = r'(.)?' # conversion type. + key_re = r'(\((?P[^)]*)\))?' # (optional) parenthesised sequence of characters. + flags_re = r'(?P[#0\-+ ]*)' # (optional) sequence of flags. + width_re = r'(?P[1-9][0-9]*|\*)?' # (optional) minimum field width (* or numbers). + precision_re = r'(?:\.(?P\*|\d+)?)?' # (optional) . followed by * of numbers. + length_mod_re = r'[hlL]?' # (optional) length modifier (unused). + type_re = r'(?P.)?' # conversion type. format_re = '%' + key_re + flags_re + width_re + precision_re + length_mod_re + type_re - return re.compile(format_re) + return re.compile('({})'.format(format_re)) def compile_new_format_re(custom_spec: bool) -> Pattern[str]: @@ -133,7 +133,8 @@ def __init__(self, key: Optional[str], flags: str, width: str, precision: str, type: str, format_spec: Optional[str] = None, conversion: Optional[str] = None, - field: Optional[str] = None) -> None: + field: Optional[str] = None, + whole_seq: Optional[str] = None) -> None: self.key = key self.flags = flags self.width = width @@ -147,6 +148,7 @@ def __init__(self, key: Optional[str], # Full formatted expression (i.e. key plus following attributes and/or indexes). # Used only for str.format() calls. self.field = field + self.whole_seq = whole_seq @classmethod def from_match(cls, match_obj: Match[str], @@ -637,10 +639,12 @@ def check_str_interpolation(self, def parse_conversion_specifiers(self, format: str) -> List[ConversionSpecifier]: specifiers: List[ConversionSpecifier] = [] - for parens_key, key, flags, width, precision, type in FORMAT_RE.findall(format): + for whole_seq, parens_key, key, flags, width, precision, type \ + in FORMAT_RE.findall(format): if parens_key == '': key = None - specifiers.append(ConversionSpecifier(key, flags, width, precision, type)) + specifiers.append(ConversionSpecifier(key, flags, width, precision, type, + whole_seq=whole_seq)) return specifiers def analyze_conversion_specifiers(self, specifiers: List[ConversionSpecifier], diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index e8833830e585..4833a728eb4e 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -569,7 +569,8 @@ def transform_basic_comparison(builder: IRBuilder, def translate_str_format_percent_sign(builder: IRBuilder, format_expr: StrExpr, rhs: Expression) -> Value: - literals, conversion_types = tokenizer_printf_style(format_expr.value) + literals, conversion_specifiers = tokenizer_printf_style(format_expr.value) + variables = [] if isinstance(rhs, TupleExpr): raw_variables = rhs.items @@ -578,15 +579,16 @@ def translate_str_format_percent_sign(builder: IRBuilder, else: raw_variables = [] - is_conversion_matched = (len(conversion_types) == len(raw_variables)) + is_conversion_matched = (len(conversion_specifiers) == len(raw_variables)) if is_conversion_matched: - for typ, var in zip(conversion_types, raw_variables): + for specifier, var in zip(conversion_specifiers, raw_variables): node_type = builder.node_type(var) - if typ == '%d' and (is_int_rprimitive(node_type) - or is_short_int_rprimitive(node_type)): + format_type = specifier.whole_seq + if format_type == '%d' and (is_int_rprimitive(node_type) + or is_short_int_rprimitive(node_type)): var_str = builder.call_c(int_to_str_op, [builder.accept(var)], format_expr.line) - elif typ == '%s': + elif format_type == '%s': if is_str_rprimitive(node_type): var_str = builder.accept(var) else: diff --git a/mypyc/irbuild/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index 5354d3ddc2f6..e51231d946ed 100644 --- a/mypyc/irbuild/format_str_tokenizer.py +++ b/mypyc/irbuild/format_str_tokenizer.py @@ -3,46 +3,39 @@ import re from typing import List, Tuple +from mypy.checkstrformat import ( + FORMAT_RE, ConversionSpecifier +) from mypyc.ir.ops import Value, Integer from mypyc.ir.rtypes import c_pyssize_t_rprimitive from mypyc.irbuild.builder import IRBuilder from mypyc.primitives.str_ops import str_build_op -# printf-style String Formatting: -# https://docs.python.org/3/library/stdtypes.html#old-string-formatting -printf_style_pattern = re.compile(r""" - ( - % # Start sign - (?:\((?P[^)]*)\))? # Optional: Mapping key - (?P[-+#0 ]+)? # Optional: Conversion flags - (?P\d+|\*)? # Optional: Minimum field width - (?:\.(?P\d+|\*))? # Optional: Precision - [hlL]? # Optional: Length modifier, Ignored - (?P[diouxXeEfFgGcrsa]) # Conversion type - | %%) - """, re.VERBOSE) - -def tokenizer_printf_style(format_str: str) -> Tuple[List[str], List[str]]: +def tokenizer_printf_style(format_str: str) -> Tuple[List[str], List[ConversionSpecifier]]: """Tokenize a printf-style format string using regex. Return: A list of string literals and a list of conversion operations """ - literals = [] - format_ops = [] + literals: List[str] = [] + specifiers: List[ConversionSpecifier] = [] last_end = 0 - for m in re.finditer(printf_style_pattern, format_str): + for m in re.finditer(FORMAT_RE, format_str): + whole_seq, parens_key, key, flags, width, precision, conversion_type = m.groups() + if parens_key == '': + key = None + specifiers.append(ConversionSpecifier(key, flags, width, precision, + conversion_type, whole_seq=whole_seq)) + cur_start = m.start(1) - format_tmp = m.group(1) literals.append(format_str[last_end:cur_start]) - format_ops.append(format_tmp) - last_end = cur_start + len(format_tmp) + last_end = cur_start + len(whole_seq) literals.append(format_str[last_end:]) - return literals, format_ops + return literals, specifiers def join_formatted_strings(builder: IRBuilder, literals: List[str], From ca18762a2022acd44c34f7b5d6bc8c7416039659 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Mon, 26 Jul 2021 19:29:28 +0800 Subject: [PATCH 14/19] Fix unit test --- mypyc/test/test_stringformatting.py | 32 +++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/mypyc/test/test_stringformatting.py b/mypyc/test/test_stringformatting.py index dc7611f6d25f..777d0c35a595 100644 --- a/mypyc/test/test_stringformatting.py +++ b/mypyc/test/test_stringformatting.py @@ -1,16 +1,30 @@ import unittest +from typing import List from mypyc.irbuild.format_str_tokenizer import tokenizer_printf_style class TestStringFormatting(unittest.TestCase): + + def tokenizer_printf_style_helper(self, format_str: str, + literals: List[str], conversion: List[str]) -> bool: + l, specs = tokenizer_printf_style(format_str) + return literals == l and conversion == [x.whole_seq for x in specs] + def test_tokenizer_printf_style(self) -> None: - assert tokenizer_printf_style("I'm %s, id years old") == \ - (["I'm ", ', id years old'], ['%s']) - assert tokenizer_printf_style("Test: %i%%, Test: %02d, Test: %.2f") == \ - (['Test: ', '', ', Test: ', ', Test: ', ''], ['%i', '%%', '%02d', '%.2f']) - assert tokenizer_printf_style("ioasdfyuia%i%%%20s%d%sdafafadfa%s%d%x%E%.2f") == \ - (['ioasdfyuia', '', '', '', '', 'dafafadfa', '', '', '', '', ''], - ['%i', '%%', '%20s', '%d', '%s', '%s', '%d', '%x', '%E', '%.2f']) - assert tokenizer_printf_style("Special: %#20.2f%d, test: ") == \ - (['Special: ', '', ', test: '], ['%#20.2f', '%d']) + assert self.tokenizer_printf_style_helper( + "I'm %s, id years old", + ["I'm ", ', id years old'], + ['%s']) + assert self.tokenizer_printf_style_helper( + "Test: %i%%, Test: %02d, Test: %.2f", + ['Test: ', '', ', Test: ', ', Test: ', ''], + ['%i', '%%', '%02d', '%.2f']) + assert self.tokenizer_printf_style_helper( + "ioasdfyuia%i%%%20s%d%sdafafadfa%s%d%x%E%.2f", + ['ioasdfyuia', '', '', '', '', 'dafafadfa', '', '', '', '', ''], + ['%i', '%%', '%20s', '%d', '%s', '%s', '%d', '%x', '%E', '%.2f']) + assert self.tokenizer_printf_style_helper( + "Special: %#20.2f%d, test: ", + ['Special: ', '', ', test: '], + ['%#20.2f', '%d']) From 868923cea759eb55b90fb51ccdb5b7ca3d569261 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Mon, 26 Jul 2021 20:08:55 +0800 Subject: [PATCH 15/19] Fix type and code style error --- mypy/checkstrformat.py | 12 ++++++------ mypyc/irbuild/format_str_tokenizer.py | 2 -- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index b0a4729e0ac7..8db458cb4c91 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -50,12 +50,12 @@ def compile_format_re() -> Pattern[str]: See https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting The regexp is intentionally a bit wider to report better errors. """ - key_re = r'(\((?P[^)]*)\))?' # (optional) parenthesised sequence of characters. - flags_re = r'(?P[#0\-+ ]*)' # (optional) sequence of flags. - width_re = r'(?P[1-9][0-9]*|\*)?' # (optional) minimum field width (* or numbers). - precision_re = r'(?:\.(?P\*|\d+)?)?' # (optional) . followed by * of numbers. - length_mod_re = r'[hlL]?' # (optional) length modifier (unused). - type_re = r'(?P.)?' # conversion type. + key_re = r'(\((?P[^)]*)\))?' # (optional) parenthesised sequence of characters. + flags_re = r'(?P[#0\-+ ]*)' # (optional) sequence of flags. + width_re = r'(?P[1-9][0-9]*|\*)?' # (optional) minimum field width (* or numbers). + precision_re = r'(?:\.(?P\*|\d+)?)?' # (optional) . followed by * of numbers. + length_mod_re = r'[hlL]?' # (optional) length modifier (unused). + type_re = r'(?P.)?' # conversion type. format_re = '%' + key_re + flags_re + width_re + precision_re + length_mod_re + type_re return re.compile('({})'.format(format_re)) diff --git a/mypyc/irbuild/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index e51231d946ed..57480eaf70a7 100644 --- a/mypyc/irbuild/format_str_tokenizer.py +++ b/mypyc/irbuild/format_str_tokenizer.py @@ -24,8 +24,6 @@ def tokenizer_printf_style(format_str: str) -> Tuple[List[str], List[ConversionS for m in re.finditer(FORMAT_RE, format_str): whole_seq, parens_key, key, flags, width, precision, conversion_type = m.groups() - if parens_key == '': - key = None specifiers.append(ConversionSpecifier(key, flags, width, precision, conversion_type, whole_seq=whole_seq)) From 1ea8fbb034791adc78e3993e3ded97dd963ad6f6 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Tue, 27 Jul 2021 06:45:17 +0800 Subject: [PATCH 16/19] Fix --- mypyc/irbuild/format_str_tokenizer.py | 2 ++ mypyc/test/test_stringformatting.py | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/mypyc/irbuild/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index 57480eaf70a7..e51231d946ed 100644 --- a/mypyc/irbuild/format_str_tokenizer.py +++ b/mypyc/irbuild/format_str_tokenizer.py @@ -24,6 +24,8 @@ def tokenizer_printf_style(format_str: str) -> Tuple[List[str], List[ConversionS for m in re.finditer(FORMAT_RE, format_str): whole_seq, parens_key, key, flags, width, precision, conversion_type = m.groups() + if parens_key == '': + key = None specifiers.append(ConversionSpecifier(key, flags, width, precision, conversion_type, whole_seq=whole_seq)) diff --git a/mypyc/test/test_stringformatting.py b/mypyc/test/test_stringformatting.py index 777d0c35a595..77c0a95ab60c 100644 --- a/mypyc/test/test_stringformatting.py +++ b/mypyc/test/test_stringformatting.py @@ -6,25 +6,26 @@ class TestStringFormatting(unittest.TestCase): - def tokenizer_printf_style_helper(self, format_str: str, - literals: List[str], conversion: List[str]) -> bool: - l, specs = tokenizer_printf_style(format_str) - return literals == l and conversion == [x.whole_seq for x in specs] - def test_tokenizer_printf_style(self) -> None: - assert self.tokenizer_printf_style_helper( + + def tokenizer_printf_style_helper(format_str: str, + literals: List[str], conversion: List[str]) -> bool: + l, specs = tokenizer_printf_style(format_str) + return literals == l and conversion == [x.whole_seq for x in specs] + + assert tokenizer_printf_style_helper( "I'm %s, id years old", ["I'm ", ', id years old'], ['%s']) - assert self.tokenizer_printf_style_helper( + assert tokenizer_printf_style_helper( "Test: %i%%, Test: %02d, Test: %.2f", ['Test: ', '', ', Test: ', ', Test: ', ''], ['%i', '%%', '%02d', '%.2f']) - assert self.tokenizer_printf_style_helper( + assert tokenizer_printf_style_helper( "ioasdfyuia%i%%%20s%d%sdafafadfa%s%d%x%E%.2f", ['ioasdfyuia', '', '', '', '', 'dafafadfa', '', '', '', '', ''], ['%i', '%%', '%20s', '%d', '%s', '%s', '%d', '%x', '%E', '%.2f']) - assert self.tokenizer_printf_style_helper( + assert tokenizer_printf_style_helper( "Special: %#20.2f%d, test: ", ['Special: ', '', ', test: '], ['%#20.2f', '%d']) From f32ae8d50ebc858c473a24fa51999068ed875899 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Wed, 28 Jul 2021 23:38:30 +0800 Subject: [PATCH 17/19] Replace /d with [0-9] --- mypy/checkstrformat.py | 2 +- mypyc/irbuild/format_str_tokenizer.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index c678dc5db71e..e2a64f7694ca 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -53,7 +53,7 @@ def compile_format_re() -> Pattern[str]: key_re = r'(\((?P[^)]*)\))?' # (optional) parenthesised sequence of characters. flags_re = r'(?P[#0\-+ ]*)' # (optional) sequence of flags. width_re = r'(?P[1-9][0-9]*|\*)?' # (optional) minimum field width (* or numbers). - precision_re = r'(?:\.(?P\*|\d+)?)?' # (optional) . followed by * of numbers. + precision_re = r'(?:\.(?P\*|[0-9]+)?)?' # (optional) . followed by * of numbers. length_mod_re = r'[hlL]?' # (optional) length modifier (unused). type_re = r'(?P.)?' # conversion type. format_re = '%' + key_re + flags_re + width_re + precision_re + length_mod_re + type_re diff --git a/mypyc/irbuild/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index e51231d946ed..57480eaf70a7 100644 --- a/mypyc/irbuild/format_str_tokenizer.py +++ b/mypyc/irbuild/format_str_tokenizer.py @@ -24,8 +24,6 @@ def tokenizer_printf_style(format_str: str) -> Tuple[List[str], List[ConversionS for m in re.finditer(FORMAT_RE, format_str): whole_seq, parens_key, key, flags, width, precision, conversion_type = m.groups() - if parens_key == '': - key = None specifiers.append(ConversionSpecifier(key, flags, width, precision, conversion_type, whole_seq=whole_seq)) From 77d85e1cd2958e62c72c7a33607263e85e222b4f Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Thu, 29 Jul 2021 01:54:14 +0800 Subject: [PATCH 18/19] Refactor: Move type to the first attribute of ConversionSpecifier --- mypy/checkstrformat.py | 27 ++++++++++++++++----------- mypyc/irbuild/format_str_tokenizer.py | 4 ++-- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index e2a64f7694ca..61ed124c9e0a 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -114,17 +114,20 @@ def compile_new_format_re(custom_spec: bool) -> Pattern[str]: class ConversionSpecifier: - def __init__(self, key: Optional[str], - flags: str, width: str, precision: str, type: str, + def __init__(self, type: str, + key: Optional[str], + flags: Optional[str], + width: Optional[str], + precision: Optional[str], format_spec: Optional[str] = None, conversion: Optional[str] = None, field: Optional[str] = None, whole_seq: Optional[str] = None) -> None: + self.type = type self.key = key self.flags = flags self.width = width self.precision = precision - self.type = type # Used only for str.format() calls (it may be custom for types with __format__()). self.format_spec = format_spec self.non_standard_format_spec = False @@ -136,22 +139,24 @@ def __init__(self, key: Optional[str], self.whole_seq = whole_seq @classmethod - def from_match(cls, match_obj: Match[str], + def from_match(cls, match: Match[str], non_standard_spec: bool = False) -> 'ConversionSpecifier': """Construct specifier from match object resulted from parsing str.format() call.""" - match = cast(Any, match_obj) # TODO: remove this once typeshed is fixed. if non_standard_spec: - spec = cls(match.group('key'), - flags='', width='', precision='', type='', + spec = cls(type='', + key=match.group('key'), + flags='', width='', precision='', format_spec=match.group('format_spec'), conversion=match.group('conversion'), field=match.group('field')) spec.non_standard_format_spec = True return spec # Replace unmatched optional groups with empty matches (for convenience). - return cls(match.group('key'), - flags=match.group('flags') or '', width=match.group('width') or '', - precision=match.group('precision') or '', type=match.group('type') or '', + return cls(type=match.group('type') or '', + key=match.group('key'), + flags=match.group('flags') or '', + width=match.group('width') or '', + precision=match.group('precision') or '', format_spec=match.group('format_spec'), conversion=match.group('conversion'), field=match.group('field')) @@ -628,7 +633,7 @@ def parse_conversion_specifiers(self, format: str) -> List[ConversionSpecifier]: in FORMAT_RE.findall(format): if parens_key == '': key = None - specifiers.append(ConversionSpecifier(key, flags, width, precision, type, + specifiers.append(ConversionSpecifier(type, key, flags, width, precision, whole_seq=whole_seq)) return specifiers diff --git a/mypyc/irbuild/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index 57480eaf70a7..a09c1dffb597 100644 --- a/mypyc/irbuild/format_str_tokenizer.py +++ b/mypyc/irbuild/format_str_tokenizer.py @@ -24,8 +24,8 @@ def tokenizer_printf_style(format_str: str) -> Tuple[List[str], List[ConversionS for m in re.finditer(FORMAT_RE, format_str): whole_seq, parens_key, key, flags, width, precision, conversion_type = m.groups() - specifiers.append(ConversionSpecifier(key, flags, width, precision, - conversion_type, whole_seq=whole_seq)) + specifiers.append(ConversionSpecifier(conversion_type, key, flags, width, precision, + whole_seq=whole_seq)) cur_start = m.start(1) literals.append(format_str[last_end:cur_start]) From 3d99bcb39d33625c7700cf412c92c4e0a50b15c0 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Thu, 29 Jul 2021 02:34:56 +0800 Subject: [PATCH 19/19] Remove unused typing.Any --- mypy/checkstrformat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index 61ed124c9e0a..302f077b5bd9 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -13,7 +13,7 @@ import re from typing import ( - cast, List, Tuple, Dict, Callable, Union, Optional, Pattern, Match, Set, Any + cast, List, Tuple, Dict, Callable, Union, Optional, Pattern, Match, Set ) from typing_extensions import Final, TYPE_CHECKING