Skip to content

Commit

Permalink
Now "def func[T]" like TypeVars can also be analyzed. Adjusted tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
Masara committed Feb 25, 2024
1 parent 79913eb commit 9c30988
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 56 deletions.
2 changes: 1 addition & 1 deletion src/safeds_stubgen/api_analyzer/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
41 changes: 30 additions & 11 deletions src/safeds_stubgen/api_analyzer/_ast_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
208 changes: 170 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,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({
Expand Down Expand Up @@ -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({
Expand All @@ -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([
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -5205,14 +5341,30 @@
}),
])
# ---
# 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({
'docstring': dict({
'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 +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': '',
Expand Down
Loading

0 comments on commit 9c30988

Please sign in to comment.