Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Correct stubs for TypeVars #67

Merged
merged 20 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
47c437f
Adjusted snapshot test files for typevar stubs
Masara Feb 25, 2024
979def4
Added another test case for typevar stubs
Masara Feb 25, 2024
1b77b68
Added typevar stubs test module and separate snapshots
Masara Feb 25, 2024
79913eb
TypeVar stub creation now works with type vars if they are existing T…
Masara Feb 25, 2024
9c30988
Now "def func[T]" like TypeVars can also be analyzed. Adjusted tests.
Masara Feb 25, 2024
a90a8af
Linter fixes
Masara Feb 25, 2024
78802d0
style: apply automated linter fixes
megalinter-bot Feb 25, 2024
1f89f5a
Removed changes for 'def func[T]' TypeData from commit 9c30988b
Masara Feb 25, 2024
206338f
Merge remote-tracking branch 'origin/63-typevar-stubs' into 63-typeva…
Masara Feb 25, 2024
aade9cb
TypeVar names are now in camelCase in stubs
Masara Feb 25, 2024
12469d5
(WIP) Added a TypeVar test case for a class with type parameters.
Masara Feb 26, 2024
d3ef10f
Fixed how classes and their methods generate stubs if the classes use…
Masara Feb 28, 2024
8e01d2d
Added more snapshot tests for type var and adjusted the variance tests
Masara Feb 29, 2024
fec471a
snapshot test update
Masara Feb 29, 2024
c469f78
Fixed variance generation in stubs
Masara Feb 29, 2024
5c1e9f3
style: apply automated linter fixes
megalinter-bot Feb 29, 2024
fafa108
type vars in function stubs now can also have upper bound
Masara Feb 29, 2024
480b744
Merge remote-tracking branch 'origin/63-typevar-stubs' into 63-typeva…
Masara Feb 29, 2024
68531d4
linter fix
Masara Feb 29, 2024
b2275b6
style: apply automated linter fixes
megalinter-bot Feb 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/safeds_stubgen/api_analyzer/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
ResultDocstring,
)

from ._types import AbstractType
from ._types import AbstractType, TypeVarType

API_SCHEMA_VERSION = 1

Expand Down Expand Up @@ -237,6 +237,7 @@ class Function:
is_static: bool
is_class_method: bool
is_property: bool
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)
Expand Down
19 changes: 16 additions & 3 deletions src/safeds_stubgen/api_analyzer/_ast_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -229,11 +231,19 @@ 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: list[sds_types.TypeVarType] = []
# Reset the type_var_types list
self.type_var_types = set()
if getattr(node, "arguments", None) is not None:
arguments = self._parse_parameter_data(node, function_id)

if 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)

Expand All @@ -252,6 +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,
)
self.__declaration_stack.append(function)

Expand Down Expand Up @@ -666,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

Expand Down Expand Up @@ -827,7 +838,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],
Expand Down
18 changes: 14 additions & 4 deletions src/safeds_stubgen/stubs_generator/_generate_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand Down
4 changes: 1 addition & 3 deletions tests/data/various_modules_package/attribute_module.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -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
6 changes: 1 addition & 5 deletions tests/data/various_modules_package/function_module.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -187,10 +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]: ...


class FunctionModulePropertiesClass:
@property
def property_function(self): ...
Expand Down
14 changes: 14 additions & 0 deletions tests/data/various_modules_package/type_var_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import TypeVar


class TypeVarClass:
type_var = TypeVar("type_var")


_type_var = TypeVar("_type_var")
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]: ...
111 changes: 73 additions & 38 deletions tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -1859,6 +1844,10 @@
}),
])
# ---
# name: test_class_methods[TypeVarClass]
list([
])
# ---
# name: test_class_methods[_ClassModulePrivateDoubleNestedClassF]
list([
dict({
Expand Down Expand Up @@ -3401,6 +3390,42 @@
}),
])
# ---
# 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({
Expand Down Expand Up @@ -4455,9 +4480,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([
Expand Down Expand Up @@ -4978,6 +5003,36 @@
}),
])
# ---
# 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({
Expand Down Expand Up @@ -5212,7 +5267,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',
Expand Down Expand Up @@ -6106,26 +6161,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': '',
Expand Down
11 changes: 9 additions & 2 deletions tests/safeds_stubgen/api_analyzer/test__get_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ############################## #
Expand Down Expand Up @@ -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"),
Expand All @@ -349,6 +351,7 @@ def test_global_functions(module_name: str, snapshot: SnapshotAssertion) -> None
"FunctionModuleClassC",
"FunctionModulePropertiesClass",
"InferMyTypes",
"TypeVarClass",
"ReexportClass",
"AbstractModuleClass",
"EpydocDocstringClass",
Expand Down Expand Up @@ -390,7 +393,8 @@ 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"),
("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"),
Expand Down Expand Up @@ -422,6 +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",
"multiple_type_var",
"abstract_method_params",
"abstract_static_method_params",
"abstract_property_method",
Expand Down Expand Up @@ -480,7 +485,8 @@ 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"),
("multiple_type_var", _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"),
Expand Down Expand Up @@ -521,6 +527,7 @@ def test_function_parameters(
"callable_type",
"result_from_outside_the_package",
"type_var_func",
"multiple_type_var",
"instance_method",
"static_method_params",
"class_method_params",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading