diff --git a/src/safeds_stubgen/api_analyzer/__init__.py b/src/safeds_stubgen/api_analyzer/__init__.py index 5a35b0d6..8c5ea018 100644 --- a/src/safeds_stubgen/api_analyzer/__init__.py +++ b/src/safeds_stubgen/api_analyzer/__init__.py @@ -16,6 +16,7 @@ VarianceKind, WildcardImport, ) +from ._ast_visitor import result_name_generator from ._get_api import get_api from ._mypy_helpers import get_classdef_definitions, get_funcdef_definitions, get_mypyfile_definitions from ._package_metadata import distribution, distribution_version, package_root @@ -62,6 +63,7 @@ "ParameterAssignment", "QualifiedImport", "Result", + "result_name_generator", "SetType", "TupleType", "TypeVarType", diff --git a/src/safeds_stubgen/api_analyzer/_api.py b/src/safeds_stubgen/api_analyzer/_api.py index de9f22ed..f485e131 100644 --- a/src/safeds_stubgen/api_analyzer/_api.py +++ b/src/safeds_stubgen/api_analyzer/_api.py @@ -242,6 +242,7 @@ class Function: is_static: bool is_class_method: bool is_property: bool + result_docstrings: list[ResultDocstring] type_var_types: list[TypeVarType] = field(default_factory=list) results: list[Result] = field(default_factory=list) reexported_by: list[Module] = field(default_factory=list) @@ -339,13 +340,11 @@ class Result: id: str name: str type: AbstractType | None - docstring: ResultDocstring def to_dict(self) -> dict[str, Any]: return { "id": self.id, "name": self.name, - "docstring": self.docstring.to_dict(), "type": self.type.to_dict() if self.type is not None else None, } diff --git a/src/safeds_stubgen/api_analyzer/_ast_visitor.py b/src/safeds_stubgen/api_analyzer/_ast_visitor.py index dad3850b..351e5c78 100644 --- a/src/safeds_stubgen/api_analyzer/_ast_visitor.py +++ b/src/safeds_stubgen/api_analyzer/_ast_visitor.py @@ -8,6 +8,7 @@ import mypy.types as mp_types import safeds_stubgen.api_analyzer._types as sds_types +from safeds_stubgen.docstring_parsing import ResultDocstring from ._api import ( API, @@ -37,8 +38,10 @@ ) if TYPE_CHECKING: + from collections.abc import Generator + from safeds_stubgen.api_analyzer._types import AbstractType - from safeds_stubgen.docstring_parsing import AbstractDocstringParser, ResultDocstring + from safeds_stubgen.docstring_parsing import AbstractDocstringParser class MyPyAstVisitor: @@ -256,8 +259,9 @@ def enter_funcdef(self, node: mp_nodes.FuncDef) -> None: # Sort for the snapshot tests type_var_types.sort(key=lambda x: x.name) - # Create results - results = self._parse_results(node, function_id) + # Create results and result docstrings + result_docstrings = self.docstring_parser.get_result_documentation(node.fullname) + results = self._parse_results(node, function_id, result_docstrings) # Get reexported data reexported_by = self._get_reexported_by(name) @@ -275,6 +279,7 @@ def enter_funcdef(self, node: mp_nodes.FuncDef) -> None: reexported_by=reexported_by, parameters=arguments, type_var_types=type_var_types, + result_docstrings=result_docstrings, ) self.__declaration_stack.append(function) @@ -400,7 +405,12 @@ def leave_assignmentstmt(self, _: mp_nodes.AssignmentStmt) -> None: # #### Result utilities - def _parse_results(self, node: mp_nodes.FuncDef, function_id: str) -> list[Result]: + def _parse_results( + self, + node: mp_nodes.FuncDef, + function_id: str, + result_docstrings: list[ResultDocstring], + ) -> list[Result]: # __init__ functions aren't supposed to have returns, so we can ignore them if node.name == "__init__": return [] @@ -414,8 +424,16 @@ def _parse_results(self, node: mp_nodes.FuncDef, function_id: str) -> list[Resul node_ret_type = node_type.ret_type if not isinstance(node_ret_type, mp_types.NoneType): - if isinstance(node_ret_type, mp_types.AnyType) and not has_correct_type_of_any( - node_ret_type.type_of_any, + unanalyzed_ret_type = getattr(node.unanalyzed_type, "ret_type", None) + + if ( + ( + not unanalyzed_ret_type + or getattr(unanalyzed_ret_type, "literal_value", "") is None + or isinstance(unanalyzed_ret_type, mp_types.AnyType) + ) + and isinstance(node_ret_type, mp_types.AnyType) + and not has_correct_type_of_any(node_ret_type.type_of_any) ): # In this case, the "Any" type was given because it was not explicitly annotated. # Therefor we have to try to infer the type. @@ -423,7 +441,6 @@ def _parse_results(self, node: mp_nodes.FuncDef, function_id: str) -> list[Resul type_is_inferred = ret_type is not None else: # Otherwise, we can parse the type normally - unanalyzed_ret_type = getattr(node.unanalyzed_type, "ret_type", None) ret_type = self.mypy_type_to_abstract_type(node_ret_type, unanalyzed_ret_type) else: # Infer type @@ -433,25 +450,36 @@ def _parse_results(self, node: mp_nodes.FuncDef, function_id: str) -> list[Resul if ret_type is None: return [] - result_docstring = self.docstring_parser.get_result_documentation(node.fullname) - if type_is_inferred and isinstance(ret_type, sds_types.TupleType): - return self._create_inferred_results(ret_type, result_docstring, function_id) + return self._create_inferred_results(ret_type, result_docstrings, function_id) # If we got a TupleType, we can iterate it for the results, but if we got a NamedType, we have just one result return_results = ret_type.types if isinstance(ret_type, sds_types.TupleType) else [ret_type] - return [ - Result( - id=f"{function_id}/result_{i + 1}", + + # Create Result objects and try to find a matching docstring name + all_results = [] + name_generator = result_name_generator() + for type_ in return_results: + result_docstring = ResultDocstring() + for docstring in result_docstrings: + if hash(docstring.type) == hash(type_): + result_docstring = docstring + break + + result_name = result_docstring.name if result_docstring.name else next(name_generator) + + result = Result( + id=f"{function_id}/{result_name}", type=type_, - name=f"result_{i + 1}", - docstring=result_docstring, + name=f"{result_name}", ) - for i, type_ in enumerate(return_results) - ] + + all_results.append(result) + + return all_results @staticmethod - def _infer_type_from_return_stmts(func_node: mp_nodes.FuncDef) -> sds_types.NamedType | sds_types.TupleType | None: + def _infer_type_from_return_stmts(func_node: mp_nodes.FuncDef) -> sds_types.TupleType | None: # To infer the type, we iterate through all return statements we find in the function func_defn = get_funcdef_definitions(func_node) return_stmts = find_return_stmts_recursive(func_defn) @@ -483,16 +511,13 @@ def _infer_type_from_return_stmts(func_node: mp_nodes.FuncDef) -> sds_types.Name key=lambda x: (x.name if isinstance(x, sds_types.NamedType) else str(len(x.types))), ) - if len(return_stmt_types) == 1: - return return_stmt_types[0] - elif len(return_stmt_types) >= 2: - return sds_types.TupleType(types=return_stmt_types) + return sds_types.TupleType(types=return_stmt_types) return None @staticmethod def _create_inferred_results( results: sds_types.TupleType, - result_docstring: ResultDocstring, + docstrings: list[ResultDocstring], function_id: str, ) -> list[Result]: """Create Result objects with inferred results. @@ -516,7 +541,8 @@ def _create_inferred_results( list[Result] A list of Results objects representing the possible results of a funtion. """ - result_array: list[list] = [] + result_array: list[list[AbstractType]] = [] + longest_inner_list = 1 for type_ in results.types: if isinstance(type_, sds_types.NamedType): if result_array: @@ -526,27 +552,56 @@ def _create_inferred_results( elif isinstance(type_, sds_types.TupleType): for i, type__ in enumerate(type_.types): if len(result_array) > i: - result_array[i].append(type__) + if type__ not in result_array[i]: + result_array[i].append(type__) + + if len(result_array[i]) > longest_inner_list: + longest_inner_list = len(result_array[i]) else: result_array.append([type__]) else: # pragma: no cover raise TypeError(f"Expected NamedType or TupleType, received {type(type_)}") + # If there are any arrays longer than others, these "others" are optional types and can be None + none_element = sds_types.NamedType(name="None", qname="builtins.None") + for array in result_array: + if len(array) < longest_inner_list and none_element not in array: + array.append(none_element) + + # Create Result objects + name_generator = result_name_generator() inferred_results = [] - for i, result_list in enumerate(result_array): + for result_list in result_array: result_count = len(result_list) if result_count == 1: result_type = result_list[0] else: result_type = sds_types.UnionType(result_list) - name = f"result_{i + 1}" + # Search for matching docstrings for each result for the name + result_docstring = ResultDocstring() + if docstrings: + if isinstance(result_type, sds_types.UnionType): + possible_type: sds_types.AbstractType | None + if len(docstrings) > 1: + docstring_types = [docstring.type for docstring in docstrings if docstring.type is not None] + possible_type = sds_types.UnionType(types=docstring_types) + else: + possible_type = docstrings[0].type + if possible_type == result_type: + result_docstring = docstrings[0] + else: + for docstring in docstrings: + if hash(docstring.type) == hash(result_type): + result_docstring = docstring + break + + result_name = result_docstring.name or next(name_generator) inferred_results.append( Result( - id=f"{function_id}/{name}", + id=f"{function_id}/{result_name}", type=result_type, - name=name, - docstring=result_docstring, + name=result_name, ), ) @@ -843,6 +898,8 @@ def mypy_type_to_abstract_type( # not allowed. In this case mypy interprets the type as "list[Any]", but we want the real types # of the list arguments, which we cant get through the "unanalyzed_type" attribute return self.mypy_type_to_abstract_type(unanalyzed_type) + elif isinstance(unanalyzed_type, mp_types.TupleType): + return sds_types.TupleType(types=[self.mypy_type_to_abstract_type(item) for item in unanalyzed_type.items]) # Iterable mypy types if isinstance(mypy_type, mp_types.TupleType): @@ -1128,3 +1185,10 @@ def _check_publicity_in_reexports(self, name: str, qname: str, parent: Module | def is_internal(name: str) -> bool: return name.startswith("_") + + +def result_name_generator() -> Generator: + """Generate a name for callable type parameters starting from 'a' until 'zz'.""" + while True: + for x in range(1, 1000): + yield f"result_{x}" diff --git a/src/safeds_stubgen/api_analyzer/_types.py b/src/safeds_stubgen/api_analyzer/_types.py index 4c5d7527..003331c5 100644 --- a/src/safeds_stubgen/api_analyzer/_types.py +++ b/src/safeds_stubgen/api_analyzer/_types.py @@ -2,6 +2,7 @@ import re from abc import ABCMeta, abstractmethod +from collections import Counter from dataclasses import dataclass, field from typing import TYPE_CHECKING, Any, ClassVar @@ -244,6 +245,11 @@ def to_dict(self) -> dict[str, Any]: def __hash__(self) -> int: return hash(frozenset(self.types)) + def __eq__(self, other: object) -> bool: + if not isinstance(other, UnionType): # pragma: no cover + return NotImplemented + return Counter(self.types) == Counter(other.types) + @dataclass(frozen=True) class ListType(AbstractType): diff --git a/src/safeds_stubgen/docstring_parsing/__init__.py b/src/safeds_stubgen/docstring_parsing/__init__.py index d6d1cdb1..c61afd94 100644 --- a/src/safeds_stubgen/docstring_parsing/__init__.py +++ b/src/safeds_stubgen/docstring_parsing/__init__.py @@ -2,7 +2,13 @@ from ._abstract_docstring_parser import AbstractDocstringParser from ._create_docstring_parser import create_docstring_parser -from ._docstring import AttributeDocstring, ClassDocstring, FunctionDocstring, ParameterDocstring, ResultDocstring +from ._docstring import ( + AttributeDocstring, + ClassDocstring, + FunctionDocstring, + ParameterDocstring, + ResultDocstring, +) from ._docstring_parser import DocstringParser from ._docstring_style import DocstringStyle from ._plaintext_docstring_parser import PlaintextDocstringParser diff --git a/src/safeds_stubgen/docstring_parsing/_abstract_docstring_parser.py b/src/safeds_stubgen/docstring_parsing/_abstract_docstring_parser.py index 4a4f1f62..ef1c9530 100644 --- a/src/safeds_stubgen/docstring_parsing/_abstract_docstring_parser.py +++ b/src/safeds_stubgen/docstring_parsing/_abstract_docstring_parser.py @@ -42,5 +42,5 @@ def get_attribute_documentation( pass # pragma: no cover @abstractmethod - def get_result_documentation(self, function_qname: str) -> ResultDocstring: + def get_result_documentation(self, function_qname: str) -> list[ResultDocstring]: pass # pragma: no cover diff --git a/src/safeds_stubgen/docstring_parsing/_docstring.py b/src/safeds_stubgen/docstring_parsing/_docstring.py index 24d501cf..d4de3429 100644 --- a/src/safeds_stubgen/docstring_parsing/_docstring.py +++ b/src/safeds_stubgen/docstring_parsing/_docstring.py @@ -51,6 +51,4 @@ def to_dict(self) -> dict[str, Any]: class ResultDocstring: type: AbstractType | None = None description: str = "" - - def to_dict(self) -> dict[str, Any]: - return dataclasses.asdict(self) + name: str = "" diff --git a/src/safeds_stubgen/docstring_parsing/_docstring_parser.py b/src/safeds_stubgen/docstring_parsing/_docstring_parser.py index 3fac378d..00d72d14 100644 --- a/src/safeds_stubgen/docstring_parsing/_docstring_parser.py +++ b/src/safeds_stubgen/docstring_parsing/_docstring_parser.py @@ -7,7 +7,7 @@ from griffe.docstrings.dataclasses import DocstringAttribute, DocstringParameter from griffe.docstrings.utils import parse_annotation from griffe.enumerations import DocstringSectionKind, Parser -from griffe.expressions import Expr, ExprName, ExprSubscript, ExprTuple +from griffe.expressions import Expr, ExprBinOp, ExprName, ExprSubscript, ExprTuple # noinspection PyProtectedMember import safeds_stubgen.api_analyzer._types as sds_types @@ -173,13 +173,12 @@ def get_attribute_documentation( description=last_attribute.description.strip("\n"), ) - # Todo handle multiple results - def get_result_documentation(self, function_qname: str) -> ResultDocstring: + def get_result_documentation(self, function_qname: str) -> list[ResultDocstring]: # Find matching parameter docstrings griffe_docstring = self.__get_cached_docstring(function_qname) if griffe_docstring is None: - return ResultDocstring() + return [] all_returns = None for docstring_section in griffe_docstring.parsed: @@ -188,12 +187,39 @@ def get_result_documentation(self, function_qname: str) -> ResultDocstring: break if not all_returns: - return ResultDocstring() + return [] - return ResultDocstring( - type=self._griffe_annotation_to_api_type(all_returns.value[0].annotation, griffe_docstring), - description=all_returns.value[0].description.strip("\n"), - ) + # Multiple results are handled differently for numpy, since there we can define multiple named results. + if self.parser == Parser.numpy: + results = [ + ResultDocstring( + type=self._griffe_annotation_to_api_type(result.annotation, griffe_docstring), + description=result.description.strip("\n"), + name=result.name or "", + ) + for result in all_returns.value + ] + else: + return_value = all_returns.value[0] + # If a GoogleDoc result docstring only has a type and no name Griffe parses it wrong and saves the + # type as the name... + if self.parser == Parser.google and return_value.annotation is None: + annotation = return_value.name + else: + annotation = return_value.annotation + + type_ = None + if annotation: + type_ = self._griffe_annotation_to_api_type(annotation, griffe_docstring) + + results = [ + ResultDocstring( + type=type_, + description=return_value.description.strip("\n"), + ), + ] + + return results @staticmethod def _get_matching_docstrings( @@ -276,7 +302,7 @@ def _griffe_annotation_to_api_type(self, annotation: Expr | str, docstring: Docs elements.append(sds_types.NamedType(name="None", qname="builtins.None")) return sds_types.UnionType(elements) else: - return sds_types.UnionType(elements) + return sds_types.TupleType(elements) elif isinstance(annotation, str): new_annotation = self._remove_default_from_griffe_annotation(annotation) parsed_annotation = parse_annotation(new_annotation, docstring) @@ -290,9 +316,18 @@ def _griffe_annotation_to_api_type(self, annotation: Expr | str, docstring: Docs ) else: return self._griffe_annotation_to_api_type(parsed_annotation, docstring) + elif isinstance(annotation, ExprBinOp): + types = [self._griffe_annotation_to_api_type(annotation.right, docstring)] + left_bin = annotation.left + if isinstance(left_bin, ExprBinOp): + while isinstance(left_bin, ExprBinOp): + types.append(self._griffe_annotation_to_api_type(left_bin.right, docstring)) + left_bin = left_bin.left + types.append(self._griffe_annotation_to_api_type(left_bin, docstring)) + return sds_types.UnionType(types=types) else: # pragma: no cover raise TypeError( - f"Can't parse unexpected type from docstring: {annotation}. This case is not handled by us" + f"Can't parse unexpected type from docstring: {annotation}. This case is not handled by us " f"(yet), please report this.", ) diff --git a/src/safeds_stubgen/docstring_parsing/_plaintext_docstring_parser.py b/src/safeds_stubgen/docstring_parsing/_plaintext_docstring_parser.py index 799162fc..2ac1c9ab 100644 --- a/src/safeds_stubgen/docstring_parsing/_plaintext_docstring_parser.py +++ b/src/safeds_stubgen/docstring_parsing/_plaintext_docstring_parser.py @@ -3,7 +3,13 @@ from typing import TYPE_CHECKING from ._abstract_docstring_parser import AbstractDocstringParser -from ._docstring import AttributeDocstring, ClassDocstring, FunctionDocstring, ParameterDocstring, ResultDocstring +from ._docstring import ( + AttributeDocstring, + ClassDocstring, + FunctionDocstring, + ParameterDocstring, + ResultDocstring, +) from ._helpers import get_full_docstring if TYPE_CHECKING: @@ -47,5 +53,5 @@ def get_attribute_documentation( def get_result_documentation( self, function_qname: str, # noqa: ARG002 - ) -> ResultDocstring: - return ResultDocstring() + ) -> list[ResultDocstring]: + return [] diff --git a/src/safeds_stubgen/stubs_generator/_generate_stubs.py b/src/safeds_stubgen/stubs_generator/_generate_stubs.py index 801497ce..e92146e4 100644 --- a/src/safeds_stubgen/stubs_generator/_generate_stubs.py +++ b/src/safeds_stubgen/stubs_generator/_generate_stubs.py @@ -16,10 +16,10 @@ Result, UnionType, VarianceKind, + result_name_generator, ) if TYPE_CHECKING: - from safeds_stubgen.docstring_parsing import AttributeDocstring, ClassDocstring, FunctionDocstring @@ -883,18 +883,17 @@ def _create_sds_docstring( # Results full_result_docstring = "" if isinstance(node, Function): - result_docstrings = [] - for result in node.results: - result_desc = result.docstring.description - if not result_desc: - continue + name_generator = result_name_generator() - result_desc = f"\n{indentations} * ".join(result_desc.split("\n")) + for result_docstring in node.result_docstrings: + result_desc = result_docstring.description + if result_desc: + result_desc = f"\n{indentations} * ".join(result_desc.split("\n")) - result_name = _convert_name_to_convention(result.name, self.naming_convention) - result_docstrings.append(f"{indentations} * @result {result_name} {result_desc}\n") + result_name = result_docstring.name if result_docstring.name else next(name_generator) + result_name = _convert_name_to_convention(result_name, self.naming_convention) - full_result_docstring = "".join(result_docstrings) + full_result_docstring += f"{indentations} * @result {result_name} {result_desc}\n" if full_result_docstring and full_docstring: full_result_docstring = f"{indentations} *\n{full_result_docstring}" diff --git a/tests/data/docstring_parser_package/googledoc.py b/tests/data/docstring_parser_package/googledoc.py index 426c7cbd..be055c4b 100644 --- a/tests/data/docstring_parser_package/googledoc.py +++ b/tests/data/docstring_parser_package/googledoc.py @@ -112,6 +112,18 @@ def function_with_return_value_no_type() -> None: """ +def function_with_multiple_results() -> (int, bool): + """ + function_with_named_result. + + Dolor sit amet. + + Returns: + int: first result + bool: second result + """ + + def function_without_return_value(): """function_without_return_value. @@ -243,3 +255,34 @@ class ClassWithVariousAttributeTypes: optional_type_2: Optional[int] class_type: ClassWithAttributes imported_type: AnotherClass + + +def infer_types(): + """ + property_method_with_docstring. + + Dolor sit amet. + + Returns: + str: This is the first result + int: This is the second result + """ + return "Some value", 2 + + +def infer_types2(a, b): + """ + property_method_with_docstring. + + Dolor sit amet. + + Args: + a (int): The first parameter + b (bool): The second parameter + + Returns: + str | bool: This is the result + """ + if a or b: + return "A value" + return True diff --git a/tests/data/docstring_parser_package/numpydoc.py b/tests/data/docstring_parser_package/numpydoc.py index 31ba4aa7..e80545c9 100644 --- a/tests/data/docstring_parser_package/numpydoc.py +++ b/tests/data/docstring_parser_package/numpydoc.py @@ -231,6 +231,24 @@ def function_with_named_result() -> bool: """ +def function_with_multiple_results() -> (int, bool): + """ + function_with_named_result. + + Dolor sit amet. + + Returns + ------- + first_result : int + first result + second_result : bool + second result + """ + if ClassWithAttributes: + return 1 + return True + + def function_without_result_value(): """ function_without_result_value. @@ -370,3 +388,71 @@ class ClassWithVariousAttributeTypes: optional_type_2: Optional[int] class_type: ClassWithAttributes imported_type: AnotherClass + + +def infer_types(): + """ + property_method_with_docstring. + + Dolor sit amet. + + Returns + ------- + first_result : bool + This is the first result + second_result : str + This is the second result + """ + return True, "Another value" + + +def infer_types2(a, b): + """ + property_method_with_docstring. + + Dolor sit amet. + + Parameters + ---------- + a : str + The first parameter + b : bool + The second parameter + + Returns + ------- + func_result : str | bool | int + This is the result + """ + if b: + return "A value" + if a: + return int + return True + + +def infer_types3(a, b): + """ + property_method_with_docstring. + + Dolor sit amet. + + Parameters + ---------- + a : str + The first parameter + b : bool + The second parameter + + Returns + ------- + func_result : str + This is the result + func_result_2 : int + This is the second result + """ + if b: + return "A value" + if a: + return int + return True diff --git a/tests/data/docstring_parser_package/restdoc.py b/tests/data/docstring_parser_package/restdoc.py index 738878b8..ef420705 100644 --- a/tests/data/docstring_parser_package/restdoc.py +++ b/tests/data/docstring_parser_package/restdoc.py @@ -91,6 +91,22 @@ def function_with_return_value_no_type() -> None: """ +def function_with_multiple_results() -> (int, bool): + """ + function_with_named_result. + + Dolor sit amet. + + :return: first result + :rtype: int + :return: second result + :rtype: bool + """ + if ClassWithMethod: + return 1 + return True + + def function_without_return_value(): """ function_without_return_value. @@ -193,6 +209,38 @@ def __init__( pass +def infer_types(): + """ + property_method_with_docstring. + + Dolor sit amet. + + :return: return value + :rtype: str + :return: return value + :rtype: int + """ + return "Some value", 1 + + +def infer_types2(a, b): + """ + property_method_with_docstring. + + Dolor sit amet. + + :param a: The first parameter + :type a: int + :param b: The second parameter + :type b: bool + :return: return value + :rtype: str | bool + """ + if a or b: + return "A value" + return True + + # Todo Currently disabled, since Griffe can't analyze ReST (Sphinx) attributes (see issue #98) # class ClassWithVariousAttributeTypes: # """ diff --git a/tests/safeds_stubgen/__snapshots__/test_main.ambr b/tests/safeds_stubgen/__snapshots__/test_main.ambr index e0bf671c..4390aa6d 100644 --- a/tests/safeds_stubgen/__snapshots__/test_main.ambr +++ b/tests/safeds_stubgen/__snapshots__/test_main.ambr @@ -815,10 +815,6 @@ ]), 'results': list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/main_package/another_path/another_module/yetAnotherClass/another_function/result_1', 'name': 'result_1', 'type': dict({ @@ -828,10 +824,6 @@ }), }), dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/main_package/main_module/ModuleClass/NestedClass/nested_class_function/result_1', 'name': 'result_1', 'type': dict({ @@ -856,10 +848,6 @@ }), }), dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/main_package/main_module/ModuleClass/_some_function/result_1', 'name': 'result_1', 'type': dict({ @@ -869,10 +857,6 @@ }), }), dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/main_package/main_module/_private_global_func/result_1', 'name': 'result_1', 'type': dict({ @@ -897,10 +881,6 @@ }), }), dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/main_package/main_module/global_func/result_1', 'name': 'result_1', 'type': dict({ diff --git a/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr b/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr index 3c6c1319..5d2ac536 100644 --- a/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr +++ b/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr @@ -4567,10 +4567,6 @@ # name: test_function_results[abstract_method_params] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/abstract_module/AbstractModuleClass/abstract_method_params/result_1', 'name': 'result_1', 'type': dict({ @@ -4594,10 +4590,6 @@ # name: test_function_results[abstract_property_method] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/abstract_module/AbstractModuleClass/abstract_property_method/result_1', 'name': 'result_1', 'type': dict({ @@ -4607,10 +4599,6 @@ }), }), dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/abstract_module/AbstractModuleClass/abstract_property_method/result_2', 'name': 'result_2', 'type': dict({ @@ -4624,10 +4612,6 @@ # name: test_function_results[abstract_static_method_params] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/abstract_module/AbstractModuleClass/abstract_static_method_params/result_1', 'name': 'result_1', 'type': dict({ @@ -4641,10 +4625,6 @@ # name: test_function_results[any_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/any_results/result_1', 'name': 'result_1', 'type': dict({ @@ -4658,10 +4638,6 @@ # name: test_function_results[callable_type] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/callable_type/result_1', 'name': 'result_1', 'type': dict({ @@ -4698,10 +4674,6 @@ # name: test_function_results[class_method_params] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/FunctionModuleClassB/class_method_params/result_1', 'name': 'result_1', 'type': dict({ @@ -4715,10 +4687,6 @@ # name: test_function_results[dictionary_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/dictionary_results/result_1', 'name': 'result_1', 'type': dict({ @@ -4740,10 +4708,6 @@ # name: test_function_results[dictionary_results_no_key_no_value] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/dictionary_results_no_key_no_value/result_1', 'name': 'result_1', 'type': dict({ @@ -4765,10 +4729,6 @@ # name: test_function_results[float_result] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/float_result/result_1', 'name': 'result_1', 'type': dict({ @@ -4782,16 +4742,6 @@ # name: test_function_results[google_docstring_func] list([ dict({ - 'docstring': dict({ - 'description': ''' - Checks if the sum of x and y is greater than 10 and returns - a boolean value. (Google Style) - ''', - 'type': dict({ - 'name': 'bool', - 'qname': 'builtins.bool', - }), - }), 'id': 'tests/data/various_modules_package/docstring_module/GoogleDocstringClass/google_docstring_func/result_1', 'name': 'result_1', 'type': dict({ @@ -4805,10 +4755,6 @@ # name: test_function_results[illegal_dictionary_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/illegal_dictionary_results/result_1', 'name': 'result_1', 'type': dict({ @@ -4830,10 +4776,6 @@ # name: test_function_results[illegal_list_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/illegal_list_results/result_1', 'name': 'result_1', 'type': dict({ @@ -4857,10 +4799,6 @@ # name: test_function_results[illegal_set_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/illegal_set_results/result_1', 'name': 'result_1', 'type': dict({ @@ -4884,10 +4822,6 @@ # name: test_function_results[infer_function] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/infer_types_module/InferMyTypes/infer_function/result_1', 'name': 'result_1', 'type': dict({ @@ -4947,10 +4881,6 @@ }), }), dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/infer_types_module/InferMyTypes/infer_function/result_2', 'name': 'result_2', 'type': dict({ @@ -4970,16 +4900,22 @@ }), }), dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/infer_types_module/InferMyTypes/infer_function/result_3', 'name': 'result_3', 'type': dict({ - 'kind': 'NamedType', - 'name': 'float', - 'qname': 'builtins.float', + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'float', + 'qname': 'builtins.float', + }), + dict({ + 'kind': 'NamedType', + 'name': 'None', + 'qname': 'builtins.None', + }), + ]), }), }), ]) @@ -4987,10 +4923,6 @@ # name: test_function_results[instance_method] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/FunctionModuleClassB/instance_method/result_1', 'name': 'result_1', 'type': dict({ @@ -5004,10 +4936,6 @@ # name: test_function_results[int_result] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/int_result/result_1', 'name': 'result_1', 'type': dict({ @@ -5021,10 +4949,6 @@ # name: test_function_results[list_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/list_results/result_1', 'name': 'result_1', 'type': dict({ @@ -5043,10 +4967,6 @@ # name: test_function_results[literal_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/literal_results/result_1', 'name': 'result_1', 'type': dict({ @@ -5061,10 +4981,6 @@ # name: test_function_results[multiple_type_var] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/type_var_module/multiple_type_var/result_1', 'name': 'result_1', 'type': dict({ @@ -5093,10 +5009,6 @@ # name: test_function_results[nested_class_function] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/FunctionModuleClassB/FunctionModuleClassC/nested_class_function/result_1', 'name': 'result_1', 'type': dict({ @@ -5114,13 +5026,6 @@ # name: test_function_results[numpy_docstring_func] list([ dict({ - 'docstring': dict({ - 'description': 'Checks if the sum of `x` and `y` is greater than 10. (Numpy)', - 'type': dict({ - 'name': 'bool', - 'qname': 'builtins.bool', - }), - }), 'id': 'tests/data/various_modules_package/docstring_module/NumpyDocstringClass/numpy_docstring_func/result_1', 'name': 'result_1', 'type': dict({ @@ -5134,10 +5039,6 @@ # name: test_function_results[obj_result] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/obj_result/result_1', 'name': 'result_1', 'type': dict({ @@ -5151,10 +5052,6 @@ # name: test_function_results[optional_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/optional_results/result_1', 'name': 'result_1', 'type': dict({ @@ -5182,10 +5079,6 @@ # name: test_function_results[property_function_infer] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/FunctionModulePropertiesClass/property_function_infer/result_1', 'name': 'result_1', 'type': dict({ @@ -5199,10 +5092,6 @@ # name: test_function_results[property_function_params] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/FunctionModulePropertiesClass/property_function_params/result_1', 'name': 'result_1', 'type': dict({ @@ -5216,13 +5105,6 @@ # name: test_function_results[rest_docstring_func] list([ dict({ - 'docstring': dict({ - 'description': 'Checks if the sum of x and y is greater than 10. (ReST)', - 'type': dict({ - 'name': 'bool', - 'qname': 'builtins.bool', - }), - }), 'id': 'tests/data/various_modules_package/docstring_module/RestDocstringClass/rest_docstring_func/result_1', 'name': 'result_1', 'type': dict({ @@ -5236,10 +5118,6 @@ # name: test_function_results[result_from_outside_the_package] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/result_from_outside_the_package/result_1', 'name': 'result_1', 'type': dict({ @@ -5253,10 +5131,6 @@ # name: test_function_results[set_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/set_results/result_1', 'name': 'result_1', 'type': dict({ @@ -5279,10 +5153,6 @@ # name: test_function_results[str_result] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/str_result/result_1', 'name': 'result_1', 'type': dict({ @@ -5296,10 +5166,6 @@ # name: test_function_results[tuple_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/tuple_results/result_1', 'name': 'result_1', 'type': dict({ @@ -5309,10 +5175,6 @@ }), }), dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/tuple_results/result_2', 'name': 'result_2', 'type': dict({ @@ -5326,10 +5188,6 @@ # name: test_function_results[type_var_func] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/type_var_module/type_var_func/result_1', 'name': 'result_1', 'type': dict({ @@ -5348,10 +5206,6 @@ # name: test_function_results[union_dictionary_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/union_dictionary_results/result_1', 'name': 'result_1', 'type': dict({ @@ -5393,10 +5247,6 @@ # name: test_function_results[union_results] list([ dict({ - 'docstring': dict({ - 'description': '', - 'type': None, - }), 'id': 'tests/data/various_modules_package/function_module/union_results/result_1', 'name': 'result_1', 'type': dict({ diff --git a/tests/safeds_stubgen/api_analyzer/test_types.py b/tests/safeds_stubgen/api_analyzer/test_types.py index 17e99220..a19c4ddf 100644 --- a/tests/safeds_stubgen/api_analyzer/test_types.py +++ b/tests/safeds_stubgen/api_analyzer/test_types.py @@ -29,7 +29,7 @@ def test_correct_hash() -> None: is_optional=True, default_value="test_str", assigned_by=ParameterAssignment.POSITION_OR_NAME, - docstring=ParameterDocstring("'hashvalue'", "r", "r"), + docstring=ParameterDocstring(None, "r", "r"), type=NamedType(name="str", qname=""), ) assert hash(parameter) == hash(deepcopy(parameter)) @@ -123,6 +123,10 @@ def test_union_type() -> None: assert UnionType([NamedType("a", "")]) != UnionType([NamedType("b", "")]) assert hash(UnionType([NamedType("a", "")])) != hash(UnionType([NamedType("b", "")])) + assert UnionType([NamedType("a", ""), LiteralType(["b"])]) == UnionType([LiteralType(["b"]), NamedType("a", "")]) + assert UnionType([NamedType("a", ""), LiteralType(["b"])]) != UnionType([LiteralType(["a"]), NamedType("b", "")]) + assert UnionType([NamedType("a", ""), NamedType("b", "")]) != UnionType([NamedType("a", ""), NamedType("c", "")]) + def test_callable_type() -> None: callable_type = CallableType( diff --git a/tests/safeds_stubgen/docstring_parsing/test_googledoc_parser.py b/tests/safeds_stubgen/docstring_parsing/test_googledoc_parser.py index f149313b..38ff3a11 100644 --- a/tests/safeds_stubgen/docstring_parsing/test_googledoc_parser.py +++ b/tests/safeds_stubgen/docstring_parsing/test_googledoc_parser.py @@ -265,7 +265,7 @@ def test_get_function_documentation( True, "multiple_types", ParameterDocstring( - type=UnionType( + type=TupleType( types=[NamedType(name="int", qname="builtins.int"), NamedType(name="bool", qname="builtins.bool")], ), ), @@ -542,7 +542,7 @@ def test_get_parameter_documentation( "ClassWithVariousAttributeTypes", "multiple_types", AttributeDocstring( - type=UnionType( + type=TupleType( types=[NamedType(name="int", qname="builtins.int"), NamedType(name="bool", qname="builtins.bool")], ), ), @@ -718,23 +718,49 @@ def test_get_attribute_documentation( [ ( "function_with_return_value_and_type", - ResultDocstring( - type=NamedType(name="bool", qname="builtins.bool"), - description="this will be the return value.", - ), + [ + ResultDocstring( + type=NamedType(name="bool", qname="builtins.bool"), + description="this will be the return value.", + ), + ], ), ( "function_with_return_value_no_type", - ResultDocstring(type=NamedType(name="None", qname="builtins.None"), description="None"), + [ + ResultDocstring( + type=NamedType(name="None", qname="builtins.None"), + description="None", + ), + ], + ), + ("function_without_return_value", []), + ( + "function_with_multiple_results", + [ + ResultDocstring( + type=TupleType( + types=[ + NamedType(name="int", qname="builtins.int"), + NamedType(name="bool", qname="builtins.bool"), + ], + ), + description="first result", + ), + ], ), - ("function_without_return_value", ResultDocstring(type=None, description="")), ], - ids=["existing return value and type", "existing return value no description", "function without return value"], + ids=[ + "existing return value and type", + "existing return value no description", + "function without return value", + "function with multiple results", + ], ) def test_get_result_documentation( googlestyledoc_parser: DocstringParser, function_name: str, - expected_result_documentation: ResultDocstring, + expected_result_documentation: list[ResultDocstring], ) -> None: node = get_specific_mypy_node(mypy_file, function_name) assert isinstance(node, nodes.FuncDef) diff --git a/tests/safeds_stubgen/docstring_parsing/test_numpydoc_parser.py b/tests/safeds_stubgen/docstring_parsing/test_numpydoc_parser.py index 0205bf1d..ed401cc6 100644 --- a/tests/safeds_stubgen/docstring_parsing/test_numpydoc_parser.py +++ b/tests/safeds_stubgen/docstring_parsing/test_numpydoc_parser.py @@ -356,7 +356,7 @@ def test_get_function_documentation( True, "multiple_types", ParameterDocstring( - type=UnionType( + type=TupleType( types=[NamedType(name="int", qname="builtins.int"), NamedType(name="bool", qname="builtins.bool")], ), ), @@ -712,7 +712,7 @@ def test_get_parameter_documentation( "ClassWithVariousAttributeTypes", "multiple_types", AttributeDocstring( - type=UnionType( + type=TupleType( types=[NamedType(name="int", qname="builtins.int"), NamedType(name="bool", qname="builtins.bool")], ), ), @@ -923,19 +923,43 @@ def test_get_attribute_documentation( [ ( "function_with_result_value_and_type", - ResultDocstring( - type=NamedType(name="bool", qname="builtins.bool"), - description="this will be the return value", - ), + [ + ResultDocstring( + type=NamedType(name="bool", qname="builtins.bool"), + description="this will be the return value", + ), + ], + ), + ( + "function_without_result_value", + [], ), - ("function_without_result_value", ResultDocstring(type=None, description="")), + ( + "function_with_multiple_results", + [ + ResultDocstring( + type=NamedType(name="int", qname="builtins.int"), + description="first result", + name="first_result", + ), + ResultDocstring( + type=NamedType(name="bool", qname="builtins.bool"), + description="second result", + name="second_result", + ), + ], + ), + ], + ids=[ + "existing return value and type", + "function without return value", + "function with multiple results", ], - ids=["existing return value and type", "function without return value"], ) def test_get_result_documentation( numpydoc_parser: DocstringParser, function_name: str, - expected_result_documentation: ResultDocstring, + expected_result_documentation: list[ResultDocstring], ) -> None: node = get_specific_mypy_node(mypy_file, function_name) assert isinstance(node, nodes.FuncDef) diff --git a/tests/safeds_stubgen/docstring_parsing/test_plaintext_docstring_parser.py b/tests/safeds_stubgen/docstring_parsing/test_plaintext_docstring_parser.py index 72d8b4b4..01de56a1 100644 --- a/tests/safeds_stubgen/docstring_parsing/test_plaintext_docstring_parser.py +++ b/tests/safeds_stubgen/docstring_parsing/test_plaintext_docstring_parser.py @@ -182,11 +182,11 @@ def test_get_attribute_documentation( [ ( "function_with_documentation", - ResultDocstring(), + [], ), ( "function_without_documentation", - ResultDocstring(), + [], ), ], ids=[ @@ -197,7 +197,7 @@ def test_get_attribute_documentation( def test_get_result_documentation( plaintext_docstring_parser: PlaintextDocstringParser, function_name: str, - expected_result_documentation: ParameterDocstring, + expected_result_documentation: list[ResultDocstring], ) -> None: node = get_specific_mypy_node(mypy_file, function_name) assert isinstance(node, nodes.FuncDef) diff --git a/tests/safeds_stubgen/docstring_parsing/test_restdoc_parser.py b/tests/safeds_stubgen/docstring_parsing/test_restdoc_parser.py index edf89a6a..3432e754 100644 --- a/tests/safeds_stubgen/docstring_parsing/test_restdoc_parser.py +++ b/tests/safeds_stubgen/docstring_parsing/test_restdoc_parser.py @@ -251,7 +251,7 @@ def test_get_function_documentation( True, "multiple_types", ParameterDocstring( - type=UnionType( + type=TupleType( types=[NamedType(name="int", qname="builtins.int"), NamedType(name="bool", qname="builtins.bool")], ), ), @@ -537,7 +537,7 @@ def test_get_parameter_documentation( # "ClassWithVariousAttributeTypes", # "multiple_types", # AttributeDocstring( -# type=UnionType( +# type=TupleType( # types=[NamedType(name="int", qname="builtins.int"), # NamedType(name="bool", qname="builtins.bool")], # ), @@ -706,20 +706,47 @@ def test_get_parameter_documentation( [ ( "function_with_return_value_and_type", - ResultDocstring(type=NamedType(name="bool", qname="builtins.bool"), description="return value"), + [ + ResultDocstring( + type=NamedType(name="bool", qname="builtins.bool"), + description="return value", + ), + ], ), ( "function_with_return_value_no_type", - ResultDocstring(type=NamedType(name="None", qname="builtins.None"), description="return value"), + [ + ResultDocstring( + type=NamedType(name="None", qname="builtins.None"), + description="return value", + ), + ], + ), + ( + "function_without_return_value", + [], + ), + ( + "function_with_multiple_results", + [ + ResultDocstring( + type=NamedType(name="bool", qname="builtins.bool"), + description="second result", + ), + ], ), - ("function_without_return_value", ResultDocstring(type=None, description="")), ], - ids=["existing return value and type", "existing return value no type", "function without return value"], + ids=[ + "existing return value and type", + "existing return value no type", + "function without return value", + "function with multiple results", + ], ) def test_get_result_documentation( restdoc_parser: DocstringParser, function_name: str, - expected_result_documentation: ResultDocstring, + expected_result_documentation: list[ResultDocstring], ) -> None: node = get_specific_mypy_node(mypy_file, function_name) assert isinstance(node, nodes.FuncDef) diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/TestStubFileGeneration.test_stub_creation[infer_types_module].sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/TestStubFileGeneration.test_stub_creation[infer_types_module].sdsstub index 7a3f0640..806bc96f 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/TestStubFileGeneration.test_stub_creation[infer_types_module].sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/TestStubFileGeneration.test_stub_creation[infer_types_module].sdsstub @@ -51,7 +51,7 @@ class InferMyTypes( static fun inferFunction( @PythonName("infer_param") inferParam: Int = 1, @PythonName("infer_param_2") inferParam2: Int = "Something" - ) -> (result1: union, result2: union, result3: Float) + ) -> (result1: union, result2: union, result3: Float?) /** * Test for inferring results with just one possible result, and not a tuple of results. diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[googledoc-GOOGLE].sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[googledoc-GOOGLE].sdsstub index f69f4240..f75d8661 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[googledoc-GOOGLE].sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[googledoc-GOOGLE].sdsstub @@ -83,11 +83,24 @@ fun functionWithReturnValueAndType() -> result1: Boolean * function_with_return_value_no_type. * * Dolor sit amet. + * + * @result result1 None */ @Pure @PythonName("function_with_return_value_no_type") fun functionWithReturnValueNoType() +/** + * function_with_named_result. + * + * Dolor sit amet. + * + * @result result1 first result + */ +@Pure +@PythonName("function_with_multiple_results") +fun functionWithMultipleResults() -> (result1: Int, result2: Boolean) + // TODO Result type information missing. /** * function_without_return_value. @@ -98,6 +111,35 @@ fun functionWithReturnValueNoType() @PythonName("function_without_return_value") fun functionWithoutReturnValue() +/** + * property_method_with_docstring. + * + * Dolor sit amet. + * + * @result result1 This is the first result + */ +@Pure +@PythonName("infer_types") +fun inferTypes() -> (result1: String, result2: Int) + +// TODO Some parameter have no type information. +/** + * property_method_with_docstring. + * + * Dolor sit amet. + * + * @param a The first parameter + * @param b The second parameter + * + * @result result1 str | bool: This is the result + */ +@Pure +@PythonName("infer_types2") +fun inferTypes2( + a, + b +) -> result1: union + /** * ClassWithDocumentation. Code:: * diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[numpydoc-NUMPYDOC].sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[numpydoc-NUMPYDOC].sdsstub index 90037523..5d09f3a5 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[numpydoc-NUMPYDOC].sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[numpydoc-NUMPYDOC].sdsstub @@ -78,11 +78,23 @@ fun functionWithResultValueAndType() -> result1: Boolean * * Dolor sit amet. * - * @result result1 this will be the return value + * @result namedResult this will be the return value */ @Pure @PythonName("function_with_named_result") -fun functionWithNamedResult() -> result1: Boolean +fun functionWithNamedResult() -> namedResult: Boolean + +/** + * function_with_named_result. + * + * Dolor sit amet. + * + * @result firstResult first result + * @result secondResult second result + */ +@Pure +@PythonName("function_with_multiple_results") +fun functionWithMultipleResults() -> (firstResult: Int, secondResult: Boolean) // TODO Result type information missing. /** @@ -94,6 +106,55 @@ fun functionWithNamedResult() -> result1: Boolean @PythonName("function_without_result_value") fun functionWithoutResultValue() +/** + * property_method_with_docstring. + * + * Dolor sit amet. + * + * @result firstResult This is the first result + * @result secondResult This is the second result + */ +@Pure +@PythonName("infer_types") +fun inferTypes() -> (firstResult: Boolean, secondResult: String) + +// TODO Some parameter have no type information. +/** + * property_method_with_docstring. + * + * Dolor sit amet. + * + * @param a The first parameter + * @param b The second parameter + * + * @result funcResult This is the result + */ +@Pure +@PythonName("infer_types2") +fun inferTypes2( + a, + b +) -> funcResult: union + +// TODO Some parameter have no type information. +/** + * property_method_with_docstring. + * + * Dolor sit amet. + * + * @param a The first parameter + * @param b The second parameter + * + * @result funcResult This is the result + * @result funcResult2 This is the second result + */ +@Pure +@PythonName("infer_types3") +fun inferTypes3( + a, + b +) -> result1: union + /** * ClassWithDocumentation. Code:: * @@ -236,13 +297,13 @@ class ClassWithMethod() { * * Dolor sit amet. * - * @result result1 this will be the return value + * @result namedResult this will be the return value */ @Pure @PythonName("method_with_docstring") fun methodWithDocstring( a - ) -> result1: Boolean + ) -> namedResult: Boolean } // TODO Some parameter have no type information. diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[restdoc-REST].sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[restdoc-REST].sdsstub index 6a696b08..29c2c861 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[restdoc-REST].sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[restdoc-REST].sdsstub @@ -66,11 +66,24 @@ fun functionWithReturnValueAndType() -> result1: Boolean * function_with_return_value_no_type. * * Dolor sit amet. + * + * @result result1 return value */ @Pure @PythonName("function_with_return_value_no_type") fun functionWithReturnValueNoType() +/** + * function_with_named_result. + * + * Dolor sit amet. + * + * @result result1 second result + */ +@Pure +@PythonName("function_with_multiple_results") +fun functionWithMultipleResults() -> (result1: Int, result2: Boolean) + // TODO Result type information missing. /** * function_without_return_value. @@ -81,6 +94,35 @@ fun functionWithReturnValueNoType() @PythonName("function_without_return_value") fun functionWithoutReturnValue() +/** + * property_method_with_docstring. + * + * Dolor sit amet. + * + * @result result1 return value + */ +@Pure +@PythonName("infer_types") +fun inferTypes() -> (result1: String, result2: Int) + +// TODO Some parameter have no type information. +/** + * property_method_with_docstring. + * + * Dolor sit amet. + * + * @param a The first parameter + * @param b The second parameter + * + * @result result1 return value + */ +@Pure +@PythonName("infer_types2") +fun inferTypes2( + a, + b +) -> result1: union + /** * ClassWithDocumentation. Code:: *