diff --git a/rosidl_parser/rosidl_parser/definition.py b/rosidl_parser/rosidl_parser/definition.py index 273c306b9..26587b9b3 100644 --- a/rosidl_parser/rosidl_parser/definition.py +++ b/rosidl_parser/rosidl_parser/definition.py @@ -13,71 +13,80 @@ # limitations under the License. import pathlib +from typing import Dict +from typing import Final from typing import Iterable +from typing import List +from typing import Optional +from typing import Set from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union # Basic types as defined by the IDL specification # 7.4.1.4.4.2 Basic Types -SIGNED_NONEXPLICIT_INTEGER_TYPES = ( # rules (26) +SIGNED_NONEXPLICIT_INTEGER_TYPES: Final = ( # rules (26) 'short', # rule (27) 'long', # rule (28) 'long long', # rule (29) ) -UNSIGNED_NONEXPLICIT_INTEGER_TYPES = ( # rules (30) +UNSIGNED_NONEXPLICIT_INTEGER_TYPES: Final = ( # rules (30) 'unsigned short', # rule (31) 'unsigned long', # rule (32) 'unsigned long long', # rule (33) ) -NONEXPLICIT_INTEGER_TYPES = ( +NONEXPLICIT_INTEGER_TYPES: Final = ( *SIGNED_NONEXPLICIT_INTEGER_TYPES, *UNSIGNED_NONEXPLICIT_INTEGER_TYPES, ) -FLOATING_POINT_TYPES = ( # rule (24) +FLOATING_POINT_TYPES: Final = ( # rule (24) 'float', 'double', 'long double', ) -CHARACTER_TYPES = ( +CHARACTER_TYPES: Final = ( 'char', # rule (34) 'wchar', # rule (35) ) -BOOLEAN_TYPE = 'boolean' # rule (36) -OCTET_TYPE = 'octet' # rule (37) +BOOLEAN_TYPE: Final = 'boolean' # rule (36) +OCTET_TYPE: Final = 'octet' # rule (37) # 7.4.13.4.4 Integers restricted to holding 8-bits of information # 7.4.13.4.5 Explicitly-named Integer Types -SIGNED_EXPLICIT_INTEGER_TYPES = ( +SIGNED_EXPLICIT_INTEGER_TYPES: Final = ( 'int8', # rule (208) 'int16', # rule (210) 'int32', # rule (211) 'int64', # rule (212) ) -UNSIGNED_EXPLICIT_INTEGER_TYPES = ( +UNSIGNED_EXPLICIT_INTEGER_TYPES: Final = ( 'uint8', # rule (209) 'uint16', # rule (213) 'uint32', # rule (214) 'uint64', # rule (215) ) -EXPLICIT_INTEGER_TYPES = ( +EXPLICIT_INTEGER_TYPES: Final = ( *SIGNED_EXPLICIT_INTEGER_TYPES, *UNSIGNED_EXPLICIT_INTEGER_TYPES, ) -SIGNED_INTEGER_TYPES = ( # rules (26) + (208) + (210-212) +SIGNED_INTEGER_TYPES: Final = ( # rules (26) + (208) + (210-212) *SIGNED_NONEXPLICIT_INTEGER_TYPES, *SIGNED_EXPLICIT_INTEGER_TYPES, ) -UNSIGNED_INTEGER_TYPES = ( # rules (30) + (209) + (213-215) +UNSIGNED_INTEGER_TYPES: Final = ( # rules (30) + (209) + (213-215) *UNSIGNED_NONEXPLICIT_INTEGER_TYPES, *UNSIGNED_EXPLICIT_INTEGER_TYPES, ) -INTEGER_TYPES = ( # rules (25) + (206-207) + (210-215) +INTEGER_TYPES: Final = ( # rules (25) + (206-207) + (210-215) *SIGNED_INTEGER_TYPES, *UNSIGNED_INTEGER_TYPES, ) """All basic types as defined by the IDL specification.""" -BASIC_TYPES = ( +BASIC_TYPES: Final = ( *INTEGER_TYPES, *FLOATING_POINT_TYPES, *CHARACTER_TYPES, @@ -85,21 +94,51 @@ OCTET_TYPE, ) -EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME = 'structure_needs_at_least_one_member' +if TYPE_CHECKING: + from typing import Literal, TypeAlias + SignedNonexplicitIntegerTypeValues = Literal['short', 'long', 'long long'] + UnsignedNonexplicitIntegerTypeValues = Literal['unsigned short', 'unsigned long', + 'unsigned long long'] -CONSTANT_MODULE_SUFFIX = '_Constants' + NonexplicitIntegerTypeValues = Union[SignedNonexplicitIntegerTypeValues, + UnsignedNonexplicitIntegerTypeValues] -SERVICE_REQUEST_MESSAGE_SUFFIX = '_Request' -SERVICE_RESPONSE_MESSAGE_SUFFIX = '_Response' -SERVICE_EVENT_MESSAGE_SUFFIX = '_Event' + FloatingPointTypeValues = Literal['float', 'double', 'long double'] + CharacterTypeValues = Literal['char', 'wchar'] + BooleanValue = Literal['boolean'] + OctetValue = Literal['octet'] -ACTION_GOAL_SUFFIX = '_Goal' -ACTION_RESULT_SUFFIX = '_Result' -ACTION_FEEDBACK_SUFFIX = '_Feedback' + SignedExplicitIntegerTypeValues = Literal['int8', 'int16', 'int32' 'int64'] + UnsignedExplicitIntegerTypeValues = Literal['uint8', 'uint16', 'uint32', 'uint64'] -ACTION_GOAL_SERVICE_SUFFIX = '_SendGoal' -ACTION_RESULT_SERVICE_SUFFIX = '_GetResult' -ACTION_FEEDBACK_MESSAGE_SUFFIX = '_FeedbackMessage' + ExplicitIntegerTypeValues = Union[SignedExplicitIntegerTypeValues, + UnsignedExplicitIntegerTypeValues] + + SignedIntegerTypeValues = Union[SignedNonexplicitIntegerTypeValues, + SignedExplicitIntegerTypeValues] + UnsignedIntegerTypeValues = Union[UnsignedNonexplicitIntegerTypeValues, + UnsignedExplicitIntegerTypeValues] + IntegerTypeValues = Union[SignedIntegerTypeValues, UnsignedIntegerTypeValues] + + BasicTypeValues = Union[IntegerTypeValues, FloatingPointTypeValues, + CharacterTypeValues, BooleanValue, + OctetValue] + +EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME: Final = 'structure_needs_at_least_one_member' + +CONSTANT_MODULE_SUFFIX: Final = '_Constants' + +SERVICE_REQUEST_MESSAGE_SUFFIX: Final = '_Request' +SERVICE_RESPONSE_MESSAGE_SUFFIX: Final = '_Response' +SERVICE_EVENT_MESSAGE_SUFFIX: Final = '_Event' + +ACTION_GOAL_SUFFIX: Final = '_Goal' +ACTION_RESULT_SUFFIX: Final = '_Result' +ACTION_FEEDBACK_SUFFIX: Final = '_Feedback' + +ACTION_GOAL_SERVICE_SUFFIX: Final = '_SendGoal' +ACTION_RESULT_SERVICE_SUFFIX: Final = '_GetResult' +ACTION_FEEDBACK_MESSAGE_SUFFIX: Final = '_FeedbackMessage' class AbstractType: @@ -107,7 +146,7 @@ class AbstractType: __slots__ = () - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return type(self) is type(other) @@ -137,7 +176,7 @@ class BasicType(AbstractNestableType): __slots__ = ('typename', ) - def __init__(self, typename: str): + def __init__(self, typename: 'BasicTypeValues') -> None: """ Create a BasicType. @@ -147,7 +186,9 @@ def __init__(self, typename: str): assert typename in BASIC_TYPES self.typename = typename - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BasicType): + return False return super().__eq__(other) and self.typename == other.typename @@ -156,7 +197,7 @@ class NamedType(AbstractNestableType): __slots__ = ('name') - def __init__(self, name: str): + def __init__(self, name: str) -> None: """ Create a NamedType. @@ -165,7 +206,9 @@ def __init__(self, name: str): super().__init__() self.name = name - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, NamedType): + return False return super().__eq__(other) and self.name == other.name @@ -174,7 +217,7 @@ class NamespacedType(AbstractNestableType): __slots__ = ('namespaces', 'name') - def __init__(self, namespaces: Iterable[str], name: str): + def __init__(self, namespaces: Iterable[str], name: str) -> None: """ Create a NamespacedType. @@ -189,7 +232,9 @@ def __init__(self, namespaces: Iterable[str], name: str): def namespaced_name(self) -> Tuple[str, ...]: return (*self.namespaces, self.name) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, NamespacedType): + return False return super().__eq__(other) and \ self.namespaces == other.namespaces and self.name == other.name @@ -199,7 +244,7 @@ class AbstractGenericString(AbstractNestableType): __slots__ = () - def has_maximum_size(self): + def has_maximum_size(self) -> bool: raise NotImplementedError('Only implemented in subclasses') @@ -214,7 +259,7 @@ class BoundedString(AbstractString): __slots__ = ('maximum_size', ) - def __init__(self, maximum_size: int): + def __init__(self, maximum_size: int) -> None: """ Create a BoundedString. @@ -224,10 +269,12 @@ def __init__(self, maximum_size: int): assert maximum_size >= 0 self.maximum_size = maximum_size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BoundedString): + return False return super().__eq__(other) and \ self.maximum_size == other.maximum_size @@ -237,7 +284,7 @@ class UnboundedString(AbstractString): __slots__ = () - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[False]': return False @@ -252,7 +299,7 @@ class BoundedWString(AbstractWString): __slots__ = ('maximum_size', ) - def __init__(self, maximum_size: int): + def __init__(self, maximum_size: int) -> None: """ Create a BoundedWString. @@ -265,10 +312,12 @@ def __init__(self, maximum_size: int): # assert maximum_size > 0 self.maximum_size = maximum_size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BoundedWString): + return False return super().__eq__(other) and \ self.maximum_size == other.maximum_size @@ -278,7 +327,7 @@ class UnboundedWString(AbstractWString): __slots__ = () - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[False]': return False @@ -294,7 +343,7 @@ class AbstractNestedType(AbstractType): __slots__ = ('value_type', ) - def __init__(self, value_type: AbstractNestableType): + def __init__(self, value_type: AbstractNestableType) -> None: """ Create an AbstractNestedType. @@ -304,10 +353,12 @@ def __init__(self, value_type: AbstractNestableType): assert isinstance(value_type, AbstractNestableType) self.value_type = value_type - def has_maximum_size(self): + def has_maximum_size(self) -> bool: raise NotImplementedError('Only implemented in subclasses') - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, AbstractNestedType): + return False return super().__eq__(other) and self.value_type == other.value_type @@ -316,7 +367,7 @@ class Array(AbstractNestedType): __slots__ = ('size') - def __init__(self, value_type: AbstractNestableType, size: int): + def __init__(self, value_type: AbstractNestableType, size: int) -> None: """ Create an Array. @@ -328,19 +379,21 @@ def __init__(self, value_type: AbstractNestableType, size: int): assert size > 0 self.size = size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, Array): + return False return super().__eq__(other) and self.size == other.size class AbstractSequence(AbstractNestedType): """The abstract base class of sequence types.""" - __slots__ = set() + __slots__: Set[str] = set() - def __init__(self, value_type: AbstractNestableType): + def __init__(self, value_type: AbstractNestableType) -> None: super().__init__(value_type) @@ -349,7 +402,7 @@ class BoundedSequence(AbstractSequence): __slots__ = ('maximum_size', ) - def __init__(self, value_type: AbstractNestableType, maximum_size: int): + def __init__(self, value_type: AbstractNestableType, maximum_size: int) -> None: """ Create a BoundedSequence. @@ -360,10 +413,12 @@ def __init__(self, value_type: AbstractNestableType, maximum_size: int): assert maximum_size > 0 self.maximum_size = maximum_size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BoundedSequence): + return False return super().__eq__(other) and \ self.maximum_size == other.maximum_size @@ -373,7 +428,7 @@ class UnboundedSequence(AbstractSequence): __slots__ = () - def __init__(self, value_type: AbstractNestableType): + def __init__(self, value_type: AbstractNestableType) -> None: """ Create an UnboundedSequence. @@ -381,16 +436,19 @@ def __init__(self, value_type: AbstractNestableType): """ super().__init__(value_type) - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[False]': return False +ValueType = Union[str, int, float, bool, Dict[str, Union[str, int, float, bool]], None] + + class Annotation: """An annotation identified by a name with an arbitrary value.""" __slots__ = ('name', 'value') - def __init__(self, name: str, value): + def __init__(self, name: str, value: ValueType) -> None: """ Create an Annotation. @@ -409,14 +467,14 @@ class Annotatable: __slots__ = ('annotations', ) - def __init__(self): - self.annotations = [] + def __init__(self) -> None: + self.annotations: List[Annotation] = [] - def get_annotation_value(self, name): + def get_annotation_value(self, name: str) -> ValueType: """ Get the unique value of an annotation of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: the annotation value :raises: ValueError if there is no or multiple annotations with the given name @@ -428,16 +486,16 @@ def get_annotation_value(self, name): raise ValueError(f"Multiple '{name}' annotations") return values[0] - def get_annotation_values(self, name): + def get_annotation_values(self, name: str) -> List[ValueType]: """ Get the values of annotations of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: a list of annotation values """ return [a.value for a in self.annotations if a.name == name] - def get_comment_lines(self): + def get_comment_lines(self) -> List[str]: """ Get the comment lines of the annotatable. @@ -445,28 +503,29 @@ def get_comment_lines(self): """ comments = [ x['text'] for x in self.get_annotation_values('verbatim') if + isinstance(x, Dict) and 'language' in x and 'text' in x and x['language'] == 'comment' ] - lines = [] + lines: List[str] = [] for comment in comments: - lines.extend(comment.splitlines()) + lines.extend(str(comment).splitlines()) return lines - def has_annotation(self, name): + def has_annotation(self, name: str) -> bool: """ Check if there is exactly one annotation of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: True if there is exactly one annotation, False otherwise """ values = self.get_annotation_values(name) return len(values) == 1 - def has_annotations(self, name): + def has_annotations(self, name: str) -> bool: """ Check if there are any annotations of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: True if there are any annotations, False otherwise """ annotations = self.get_annotation_values(name) @@ -496,7 +555,8 @@ class Structure(Annotatable): __slots__ = ('namespaced_type', 'members') - def __init__(self, namespaced_type: NamespacedType, members=None): + def __init__(self, namespaced_type: NamespacedType, + members: Optional[List['Member']] = None) -> None: """ Create a Structure. @@ -508,7 +568,7 @@ def __init__(self, namespaced_type: NamespacedType, members=None): self.namespaced_type = namespaced_type self.members = members or [] - def has_any_member_with_annotation(self, name: str): + def has_any_member_with_annotation(self, name: str) -> bool: """ Check whether any member has a particular annotation. @@ -524,11 +584,11 @@ class Include: __slots__ = ('locator', ) - def __init__(self, locator): + def __init__(self, locator: str) -> None: """ Create an Include. - :param str locator: a URI identifying the included file + :param locator: a URI identifying the included file """ self.locator = locator @@ -538,7 +598,8 @@ class Constant(Annotatable): __slots__ = ('name', 'type', 'value') - def __init__(self, name: str, type_: AbstractType, value): + def __init__(self, name: str, type_: AbstractType, + value: Union[str, int, float, bool]) -> None: """ Create a Constant. @@ -558,7 +619,7 @@ class Message: __slots__ = ('structure', 'constants') - def __init__(self, structure: Structure): + def __init__(self, structure: Structure) -> None: """ Create a Message. @@ -567,7 +628,7 @@ def __init__(self, structure: Structure): super().__init__() assert isinstance(structure, Structure) self.structure = structure - self.constants = [] + self.constants: List[Constant] = [] class Service: @@ -578,7 +639,7 @@ class Service: def __init__( self, namespaced_type: NamespacedType, request: Message, response: Message - ): + ) -> None: """ Create a Service. @@ -632,7 +693,7 @@ class Action: def __init__( self, namespaced_type: NamespacedType, goal: Message, result: Message, feedback: Message - ): + ) -> None: """ Create an Action. @@ -742,31 +803,38 @@ class IdlLocator: __slots__ = ('basepath', 'relative_path') - def __init__(self, basepath, relative_path): + def __init__(self, basepath: str, relative_path: str) -> None: """ Create an IdlLocator. - :param str basepath: the basepath of file - :param str relative_path: the path relative to the basepath of the file + :param basepath: the basepath of file + :param relative_path: the path relative to the basepath of the file """ super().__init__() self.basepath = pathlib.Path(basepath) self.relative_path = pathlib.Path(relative_path) - def get_absolute_path(self): + def get_absolute_path(self) -> pathlib.Path: return self.basepath / self.relative_path +IdlContentElement: 'TypeAlias' = Union[Include, Message, Service, Action] +IdlContentElementT = TypeVar('IdlContentElementT', bound=IdlContentElement) + + class IdlContent: """The content of an IDL file consisting of a list of elements.""" __slots__ = ('elements', ) - def __init__(self): + def __init__(self) -> None: super().__init__() - self.elements = [] + self.elements: List[IdlContentElement] = [] - def get_elements_of_type(self, type_): + def get_elements_of_type( + self, + type_: Type[IdlContentElementT] + ) -> List[IdlContentElementT]: return [e for e in self.elements if isinstance(e, type_)] @@ -775,12 +843,12 @@ class IdlFile: __slots__ = ('locator', 'content') - def __init__(self, locator, content): + def __init__(self, locator: IdlLocator, content: IdlContent) -> None: """ Create an IdlFile. - :param IdlLocator locator: the locator of the IDL file - :param IdlContent content: the content of the IDL file + :param locator: the locator of the IDL file + :param content: the content of the IDL file """ super().__init__() assert isinstance(locator, IdlLocator) diff --git a/rosidl_parser/rosidl_parser/py.typed b/rosidl_parser/rosidl_parser/py.typed new file mode 100644 index 000000000..e69de29bb