mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-29 01:46:38 +00:00
Add type hints to python for class constructors
* We enforce default and copy constructors where possible, this isn't very pythonic but we can't use copy.deepcopy on our types. * The structs that have specialised constructors e.g. FloatVector or Subresource also have those documented though we have no way to enforce it.
This commit is contained in:
+63
-4
@@ -390,15 +390,32 @@ def gen_class(file: Stream, class_obj: Type):
|
||||
|
||||
bases_string = ", ".join([b.__name__ for b in bases])
|
||||
|
||||
file.println(f"class {class_obj.__name__}({bases_string}):")
|
||||
file.indent()
|
||||
|
||||
if class_obj.__doc__ is None:
|
||||
raise ValueError("Unexpected None docstring")
|
||||
|
||||
lines = class_obj.__doc__.strip().splitlines()
|
||||
|
||||
constructors: List[List[Tuple[str, str]]] = []
|
||||
while lines[0].strip().startswith(class_obj.__name__ + "("):
|
||||
args = lines[0].strip()
|
||||
start = args.find("(")
|
||||
args = args[start + 1 : -1]
|
||||
annot_split = lambda arg: (arg.split(":")[0].strip(), arg.split(":")[1].strip())
|
||||
constructors.append([annot_split(arg) for arg in args.split(",") if arg != ""])
|
||||
del lines[0]
|
||||
|
||||
if len(constructors) > 0:
|
||||
file.println("from typing import overload")
|
||||
file.println("")
|
||||
|
||||
class_doc = ("\n".join(lines)).strip()
|
||||
|
||||
file.println(f"class {class_obj.__name__}({bases_string}):")
|
||||
file.indent()
|
||||
|
||||
file.println("# Original docstring")
|
||||
file.println('"""')
|
||||
file.printlines(class_obj.__doc__)
|
||||
file.printlines(class_doc)
|
||||
file.println('"""')
|
||||
file.println("")
|
||||
file.println("")
|
||||
@@ -453,6 +470,47 @@ def gen_class(file: Stream, class_obj: Type):
|
||||
file.println('"""')
|
||||
file.println("")
|
||||
else:
|
||||
for ctor in constructors:
|
||||
ctor_def = f"def __init__(self, "
|
||||
for param, annot in ctor:
|
||||
if annot != class_obj.__name__:
|
||||
# a default value comes in with the annotation,
|
||||
# we don't split it out otherwise so strip it here
|
||||
type_str = annot.split("=")[0].strip()
|
||||
add_dependencies(class_obj, deps, type_str)
|
||||
ctor_def += f"{param}: {annot}, "
|
||||
else:
|
||||
# escape any self-references in ''s
|
||||
ctor_def += f"{param}: '{annot}', "
|
||||
|
||||
ctor_def = ctor_def[:-2] + "):"
|
||||
|
||||
file.println("@overload")
|
||||
file.println(ctor_def)
|
||||
file.indent()
|
||||
file.println('"""')
|
||||
# copy constructors have only one parameter of our own type
|
||||
if len(ctor) == 1 and ctor[0][1] == class_obj.__name__:
|
||||
file.println(
|
||||
f"Construct a new {class_obj.__name__} with a deep copy of the input."
|
||||
)
|
||||
# default constructors have no parameters
|
||||
elif ctor == []:
|
||||
file.println(
|
||||
f"Construct a new default-initialised {class_obj.__name__}."
|
||||
)
|
||||
# more complex value constructor with parameters
|
||||
else:
|
||||
file.println(
|
||||
f"Construct a new {class_obj.__name__} using provided values."
|
||||
)
|
||||
file.println('"""')
|
||||
file.println("pass")
|
||||
file.dedent()
|
||||
file.println("")
|
||||
if len(constructors) > 0:
|
||||
file.println("")
|
||||
|
||||
for item_name in class_obj.__dict__.keys():
|
||||
if item_name.startswith("__"):
|
||||
continue
|
||||
@@ -506,6 +564,7 @@ def gen_class(file: Stream, class_obj: Type):
|
||||
file.println('"""')
|
||||
file.printlines(doc)
|
||||
file.println('"""')
|
||||
file.println("")
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unknown type of member {item_name} in {class_obj.__name__}"
|
||||
|
||||
+40
-11
@@ -113,14 +113,14 @@ def make_c_typeval(ret: str, pattern: bool, typelist: List[str]):
|
||||
elif ret == 'Tuple[str,str]': # special case
|
||||
ret = 'rdcstrpair'
|
||||
elif ret[0:9] == 'Callable[':
|
||||
ret = '(std::function<void\(\)>|[A-Za-z_]+Callback)' if pattern else 'std::function/NamedCallback'
|
||||
ret = r'(std::function<void\(\)>|[A-Za-z_]+Callback)' if pattern else 'std::function/NamedCallback'
|
||||
elif ret[0:5] == 'List[':
|
||||
inner = make_c_typeval(ret[5:-1], pattern, typelist)
|
||||
ret = '(const )?rdcarray<{}> ?[&*]?'.format(inner) if pattern else 'rdcarray<{}>'.format(inner)
|
||||
elif ret[0:6] == 'Tuple[':
|
||||
inners = [make_c_typeval(i.strip(), pattern, typelist) for i in ret[6:-1].split(',')]
|
||||
if pattern:
|
||||
inner = ',\s*'.join(inners)
|
||||
inner = r',\s*'.join(inners)
|
||||
else:
|
||||
inner = ', '.join(inners)
|
||||
|
||||
@@ -166,7 +166,7 @@ def check_function(parent_name, objname, obj, source, global_func, typelist):
|
||||
default_val = ''
|
||||
for p in params:
|
||||
if len(funcargs[0]) > 0:
|
||||
funcargs[0] += ',\s*'
|
||||
funcargs[0] += r',\s*'
|
||||
funcargs[1] += ', '
|
||||
|
||||
default_val = p[2].lstrip()
|
||||
@@ -177,7 +177,7 @@ def check_function(parent_name, objname, obj, source, global_func, typelist):
|
||||
funcargs[1] += make_c_typeval(p[0], False, typelist) + ' ' + p[1]
|
||||
|
||||
if default_val != "":
|
||||
funcargs[0] += f"\s*=\s*{default_val}"
|
||||
funcargs[0] += f"\\s*=\\s*{default_val}"
|
||||
funcargs[1] += f" = {default_val}"
|
||||
|
||||
result = RTYPE_PATTERN.search(docstring)
|
||||
@@ -190,7 +190,7 @@ def check_function(parent_name, objname, obj, source, global_func, typelist):
|
||||
if global_func:
|
||||
global_pattern = '(RENDERDOC_CC\s*RENDERDOC_)?'
|
||||
|
||||
pattern = '(?s){} ?{}{}\(\s*{}\)'.format(make_c_typeval(ret, True, typelist), global_pattern, objname, funcargs[0])
|
||||
pattern = r'(?s){} ?{}{}\(\s*{}\)'.format(make_c_typeval(ret, True, typelist), global_pattern, objname, funcargs[0])
|
||||
clean = '{} {}({})'.format(make_c_typeval(ret, False, typelist), objname, funcargs[1])
|
||||
|
||||
match = re.search(pattern, source, re.MULTILINE | re.DOTALL)
|
||||
@@ -198,7 +198,7 @@ def check_function(parent_name, objname, obj, source, global_func, typelist):
|
||||
pattern2 = None
|
||||
# global functions returning strings can't return an rdcstr, they have to return const char *
|
||||
if match is None and ret == 'str':
|
||||
pattern2 = '(?s)const char \*{}{}\(\s*{}\)'.format(global_pattern, objname, funcargs[0])
|
||||
pattern2 = r'(?s)const char \*{}{}\(\s*{}\)'.format(global_pattern, objname, funcargs[0])
|
||||
match = re.search(pattern2, source, re.MULTILINE | re.DOTALL)
|
||||
|
||||
if match is None:
|
||||
@@ -291,8 +291,8 @@ for mod_name in check_mods:
|
||||
print("Checking class {}".format(qualname))
|
||||
|
||||
# Grab the source to just this class to search in
|
||||
source = re.search('(struct|class|union) I?' + objname + '(\n|\s*:[^A-Za-z][\s:a-zA-Z]*\n)\{.*?^}', headers, re.MULTILINE | re.DOTALL)
|
||||
|
||||
source = re.search('(struct|class|union) I?' + objname + r'(\n|\s*:[^A-Za-z][\s:a-zA-Z]*\n)\{.*?^}', headers, re.MULTILINE | re.DOTALL)
|
||||
|
||||
namespace = None
|
||||
|
||||
if source is None and objname[0:2] in ['VK', 'GL']:
|
||||
@@ -310,13 +310,19 @@ for mod_name in check_mods:
|
||||
namespace = namespace.group(0)
|
||||
|
||||
if source is None and namespace is not None:
|
||||
source = re.search('(struct|class|union) I?' + objname + '[^{]*\{.*?^}', namespace, re.MULTILINE | re.DOTALL)
|
||||
|
||||
source = re.search('(struct|class|union) I?' + objname + r'[^{]*\{.*?^}', namespace, re.MULTILINE | re.DOTALL)
|
||||
|
||||
source = source.group(0)
|
||||
|
||||
instance = None
|
||||
copyable = False
|
||||
try:
|
||||
instance = obj()
|
||||
try:
|
||||
dupe_instance = obj(instance)
|
||||
copyable = True
|
||||
except NotImplementedError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
@@ -328,6 +334,29 @@ for mod_name in check_mods:
|
||||
|
||||
instance_warned = False
|
||||
|
||||
# for types that we can create, we expect by default to
|
||||
# see a default constructor and a copy constructor,
|
||||
# unless we see a note that the type is not copyable
|
||||
if instance is not None:
|
||||
lines = docstring.strip().splitlines()
|
||||
if lines[0].strip() != f"{obj.__name__}()":
|
||||
count += 1
|
||||
print(
|
||||
f"Error {count:3}: {obj.__name__} can be created, "
|
||||
"expect default constructor as first real line of its docstring."
|
||||
)
|
||||
elif (
|
||||
copyable
|
||||
and lines[1].strip() != f"{obj.__name__}(other: {obj.__name__})"
|
||||
):
|
||||
count += 1
|
||||
print(
|
||||
f"Error {count:3}: {obj.__name__} can be copied, "
|
||||
"expect copy constructor as second entry in its docstring:\n"
|
||||
f"Actual > {lines[1]}\n"
|
||||
f"Expected > {obj.__name__}(other: {obj.__name__})"
|
||||
)
|
||||
|
||||
for member_name in obj.__dict__.keys():
|
||||
if '__' in member_name or member_name in ['this', 'thisown']:
|
||||
continue
|
||||
@@ -407,7 +436,7 @@ for mod_name in check_mods:
|
||||
count += 1
|
||||
print("Error {:3}: {}.{} is missing :type: declaration, should be {}".format(count, qualname, member_name, type_name))
|
||||
else:
|
||||
type_decl = re.sub('Tuple\[.*\]', 'tuple', type_decl)
|
||||
type_decl = re.sub(r'Tuple\[.*\]', 'tuple', type_decl)
|
||||
if type_decl != type_name:
|
||||
count += 1
|
||||
print("Error {:3}: {}.{} has wrong :type: declaration {}, should be {}".format(count, qualname, member_name, type_decl, type_name))
|
||||
|
||||
Reference in New Issue
Block a user