From 585711e67a685df5c90cf69a855143455f59474f Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Fri, 22 Nov 2024 13:38:26 -0500 Subject: [PATCH] Finish adding types to `rosidl_parser` (#832) * Add types to defintions Signed-off-by: Michael Carlstrom * Move Literal import Signed-off-by: Michael Carlstrom * Cleaner Literals Signed-off-by: Michael Carlstrom * Add py.typed Signed-off-by: Michael Carlstrom * init Signed-off-by: Michael Carlstrom * type complete Signed-off-by: Michael Carlstrom * flake8 fixes Signed-off-by: Michael Carlstrom * add dep to package.xml Signed-off-by: Michael Carlstrom * fix None return bug Signed-off-by: Michael Carlstrom * path recursive Signed-off-by: Michael Carlstrom * fix build error Signed-off-by: Michael Carlstrom * remove no any return Signed-off-by: Michael Carlstrom * cmake mypy Signed-off-by: Michael Carlstrom * resolve constant bug Signed-off-by: Michael Carlstrom * Add Branch defintion Signed-off-by: Michael Carlstrom * Moved ParseTree into try except Signed-off-by: Michael Carlstrom * remove try except Signed-off-by: Michael Carlstrom * fix comment Signed-off-by: Michael Carlstrom * use Match and Pattern from typing module Signed-off-by: Michael Carlstrom * old lark version work arounds Signed-off-by: Michael Carlstrom --------- Signed-off-by: Michael Carlstrom --- rosidl_parser/CMakeLists.txt | 1 + rosidl_parser/package.xml | 1 + rosidl_parser/rosidl_parser/definition.py | 9 +- rosidl_parser/rosidl_parser/parser.py | 206 ++++++++++++++++------ rosidl_parser/test/test_parser.py | 55 +++--- 5 files changed, 183 insertions(+), 89 deletions(-) diff --git a/rosidl_parser/CMakeLists.txt b/rosidl_parser/CMakeLists.txt index 9c282bca0..166758c2a 100644 --- a/rosidl_parser/CMakeLists.txt +++ b/rosidl_parser/CMakeLists.txt @@ -9,6 +9,7 @@ ament_python_install_package(${PROJECT_NAME}) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) + find_package(ament_cmake_mypy REQUIRED) ament_lint_auto_find_test_dependencies() endif() diff --git a/rosidl_parser/package.xml b/rosidl_parser/package.xml index 871384b36..e0b4eb514 100644 --- a/rosidl_parser/package.xml +++ b/rosidl_parser/package.xml @@ -21,6 +21,7 @@ python3-lark-parser rosidl_adapter + ament_cmake_mypy ament_cmake_pytest ament_lint_auto ament_lint_common diff --git a/rosidl_parser/rosidl_parser/definition.py b/rosidl_parser/rosidl_parser/definition.py index 26587b9b3..5873ba470 100644 --- a/rosidl_parser/rosidl_parser/definition.py +++ b/rosidl_parser/rosidl_parser/definition.py @@ -17,6 +17,7 @@ from typing import Final from typing import Iterable from typing import List +from typing import Literal from typing import Optional from typing import Set from typing import Tuple @@ -95,7 +96,7 @@ ) if TYPE_CHECKING: - from typing import Literal, TypeAlias + from typing_extensions import TypeAlias SignedNonexplicitIntegerTypeValues = Literal['short', 'long', 'long long'] UnsignedNonexplicitIntegerTypeValues = Literal['unsigned short', 'unsigned long', 'unsigned long long'] @@ -108,7 +109,7 @@ BooleanValue = Literal['boolean'] OctetValue = Literal['octet'] - SignedExplicitIntegerTypeValues = Literal['int8', 'int16', 'int32' 'int64'] + SignedExplicitIntegerTypeValues = Literal['int8', 'int16', 'int32', 'int64'] UnsignedExplicitIntegerTypeValues = Literal['uint8', 'uint16', 'uint32', 'uint64'] ExplicitIntegerTypeValues = Union[SignedExplicitIntegerTypeValues, @@ -299,7 +300,7 @@ class BoundedWString(AbstractWString): __slots__ = ('maximum_size', ) - def __init__(self, maximum_size: int) -> None: + def __init__(self, maximum_size: Union[int, str]) -> None: """ Create a BoundedWString. @@ -803,7 +804,7 @@ class IdlLocator: __slots__ = ('basepath', 'relative_path') - def __init__(self, basepath: str, relative_path: str) -> None: + def __init__(self, basepath: pathlib.Path, relative_path: pathlib.Path) -> None: """ Create an IdlLocator. diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 8aceb02df..399ceee5d 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -16,12 +16,23 @@ import os import re import sys +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import Literal +from typing import Match +from typing import Optional +from typing import Pattern +from typing import TYPE_CHECKING +from typing import Union from lark import Lark from lark.lexer import Token from lark.tree import pydot__tree_to_png from lark.tree import Tree +from rosidl_parser.definition import AbstractNestableType from rosidl_parser.definition import AbstractNestedType from rosidl_parser.definition import AbstractType from rosidl_parser.definition import Action @@ -38,6 +49,7 @@ from rosidl_parser.definition import CONSTANT_MODULE_SUFFIX from rosidl_parser.definition import IdlContent from rosidl_parser.definition import IdlFile +from rosidl_parser.definition import IdlLocator from rosidl_parser.definition import Include from rosidl_parser.definition import Member from rosidl_parser.definition import Message @@ -50,15 +62,30 @@ from rosidl_parser.definition import UnboundedSequence from rosidl_parser.definition import UnboundedString from rosidl_parser.definition import UnboundedWString +from rosidl_parser.definition import ValueType + +if TYPE_CHECKING: + from typing_extensions import TypeAlias + from typing import TypeVar + + from rosidl_parser.definition import BasicTypeValues + + # Definitions taken from lark.tree + # Since lark version's used by Windows and rhel does not have Branch or ParseTree. + _Leaf_T = TypeVar('_Leaf_T') + Branch: TypeAlias = Union[_Leaf_T, Tree] + ParseTree: TypeAlias = Tree + +AbstractTypeAlias = Union[AbstractNestableType, BasicType, BoundedSequence, UnboundedSequence] grammar_file = os.path.join(os.path.dirname(__file__), 'grammar.lark') with open(grammar_file, mode='r', encoding='utf-8') as h: grammar = h.read() -_parser = None +_parser: Optional[Lark] = None -def parse_idl_file(locator, png_file=None): +def parse_idl_file(locator: IdlLocator, png_file: Optional[str] = None) -> IdlFile: string = locator.get_absolute_path().read_text(encoding='utf-8') try: content = parse_idl_string(string, png_file=png_file) @@ -68,7 +95,7 @@ def parse_idl_file(locator, png_file=None): return IdlFile(locator, content) -def parse_idl_string(idl_string, png_file=None): +def parse_idl_string(idl_string: str, png_file: Optional[str] = None) -> IdlContent: tree = get_ast_from_idl_string(idl_string) content = extract_content_from_ast(tree) @@ -82,25 +109,27 @@ def parse_idl_string(idl_string, png_file=None): return content -def get_ast_from_idl_string(idl_string): +def get_ast_from_idl_string(idl_string: str) -> 'ParseTree': global _parser if _parser is None: _parser = Lark(grammar, start='specification', maybe_placeholders=False) return _parser.parse(idl_string) -def extract_content_from_ast(tree): +def extract_content_from_ast(tree: 'ParseTree') -> IdlContent: content = IdlContent() include_directives = tree.find_data('include_directive') for include_directive in include_directives: assert len(include_directive.children) == 1 child = include_directive.children[0] + assert isinstance(child, Tree) assert child.data in ('h_char_sequence', 'q_char_sequence') include_token = next(child.scan_values(_find_tokens(None))) - content.elements.append(Include(include_token.value)) + # Type ignore around lark-parser typing bugging in old version + content.elements.append(Include(include_token.value)) # type: ignore[attr-defined] - constants = {} + constants: Dict[str, List[Constant]] = {} const_dcls = tree.find_data('const_dcl') for const_dcl in const_dcls: annotations = get_annotations(const_dcl) @@ -116,25 +145,28 @@ def extract_content_from_ast(tree): constant.annotations = annotations module_comments.append(constant) - typedefs = {} + typedefs: Dict[Any, Union[Array, AbstractTypeAlias]] = {} typedef_dcls = tree.find_data('typedef_dcl') for typedef_dcl in typedef_dcls: assert len(typedef_dcl.children) == 1 child = typedef_dcl.children[0] + assert isinstance(child, Tree) assert 'type_declarator' == child.data assert len(child.children) == 2 abstract_type = get_abstract_type(child.children[0]) child = child.children[1] + assert isinstance(child, Tree) assert 'any_declarators' == child.data assert len(child.children) == 1, 'Only support single typedefs atm' child = child.children[0] + assert isinstance(child, Tree) identifier = get_first_identifier_value(child) - abstract_type = get_abstract_type_optionally_as_array( + abstract_type_array = get_abstract_type_optionally_as_array( abstract_type, child) if identifier in typedefs: - assert typedefs[identifier] == abstract_type + assert typedefs[identifier] == abstract_type_array else: - typedefs[identifier] = abstract_type + typedefs[identifier] = abstract_type_array struct_defs = list(tree.find_data('struct_def')) if len(struct_defs) == 1: @@ -259,7 +291,8 @@ def extract_content_from_ast(tree): return content -def resolve_typedefed_names(structure, typedefs): +def resolve_typedefed_names(structure: Structure, + typedefs: Dict[Any, Union[Array, AbstractTypeAlias]]) -> None: for member in structure.members: type_ = member.type if isinstance(type_, AbstractNestedType): @@ -274,22 +307,26 @@ def resolve_typedefed_names(structure, typedefs): if isinstance(typedefed_type.value_type, NamedType): assert typedefed_type.value_type.name in typedefs, \ 'Unknown named type: ' + typedefed_type.value_type.name - typedefed_type.value_type = \ - typedefs[typedefed_type.value_type.name] + + typedef = typedefs[typedefed_type.value_type.name] + assert isinstance(typedef, AbstractNestableType) + typedefed_type.value_type = typedef if isinstance(member.type, AbstractNestedType): + assert isinstance(typedefed_type, AbstractNestableType) member.type.value_type = typedefed_type else: member.type = typedefed_type -def get_first_identifier_value(tree): +def get_first_identifier_value(tree: 'ParseTree') -> Any: """Get the value of the first identifier token for a node.""" identifier_token = next(tree.scan_values(_find_tokens('IDENTIFIER'))) - return identifier_token.value + # Type ignore around lark-parser typing bugging in old version + return identifier_token.value # type: ignore[attr-defined] -def get_child_identifier_value(tree): +def get_child_identifier_value(tree: 'ParseTree') -> Any: """Get the value of the first child identifier token for a node.""" for c in tree.children: if not isinstance(c, Token): @@ -299,15 +336,18 @@ def get_child_identifier_value(tree): return None -def _find_tokens(token_type): - def find(t): +def _find_tokens( + token_type: Optional[Literal['IDENTIFIER']] +) -> Callable[['Branch[Union[str, Token]]'], bool]: + def find(t: 'Branch[Union[str, Token]]') -> bool: if isinstance(t, Token): if token_type is None or t.type == token_type: - return t + return True + return False return find -def get_module_identifier_values(tree, target): +def get_module_identifier_values(tree: 'ParseTree', target: 'ParseTree') -> List[Any]: """Get all module names between a tree node and a specific target node.""" path = _find_path(tree, target) modules = [n for n in path if n.data == 'module_dcl'] @@ -315,23 +355,33 @@ def get_module_identifier_values(tree, target): get_first_identifier_value(n) for n in modules] -def _find_path(node, target): +def _find_path(node: 'ParseTree', target: 'ParseTree') -> List['ParseTree']: + path = _find_path_recursive(node, target) + if path is None: + raise ValueError(f'No path found between {node} and {target}') + return path + + +def _find_path_recursive(node: 'ParseTree', target: 'ParseTree') -> Optional[List['ParseTree']]: if node == target: return [node] for c in node.children: if not isinstance(c, Tree): continue - tail = _find_path(c, target) + tail = _find_path_recursive(c, target) if tail is not None: return [node] + tail return None -def get_abstract_type_from_const_expr(const_expr, value): +def get_abstract_type_from_const_expr(const_expr: 'ParseTree', value: Union[str, int, float, bool] + ) -> Union[BoundedString, BoundedWString, BasicType]: assert len(const_expr.children) == 1 child = const_expr.children[0] + assert isinstance(child, Tree) if child.data in ('string_type', 'wide_string_type'): + assert isinstance(value, str) if 'string_type' == child.data: return BoundedString(len(value)) if 'wide_string_type' == child.data: @@ -340,24 +390,32 @@ def get_abstract_type_from_const_expr(const_expr, value): while len(child.children) == 1: child = child.children[0] + assert isinstance(child, Tree) return BasicType(BASE_TYPE_SPEC_TO_IDL_TYPE[child.data]) -def get_abstract_type_optionally_as_array(abstract_type, declarator): +def get_abstract_type_optionally_as_array( + abstract_type: AbstractTypeAlias, + declarator: 'ParseTree' +) -> Union[Array, AbstractTypeAlias]: assert len(declarator.children) == 1 child = declarator.children[0] + assert isinstance(child, Tree) if child.data == 'array_declarator': + assert isinstance(abstract_type, AbstractNestableType) fixed_array_sizes = list(child.find_data('fixed_array_size')) assert len(fixed_array_sizes) == 1, \ 'Unsupported multidimensional array: ' + str(declarator) positive_int_const = next( fixed_array_sizes[0].find_data('positive_int_const')) size = get_positive_int_const(positive_int_const) - abstract_type = Array(abstract_type, size) + if isinstance(size, str): + raise ValueError('Arrays only support Literal Sizes not constants') + return Array(abstract_type, size) return abstract_type -def add_message_members(msg, tree): +def add_message_members(msg: Message, tree: 'ParseTree') -> None: members = tree.find_data('member') for member in members: # the find_data methods seems to traverse the tree in post order @@ -370,20 +428,27 @@ def add_message_members(msg, tree): for declarator in declarators: assert len(declarator.children) == 1 child = declarator.children[0] + assert isinstance(child, Tree) if child.data == 'array_declarator': + assert isinstance(abstract_type, AbstractNestableType) fixed_array_sizes = list(child.find_data('fixed_array_size')) assert len(fixed_array_sizes) == 1, \ 'Unsupported multidimensional array: ' + str(member) positive_int_const = next( fixed_array_sizes[0].find_data('positive_int_const')) size = get_positive_int_const(positive_int_const) - abstract_type = Array(abstract_type, size) - m = Member(abstract_type, get_first_identifier_value(declarator)) + if isinstance(size, str): + raise ValueError('Arrays only support Literal Sizes not constants') + member_abstract_type: Union[Array, AbstractTypeAlias] = \ + Array(abstract_type, size) + else: + member_abstract_type = abstract_type + m = Member(member_abstract_type, get_first_identifier_value(declarator)) m.annotations += annotations msg.structure.members.append(m) -BASE_TYPE_SPEC_TO_IDL_TYPE = { +BASE_TYPE_SPEC_TO_IDL_TYPE: Dict[str, 'BasicTypeValues'] = { 'floating_pt_type_float': 'float', 'floating_pt_type_double': 'double', 'floating_pt_type_long_double': 'long double', @@ -402,20 +467,23 @@ def add_message_members(msg, tree): } -def get_abstract_type_from_type_spec(type_spec): +def get_abstract_type_from_type_spec(type_spec: 'ParseTree') -> AbstractTypeAlias: assert len(type_spec.children) == 1 child = type_spec.children[0] return get_abstract_type(child) -def get_abstract_type(tree): +def get_abstract_type(tree: 'Branch[Union[str, Token]]') -> AbstractTypeAlias: + assert isinstance(tree, Tree) if 'simple_type_spec' == tree.data: assert len(tree.children) == 1 child = tree.children[0] + assert isinstance(child, Tree) if 'base_type_spec' == child.data: while len(child.children) == 1: child = child.children[0] + assert isinstance(child, Tree) return BasicType(BASE_TYPE_SPEC_TO_IDL_TYPE[child.data]) if 'scoped_name' == child.data: @@ -432,6 +500,7 @@ def get_abstract_type(tree): if 'template_type_spec' == tree.data: assert len(tree.children) == 1 child = tree.children[0] + assert isinstance(child, Tree) if 'sequence_type' == child.data: # the find_data methods seems to traverse the tree in post order @@ -439,6 +508,7 @@ def get_abstract_type(tree): type_specs = list(child.find_data('type_spec')) type_spec = type_specs[-1] basetype = get_abstract_type_from_type_spec(type_spec) + assert isinstance(basetype, AbstractNestableType) positive_int_consts = list(child.find_data('positive_int_const')) if positive_int_consts: path = _find_path(child, positive_int_consts[0]) @@ -446,15 +516,21 @@ def get_abstract_type(tree): positive_int_consts.pop(0) if positive_int_consts: maximum_size = get_positive_int_const(positive_int_consts[-1]) + if isinstance(maximum_size, str): + raise ValueError('BoundedSequence only support Literal Sizes not constants') return BoundedSequence(basetype, maximum_size) else: return UnboundedSequence(basetype) if child.data in ('string_type', 'wide_string_type'): if len(child.children) == 1: - assert child.children[0].data == 'positive_int_const' - maximum_size = get_positive_int_const(child.children[0]) + child_child = child.children[0] + assert isinstance(child_child, Tree) + assert child_child.data == 'positive_int_const' + maximum_size = get_positive_int_const(child_child) if 'string_type' == child.data: + if isinstance(maximum_size, str): + raise ValueError('BoundedString only support Literal Sizes not constants') assert maximum_size > 0 return BoundedString(maximum_size=maximum_size) if 'wide_string_type' == child.data: @@ -473,7 +549,7 @@ def get_abstract_type(tree): assert False, 'Unsupported tree: ' + str(tree) -def get_positive_int_const(positive_int_const): +def get_positive_int_const(positive_int_const: 'ParseTree') -> Union[int, str]: assert positive_int_const.data == 'positive_int_const' # TODO support arbitrary expressions try: @@ -483,6 +559,7 @@ def get_positive_int_const(positive_int_const): else: digits = '' for child in decimal_literal.children: + assert isinstance(child, Token) digits += child.value return int(digits) @@ -493,13 +570,14 @@ def get_positive_int_const(positive_int_const): pass else: # TODO ensure that identifier resolves to a positive integer - return identifier_token.value + # Type ignore around lark-parser typing bugging in old version + return str(identifier_token.value) # type: ignore[attr-defined] assert False, 'Unsupported tree: ' + str(positive_int_const) -def get_annotations(tree): - annotations = [] +def get_annotations(tree: 'ParseTree') -> List[Annotation]: + annotations: List[Annotation] = [] for c in tree.children: if not isinstance(c, Tree): continue @@ -508,11 +586,12 @@ def get_annotations(tree): annotation_appl = c params = list(annotation_appl.find_data('annotation_appl_param')) if params: - value = {} + value_dict: Dict[Any, Union[str, int, float, bool]] = {} for param in params: const_expr = next(param.find_data('const_expr')) - value[get_first_identifier_value(param)] = \ + value_dict[get_first_identifier_value(param)] = \ get_const_expr_value(const_expr) + value: ValueType = value_dict elif len(annotation_appl.children) == 1: value = None else: @@ -524,13 +603,15 @@ def get_annotations(tree): return annotations -def get_const_expr_value(const_expr): +def get_const_expr_value(const_expr: 'Branch[Union[str, Token]]') -> Union[str, int, float, bool]: + assert isinstance(const_expr, Tree) # TODO support arbitrary expressions expr = list(const_expr.find_data('primary_expr')) assert len(expr) == 1, str(expr) primary_expr = expr[0] assert len(primary_expr.children) == 1 child = primary_expr.children[0] + assert isinstance(child, Tree) if 'scoped_name' == child.data: return str(child.children[0]) elif 'literal' == child.data: @@ -540,13 +621,15 @@ def get_const_expr_value(const_expr): assert len(literal.children) == 1 child = literal.children[0] + assert isinstance(child, Tree) if child.data == 'integer_literal': assert len(child.children) == 1 child = child.children[0] + assert isinstance(child, Tree) if child.data == 'decimal_literal': - value = get_decimal_literal_value(child) + value: Union[int, float] = get_decimal_literal_value(child) if negate_value: value = -value return value @@ -568,6 +651,7 @@ def get_const_expr_value(const_expr): if child.data == 'boolean_literal': assert len(child.children) == 1 child = child.children[0] + assert isinstance(child, Tree) assert child.data in ('boolean_literal_true', 'boolean_literal_false') return child.data == 'boolean_literal_true' @@ -584,14 +668,17 @@ def get_const_expr_value(const_expr): assert False, 'Unsupported tree: ' + str(const_expr) -def get_decimal_literal_value(decimal_literal): +def get_decimal_literal_value(decimal_literal: 'ParseTree') -> int: value = '' for child in decimal_literal.children: - value += child.value + if isinstance(child, Token): + value += child.value + else: + assert False, 'Unsupported tree: ' + str(decimal_literal) return int(value) -def get_floating_pt_literal_value(floating_pt_literal): +def get_floating_pt_literal_value(floating_pt_literal: 'ParseTree') -> float: value = '' for child in floating_pt_literal.children: if isinstance(child, Token): @@ -601,7 +688,7 @@ def get_floating_pt_literal_value(floating_pt_literal): return float(value) -def get_fixed_pt_literal_value(fixed_pt_literal): +def get_fixed_pt_literal_value(fixed_pt_literal: 'ParseTree') -> float: value = '' for child in fixed_pt_literal.children: if isinstance(child, Token): @@ -611,7 +698,8 @@ def get_fixed_pt_literal_value(fixed_pt_literal): return float(value) -def get_string_literals_value(string_literals, *, allow_unicode=False): +def get_string_literals_value(string_literals: 'ParseTree', *, + allow_unicode: bool = False) -> str: assert len(string_literals.children) > 0 value = '' for string_literal in string_literals.children: @@ -620,7 +708,9 @@ def get_string_literals_value(string_literals, *, allow_unicode=False): return value -def get_string_literal_value(string_literal, *, allow_unicode=False): +def get_string_literal_value(string_literal: 'Branch[Union[str, Token]]', *, + allow_unicode: bool = False) -> str: + assert isinstance(string_literal, Tree) if len(string_literal.children) == 0: return '' assert len(string_literal.children) == 1 @@ -639,18 +729,18 @@ def get_string_literal_value(string_literal, *, allow_unicode=False): value = value[1:-1] regex = _get_escape_sequences_regex(allow_unicode=allow_unicode) - value = regex.sub(_decode_escape_sequence, value) - # unescape double quote and backslash if preceeded by a backslash + str_value = regex.sub(_decode_escape_sequence, value) + # unescape double quote and backslash if preceded by a backslash i = 0 - while i < len(value): - if value[i] == '\\': - if i + 1 < len(value) and value[i + 1] in ('"', '\\'): - value = value[:i] + value[i + 1:] + while i < len(str_value): + if str_value[i] == '\\': + if i + 1 < len(str_value) and str_value[i + 1] in ('"', '\\'): + str_value = str_value[:i] + str_value[i + 1:] i += 1 - return value + return str_value -def _get_escape_sequences_regex(*, allow_unicode): +def _get_escape_sequences_regex(*, allow_unicode: bool) -> Pattern[str]: # IDL Table 7-9: Escape sequences pattern = '(' # newline, horizontal tab, vertical tab, backspace, carriage return, @@ -668,5 +758,5 @@ def _get_escape_sequences_regex(*, allow_unicode): return re.compile(pattern) -def _decode_escape_sequence(match): - return codecs.decode(match.group(0), 'unicode-escape') +def _decode_escape_sequence(match: Match[str]) -> str: + return codecs.decode(match.group(0), 'unicode-escape') # type: ignore diff --git a/rosidl_parser/test/test_parser.py b/rosidl_parser/test/test_parser.py index b58f55721..61b164742 100644 --- a/rosidl_parser/test/test_parser.py +++ b/rosidl_parser/test/test_parser.py @@ -21,6 +21,7 @@ from rosidl_parser.definition import BoundedSequence from rosidl_parser.definition import BoundedString from rosidl_parser.definition import BoundedWString +from rosidl_parser.definition import IdlFile from rosidl_parser.definition import IdlLocator from rosidl_parser.definition import Include from rosidl_parser.definition import Message @@ -43,11 +44,11 @@ @pytest.fixture(scope='module') -def message_idl_file(): +def message_idl_file() -> IdlFile: return parse_idl_file(MESSAGE_IDL_LOCATOR) -def test_whitespace_at_start_of_string(): +def test_whitespace_at_start_of_string() -> None: # Repeat to check ros2/rosidl#676 for _ in range(10): ast = get_ast_from_idl_string('const string foo = " e";') @@ -55,7 +56,7 @@ def test_whitespace_at_start_of_string(): assert ' e' == get_string_literals_value(token) -def test_whitespace_at_start_of_wide_string(): +def test_whitespace_at_start_of_wide_string() -> None: # Repeat to check ros2/rosidl#676 for _ in range(10): ast = get_ast_from_idl_string('const wstring foo = L" e";') @@ -63,7 +64,7 @@ def test_whitespace_at_start_of_wide_string(): assert ' e' == get_string_literals_value(token, allow_unicode=True) -def test_whitespace_at_end_of_string(): +def test_whitespace_at_end_of_string() -> None: # Repeat to check ros2/rosidl#676 for _ in range(10): ast = get_ast_from_idl_string('const string foo = "e ";') @@ -71,7 +72,7 @@ def test_whitespace_at_end_of_string(): assert 'e ' == get_string_literals_value(token) -def test_whitespace_at_end_of_wide_string(): +def test_whitespace_at_end_of_wide_string() -> None: # Repeat to check ros2/rosidl#676 for _ in range(10): ast = get_ast_from_idl_string('const wstring foo = L"e ";') @@ -79,19 +80,19 @@ def test_whitespace_at_end_of_wide_string(): assert 'e ' == get_string_literals_value(token, allow_unicode=True) -def test_message_parser(message_idl_file): +def test_message_parser(message_idl_file: IdlFile) -> None: messages = message_idl_file.content.get_elements_of_type(Message) assert len(messages) == 1 -def test_message_parser_includes(message_idl_file): +def test_message_parser_includes(message_idl_file: IdlFile) -> None: includes = message_idl_file.content.get_elements_of_type(Include) assert len(includes) == 2 assert includes[0].locator == 'OtherMessage.idl' assert includes[1].locator == 'pkgname/msg/OtherMessage.idl' -def test_message_parser_structure(message_idl_file): +def test_message_parser_structure(message_idl_file: IdlFile) -> None: messages = message_idl_file.content.get_elements_of_type(Message) assert len(messages) == 1 @@ -185,19 +186,19 @@ def test_message_parser_structure(message_idl_file): assert structure.members[31].name == 'array_short_values' -def test_message_parser_annotations(message_idl_file): +def test_message_parser_annotations(message_idl_file: IdlFile) -> None: messages = message_idl_file.content.get_elements_of_type(Message) assert len(messages) == 1 structure = messages[0].structure assert len(structure.annotations) == 2 assert structure.annotations[0].name == 'verbatim' - assert len(structure.annotations[0].value) == 2 - assert 'language' in structure.annotations[0].value - assert structure.annotations[0].value['language'] == 'comment' - assert 'text' in structure.annotations[0].value - assert structure.annotations[0].value['text'] == \ - 'Documentation of MyMessage.Adjacent string literal.' + assert len(structure.annotations[0].value) == 2 # type: ignore[arg-type] + assert 'language' in structure.annotations[0].value # type: ignore[operator] + assert structure.annotations[0].value['language'] == 'comment' # type: ignore[index] + assert 'text' in structure.annotations[0].value # type: ignore[operator] + text = structure.annotations[0].value['text'] # type: ignore[index] + assert text == 'Documentation of MyMessage.Adjacent string literal.' assert structure.annotations[1].name == 'transfer_mode' assert structure.annotations[1].value == 'SHMEM_REF' @@ -206,9 +207,9 @@ def test_message_parser_annotations(message_idl_file): assert structure.has_any_member_with_annotation('autoid') is False assert structure.members[2].annotations[0].name == 'default' - assert len(structure.members[2].annotations[0].value) == 1 - assert 'value' in structure.members[2].annotations[0].value - assert structure.members[2].annotations[0].value['value'] == 123 + assert len(structure.members[2].annotations[0].value) == 1 # type: ignore[arg-type] + assert 'value' in structure.members[2].annotations[0].value # type: ignore[operator] + assert structure.members[2].annotations[0].value['value'] == 123 # type: ignore[index] assert structure.has_any_member_with_annotation('default') assert len(structure.members[3].annotations) == 2 @@ -218,11 +219,11 @@ def test_message_parser_annotations(message_idl_file): assert structure.has_any_member_with_annotation('key') assert structure.members[3].annotations[1].name == 'range' - assert len(structure.members[3].annotations[1].value) == 2 - assert 'min' in structure.members[3].annotations[1].value - assert structure.members[3].annotations[1].value['min'] == -10 - assert 'max' in structure.members[3].annotations[1].value - assert structure.members[3].annotations[1].value['max'] == 10 + assert len(structure.members[3].annotations[1].value) == 2 # type: ignore[arg-type] + assert 'min' in structure.members[3].annotations[1].value # type: ignore[operator] + assert structure.members[3].annotations[1].value['min'] == -10 # type: ignore[index] + assert 'max' in structure.members[3].annotations[1].value # type: ignore[operator] + assert structure.members[3].annotations[1].value['max'] == 10 # type: ignore[index] assert structure.has_any_member_with_annotation('range') assert isinstance(structure.members[32].type, BasicType) @@ -305,11 +306,11 @@ def test_message_parser_annotations(message_idl_file): @pytest.fixture(scope='module') -def service_idl_file(): +def service_idl_file() -> IdlFile: return parse_idl_file(SERVICE_IDL_LOCATOR) -def test_service_parser(service_idl_file): +def test_service_parser(service_idl_file: IdlFile) -> None: services = service_idl_file.content.get_elements_of_type(Service) assert len(services) == 1 @@ -345,11 +346,11 @@ def test_service_parser(service_idl_file): @pytest.fixture(scope='module') -def action_idl_file(): +def action_idl_file() -> IdlFile: return parse_idl_file(ACTION_IDL_LOCATOR) -def test_action_parser(action_idl_file): +def test_action_parser(action_idl_file: IdlFile) -> None: actions = action_idl_file.content.get_elements_of_type(Action) assert len(actions) == 1