diff --git a/gdtoolkit/common/ast.py b/gdtoolkit/common/ast.py index 878b1501..4218f0a4 100644 --- a/gdtoolkit/common/ast.py +++ b/gdtoolkit/common/ast.py @@ -31,6 +31,7 @@ def __init__(self, node: Tree, annotations: List[Annotation] = []): self._load_sub_statements() + # pylint: disable=too-many-branches def _load_sub_statements(self): if self.kind == "class_def": pass # TODO: implement @@ -48,6 +49,8 @@ def _load_sub_statements(self): self.sub_statements = [Statement(n) for n in self.lark_node.children[1:]] elif self.kind == "for_stmt": self.sub_statements = [Statement(n) for n in self.lark_node.children[2:]] + elif self.kind == "for_stmt_typed": + self.sub_statements = [Statement(n) for n in self.lark_node.children[3:]] elif self.kind == "match_stmt": for branch in self.lark_node.children: self.sub_statements += [Statement(n) for n in branch.children[1:]] diff --git a/gdtoolkit/formatter/function_statement.py b/gdtoolkit/formatter/function_statement.py index 7b01848a..985847d3 100644 --- a/gdtoolkit/formatter/function_statement.py +++ b/gdtoolkit/formatter/function_statement.py @@ -26,6 +26,7 @@ def format_func_statement(statement: Tree, context: Context) -> Outcome: "if_stmt": _format_if_statement, "while_stmt": partial(_format_branch, "while ", ":", 0), "for_stmt": _format_for_statement, + "for_stmt_typed": _format_for_statement_typed, "match_stmt": _format_match_statement, "annotation": format_standalone_annotation, # fake statements: @@ -83,6 +84,13 @@ def _format_for_statement(statement: Tree, context: Context) -> Outcome: return _format_branch(prefix, suffix, expr_position, statement, context) +def _format_for_statement_typed(statement: Tree, context: Context) -> Outcome: + prefix = f"for {statement.children[0].value}: {statement.children[1].value} in " + suffix = ":" + expr_position = 2 + return _format_branch(prefix, suffix, expr_position, statement, context) + + def _format_match_statement(statement: Tree, context: Context) -> Outcome: prefix = "match " suffix = ":" diff --git a/gdtoolkit/gd2py/__init__.py b/gdtoolkit/gd2py/__init__.py index 4b66b830..534d83d1 100644 --- a/gdtoolkit/gd2py/__init__.py +++ b/gdtoolkit/gd2py/__init__.py @@ -100,6 +100,14 @@ def _convert_statement(statement: Tree, context: Context) -> List[str]: ) ] + _convert_block(s.children[2:], c.create_child_context(-1)), + "for_stmt_typed": lambda s, c: [ + "{}for {} in {}:".format( + c.indent_string, + s.children[0].value, + _convert_expression_to_str(s.children[2]), + ) + ] + + _convert_block(s.children[3:], c.create_child_context(-2)), "match_stmt": _convert_match_statement, "match_branch": partial(_convert_branch_with_expression, "elif"), } # type: Dict[str, Callable] diff --git a/gdtoolkit/linter/name_checks.py b/gdtoolkit/linter/name_checks.py index 202c37e3..0d501559 100644 --- a/gdtoolkit/linter/name_checks.py +++ b/gdtoolkit/linter/name_checks.py @@ -22,6 +22,7 @@ def lint(parse_tree: Tree, config: MappingProxyType) -> List[Problem]: "enum_named", "enum_element", "for_stmt", + "for_stmt_typed", "func_arg_regular", "func_arg_inf", "func_arg_typed", @@ -93,7 +94,7 @@ def lint(parse_tree: Tree, config: MappingProxyType) -> List[Problem]: partial( _generic_name_check, config["loop-variable-name"], - rule_name_tokens["for_stmt"], + rule_name_tokens["for_stmt"] + rule_name_tokens["for_stmt_typed"], "loop-variable-name", 'Loop variable name "{}" is not valid', ), diff --git a/gdtoolkit/parser/gdscript.lark b/gdtoolkit/parser/gdscript.lark index 739a6fcb..19a94c62 100644 --- a/gdtoolkit/parser/gdscript.lark +++ b/gdtoolkit/parser/gdscript.lark @@ -46,6 +46,7 @@ _var_typed: "var" NAME ":" TYPE_HINT class_var_typed: _var_typed [inline_property_body] class_var_typed_assgnd: _var_typed "=" expr [inline_property_body] class_var_inf: "var" NAME ":" "=" expr [inline_property_body] +_for_iterator_var_typed: NAME ":" TYPE_HINT inline_property_body: ":" [property_delegate_set ["," property_delegate_get]] | ":" [property_delegate_get ["," property_delegate_set]] const_stmt: const_assigned @@ -95,6 +96,7 @@ _simple_func_stmt: annotation* single_func_stmt (";" annotation* single_func_stm ?compound_func_stmt: if_stmt | while_stmt | for_stmt + | for_stmt_typed | match_stmt return_stmt: "return" [expr] func_var_stmt: func_var_empty @@ -117,6 +119,7 @@ elif_branch: "elif" expr ":" _func_suite else_branch: "else" ":" _func_suite while_stmt: "while" expr ":" _func_suite for_stmt: "for" NAME "in" expr ":" _func_suite +for_stmt_typed: "for" _for_iterator_var_typed "in" expr ":" _func_suite match_stmt: "match" expr ":" _match_body _match_body: _NL _INDENT match_branch+ _DEDENT match_branch: pattern ":" _func_suite diff --git a/tests/formatter/input-output-pairs/for_loop_typed.in.gd b/tests/formatter/input-output-pairs/for_loop_typed.in.gd new file mode 100644 index 00000000..91ea8460 --- /dev/null +++ b/tests/formatter/input-output-pairs/for_loop_typed.in.gd @@ -0,0 +1,5 @@ +class X: + func foo(): + var a: Array[int] = [1] + for i:int in a: + print(a[i]) \ No newline at end of file diff --git a/tests/formatter/input-output-pairs/for_loop_typed.out.gd b/tests/formatter/input-output-pairs/for_loop_typed.out.gd new file mode 100644 index 00000000..4c0012cb --- /dev/null +++ b/tests/formatter/input-output-pairs/for_loop_typed.out.gd @@ -0,0 +1,5 @@ +class X: + func foo(): + var a: Array[int] = [1] + for i: int in a: + print(a[i]) diff --git a/tests/gd2py/input-output-pairs/func_level_statements.in.gd b/tests/gd2py/input-output-pairs/func_level_statements.in.gd index e4370a08..01bb22ed 100644 --- a/tests/gd2py/input-output-pairs/func_level_statements.in.gd +++ b/tests/gd2py/input-output-pairs/func_level_statements.in.gd @@ -16,6 +16,8 @@ func foo(): break for i in 1: continue + for j: int in 1: + continue match 1: 1: pass diff --git a/tests/gd2py/input-output-pairs/func_level_statements.out.py b/tests/gd2py/input-output-pairs/func_level_statements.out.py index 4daf5145..2e24393c 100644 --- a/tests/gd2py/input-output-pairs/func_level_statements.out.py +++ b/tests/gd2py/input-output-pairs/func_level_statements.out.py @@ -16,6 +16,8 @@ def foo(): break for i in 1: continue + for j in 1: + continue if 1: pass elif 1: diff --git a/tests/linter/test_name_checks.py b/tests/linter/test_name_checks.py index c1fe1d42..404c7435 100644 --- a/tests/linter/test_name_checks.py +++ b/tests/linter/test_name_checks.py @@ -193,6 +193,18 @@ def test_enum_element_name_nok(code): for aaa_bbb in y: pass """, +"""func foo(): + for _x: int in y: + pass +""", +"""func foo(): + for xyz: int in y: + pass +""", +"""func foo(): + for aaa_bbb: int in y: + pass +""", ]) def test_loop_variable_name_ok(code): simple_ok_check(code) @@ -211,6 +223,18 @@ def test_loop_variable_name_ok(code): for X_X in y: pass """, +"""func foo(): + for x_: int in y: + pass +""", +"""func foo(): + for xX: int in y: + pass +""", +"""func foo(): + for X_X: int in y: + pass +""", ]) def test_loop_variable_name_nok(code): simple_nok_check(code, 'loop-variable-name') diff --git a/tests/valid-gd-scripts/typed_for_loop.gd b/tests/valid-gd-scripts/typed_for_loop.gd new file mode 100644 index 00000000..5d62a6dc --- /dev/null +++ b/tests/valid-gd-scripts/typed_for_loop.gd @@ -0,0 +1,6 @@ +extends Node + +func _ready(): + var a: Array[int] = [1] + for i: int in a: + print(a)