From 47c437fb7d51f60abfff46f3fa29c2b85f1b35bb Mon Sep 17 00:00:00 2001 From: Arsam Date: Sun, 25 Feb 2024 13:17:22 +0100 Subject: [PATCH 01/18] Adjusted snapshot test files for typevar stubs --- .../test_class_attribute_creation.sdsstub | 3 --- .../test_generate_stubs/test_function_creation.sdsstub | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_class_attribute_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_class_attribute_creation.sdsstub index 858101a2..f9ef79bc 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_class_attribute_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_class_attribute_creation.sdsstub @@ -93,9 +93,6 @@ class AttributesClassB() { static attr attrTypeFromOutsidePackage: AnotherClass @PythonName("attr_default_value_from_outside_package") static attr attrDefaultValueFromOutsidePackage: () -> a: AnotherClass - // TODO Attribute has no type information. - @PythonName("type_var") - static attr typeVar @PythonName("init_attr") attr initAttr: Boolean diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub index d63a22d9..39ae309c 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub @@ -248,9 +248,9 @@ fun resultFromOutsideThePackage() -> result1: AnotherClass @Pure @PythonName("type_var_func") -fun typeVarFunc( - @PythonName("type_var_list") typeVarList: List<> -) -> result1: List<> +fun typeVarFunc( + @PythonName("type_var_list") typeVarList: List +) -> result1: List @Pure @PythonName("ret_conditional_statement") From 979def4837f5e0fab19182fe91172a02ebbb21a7 Mon Sep 17 00:00:00 2001 From: Arsam Date: Sun, 25 Feb 2024 13:26:31 +0100 Subject: [PATCH 02/18] Added another test case for typevar stubs --- .../data/various_modules_package/function_module.py | 3 +++ .../test_function_creation.sdsstub | 12 +++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/data/various_modules_package/function_module.py b/tests/data/various_modules_package/function_module.py index 60c3cad6..e5808f3e 100644 --- a/tests/data/various_modules_package/function_module.py +++ b/tests/data/various_modules_package/function_module.py @@ -191,6 +191,9 @@ def result_from_outside_the_package() -> AnotherClass: ... def type_var_func(type_var_list: list[_type_var]) -> list[_type_var]: ... +def type_var_func2[T](a: T) -> T: ... + + class FunctionModulePropertiesClass: @property def property_function(self): ... diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub index 39ae309c..bc853ed3 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub @@ -248,9 +248,15 @@ fun resultFromOutsideThePackage() -> result1: AnotherClass @Pure @PythonName("type_var_func") -fun typeVarFunc( - @PythonName("type_var_list") typeVarList: List -) -> result1: List +fun typeVarFunc<_type_var>( + @PythonName("type_var_list") typeVarList: List<_type_var> +) -> result1: List<_type_var> + +@Pure +@PythonName("type_var_func2") +fun typeVarFunc2( + a: T +) -> result1: T @Pure @PythonName("ret_conditional_statement") From 1b77b68994c6ab829ddbe16631a1aae33b343d5e Mon Sep 17 00:00:00 2001 From: Arsam Date: Sun, 25 Feb 2024 13:37:45 +0100 Subject: [PATCH 03/18] Added typevar stubs test module and separate snapshots --- .../various_modules_package/attribute_module.py | 4 +--- .../various_modules_package/function_module.py | 9 +-------- .../various_modules_package/type_var_module.py | 12 ++++++++++++ .../test_function_creation.sdsstub | 12 ------------ .../test_type_var_creation.sdsstub | 16 ++++++++++++++++ .../stubs_generator/test_generate_stubs.py | 4 ++++ 6 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 tests/data/various_modules_package/type_var_module.py create mode 100644 tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub diff --git a/tests/data/various_modules_package/attribute_module.py b/tests/data/various_modules_package/attribute_module.py index 0b021cc3..05818940 100644 --- a/tests/data/various_modules_package/attribute_module.py +++ b/tests/data/various_modules_package/attribute_module.py @@ -1,4 +1,4 @@ -from typing import Optional, Final, Literal, TypeVar +from typing import Optional, Final, Literal from tests.data.main_package.another_path.another_module import AnotherClass @@ -66,7 +66,5 @@ def some_func() -> bool: attr_type_from_outside_package: AnotherClass attr_default_value_from_outside_package = AnotherClass - type_var = TypeVar("type_var") - def __init__(self): self.init_attr: bool = False diff --git a/tests/data/various_modules_package/function_module.py b/tests/data/various_modules_package/function_module.py index e5808f3e..0a7a1a38 100644 --- a/tests/data/various_modules_package/function_module.py +++ b/tests/data/various_modules_package/function_module.py @@ -1,4 +1,4 @@ -from typing import Callable, Optional, Literal, Any, TypeVar +from typing import Callable, Optional, Literal, Any from tests.data.main_package.another_path.another_module import AnotherClass @@ -187,13 +187,6 @@ def param_from_outside_the_package(param_type: AnotherClass, param_value=Another def result_from_outside_the_package() -> AnotherClass: ... -_type_var = TypeVar("_type_var") -def type_var_func(type_var_list: list[_type_var]) -> list[_type_var]: ... - - -def type_var_func2[T](a: T) -> T: ... - - class FunctionModulePropertiesClass: @property def property_function(self): ... diff --git a/tests/data/various_modules_package/type_var_module.py b/tests/data/various_modules_package/type_var_module.py new file mode 100644 index 00000000..1ab87a8a --- /dev/null +++ b/tests/data/various_modules_package/type_var_module.py @@ -0,0 +1,12 @@ +from typing import TypeVar + + +class TypeVarClass: + type_var = TypeVar("type_var") + + +_type_var = TypeVar("_type_var") +def type_var_func(type_var_list: list[_type_var]) -> list[_type_var]: ... + + +def type_var_func2[T](a: T) -> T: ... diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub index bc853ed3..cd5f8643 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_function_creation.sdsstub @@ -246,18 +246,6 @@ fun paramFromOutsideThePackage( @PythonName("result_from_outside_the_package") fun resultFromOutsideThePackage() -> result1: AnotherClass -@Pure -@PythonName("type_var_func") -fun typeVarFunc<_type_var>( - @PythonName("type_var_list") typeVarList: List<_type_var> -) -> result1: List<_type_var> - -@Pure -@PythonName("type_var_func2") -fun typeVarFunc2( - a: T -) -> result1: T - @Pure @PythonName("ret_conditional_statement") fun retConditionalStatement() -> result1: union diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub new file mode 100644 index 00000000..58bfbf9e --- /dev/null +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub @@ -0,0 +1,16 @@ +@PythonModule("various_modules_package.attribute_module") +package variousModulesPackage.typeVarModule + +class TypeVarClass() + +@Pure +@PythonName("type_var_func") +fun typeVarFunc<_type_var>( + @PythonName("type_var_list") typeVarList: List<_type_var> +) -> result1: List<_type_var> + +@Pure +@PythonName("type_var_func2") +fun typeVarFunc2( + a: T +) -> result1: T diff --git a/tests/safeds_stubgen/stubs_generator/test_generate_stubs.py b/tests/safeds_stubgen/stubs_generator/test_generate_stubs.py index 0a3fa63b..dbb71aee 100644 --- a/tests/safeds_stubgen/stubs_generator/test_generate_stubs.py +++ b/tests/safeds_stubgen/stubs_generator/test_generate_stubs.py @@ -100,6 +100,10 @@ def test_abstract_creation(snapshot_sds_stub: SnapshotAssertion) -> None: assert_stubs_snapshot("abstract_module", snapshot_sds_stub) +def test_type_var_creation(snapshot_sds_stub: SnapshotAssertion) -> None: + assert_stubs_snapshot("type_var_module", snapshot_sds_stub) + + @pytest.mark.parametrize("file_name", ["aliasing_module_1", "aliasing_module_2", "aliasing_module_3"]) def test_alias_creation(file_name: str, snapshot_sds_stub: SnapshotAssertion) -> None: file_data = "" From 79913eb47a2379aabb2d33b73311f9e365863fdd Mon Sep 17 00:00:00 2001 From: Arsam Date: Sun, 25 Feb 2024 14:24:57 +0100 Subject: [PATCH 04/18] TypeVar stub creation now works with type vars if they are existing TypeVar() attributes. Also added test data for typevars --- src/safeds_stubgen/api_analyzer/_api.py | 3 ++- .../api_analyzer/_ast_visitor.py | 13 ++++++++++- .../stubs_generator/_generate_stubs.py | 18 +++++++++++---- .../type_var_module.py | 10 ++++++++- .../test_type_var_creation.sdsstub | 22 +++++++++++++++---- 5 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/safeds_stubgen/api_analyzer/_api.py b/src/safeds_stubgen/api_analyzer/_api.py index 522c2120..fcdfc3a8 100644 --- a/src/safeds_stubgen/api_analyzer/_api.py +++ b/src/safeds_stubgen/api_analyzer/_api.py @@ -16,7 +16,7 @@ ResultDocstring, ) - from ._types import AbstractType + from ._types import AbstractType, TypeVarType API_SCHEMA_VERSION = 1 @@ -237,6 +237,7 @@ class Function: is_static: bool is_class_method: bool is_property: bool + type_var_types: set[TypeVarType] = field(default_factory=set) results: list[Result] = field(default_factory=list) reexported_by: list[Module] = field(default_factory=list) parameters: list[Parameter] = field(default_factory=list) diff --git a/src/safeds_stubgen/api_analyzer/_ast_visitor.py b/src/safeds_stubgen/api_analyzer/_ast_visitor.py index 811773b8..b58354b6 100644 --- a/src/safeds_stubgen/api_analyzer/_ast_visitor.py +++ b/src/safeds_stubgen/api_analyzer/_ast_visitor.py @@ -50,6 +50,8 @@ def __init__(self, docstring_parser: AbstractDocstringParser, api: API, aliases: self.__declaration_stack: list[Module | Class | Function | Enum | list[Attribute | EnumInstance]] = [] self.aliases = aliases self.mypy_file: mp_nodes.MypyFile | None = None + # We gather type var types used as a parameter type in a function + self.type_var_types: set[sds_types.TypeVarType] = set() def enter_moduledef(self, node: mp_nodes.MypyFile) -> None: self.mypy_file = node @@ -231,9 +233,15 @@ def enter_funcdef(self, node: mp_nodes.FuncDef) -> None: # Function args arguments: list[Parameter] = [] + type_var_types: set[sds_types.TypeVarType] = set() if getattr(node, "arguments", None) is not None: + # Empty the type_var_types list, b/c assignmentstmts can also fill this list + self.type_var_types = set() arguments = self._parse_parameter_data(node, function_id) + if self.type_var_types: + type_var_types = deepcopy(self.type_var_types) + # Create results results = self._parse_results(node, function_id) @@ -252,6 +260,7 @@ def enter_funcdef(self, node: mp_nodes.FuncDef) -> None: results=results, reexported_by=reexported_by, parameters=arguments, + type_var_types=type_var_types ) self.__declaration_stack.append(function) @@ -827,7 +836,9 @@ def mypy_type_to_abstract_type( # Special Cases elif isinstance(mypy_type, mp_types.TypeVarType): - return sds_types.TypeVarType(mypy_type.name) + type_var = sds_types.TypeVarType(mypy_type.name) + self.type_var_types.add(type_var) + return type_var elif isinstance(mypy_type, mp_types.CallableType): return sds_types.CallableType( parameter_types=[self.mypy_type_to_abstract_type(arg_type) for arg_type in mypy_type.arg_types], diff --git a/src/safeds_stubgen/stubs_generator/_generate_stubs.py b/src/safeds_stubgen/stubs_generator/_generate_stubs.py index e99d91b7..14e46a8d 100644 --- a/src/safeds_stubgen/stubs_generator/_generate_stubs.py +++ b/src/safeds_stubgen/stubs_generator/_generate_stubs.py @@ -265,6 +265,10 @@ def _create_class_attribute_string(self, attributes: list[Attribute], inner_inde if attribute.type: attribute_type = attribute.type.to_dict() + # Don't create TypeVar attributes + if attribute_type["kind"] == "TypeVarType": + continue + static_string = "static " if attribute.is_static else "" # Convert name to camelCase and add PythonName annotation @@ -317,6 +321,13 @@ def _create_function_string(self, function: Function, indentations: str = "", is is_instance_method=not is_static and is_method, ) + # TypeVar + type_var_info = "" + if function.type_var_types: + type_var_names = [type_var.name for type_var in function.type_var_types] + type_var_string = ", ".join(type_var_names) + type_var_info = f"<{type_var_string}>" + # Convert function name to camelCase name = function.name camel_case_name = self._convert_snake_to_camel_case(name) @@ -334,8 +345,8 @@ def _create_function_string(self, function: Function, indentations: str = "", is f"{self._create_todo_msg(indentations)}" f"{indentations}@Pure\n" f"{function_name_annotation}" - f"{indentations}{static}fun {camel_case_name}({func_params})" - f"{result_string}" + f"{indentations}{static}fun {camel_case_name}{type_var_info}" + f"({func_params}){result_string}" ) def _create_property_function_string(self, function: Function, indentations: str = "") -> str: @@ -621,9 +632,8 @@ def _create_type_string(self, type_data: dict | None) -> str: else: types.append(f"{literal_type}") return f"literal<{', '.join(types)}>" - # Todo See issue #63 elif kind == "TypeVarType": - return "" + return type_data["name"] raise ValueError(f"Unexpected type: {kind}") # pragma: no cover diff --git a/tests/data/various_modules_package/type_var_module.py b/tests/data/various_modules_package/type_var_module.py index 1ab87a8a..cc33a256 100644 --- a/tests/data/various_modules_package/type_var_module.py +++ b/tests/data/various_modules_package/type_var_module.py @@ -6,7 +6,15 @@ class TypeVarClass: _type_var = TypeVar("_type_var") -def type_var_func(type_var_list: list[_type_var]) -> list[_type_var]: ... +def type_var_func(a: list[_type_var]) -> list[_type_var]: ... def type_var_func2[T](a: T) -> T: ... + + +_type_var1 = TypeVar("_type_var1") +_type_var2 = TypeVar("_type_var2") +def multiple_type_var(a: _type_var1, b: _type_var2) -> list[_type_var1 | _type_var2]: ... + + +def multiple_type_var2[T, U](a: T, b: U) -> T | U: ... diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub index 58bfbf9e..c0fd9450 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub @@ -1,12 +1,10 @@ -@PythonModule("various_modules_package.attribute_module") +@PythonModule("various_modules_package.type_var_module") package variousModulesPackage.typeVarModule -class TypeVarClass() - @Pure @PythonName("type_var_func") fun typeVarFunc<_type_var>( - @PythonName("type_var_list") typeVarList: List<_type_var> + a: List<_type_var> ) -> result1: List<_type_var> @Pure @@ -14,3 +12,19 @@ fun typeVarFunc<_type_var>( fun typeVarFunc2( a: T ) -> result1: T + +@Pure +@PythonName("multiple_type_var") +fun multipleTypeVar<_type_var2, _type_var1>( + a: _type_var1, + b: _type_var2 +) -> result1: List> + +@Pure +@PythonName("multiple_type_var2") +fun multipleTypeVar2( + a, + b +) -> result1: List> + +class TypeVarClass() From 9c30988bbba5a7232ef9f32be4f5f219ddacfbc0 Mon Sep 17 00:00:00 2001 From: Arsam Date: Sun, 25 Feb 2024 16:49:03 +0100 Subject: [PATCH 05/18] Now "def func[T]" like TypeVars can also be analyzed. Adjusted tests. --- src/safeds_stubgen/api_analyzer/_api.py | 2 +- .../api_analyzer/_ast_visitor.py | 41 +++- .../__snapshots__/test__get_api.ambr | 208 ++++++++++++++---- .../api_analyzer/test__get_api.py | 19 +- .../test_type_var_creation.sdsstub | 8 +- 5 files changed, 222 insertions(+), 56 deletions(-) diff --git a/src/safeds_stubgen/api_analyzer/_api.py b/src/safeds_stubgen/api_analyzer/_api.py index fcdfc3a8..83aafa7f 100644 --- a/src/safeds_stubgen/api_analyzer/_api.py +++ b/src/safeds_stubgen/api_analyzer/_api.py @@ -237,7 +237,7 @@ class Function: is_static: bool is_class_method: bool is_property: bool - type_var_types: set[TypeVarType] = field(default_factory=set) + type_var_types: list[TypeVarType] = field(default_factory=list) results: list[Result] = field(default_factory=list) reexported_by: list[Module] = field(default_factory=list) parameters: list[Parameter] = field(default_factory=list) diff --git a/src/safeds_stubgen/api_analyzer/_ast_visitor.py b/src/safeds_stubgen/api_analyzer/_ast_visitor.py index b58354b6..d783956f 100644 --- a/src/safeds_stubgen/api_analyzer/_ast_visitor.py +++ b/src/safeds_stubgen/api_analyzer/_ast_visitor.py @@ -231,16 +231,18 @@ def enter_funcdef(self, node: mp_nodes.FuncDef) -> None: # Get docstring docstring = self.docstring_parser.get_function_documentation(node) - # Function args + # Function args & TypeVar arguments: list[Parameter] = [] - type_var_types: set[sds_types.TypeVarType] = set() + type_var_types: list[sds_types.TypeVarType] = [] + # Reset the type_var_types list + self.type_var_types = set() if getattr(node, "arguments", None) is not None: - # Empty the type_var_types list, b/c assignmentstmts can also fill this list - self.type_var_types = set() arguments = self._parse_parameter_data(node, function_id) if self.type_var_types: - type_var_types = deepcopy(self.type_var_types) + type_var_types = list(self.type_var_types) + # Sort for the snapshot tests + type_var_types.sort(key=lambda x: x.name) # Create results results = self._parse_results(node, function_id) @@ -260,7 +262,7 @@ def enter_funcdef(self, node: mp_nodes.FuncDef) -> None: results=results, reexported_by=reexported_by, parameters=arguments, - type_var_types=type_var_types + type_var_types=type_var_types, ) self.__declaration_stack.append(function) @@ -400,11 +402,11 @@ 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, - ): - # In this case, the "Any" type was given because it was not explicitly annotated. - # Therefor we have to try to infer the type. + if (isinstance(node_ret_type, mp_types.AnyType) and + node_ret_type.type_of_any != mp_types.TypeOfAny.special_form 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, therefore we + # have to try to infer the type. For "special_form" we assume it's a TypeVar. ret_type = self._infer_type_from_return_stmts(node) type_is_inferred = ret_type is not None else: @@ -682,6 +684,10 @@ def _parse_parameter_data(self, node: mp_nodes.FuncDef, function_id: str) -> lis # Get type information for parameter if mypy_type is None: # pragma: no cover raise ValueError("Argument has no type.") + elif isinstance(mypy_type, mp_types.AnyType) and mypy_type.type_of_any == mp_types.TypeOfAny.special_form: + # In this case we assume that it's a TypeVar type + arg_type = sds_types.TypeVarType(type_annotation.name) + self.type_var_types.add(arg_type) elif isinstance(mypy_type, mp_types.AnyType) and not has_correct_type_of_any(mypy_type.type_of_any): # We try to infer the type through the default value later, if possible pass @@ -827,11 +833,24 @@ 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(mypy_type, mp_types.AnyType) and mypy_type.type_of_any == mp_types.TypeOfAny.special_form: + # We assume that "special_form" is a TypeVar + type_var = sds_types.TypeVarType(unanalyzed_type.name) + self.type_var_types.add(type_var) + return type_var # Iterable mypy types if isinstance(mypy_type, mp_types.TupleType): return sds_types.TupleType(types=[self.mypy_type_to_abstract_type(item) for item in mypy_type.items]) elif isinstance(mypy_type, mp_types.UnionType): + if unanalyzed_type is not None and hasattr(unanalyzed_type, "items") and len(unanalyzed_type.items) == len(mypy_type.items): + union_items = zip(mypy_type.items, unanalyzed_type.items, strict=True) + return sds_types.UnionType( + types=[ + self.mypy_type_to_abstract_type(mypy_type=item[0], unanalyzed_type=item[1]) + for item in union_items + ], + ) return sds_types.UnionType(types=[self.mypy_type_to_abstract_type(item) for item in mypy_type.items]) # Special Cases 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 6165c319..e2e6d51a 100644 --- a/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr +++ b/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr @@ -1021,21 +1021,6 @@ 'qname': 'builtins.int', }), }), - dict({ - 'docstring': dict({ - 'default_value': '', - 'description': '', - 'type': '', - }), - 'id': 'various_modules_package/attribute_module/AttributesClassB/type_var', - 'is_public': True, - 'is_static': True, - 'name': 'type_var', - 'type': dict({ - 'kind': 'TypeVarType', - 'name': 'type_var', - }), - }), ]) # --- # name: test_class_attributes[ClassModuleNestedClassE] @@ -1859,6 +1844,10 @@ }), ]) # --- +# name: test_class_methods[TypeVarClass] + list([ + ]) +# --- # name: test_class_methods[_ClassModulePrivateDoubleNestedClassF] list([ dict({ @@ -3401,6 +3390,78 @@ }), ]) # --- +# name: test_function_parameters[multiple_type_var2] + list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'various_modules_package/type_var_module/multiple_type_var/a', + 'is_optional': False, + 'name': 'a', + 'type': dict({ + 'kind': 'TypeVarType', + 'name': '_type_var1', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'various_modules_package/type_var_module/multiple_type_var/b', + 'is_optional': False, + 'name': 'b', + 'type': dict({ + 'kind': 'TypeVarType', + 'name': '_type_var2', + }), + }), + ]) +# --- +# name: test_function_parameters[multiple_type_var] + list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'various_modules_package/type_var_module/multiple_type_var/a', + 'is_optional': False, + 'name': 'a', + 'type': dict({ + 'kind': 'TypeVarType', + 'name': '_type_var1', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'various_modules_package/type_var_module/multiple_type_var/b', + 'is_optional': False, + 'name': 'b', + 'type': dict({ + 'kind': 'TypeVarType', + 'name': '_type_var2', + }), + }), + ]) +# --- # name: test_function_parameters[nested_class_function] list([ dict({ @@ -4445,6 +4506,26 @@ }), ]) # --- +# name: test_function_parameters[type_var_func2] + list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'various_modules_package/type_var_module/type_var_func2/a', + 'is_optional': False, + 'name': 'a', + 'type': dict({ + 'kind': 'TypeVarType', + 'name': 'T', + }), + }), + ]) +# --- # name: test_function_parameters[type_var_func] list([ dict({ @@ -4455,9 +4536,9 @@ 'description': '', 'type': '', }), - 'id': 'various_modules_package/function_module/type_var_func/type_var_list', + 'id': 'various_modules_package/type_var_module/type_var_func/a', 'is_optional': False, - 'name': 'type_var_list', + 'name': 'a', 'type': dict({ 'kind': 'ListType', 'types': list([ @@ -4978,6 +5059,61 @@ }), ]) # --- +# name: test_function_results[multiple_type_var2] + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'various_modules_package/type_var_module/multiple_type_var2/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'TypeVarType', + 'name': 'T', + }), + dict({ + 'kind': 'TypeVarType', + 'name': 'U', + }), + ]), + }), + }), + ]) +# --- +# name: test_function_results[multiple_type_var] + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'various_modules_package/type_var_module/multiple_type_var/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'ListType', + 'types': list([ + dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'TypeVarType', + 'name': '_type_var1', + }), + dict({ + 'kind': 'TypeVarType', + 'name': '_type_var2', + }), + ]), + }), + ]), + }), + }), + ]) +# --- # name: test_function_results[nested_class_function] list([ dict({ @@ -5205,6 +5341,22 @@ }), ]) # --- +# name: test_function_results[type_var_func2] + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'various_modules_package/type_var_module/type_var_func2/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'TypeVarType', + 'name': 'T', + }), + }), + ]) +# --- # name: test_function_results[type_var_func] list([ dict({ @@ -5212,7 +5364,7 @@ 'description': '', 'type': '', }), - 'id': 'various_modules_package/function_module/type_var_func/result_1', + 'id': 'various_modules_package/type_var_module/type_var_func/result_1', 'name': 'result_1', 'type': dict({ 'kind': 'ListType', @@ -6106,26 +6258,6 @@ 'various_modules_package/function_module/tuple_results/result_2', ]), }), - dict({ - 'docstring': dict({ - 'description': '', - 'full_docstring': '', - }), - 'id': 'various_modules_package/function_module/type_var_func', - 'is_class_method': False, - 'is_property': False, - 'is_public': True, - 'is_static': False, - 'name': 'type_var_func', - 'parameters': list([ - 'various_modules_package/function_module/type_var_func/type_var_list', - ]), - 'reexported_by': list([ - ]), - 'results': list([ - 'various_modules_package/function_module/type_var_func/result_1', - ]), - }), dict({ 'docstring': dict({ 'description': '', diff --git a/tests/safeds_stubgen/api_analyzer/test__get_api.py b/tests/safeds_stubgen/api_analyzer/test__get_api.py index 66606361..27d77212 100644 --- a/tests/safeds_stubgen/api_analyzer/test__get_api.py +++ b/tests/safeds_stubgen/api_analyzer/test__get_api.py @@ -107,6 +107,7 @@ def _get_specific_function_data( _import_module_name = "import_module" _abstract_module_name = "abstract_module" _infer_types_module_name = "infer_types_module" +_type_var_module_name = "type_var_module" # ############################## Tests ############################## # @@ -332,6 +333,7 @@ def test_global_functions(module_name: str, snapshot: SnapshotAssertion) -> None (_function_module_name, "FunctionModuleClassC", "plaintext"), (_function_module_name, "FunctionModulePropertiesClass", "plaintext"), (_infer_types_module_name, "InferMyTypes", "plaintext"), + (_type_var_module_name, "TypeVarClass", "plaintext"), ("_reexport_module_1", "ReexportClass", "plaintext"), (_abstract_module_name, "AbstractModuleClass", "plaintext"), (_docstring_module_name, "EpydocDocstringClass", "epydoc"), @@ -349,6 +351,7 @@ def test_global_functions(module_name: str, snapshot: SnapshotAssertion) -> None "FunctionModuleClassC", "FunctionModulePropertiesClass", "InferMyTypes", + "TypeVarClass", "ReexportClass", "AbstractModuleClass", "EpydocDocstringClass", @@ -390,7 +393,10 @@ def test_class_methods(module_name: str, class_name: str, docstring_style: str, ("args_type", _function_module_name, "", "plaintext"), ("callable_type", _function_module_name, "", "plaintext"), ("param_from_outside_the_package", _function_module_name, "", "plaintext"), - ("type_var_func", _function_module_name, "", "plaintext"), + ("type_var_func", _type_var_module_name, "", "plaintext"), + ("type_var_func2", _type_var_module_name, "", "plaintext"), + ("multiple_type_var", _type_var_module_name, "", "plaintext"), + ("multiple_type_var", _type_var_module_name, "", "plaintext"), ("abstract_method_params", _abstract_module_name, "AbstractModuleClass", "plaintext"), ("abstract_static_method_params", _abstract_module_name, "AbstractModuleClass", "plaintext"), ("abstract_property_method", _abstract_module_name, "AbstractModuleClass", "plaintext"), @@ -422,6 +428,9 @@ def test_class_methods(module_name: str, class_name: str, docstring_style: str, "callable_type", "param_from_outside_the_package", "type_var_func", + "type_var_func2", + "multiple_type_var", + "multiple_type_var2", "abstract_method_params", "abstract_static_method_params", "abstract_property_method", @@ -480,7 +489,10 @@ def test_function_parameters( ("any_results", _function_module_name, "", "plaintext"), ("callable_type", _function_module_name, "", "plaintext"), ("result_from_outside_the_package", _function_module_name, "", "plaintext"), - ("type_var_func", _function_module_name, "", "plaintext"), + ("type_var_func", _type_var_module_name, "", "plaintext"), + ("type_var_func2", _type_var_module_name, "", "plaintext"), + ("multiple_type_var", _type_var_module_name, "", "plaintext"), + ("multiple_type_var2", _type_var_module_name, "", "plaintext"), ("instance_method", _function_module_name, "FunctionModuleClassB", "plaintext"), ("static_method_params", _function_module_name, "FunctionModuleClassB", "plaintext"), ("class_method_params", _function_module_name, "FunctionModuleClassB", "plaintext"), @@ -521,6 +533,9 @@ def test_function_parameters( "callable_type", "result_from_outside_the_package", "type_var_func", + "type_var_func2", + "multiple_type_var", + "multiple_type_var2", "instance_method", "static_method_params", "class_method_params", diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub index c0fd9450..5fbca1e4 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub @@ -15,7 +15,7 @@ fun typeVarFunc2( @Pure @PythonName("multiple_type_var") -fun multipleTypeVar<_type_var2, _type_var1>( +fun multipleTypeVar<_type_var1, _type_var2>( a: _type_var1, b: _type_var2 ) -> result1: List> @@ -23,8 +23,8 @@ fun multipleTypeVar<_type_var2, _type_var1>( @Pure @PythonName("multiple_type_var2") fun multipleTypeVar2( - a, - b -) -> result1: List> + a: T, + b: U +) -> result1: union class TypeVarClass() From a90a8af4f398b92dceddc10f4380db1b5015f116 Mon Sep 17 00:00:00 2001 From: Arsam Date: Sun, 25 Feb 2024 16:57:57 +0100 Subject: [PATCH 06/18] Linter fixes --- src/safeds_stubgen/api_analyzer/_ast_visitor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/safeds_stubgen/api_analyzer/_ast_visitor.py b/src/safeds_stubgen/api_analyzer/_ast_visitor.py index d783956f..ea40f933 100644 --- a/src/safeds_stubgen/api_analyzer/_ast_visitor.py +++ b/src/safeds_stubgen/api_analyzer/_ast_visitor.py @@ -677,7 +677,7 @@ def _parse_parameter_data(self, node: mp_nodes.FuncDef, function_id: str) -> lis for argument in node.arguments: mypy_type = argument.variable.type type_annotation = argument.type_annotation - arg_type = None + arg_type: AbstractType | None = None default_value = None default_is_none = False @@ -686,7 +686,10 @@ def _parse_parameter_data(self, node: mp_nodes.FuncDef, function_id: str) -> lis raise ValueError("Argument has no type.") elif isinstance(mypy_type, mp_types.AnyType) and mypy_type.type_of_any == mp_types.TypeOfAny.special_form: # In this case we assume that it's a TypeVar type - arg_type = sds_types.TypeVarType(type_annotation.name) + name = getattr(type_annotation, "name", "") + if not name: # pragma: no cover + raise ValueError("Expected a name for the TypeVar.") + arg_type = sds_types.TypeVarType(name) self.type_var_types.add(arg_type) elif isinstance(mypy_type, mp_types.AnyType) and not has_correct_type_of_any(mypy_type.type_of_any): # We try to infer the type through the default value later, if possible From 78802d007dec79da4cf83bf8806226b25c5feb55 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Sun, 25 Feb 2024 15:59:30 +0000 Subject: [PATCH 07/18] style: apply automated linter fixes --- src/safeds_stubgen/api_analyzer/_ast_visitor.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/safeds_stubgen/api_analyzer/_ast_visitor.py b/src/safeds_stubgen/api_analyzer/_ast_visitor.py index ea40f933..91ca708a 100644 --- a/src/safeds_stubgen/api_analyzer/_ast_visitor.py +++ b/src/safeds_stubgen/api_analyzer/_ast_visitor.py @@ -402,9 +402,11 @@ 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 - node_ret_type.type_of_any != mp_types.TypeOfAny.special_form and - not has_correct_type_of_any(node_ret_type.type_of_any)): + if ( + isinstance(node_ret_type, mp_types.AnyType) + and node_ret_type.type_of_any != mp_types.TypeOfAny.special_form + 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, therefore we # have to try to infer the type. For "special_form" we assume it's a TypeVar. ret_type = self._infer_type_from_return_stmts(node) @@ -846,7 +848,11 @@ def mypy_type_to_abstract_type( if isinstance(mypy_type, mp_types.TupleType): return sds_types.TupleType(types=[self.mypy_type_to_abstract_type(item) for item in mypy_type.items]) elif isinstance(mypy_type, mp_types.UnionType): - if unanalyzed_type is not None and hasattr(unanalyzed_type, "items") and len(unanalyzed_type.items) == len(mypy_type.items): + if ( + unanalyzed_type is not None + and hasattr(unanalyzed_type, "items") + and len(unanalyzed_type.items) == len(mypy_type.items) + ): union_items = zip(mypy_type.items, unanalyzed_type.items, strict=True) return sds_types.UnionType( types=[ From 1f89f5a6a0ad5520c9a9f3dab06b81a74562b0d6 Mon Sep 17 00:00:00 2001 From: Arsam Date: Sun, 25 Feb 2024 18:36:53 +0100 Subject: [PATCH 08/18] Removed changes for 'def func[T]' TypeData from commit 9c30988b --- .../api_analyzer/_ast_visitor.py | 30 +----- .../type_var_module.py | 6 -- .../__snapshots__/test__get_api.ambr | 97 ------------------- .../api_analyzer/test__get_api.py | 8 -- .../test_type_var_creation.sdsstub | 13 --- 5 files changed, 5 insertions(+), 149 deletions(-) diff --git a/src/safeds_stubgen/api_analyzer/_ast_visitor.py b/src/safeds_stubgen/api_analyzer/_ast_visitor.py index ea40f933..2841e2ff 100644 --- a/src/safeds_stubgen/api_analyzer/_ast_visitor.py +++ b/src/safeds_stubgen/api_analyzer/_ast_visitor.py @@ -402,11 +402,11 @@ 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 - node_ret_type.type_of_any != mp_types.TypeOfAny.special_form 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, therefore we - # have to try to infer the type. For "special_form" we assume it's a TypeVar. + if 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. ret_type = self._infer_type_from_return_stmts(node) type_is_inferred = ret_type is not None else: @@ -684,13 +684,6 @@ def _parse_parameter_data(self, node: mp_nodes.FuncDef, function_id: str) -> lis # Get type information for parameter if mypy_type is None: # pragma: no cover raise ValueError("Argument has no type.") - elif isinstance(mypy_type, mp_types.AnyType) and mypy_type.type_of_any == mp_types.TypeOfAny.special_form: - # In this case we assume that it's a TypeVar type - name = getattr(type_annotation, "name", "") - if not name: # pragma: no cover - raise ValueError("Expected a name for the TypeVar.") - arg_type = sds_types.TypeVarType(name) - self.type_var_types.add(arg_type) elif isinstance(mypy_type, mp_types.AnyType) and not has_correct_type_of_any(mypy_type.type_of_any): # We try to infer the type through the default value later, if possible pass @@ -836,24 +829,11 @@ 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(mypy_type, mp_types.AnyType) and mypy_type.type_of_any == mp_types.TypeOfAny.special_form: - # We assume that "special_form" is a TypeVar - type_var = sds_types.TypeVarType(unanalyzed_type.name) - self.type_var_types.add(type_var) - return type_var # Iterable mypy types if isinstance(mypy_type, mp_types.TupleType): return sds_types.TupleType(types=[self.mypy_type_to_abstract_type(item) for item in mypy_type.items]) elif isinstance(mypy_type, mp_types.UnionType): - if unanalyzed_type is not None and hasattr(unanalyzed_type, "items") and len(unanalyzed_type.items) == len(mypy_type.items): - union_items = zip(mypy_type.items, unanalyzed_type.items, strict=True) - return sds_types.UnionType( - types=[ - self.mypy_type_to_abstract_type(mypy_type=item[0], unanalyzed_type=item[1]) - for item in union_items - ], - ) return sds_types.UnionType(types=[self.mypy_type_to_abstract_type(item) for item in mypy_type.items]) # Special Cases diff --git a/tests/data/various_modules_package/type_var_module.py b/tests/data/various_modules_package/type_var_module.py index cc33a256..6415de0c 100644 --- a/tests/data/various_modules_package/type_var_module.py +++ b/tests/data/various_modules_package/type_var_module.py @@ -9,12 +9,6 @@ class TypeVarClass: def type_var_func(a: list[_type_var]) -> list[_type_var]: ... -def type_var_func2[T](a: T) -> T: ... - - _type_var1 = TypeVar("_type_var1") _type_var2 = TypeVar("_type_var2") def multiple_type_var(a: _type_var1, b: _type_var2) -> list[_type_var1 | _type_var2]: ... - - -def multiple_type_var2[T, U](a: T, b: U) -> T | U: ... 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 e2e6d51a..71c5e059 100644 --- a/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr +++ b/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr @@ -3390,42 +3390,6 @@ }), ]) # --- -# name: test_function_parameters[multiple_type_var2] - list([ - dict({ - 'assigned_by': 'POSITION_OR_NAME', - 'default_value': None, - 'docstring': dict({ - 'default_value': '', - 'description': '', - 'type': '', - }), - 'id': 'various_modules_package/type_var_module/multiple_type_var/a', - 'is_optional': False, - 'name': 'a', - 'type': dict({ - 'kind': 'TypeVarType', - 'name': '_type_var1', - }), - }), - dict({ - 'assigned_by': 'POSITION_OR_NAME', - 'default_value': None, - 'docstring': dict({ - 'default_value': '', - 'description': '', - 'type': '', - }), - 'id': 'various_modules_package/type_var_module/multiple_type_var/b', - 'is_optional': False, - 'name': 'b', - 'type': dict({ - 'kind': 'TypeVarType', - 'name': '_type_var2', - }), - }), - ]) -# --- # name: test_function_parameters[multiple_type_var] list([ dict({ @@ -4506,26 +4470,6 @@ }), ]) # --- -# name: test_function_parameters[type_var_func2] - list([ - dict({ - 'assigned_by': 'POSITION_OR_NAME', - 'default_value': None, - 'docstring': dict({ - 'default_value': '', - 'description': '', - 'type': '', - }), - 'id': 'various_modules_package/type_var_module/type_var_func2/a', - 'is_optional': False, - 'name': 'a', - 'type': dict({ - 'kind': 'TypeVarType', - 'name': 'T', - }), - }), - ]) -# --- # name: test_function_parameters[type_var_func] list([ dict({ @@ -5059,31 +5003,6 @@ }), ]) # --- -# name: test_function_results[multiple_type_var2] - list([ - dict({ - 'docstring': dict({ - 'description': '', - 'type': '', - }), - 'id': 'various_modules_package/type_var_module/multiple_type_var2/result_1', - 'name': 'result_1', - 'type': dict({ - 'kind': 'UnionType', - 'types': list([ - dict({ - 'kind': 'TypeVarType', - 'name': 'T', - }), - dict({ - 'kind': 'TypeVarType', - 'name': 'U', - }), - ]), - }), - }), - ]) -# --- # name: test_function_results[multiple_type_var] list([ dict({ @@ -5341,22 +5260,6 @@ }), ]) # --- -# name: test_function_results[type_var_func2] - list([ - dict({ - 'docstring': dict({ - 'description': '', - 'type': '', - }), - 'id': 'various_modules_package/type_var_module/type_var_func2/result_1', - 'name': 'result_1', - 'type': dict({ - 'kind': 'TypeVarType', - 'name': 'T', - }), - }), - ]) -# --- # name: test_function_results[type_var_func] list([ dict({ diff --git a/tests/safeds_stubgen/api_analyzer/test__get_api.py b/tests/safeds_stubgen/api_analyzer/test__get_api.py index 27d77212..78c7898d 100644 --- a/tests/safeds_stubgen/api_analyzer/test__get_api.py +++ b/tests/safeds_stubgen/api_analyzer/test__get_api.py @@ -394,8 +394,6 @@ def test_class_methods(module_name: str, class_name: str, docstring_style: str, ("callable_type", _function_module_name, "", "plaintext"), ("param_from_outside_the_package", _function_module_name, "", "plaintext"), ("type_var_func", _type_var_module_name, "", "plaintext"), - ("type_var_func2", _type_var_module_name, "", "plaintext"), - ("multiple_type_var", _type_var_module_name, "", "plaintext"), ("multiple_type_var", _type_var_module_name, "", "plaintext"), ("abstract_method_params", _abstract_module_name, "AbstractModuleClass", "plaintext"), ("abstract_static_method_params", _abstract_module_name, "AbstractModuleClass", "plaintext"), @@ -428,9 +426,7 @@ def test_class_methods(module_name: str, class_name: str, docstring_style: str, "callable_type", "param_from_outside_the_package", "type_var_func", - "type_var_func2", "multiple_type_var", - "multiple_type_var2", "abstract_method_params", "abstract_static_method_params", "abstract_property_method", @@ -490,9 +486,7 @@ def test_function_parameters( ("callable_type", _function_module_name, "", "plaintext"), ("result_from_outside_the_package", _function_module_name, "", "plaintext"), ("type_var_func", _type_var_module_name, "", "plaintext"), - ("type_var_func2", _type_var_module_name, "", "plaintext"), ("multiple_type_var", _type_var_module_name, "", "plaintext"), - ("multiple_type_var2", _type_var_module_name, "", "plaintext"), ("instance_method", _function_module_name, "FunctionModuleClassB", "plaintext"), ("static_method_params", _function_module_name, "FunctionModuleClassB", "plaintext"), ("class_method_params", _function_module_name, "FunctionModuleClassB", "plaintext"), @@ -533,9 +527,7 @@ def test_function_parameters( "callable_type", "result_from_outside_the_package", "type_var_func", - "type_var_func2", "multiple_type_var", - "multiple_type_var2", "instance_method", "static_method_params", "class_method_params", diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub index 5fbca1e4..ce32d139 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub @@ -7,12 +7,6 @@ fun typeVarFunc<_type_var>( a: List<_type_var> ) -> result1: List<_type_var> -@Pure -@PythonName("type_var_func2") -fun typeVarFunc2( - a: T -) -> result1: T - @Pure @PythonName("multiple_type_var") fun multipleTypeVar<_type_var1, _type_var2>( @@ -20,11 +14,4 @@ fun multipleTypeVar<_type_var1, _type_var2>( b: _type_var2 ) -> result1: List> -@Pure -@PythonName("multiple_type_var2") -fun multipleTypeVar2( - a: T, - b: U -) -> result1: union - class TypeVarClass() From aade9cb20ce80b19542ac372a6527eedc7027f61 Mon Sep 17 00:00:00 2001 From: Arsam Date: Mon, 26 Feb 2024 00:25:31 +0100 Subject: [PATCH 09/18] TypeVar names are now in camelCase in stubs --- src/safeds_stubgen/stubs_generator/_generate_stubs.py | 6 +++++- .../test_generate_stubs/test_type_var_creation.sdsstub | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/safeds_stubgen/stubs_generator/_generate_stubs.py b/src/safeds_stubgen/stubs_generator/_generate_stubs.py index 14e46a8d..b9d7f5db 100644 --- a/src/safeds_stubgen/stubs_generator/_generate_stubs.py +++ b/src/safeds_stubgen/stubs_generator/_generate_stubs.py @@ -324,7 +324,11 @@ def _create_function_string(self, function: Function, indentations: str = "", is # TypeVar type_var_info = "" if function.type_var_types: - type_var_names = [type_var.name for type_var in function.type_var_types] + type_var_names = [] + for type_var in function.type_var_types: + type_var_name = self._convert_snake_to_camel_case(type_var.name) + type_var_name = self._replace_if_safeds_keyword(type_var_name) + type_var_names.append(type_var_name) type_var_string = ", ".join(type_var_names) type_var_info = f"<{type_var_string}>" diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub index ce32d139..5086807c 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub @@ -3,13 +3,13 @@ package variousModulesPackage.typeVarModule @Pure @PythonName("type_var_func") -fun typeVarFunc<_type_var>( +fun typeVarFunc( a: List<_type_var> ) -> result1: List<_type_var> @Pure @PythonName("multiple_type_var") -fun multipleTypeVar<_type_var1, _type_var2>( +fun multipleTypeVar( a: _type_var1, b: _type_var2 ) -> result1: List> From 12469d50b4e78f55dd6d4780ec58e78ddf19bff0 Mon Sep 17 00:00:00 2001 From: Arsam Date: Mon, 26 Feb 2024 01:12:40 +0100 Subject: [PATCH 10/18] (WIP) Added a TypeVar test case for a class with type parameters. --- .../type_var_module.py | 7 +++++++ .../__snapshots__/test__get_api.ambr | 21 +++++++++++++++++++ .../test_type_var_creation.sdsstub | 10 ++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/data/various_modules_package/type_var_module.py b/tests/data/various_modules_package/type_var_module.py index 6415de0c..7789d37a 100644 --- a/tests/data/various_modules_package/type_var_module.py +++ b/tests/data/various_modules_package/type_var_module.py @@ -1,9 +1,16 @@ from typing import TypeVar +T = TypeVar('T') + + class TypeVarClass: type_var = TypeVar("type_var") + def __init__(self, items: list[T]): ... + + def type_var_class_method(self, a: T) -> T: ... + _type_var = TypeVar("_type_var") def type_var_func(a: list[_type_var]) -> list[_type_var]: ... 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 71c5e059..318890b5 100644 --- a/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr +++ b/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr @@ -1846,6 +1846,27 @@ # --- # name: test_class_methods[TypeVarClass] list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'various_modules_package/type_var_module/TypeVarClass/get_first_item', + 'is_class_method': False, + 'is_property': False, + 'is_public': True, + 'is_static': False, + 'name': 'get_first_item', + 'parameters': list([ + 'various_modules_package/type_var_module/TypeVarClass/get_first_item/self', + 'various_modules_package/type_var_module/TypeVarClass/get_first_item/a', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'various_modules_package/type_var_module/TypeVarClass/get_first_item/result_1', + ]), + }), ]) # --- # name: test_class_methods[_ClassModulePrivateDoubleNestedClassF] diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub index 5086807c..1106d43c 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub @@ -14,4 +14,12 @@ fun multipleTypeVar( b: _type_var2 ) -> result1: List> -class TypeVarClass() +class TypeVarClass( + items: List +) { + @Pure + @PythonName("type_var_class_method") + fun typeVarClassMethod( + a: T + ) -> result1: T +} From d3ef10fdc81cdaaa08e97f3f25de2fdd1e97419e Mon Sep 17 00:00:00 2001 From: Arsam Date: Wed, 28 Feb 2024 15:43:08 +0100 Subject: [PATCH 11/18] Fixed how classes and their methods generate stubs if the classes use generics --- .../stubs_generator/_generate_stubs.py | 21 ++++++++++++------- .../type_var_module.py | 11 +++++++--- .../__snapshots__/test__get_api.ambr | 10 ++++----- .../test_type_var_creation.sdsstub | 8 +++++++ 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/safeds_stubgen/stubs_generator/_generate_stubs.py b/src/safeds_stubgen/stubs_generator/_generate_stubs.py index b9d7f5db..ec17e1dc 100644 --- a/src/safeds_stubgen/stubs_generator/_generate_stubs.py +++ b/src/safeds_stubgen/stubs_generator/_generate_stubs.py @@ -88,6 +88,7 @@ def __call__(self, module: Module) -> str: self.module_imports = set() self._current_todo_msgs: set[str] = set() self.module = module + self.class_generics: list = [] return self._create_module_string(module) def _create_module_string(self, module: Module) -> str: @@ -175,7 +176,8 @@ def _create_class_string(self, class_: Class, class_indentation: str = "") -> st constraints_info = "" variance_info = "" if class_.type_parameters: - variances = [] + # We collect the class generics for the methods later + self.class_generics = [] out = "out " for variance in class_.type_parameters: variance_direction = { @@ -191,10 +193,10 @@ def _create_class_string(self, class_: Class, class_indentation: str = "") -> st variance_item = f"{variance_direction}{variance_name_camel_case}" if variance_direction == out: variance_item = f"{variance_item} sub {self._create_type_string(variance.type.to_dict())}" - variances.append(variance_item) + self.class_generics.append(variance_item) - if variances: - variance_info = f"<{', '.join(variances)}>" + if self.class_generics: + variance_info = f"<{', '.join(self.class_generics)}>" # Class name - Convert to camelCase and check for keywords class_name = class_.name @@ -328,9 +330,14 @@ def _create_function_string(self, function: Function, indentations: str = "", is for type_var in function.type_var_types: type_var_name = self._convert_snake_to_camel_case(type_var.name) type_var_name = self._replace_if_safeds_keyword(type_var_name) - type_var_names.append(type_var_name) - type_var_string = ", ".join(type_var_names) - type_var_info = f"<{type_var_string}>" + + # We don't have to display generic types in methods if they were already displayed in the class + if not is_method or (is_method and type_var_name not in self.class_generics): + type_var_names.append(type_var_name) + + if type_var_names: + type_var_string = ", ".join(type_var_names) + type_var_info = f"<{type_var_string}>" # Convert function name to camelCase name = function.name diff --git a/tests/data/various_modules_package/type_var_module.py b/tests/data/various_modules_package/type_var_module.py index 7789d37a..de0310b2 100644 --- a/tests/data/various_modules_package/type_var_module.py +++ b/tests/data/various_modules_package/type_var_module.py @@ -1,10 +1,9 @@ -from typing import TypeVar - +from typing import TypeVar, Generic T = TypeVar('T') -class TypeVarClass: +class TypeVarClass(Generic[T]): type_var = TypeVar("type_var") def __init__(self, items: list[T]): ... @@ -12,6 +11,12 @@ def __init__(self, items: list[T]): ... def type_var_class_method(self, a: T) -> T: ... +class TypeVarClass2(Generic[T]): + type_var = TypeVar("type_var") + + def type_var_class_method2(self, a: T) -> T: ... + + _type_var = TypeVar("_type_var") def type_var_func(a: list[_type_var]) -> list[_type_var]: ... 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 318890b5..fabda695 100644 --- a/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr +++ b/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr @@ -1851,20 +1851,20 @@ 'description': '', 'full_docstring': '', }), - 'id': 'various_modules_package/type_var_module/TypeVarClass/get_first_item', + 'id': 'various_modules_package/type_var_module/TypeVarClass/type_var_class_method', 'is_class_method': False, 'is_property': False, 'is_public': True, 'is_static': False, - 'name': 'get_first_item', + 'name': 'type_var_class_method', 'parameters': list([ - 'various_modules_package/type_var_module/TypeVarClass/get_first_item/self', - 'various_modules_package/type_var_module/TypeVarClass/get_first_item/a', + 'various_modules_package/type_var_module/TypeVarClass/type_var_class_method/self', + 'various_modules_package/type_var_module/TypeVarClass/type_var_class_method/a', ]), 'reexported_by': list([ ]), 'results': list([ - 'various_modules_package/type_var_module/TypeVarClass/get_first_item/result_1', + 'various_modules_package/type_var_module/TypeVarClass/type_var_class_method/result_1', ]), }), ]) diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub index 1106d43c..38338373 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub @@ -23,3 +23,11 @@ class TypeVarClass( a: T ) -> result1: T } + +class TypeVarClass2() { + @Pure + @PythonName("type_var_class_method2") + fun typeVarClassMethod2( + a: T + ) -> result1: T +} From 8e01d2d91f1366676e963d00dfd465c427f55166 Mon Sep 17 00:00:00 2001 From: Arsam Date: Thu, 29 Feb 2024 13:44:50 +0100 Subject: [PATCH 12/18] Added more snapshot tests for type var and adjusted the variance tests --- .../type_var_module.py | 4 ++++ .../variance_module.py | 23 +++++++++++++++---- .../test_type_var_creation.sdsstub | 6 +++++ .../test_variance_creation.sdsstub | 8 +++++-- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/tests/data/various_modules_package/type_var_module.py b/tests/data/various_modules_package/type_var_module.py index de0310b2..d286c4d2 100644 --- a/tests/data/various_modules_package/type_var_module.py +++ b/tests/data/various_modules_package/type_var_module.py @@ -24,3 +24,7 @@ def type_var_func(a: list[_type_var]) -> list[_type_var]: ... _type_var1 = TypeVar("_type_var1") _type_var2 = TypeVar("_type_var2") def multiple_type_var(a: _type_var1, b: _type_var2) -> list[_type_var1 | _type_var2]: ... + + +T_in = TypeVar("T_in", bound=int) +def type_var_fun_invariance_with_bound(a: list[T_in]) -> T_in: ... diff --git a/tests/data/various_modules_package/variance_module.py b/tests/data/various_modules_package/variance_module.py index acbac3ef..de3a8ded 100644 --- a/tests/data/various_modules_package/variance_module.py +++ b/tests/data/various_modules_package/variance_module.py @@ -5,14 +5,27 @@ class A: ... -_T_co = TypeVar("_T_co", covariant=True, bound=str) -_T_con = TypeVar("_T_con", contravariant=True, bound=A) -_T_in = TypeVar("_T_in", int, Literal[1, 2]) +_T_in = TypeVar("_T_in") +_T_co = TypeVar("_T_co", covariant=True) +_T_con = TypeVar("_T_con", contravariant=True) -class VarianceClassAll(Generic[_T_co, _T_con, _T_in]): +class VarianceClassOnlyCovarianceNoBound(Generic[_T_co]): ... -class VarianceClassOnlyInvariance(Generic[_T_in]): +class VarianceClassOnlyVarianceNoBound(Generic[_T_in]): + ... + + +class VarianceClassOnlyContravarianceNoBound(Generic[_T_con]): + ... + + +_T_co2 = TypeVar("_T_co2", covariant=True, bound=str) +_T_con2 = TypeVar("_T_con2", contravariant=True, bound=A) +_T_in2 = TypeVar("_T_in2", int, Literal[1, 2]) + + +class VarianceClassAll(Generic[_T_co2, _T_con2, _T_in2]): ... diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub index 38338373..68ab96e8 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub @@ -31,3 +31,9 @@ class TypeVarClass2() { a: T ) -> result1: T } + +@Pure +@PythonName("type_var_fun_invariance_with_bound") +fun typeVarFunInvarianceWithBound( + a: TIn, +) -> result1: TIn diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_variance_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_variance_creation.sdsstub index 46fdfc17..1775853f 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_variance_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_variance_creation.sdsstub @@ -3,6 +3,10 @@ package variousModulesPackage.varianceModule class A() -class VarianceClassAll() +class VarianceClassOnlyCovarianceNoBound() -class VarianceClassOnlyInvariance() +class VarianceClassOnlyVarianceNoBound() + +class VarianceClassOnlyContravarianceNoBound() + +class VarianceClassAll>>() From fec471a298b57615dc17ce77441a85fce2d2bcd5 Mon Sep 17 00:00:00 2001 From: Arsam Date: Thu, 29 Feb 2024 15:03:08 +0100 Subject: [PATCH 13/18] snapshot test update --- .../__snapshots__/test__get_api.ambr | 61 +------------------ .../test_type_var_creation.sdsstub | 22 +++---- 2 files changed, 14 insertions(+), 69 deletions(-) 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 fabda695..1c67d703 100644 --- a/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr +++ b/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr @@ -2472,7 +2472,7 @@ ]), 'type_parameters': list([ dict({ - 'name': '_T_co', + 'name': '_T_co2', 'type': dict({ 'kind': 'NamedType', 'name': 'str', @@ -2481,7 +2481,7 @@ 'variance_type': 'COVARIANT', }), dict({ - 'name': '_T_con', + 'name': '_T_con2', 'type': dict({ 'kind': 'NamedType', 'name': 'A', @@ -2490,62 +2490,7 @@ 'variance_type': 'CONTRAVARIANT', }), dict({ - 'name': '_T_in', - 'type': dict({ - 'kind': 'UnionType', - 'types': list([ - dict({ - 'kind': 'NamedType', - 'name': 'int', - 'qname': 'builtins.int', - }), - dict({ - 'kind': 'UnionType', - 'types': list([ - dict({ - 'kind': 'LiteralType', - 'literals': list([ - 1, - ]), - }), - dict({ - 'kind': 'LiteralType', - 'literals': list([ - 2, - ]), - }), - ]), - }), - ]), - }), - 'variance_type': 'INVARIANT', - }), - ]), - }) -# --- -# name: test_classes[VarianceClassOnlyInvariance] - dict({ - 'attributes': list([ - ]), - 'classes': list([ - ]), - 'constructor': None, - 'docstring': dict({ - 'description': '', - 'full_docstring': '', - }), - 'id': 'various_modules_package/variance_module/VarianceClassOnlyInvariance', - 'is_public': True, - 'methods': list([ - ]), - 'name': 'VarianceClassOnlyInvariance', - 'reexported_by': list([ - ]), - 'superclasses': list([ - ]), - 'type_parameters': list([ - dict({ - 'name': '_T_in', + 'name': '_T_in2', 'type': dict({ 'kind': 'UnionType', 'types': list([ diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub index 68ab96e8..e55164ca 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_type_var_creation.sdsstub @@ -4,15 +4,21 @@ package variousModulesPackage.typeVarModule @Pure @PythonName("type_var_func") fun typeVarFunc( - a: List<_type_var> -) -> result1: List<_type_var> + a: List +) -> result1: List @Pure @PythonName("multiple_type_var") fun multipleTypeVar( - a: _type_var1, - b: _type_var2 -) -> result1: List> + a: typeVar1, + b: typeVar2 +) -> result1: List> + +@Pure +@PythonName("type_var_fun_invariance_with_bound") +fun typeVarFunInvarianceWithBound( + a: List +) -> result1: TIn class TypeVarClass( items: List @@ -31,9 +37,3 @@ class TypeVarClass2() { a: T ) -> result1: T } - -@Pure -@PythonName("type_var_fun_invariance_with_bound") -fun typeVarFunInvarianceWithBound( - a: TIn, -) -> result1: TIn From c469f783177581da4e0f6800535958d1bbc30725 Mon Sep 17 00:00:00 2001 From: Arsam Date: Thu, 29 Feb 2024 15:55:46 +0100 Subject: [PATCH 14/18] Fixed variance generation in stubs --- src/safeds_stubgen/api_analyzer/_api.py | 8 +- .../api_analyzer/_ast_visitor.py | 14 +-- .../stubs_generator/_generate_stubs.py | 2 +- .../__snapshots__/test__get_api.ambr | 87 +++++++++++++++++++ .../api_analyzer/test__get_api.py | 8 +- 5 files changed, 109 insertions(+), 10 deletions(-) diff --git a/src/safeds_stubgen/api_analyzer/_api.py b/src/safeds_stubgen/api_analyzer/_api.py index 83aafa7f..1a0e13da 100644 --- a/src/safeds_stubgen/api_analyzer/_api.py +++ b/src/safeds_stubgen/api_analyzer/_api.py @@ -312,11 +312,15 @@ class ParameterAssignment(PythonEnum): @dataclass(frozen=True) class TypeParameter: name: str - type: AbstractType + type: AbstractType | None variance: VarianceKind def to_dict(self) -> dict[str, Any]: - return {"name": self.name, "type": self.type.to_dict(), "variance_type": self.variance.name} + return { + "name": self.name, + "type": self.type.to_dict() if self.type is not None else None, + "variance_type": self.variance.name + } class VarianceKind(PythonEnum): diff --git a/src/safeds_stubgen/api_analyzer/_ast_visitor.py b/src/safeds_stubgen/api_analyzer/_ast_visitor.py index 2841e2ff..61ff123f 100644 --- a/src/safeds_stubgen/api_analyzer/_ast_visitor.py +++ b/src/safeds_stubgen/api_analyzer/_ast_visitor.py @@ -155,13 +155,17 @@ def enter_classdef(self, node: mp_nodes.ClassDef) -> None: for generic_type in generic_types: variance_type = mypy_variance_parser(generic_type.variance) - variance_values: sds_types.AbstractType + variance_values: sds_types.AbstractType | None = None if variance_type == VarianceKind.INVARIANT: - variance_values = sds_types.UnionType( - [self.mypy_type_to_abstract_type(value) for value in generic_type.values], - ) + values = [self.mypy_type_to_abstract_type(value) for value in generic_type.values] + if values: + variance_values = sds_types.UnionType( + [self.mypy_type_to_abstract_type(value) for value in generic_type.values], + ) else: - variance_values = self.mypy_type_to_abstract_type(generic_type.upper_bound) + upper_bound = generic_type.upper_bound + if upper_bound.__str__() != "builtins.object": + variance_values = self.mypy_type_to_abstract_type(generic_type.upper_bound) type_parameters.append( TypeParameter( diff --git a/src/safeds_stubgen/stubs_generator/_generate_stubs.py b/src/safeds_stubgen/stubs_generator/_generate_stubs.py index ec17e1dc..165cc906 100644 --- a/src/safeds_stubgen/stubs_generator/_generate_stubs.py +++ b/src/safeds_stubgen/stubs_generator/_generate_stubs.py @@ -191,7 +191,7 @@ def _create_class_string(self, class_: Class, class_indentation: str = "") -> st variance_name_camel_case = self._replace_if_safeds_keyword(variance_name_camel_case) variance_item = f"{variance_direction}{variance_name_camel_case}" - if variance_direction == out: + if variance.type is not None: variance_item = f"{variance_item} sub {self._create_type_string(variance.type.to_dict())}" self.class_generics.append(variance_item) 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 1c67d703..c0ea704c 100644 --- a/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr +++ b/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr @@ -2523,6 +2523,93 @@ ]), }) # --- +# name: test_classes[VarianceClassOnlyContravarianceNoBound] + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'various_modules_package/variance_module/VarianceClassOnlyContravarianceNoBound', + 'is_public': True, + 'methods': list([ + ]), + 'name': 'VarianceClassOnlyContravarianceNoBound', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + 'type_parameters': list([ + dict({ + 'name': '_T_con', + 'type': None, + 'variance_type': 'CONTRAVARIANT', + }), + ]), + }) +# --- +# name: test_classes[VarianceClassOnlyCovarianceNoBound] + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'various_modules_package/variance_module/VarianceClassOnlyCovarianceNoBound', + 'is_public': True, + 'methods': list([ + ]), + 'name': 'VarianceClassOnlyCovarianceNoBound', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + 'type_parameters': list([ + dict({ + 'name': '_T_co', + 'type': None, + 'variance_type': 'COVARIANT', + }), + ]), + }) +# --- +# name: test_classes[VarianceClassOnlyVarianceNoBound] + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'various_modules_package/variance_module/VarianceClassOnlyVarianceNoBound', + 'is_public': True, + 'methods': list([ + ]), + 'name': 'VarianceClassOnlyVarianceNoBound', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + 'type_parameters': list([ + dict({ + 'name': '_T_in', + 'type': None, + 'variance_type': 'INVARIANT', + }), + ]), + }) +# --- # name: test_classes[_ClassModulePrivateClassG] dict({ 'attributes': list([ diff --git a/tests/safeds_stubgen/api_analyzer/test__get_api.py b/tests/safeds_stubgen/api_analyzer/test__get_api.py index 78c7898d..8ef3c73c 100644 --- a/tests/safeds_stubgen/api_analyzer/test__get_api.py +++ b/tests/safeds_stubgen/api_analyzer/test__get_api.py @@ -169,8 +169,10 @@ def test_imports(module_name: str, import_type: str, snapshot: SnapshotAssertion (_class_module_name, "ClassModuleNestedClassE", "plaintext"), (_class_module_name, "_ClassModulePrivateDoubleNestedClassF", "plaintext"), (_class_module_name, "_ClassModulePrivateClassG", "plaintext"), + ("variance_module", "VarianceClassOnlyCovarianceNoBound", "plaintext"), + ("variance_module", "VarianceClassOnlyVarianceNoBound", "plaintext"), + ("variance_module", "VarianceClassOnlyContravarianceNoBound", "plaintext"), ("variance_module", "VarianceClassAll", "plaintext"), - ("variance_module", "VarianceClassOnlyInvariance", "plaintext"), (_infer_types_module_name, "InferMyTypes", "plaintext"), (_docstring_module_name, "EpydocDocstringClass", "epydoc"), (_docstring_module_name, "RestDocstringClass", "rest"), @@ -190,8 +192,10 @@ def test_imports(module_name: str, import_type: str, snapshot: SnapshotAssertion "ClassModuleNestedClassE", "_ClassModulePrivateDoubleNestedClassF", "_ClassModulePrivateClassG", + "VarianceClassOnlyCovarianceNoBound", + "VarianceClassOnlyVarianceNoBound", + "VarianceClassOnlyContravarianceNoBound", "VarianceClassAll", - "VarianceClassOnlyInvariance", "InferMyTypes", "EpydocDocstringClass", "RestDocstringClass", From 5c1e9f32a2aaf77de4af14e7b87b8be58c7a40f7 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:57:44 +0000 Subject: [PATCH 15/18] style: apply automated linter fixes --- src/safeds_stubgen/api_analyzer/_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/safeds_stubgen/api_analyzer/_api.py b/src/safeds_stubgen/api_analyzer/_api.py index 1a0e13da..7971e426 100644 --- a/src/safeds_stubgen/api_analyzer/_api.py +++ b/src/safeds_stubgen/api_analyzer/_api.py @@ -319,7 +319,7 @@ def to_dict(self) -> dict[str, Any]: return { "name": self.name, "type": self.type.to_dict() if self.type is not None else None, - "variance_type": self.variance.name + "variance_type": self.variance.name, } From fafa1088d67080d29da6cc8515ee387d97e5111c Mon Sep 17 00:00:00 2001 From: Arsam Date: Thu, 29 Feb 2024 16:26:14 +0100 Subject: [PATCH 16/18] type vars in function stubs now can also have upper bound --- src/safeds_stubgen/api_analyzer/_ast_visitor.py | 9 +++++++-- src/safeds_stubgen/api_analyzer/_types.py | 13 +++++++++---- .../stubs_generator/_generate_stubs.py | 5 ++++- .../api_analyzer/__snapshots__/test__get_api.ambr | 6 ++++++ tests/safeds_stubgen/api_analyzer/test_types.py | 9 +++++---- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/safeds_stubgen/api_analyzer/_ast_visitor.py b/src/safeds_stubgen/api_analyzer/_ast_visitor.py index 61ff123f..da156e82 100644 --- a/src/safeds_stubgen/api_analyzer/_ast_visitor.py +++ b/src/safeds_stubgen/api_analyzer/_ast_visitor.py @@ -165,7 +165,7 @@ def enter_classdef(self, node: mp_nodes.ClassDef) -> None: else: upper_bound = generic_type.upper_bound if upper_bound.__str__() != "builtins.object": - variance_values = self.mypy_type_to_abstract_type(generic_type.upper_bound) + variance_values = self.mypy_type_to_abstract_type(upper_bound) type_parameters.append( TypeParameter( @@ -842,7 +842,12 @@ def mypy_type_to_abstract_type( # Special Cases elif isinstance(mypy_type, mp_types.TypeVarType): - type_var = sds_types.TypeVarType(mypy_type.name) + upper_bound = mypy_type.upper_bound + type_ = None + if upper_bound.__str__() != "builtins.object": + type_ = self.mypy_type_to_abstract_type(upper_bound) + + type_var = sds_types.TypeVarType(name=mypy_type.name, upper_bound=type_) self.type_var_types.add(type_var) return type_var elif isinstance(mypy_type, mp_types.CallableType): diff --git a/src/safeds_stubgen/api_analyzer/_types.py b/src/safeds_stubgen/api_analyzer/_types.py index 8f69db37..49b07a32 100644 --- a/src/safeds_stubgen/api_analyzer/_types.py +++ b/src/safeds_stubgen/api_analyzer/_types.py @@ -391,16 +391,21 @@ def __hash__(self) -> int: @dataclass(frozen=True) class TypeVarType(AbstractType): name: str + upper_bound: AbstractType | None = None @classmethod - def from_dict(cls, d: dict[str, str]) -> TypeVarType: - return TypeVarType(d["name"]) + def from_dict(cls, d: dict[str, Any]) -> TypeVarType: + return TypeVarType(d["name"], d["upper_bound"]) def to_dict(self) -> dict[str, str]: - return {"kind": self.__class__.__name__, "name": self.name} + return { + "kind": self.__class__.__name__, + "name": self.name, + "upper_bound": self.upper_bound.to_dict() if self.upper_bound is not None else None + } def __hash__(self) -> int: - return hash(frozenset([self.name])) + return hash(frozenset([self.name, self.upper_bound])) # ############################## Utilities ############################## # diff --git a/src/safeds_stubgen/stubs_generator/_generate_stubs.py b/src/safeds_stubgen/stubs_generator/_generate_stubs.py index 165cc906..9dd2c7c0 100644 --- a/src/safeds_stubgen/stubs_generator/_generate_stubs.py +++ b/src/safeds_stubgen/stubs_generator/_generate_stubs.py @@ -333,6 +333,8 @@ def _create_function_string(self, function: Function, indentations: str = "", is # We don't have to display generic types in methods if they were already displayed in the class if not is_method or (is_method and type_var_name not in self.class_generics): + if type_var.upper_bound is not None: + type_var_name += f" sub {self._create_type_string(type_var.upper_bound.to_dict())}" type_var_names.append(type_var_name) if type_var_names: @@ -644,7 +646,8 @@ def _create_type_string(self, type_data: dict | None) -> str: types.append(f"{literal_type}") return f"literal<{', '.join(types)}>" elif kind == "TypeVarType": - return type_data["name"] + name = self._convert_snake_to_camel_case(type_data["name"]) + return self._replace_if_safeds_keyword(name) raise ValueError(f"Unexpected type: {kind}") # pragma: no cover 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 c0ea704c..e5a230db 100644 --- a/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr +++ b/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr @@ -3459,6 +3459,7 @@ 'type': dict({ 'kind': 'TypeVarType', 'name': '_type_var1', + 'upper_bound': None, }), }), dict({ @@ -3475,6 +3476,7 @@ 'type': dict({ 'kind': 'TypeVarType', 'name': '_type_var2', + 'upper_bound': None, }), }), ]) @@ -4542,6 +4544,7 @@ dict({ 'kind': 'TypeVarType', 'name': '_type_var', + 'upper_bound': None, }), ]), }), @@ -5074,10 +5077,12 @@ dict({ 'kind': 'TypeVarType', 'name': '_type_var1', + 'upper_bound': None, }), dict({ 'kind': 'TypeVarType', 'name': '_type_var2', + 'upper_bound': None, }), ]), }), @@ -5328,6 +5333,7 @@ dict({ 'kind': 'TypeVarType', 'name': '_type_var', + 'upper_bound': None, }), ]), }), diff --git a/tests/safeds_stubgen/api_analyzer/test_types.py b/tests/safeds_stubgen/api_analyzer/test_types.py index 010b60bf..9cb40506 100644 --- a/tests/safeds_stubgen/api_analyzer/test_types.py +++ b/tests/safeds_stubgen/api_analyzer/test_types.py @@ -263,16 +263,17 @@ def test_type_var_type() -> None: type_dict = { "kind": "TypeVarType", "name": "_T", + "upper_bound": None } assert AbstractType.from_dict(type_dict) == type_ assert TypeVarType.from_dict(type_dict) == type_ assert type_.to_dict() == type_dict - assert TypeVarType("a") == TypeVarType("a") - assert hash(TypeVarType("a")) == hash(TypeVarType("a")) - assert TypeVarType("a") != TypeVarType("b") - assert hash(TypeVarType("a")) != hash(TypeVarType("b")) + assert TypeVarType("a", None) == TypeVarType("a", None) + assert hash(TypeVarType("a", None)) == hash(TypeVarType("a", None)) + assert TypeVarType("a", None) != TypeVarType("b", None) + assert hash(TypeVarType("a", None)) != hash(TypeVarType("b", None)) def test_final_type() -> None: From 68531d4be3f7805e9e7034c29cdcf7ffd179dc66 Mon Sep 17 00:00:00 2001 From: Arsam Date: Thu, 29 Feb 2024 16:29:06 +0100 Subject: [PATCH 17/18] linter fix --- src/safeds_stubgen/api_analyzer/_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/safeds_stubgen/api_analyzer/_types.py b/src/safeds_stubgen/api_analyzer/_types.py index 49b07a32..43a45f5f 100644 --- a/src/safeds_stubgen/api_analyzer/_types.py +++ b/src/safeds_stubgen/api_analyzer/_types.py @@ -397,7 +397,7 @@ class TypeVarType(AbstractType): def from_dict(cls, d: dict[str, Any]) -> TypeVarType: return TypeVarType(d["name"], d["upper_bound"]) - def to_dict(self) -> dict[str, str]: + def to_dict(self) -> dict[str, Any]: return { "kind": self.__class__.__name__, "name": self.name, From b2275b63a3e2e20c3465f35985fa673b66ed8583 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Thu, 29 Feb 2024 15:30:43 +0000 Subject: [PATCH 18/18] style: apply automated linter fixes --- src/safeds_stubgen/api_analyzer/_types.py | 2 +- tests/safeds_stubgen/api_analyzer/test_types.py | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/safeds_stubgen/api_analyzer/_types.py b/src/safeds_stubgen/api_analyzer/_types.py index 43a45f5f..4c5d7527 100644 --- a/src/safeds_stubgen/api_analyzer/_types.py +++ b/src/safeds_stubgen/api_analyzer/_types.py @@ -401,7 +401,7 @@ def to_dict(self) -> dict[str, Any]: return { "kind": self.__class__.__name__, "name": self.name, - "upper_bound": self.upper_bound.to_dict() if self.upper_bound is not None else None + "upper_bound": self.upper_bound.to_dict() if self.upper_bound is not None else None, } def __hash__(self) -> int: diff --git a/tests/safeds_stubgen/api_analyzer/test_types.py b/tests/safeds_stubgen/api_analyzer/test_types.py index 9cb40506..17e99220 100644 --- a/tests/safeds_stubgen/api_analyzer/test_types.py +++ b/tests/safeds_stubgen/api_analyzer/test_types.py @@ -260,11 +260,7 @@ def test_literal_type() -> None: def test_type_var_type() -> None: type_ = TypeVarType("_T") - type_dict = { - "kind": "TypeVarType", - "name": "_T", - "upper_bound": None - } + type_dict = {"kind": "TypeVarType", "name": "_T", "upper_bound": None} assert AbstractType.from_dict(type_dict) == type_ assert TypeVarType.from_dict(type_dict) == type_