Skip to content

Commit

Permalink
Add __iter__ methods when __getitem__ exists, and fix implicitly conv…
Browse files Browse the repository at this point in the history
…ertible types
  • Loading branch information
chadrik committed Jun 24, 2023
1 parent 425e240 commit 1f2f802
Show file tree
Hide file tree
Showing 35 changed files with 1,448 additions and 2,023 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
.nox
dist
__pycache__
*.pyc
*.pyc
usd/stubs/**/__DOC.pyi
usd/stubs/**/_[a-z]*.pyi
66 changes: 30 additions & 36 deletions katana/stubgen_katana.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import Callbacks.Callbacks
from Callbacks.Callbacks import _TypeEnum
from stubgenlib import DocstringSignatureGenerator
from stubgenlib import DocstringSignatureGenerator, CFunctionStub

EPY_REG = re.compile(r"([LC]\{([^}]+)\})")
LIST_OF_REG = re.compile(r"\b(list|Sequence|Iterable|Iterator) of (.*)")
Expand Down Expand Up @@ -196,24 +196,6 @@ def get_members(self, obj: object) -> list[tuple[str, Any]]:
return list(members.items())


class CFunctionStub:
"""
Class that mimics a C function in order to provide parseable docstrings.
"""

def __init__(self, name, doc, is_abstract=False):
self.__name__ = name
self.__doc__ = doc
self.__abstractmethod__ = is_abstract

def __get__(self):
"""
This exists to make this object look like a method descriptor and thus
return true for CStubGenerator.ismethod()
"""
pass


class CStubGenerator(mypy.stubgenc.CStubGenerator):
DATA_ATTRS = {
'DataAttribute': 'T',
Expand Down Expand Up @@ -260,40 +242,52 @@ def add(x):
is_abstract = obj.__name__ == 'DataAttribute'
# Add abstract methods that are shared by all sub-classes
add(
CFunctionStub(
"getValue",
f"getValue(self, defaultValue: {sub_type} = ..., throwOnError: bool = ...) -> {sub_type}",
CFunctionStub._from_sig(
FunctionSig(
"getValue",
[
ArgSig("defaultValue", sub_type, default=True),
ArgSig("throwOnError", "bool", default=True),
],
sub_type,
),
is_abstract=is_abstract,
)
)
add(
CFunctionStub(
"getData",
f"getData(self) -> ConstVector[{sub_type}]",
CFunctionStub._from_sig(
FunctionSig("getData", [], "ConstVector[{sub_type}]"),
is_abstract=is_abstract,
)
)
add(
CFunctionStub(
"getNearestSample",
f"getNearestSample(self, sampleTime: float) -> ConstVector[{sub_type}]",
CFunctionStub._from_sig(
FunctionSig(
"getNearestSample",
[ArgSig("sampleTime", "float")],
"ConstVector[{sub_type}]",
),
is_abstract=is_abstract,
)
)
add(
CFunctionStub(
"getSamples",
f"getSamples(self) -> Dict[float, ConstVector[{sub_type}]]",
CFunctionStub._from_sig(
FunctionSig(
"getSamples", [], "Dict[float, ConstVector[{sub_type}]]"
),
is_abstract=is_abstract,
)
)
elif isinstance(obj, type) and obj.__name__ == 'ConstVector':
add(CFunctionStub("__iter__", "__iter__(self) -> Iterator[T]"))
add(CFunctionStub._from_sig(FunctionSig("__iter__", [], "Iterator[T]")))
add(
CFunctionStub(
"__getitem__",
"__getitem__(self, arg0: int) -> T\n"
"__getitem__(self, arg0: slice) -> ConstVector[T]",
CFunctionStub._from_sigs(
[
FunctionSig("__getitem__", [ArgSig("arg0", "int")], "T"),
FunctionSig(
"__getitem__", [ArgSig("arg0", "slice")], "ConstVector[T]"
),
]
)
)

Expand Down
93 changes: 93 additions & 0 deletions stubgenlib.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import absolute_import, annotations, division, print_function

import itertools
import re
from typing import Any

Expand Down Expand Up @@ -129,6 +130,98 @@ def get_function_sig(
return infer_sig_from_docstring(docstr, ctx.name)


class CFunctionStub:
"""
Class that mimics a C function in order to provide parseable docstrings.
"""

def __init__(self, name: str, doc: str, is_abstract=False):
self.__name__ = name
self.__doc__ = doc
self.__abstractmethod__ = is_abstract

@classmethod
def _from_sig(cls, sig: FunctionSig, is_abstract=False) -> CFunctionStub:
return CFunctionStub(sig.name, sig.format_sig(suffix=""), is_abstract)

@classmethod
def _from_sigs(cls, sigs: list[FunctionSig], is_abstract=False) -> CFunctionStub:
return CFunctionStub(
sigs[0].name,
'\n'.join(sig.format_sig(suffix="") for sig in sigs),
is_abstract,
)

def __get__(self):
"""
This exists to make this object look like a method descriptor and thus
return true for CStubGenerator.ismethod()
"""
pass


def reduce_overloads(sigs: list[FunctionSig]) -> list[FunctionSig]:
"""
Remove unsupported and redundant overloads.
- Some overloads are a subset of other overloads and can be pruned.
- Some methods implement both classmethod and instancemethod overloads, and mypy prevents
mixing these and does not correctly analyze them: so we have to drop one, and we've chosen
to remove classmethods. It is possible to implement a "universalmethod" decorator, but
we could not use overloads to distinguish their arguments.
"""
# remove dups (FunctionSig is not hashable, so it's a bit cumbersome)
new_sigs = []
classmethods = []
instancmethods = []
for sig in sigs:
if sig not in new_sigs:
if sig.args and sig.args[0].name == 'self':
instancmethods.append(sig)
else:
classmethods.append(sig)
new_sigs.append(sig)
if classmethods and instancmethods:
new_sigs = instancmethods

if len(new_sigs) <= 1:
return new_sigs

sigs = sorted(new_sigs, key=lambda x: len(x.args), reverse=True)
redundant = []
for a, b in itertools.combinations(sigs, 2):
if contains_other_overload(a, b):
redundant.append(b)
elif contains_other_overload(b, a):
redundant.append(a)
results = [sig for sig in sigs if sig not in redundant]
if not results:
print("removed too much")
for x in sigs:
print(x)
raise ValueError
return results


def contains_other_overload(sig: FunctionSig, other: FunctionSig) -> bool:
"""
Return whether an overload is fully covered by another overload, and thus redundant.
"""
if other.ret_type != sig.ret_type:
# not compatible
return False
num_other_args = len(other.args)
if len(sig.args) < num_other_args:
# other has more args, sig cannot contain other
return False
if sig.args[:num_other_args] == other.args and all(
a.default for a in sig.args[num_other_args:]
):
# sig contains all of other's args, and the remaining sig args all have defaults
return True
return False


def test():
docstr = """
__init__( (object)arg1) -> None
Expand Down
Loading

0 comments on commit 1f2f802

Please sign in to comment.