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: Create stubs for public methods of inherited internal classes #69

Merged
merged 14 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
60 changes: 57 additions & 3 deletions src/safeds_stubgen/stubs_generator/_generate_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,19 @@ def _create_class_string(self, class_: Class, class_indentation: str = "") -> st
# Superclasses
superclasses = class_.superclasses
superclass_info = ""
superclass_methods_text = ""
if superclasses and not class_.is_abstract:
superclass_names = []
for superclass in superclasses:
superclass_names.append(superclass.split(".")[-1])
superclass_name = superclass.split(".")[-1]
superclass_names.append(superclass_name)
self._add_to_imports(superclass)

# If the superclass was not added to the module_imports through the _add_to_imports method, it means
# that the superclass is a class from the same module.
# For internal superclasses, we have to add their public members to subclasses.
superclass_methods_text += self._create_internal_class_methods_string(superclass, inner_indentations)

superclass_info = f" sub {', '.join(superclass_names)}"

if len(superclasses) > 1:
Expand Down Expand Up @@ -312,6 +319,9 @@ def _create_class_string(self, class_: Class, class_indentation: str = "") -> st
for inner_class in class_.classes:
class_text += f"\n{self._create_class_string(inner_class, inner_indentations)}\n"

# Superclass methods, if the superclass is an internal class
class_text += superclass_methods_text

# Methods
class_text += self._create_class_method_string(class_.methods, inner_indentations)

Expand All @@ -324,11 +334,17 @@ def _create_class_string(self, class_: Class, class_indentation: str = "") -> st

return f"{class_signature} {{{class_text}"

def _create_class_method_string(self, methods: list[Function], inner_indentations: str) -> str:
def _create_class_method_string(
self,
methods: list[Function],
inner_indentations: str,
is_internal_class: bool = False,
) -> str:
class_methods: list[str] = []
class_property_methods: list[str] = []
for method in methods:
if not method.is_public:
# Add methods of internal classes that are inherited if the methods themselfe are public
if not method.is_public and (not is_internal_class or (is_internal_class and is_internal(method.name))):
continue
elif method.is_property:
class_property_methods.append(
Expand Down Expand Up @@ -744,6 +760,29 @@ def _create_type_string(self, type_data: dict | None) -> str:

raise ValueError(f"Unexpected type: {kind}") # pragma: no cover

def _create_internal_class_methods_string(self, superclass: str, inner_indentations: str) -> str:
superclass_methods_text = ""
superclass_name = superclass.split(".")[-1]
superclass_class = None
if superclass not in self.module_imports and is_internal(superclass_name):
superclass_class = self._get_class_in_module(superclass_name)
superclass_methods_text = self._create_class_method_string(
superclass_class.methods,
inner_indentations,
is_internal_class=True,
)

if superclass_class is not None:
for superclass_superclass in superclass_class.superclasses:
name = superclass_superclass.split(".")[-1]
if is_internal(name):
superclass_methods_text += self._create_internal_class_methods_string(
superclass_superclass,
inner_indentations,
)

return superclass_methods_text

# ############################### Utilities ############################### #

def _add_to_imports(self, qname: str) -> None:
Expand Down Expand Up @@ -809,6 +848,17 @@ def _create_todo_msg(self, indentations: str) -> str:

return indentations + f"\n{indentations}".join(todo_msgs) + "\n"

def _get_class_in_module(self, class_name: str) -> Class:
if f"{self.module.id}/{class_name}" in self.api.classes:
return self.api.classes[f"{self.module.id}/{class_name}"]

# If the class is a nested class
for class_ in self.api.classes:
if class_.startswith(self.module.id) and class_.endswith(class_name):
return self.api.classes[class_]

raise LookupError(f"Expected finding class '{class_name}' in module '{self.module.id}'.") # pragma: no cover


def _callable_type_name_generator() -> Generator:
"""Generate a name for callable type parameters starting from 'a' until 'zz'."""
Expand Down Expand Up @@ -860,6 +910,10 @@ def _replace_if_safeds_keyword(keyword: str) -> str:
return keyword


def is_internal(name: str) -> bool:
return name.startswith("_")


def _convert_name_to_convention(
name: str,
naming_convention: NamingConvention,
Expand Down
35 changes: 35 additions & 0 deletions tests/data/various_modules_package/inheritance_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class PublicSuperClass:
def public_superclass_method(self) -> str: ...


class PublicSubClass(PublicSuperClass):
...


class _PrivateInternalClass:
class _PrivateInternalNestedClass:
def public_internal_nested_class_method(self, a: None) -> bool: ...

def public_internal_class_method(self, a: int) -> str: ...

def _private_internal_class_method(self, b: list) -> None: ...


class PublicSubClass2(_PrivateInternalClass):
def public_subclass_method(self) -> str: ...


class PublicSubClassFromNested(_PrivateInternalClass._PrivateInternalNestedClass):
...


class _TransitiveInternalClassA:
def transitive_class_fun(self, c: list) -> list: ...


class _TransitiveInternalClassB(_TransitiveInternalClassA):
pass


class InheritTransitively(_TransitiveInternalClassB):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
@PythonModule("various_modules_package.inheritance_module")
package variousModulesPackage.inheritanceModule

class PublicSuperClass() {
@Pure
@PythonName("public_superclass_method")
fun publicSuperclassMethod() -> result1: String
}

class PublicSubClass() sub PublicSuperClass

class PublicSubClass2() sub _PrivateInternalClass {
Masara marked this conversation as resolved.
Show resolved Hide resolved
@Pure
@PythonName("public_internal_class_method")
fun publicInternalClassMethod(
a: Int
) -> result1: String

@Pure
@PythonName("public_subclass_method")
fun publicSubclassMethod() -> result1: String
}

class PublicSubClassFromNested() sub _PrivateInternalNestedClass {
Masara marked this conversation as resolved.
Show resolved Hide resolved
@Pure
@PythonName("public_internal_nested_class_method")
fun publicInternalNestedClassMethod(
a: Nothing?
) -> result1: Boolean
}

class InheritTransitively() sub _TransitiveInternalClassB {
Masara marked this conversation as resolved.
Show resolved Hide resolved
@Pure
@PythonName("transitive_class_fun")
fun transitiveClassFun(
c: List<Any>
) -> result1: List<Any>
}