mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-04 09:00:44 +00:00
452 lines
22 KiB
Diff
452 lines
22 KiB
Diff
--- "/c/Program Files/JetBrains/PyCharm Community Edition 2020.3.2/plugins/python-ce/helpers/generator3/module_redeclarator.py" 2020-12-30 17:08:24.000000000 +0000
|
|
+++ pycharm_helpers/plugins/python-ce/helpers/generator3/module_redeclarator.py 2021-01-27 14:13:18.876000000 +0000
|
|
@@ -2,6 +2,15 @@
|
|
from generator3.util_methods import *
|
|
from generator3.util_methods import get_relative_path_by_qname
|
|
from generator3.docstring_parsing import *
|
|
+import re
|
|
+import enum
|
|
+
|
|
+# Add patterns for identifying declarations we care about
|
|
+RTYPE_PATTERN = re.compile(r"[:@]rtype:\s*(.*)")
|
|
+TYPE_PATTERN = re.compile(r"[:@]type\s*:\s*(.*)")
|
|
+PARAM_PATTERN = re.compile(r"[:@]param\s+([^:]*)\s+([^: ]*):")
|
|
+NAMESPACE_PATTERN = re.compile(r"([^[\]]+\.)*([a-zA-Z][a-zA-Z0-9_]+)")
|
|
+DEFAULTS = re.compile(r"(=.*)")
|
|
|
|
|
|
class emptylistdict(dict):
|
|
@@ -79,6 +88,8 @@
|
|
# we write things into buffers out-of-order
|
|
self.header_buf = Buf(self)
|
|
self.imports_buf = Buf(self)
|
|
+ # each class gets its own list of dependency imports, and so do functions
|
|
+ self.func_imports_buf = Buf(self)
|
|
self.functions_buf = Buf(self)
|
|
self.classes_buf = Buf(self)
|
|
self.classes_buffs = list()
|
|
@@ -94,6 +105,8 @@
|
|
self.doing_builtins = doing_builtins
|
|
self.ret_type_cache = {}
|
|
self.used_imports = emptylistdict() # qual_mod_name -> [imported_names,..]: actually used imported names
|
|
+ # if we use Typing hints, import some things for it
|
|
+ self.used_typing = False
|
|
|
|
def _initializeQApp4(self):
|
|
try: # QtGui should be imported _before_ QtCore package.
|
|
@@ -129,19 +142,23 @@
|
|
if self.split_modules:
|
|
last_pkg_dir = build_pkg_structure(self.cache_dir, self.qname)
|
|
with fopen(os.path.join(last_pkg_dir, "__init__.py"), "w") as init:
|
|
- for buf in (self.header_buf, self.imports_buf, self.functions_buf, self.classes_buf):
|
|
+ for buf in (self.header_buf, self.imports_buf, self.classes_buf):
|
|
buf.flush(init)
|
|
|
|
data = ""
|
|
- for buf in self.classes_buffs:
|
|
+ for (buf,imports) in self.classes_buffs:
|
|
with fopen(os.path.join(last_pkg_dir, buf.name) + '.py', "w") as dummy:
|
|
self.header_buf.flush(dummy)
|
|
self.imports_buf.flush(dummy)
|
|
+ imports.flush(dummy)
|
|
buf.flush(dummy)
|
|
data += self.create_local_import(buf.name)
|
|
|
|
init.write(data)
|
|
- self.footer_buf.flush(init)
|
|
+
|
|
+ # Write out functions last so they can reference the classes imported above
|
|
+ for buf in (self.func_imports_buf, self.functions_buf, self.footer_buf):
|
|
+ buf.flush(init)
|
|
else:
|
|
last_pkg_dir = build_pkg_structure(self.cache_dir, '.'.join(qname_parts[:-1]))
|
|
# In some rare cases submodules of a binary might have been generated earlier than the module
|
|
@@ -155,13 +172,16 @@
|
|
else:
|
|
skeleton_path = os.path.join(last_pkg_dir, qname_parts[-1] + '.py')
|
|
with fopen(skeleton_path, "w") as mod:
|
|
- for buf in (self.header_buf, self.imports_buf, self.functions_buf, self.classes_buf):
|
|
+ for buf in (self.header_buf, self.imports_buf, self.classes_buf):
|
|
buf.flush(mod)
|
|
|
|
- for buf in self.classes_buffs:
|
|
+ for (buf,imports) in self.classes_buffs:
|
|
+ imports.flush(mod)
|
|
buf.flush(mod)
|
|
|
|
- self.footer_buf.flush(mod)
|
|
+ # Write out functions last so they can reference the classes imported above
|
|
+ for buf in (self.func_imports_buf, self.functions_buf, self.footer_buf):
|
|
+ buf.flush(mod)
|
|
|
|
# Some builtin classes effectively change __init__ signature without overriding it.
|
|
# This callable serves as a placeholder to be replaced via REDEFINED_BUILTIN_SIGS
|
|
@@ -177,6 +197,50 @@
|
|
data += "."
|
|
data += name + " import " + name + "\n"
|
|
return data
|
|
+
|
|
+ # Parse a typing declaration and add the actual types references
|
|
+ def add_import_types(self, import_types, type_decl):
|
|
+ if '[' not in type_decl:
|
|
+ import_types.add(type_decl)
|
|
+ return
|
|
+
|
|
+ match = re.match('^[tT]uple\[(.*)\]$', type_decl)
|
|
+ if match:
|
|
+ for i in match.group(1).split(','):
|
|
+ self.add_import_types(import_types, i.strip())
|
|
+ return
|
|
+
|
|
+ match = re.match('^[lL]ist\[(.*)\]$', type_decl)
|
|
+ if match:
|
|
+ self.add_import_types(import_types, match.group(1).strip())
|
|
+ return
|
|
+
|
|
+ match = re.match('^[cC]allable\[\[(.*)\]\s*,\s*(.*)\]$', type_decl)
|
|
+ if match:
|
|
+ for i in match.group(1).split(','):
|
|
+ self.add_import_types(import_types, i.strip())
|
|
+ self.add_import_types(import_types, match.group(2).strip())
|
|
+ return
|
|
+
|
|
+ # For a given type that's referenced, figure out where to import it from and add
|
|
+ # to the list
|
|
+ def process_import_type(self, used_imports, p_modname, classname, import_type):
|
|
+ parent = None
|
|
+
|
|
+ if import_type == '...':
|
|
+ return
|
|
+
|
|
+ if import_type in dir(sys.modules[p_modname]):
|
|
+ if import_type != classname:
|
|
+ parent = '.'
|
|
+ child = import_type
|
|
+ elif '.' in import_type:
|
|
+ imp_split = import_type.split('.')
|
|
+ parent = '.'.join(imp_split)
|
|
+ child = imp_split[-1]
|
|
+
|
|
+ if parent is not None and child not in used_imports[parent]:
|
|
+ used_imports[parent].append(child)
|
|
|
|
def find_imported_name(self, item):
|
|
"""
|
|
@@ -433,7 +497,43 @@
|
|
if first_param:
|
|
seq.insert(0, first_param)
|
|
seq = make_names_unique(seq)
|
|
- return (seq, ret_type, doc_node)
|
|
+
|
|
+ import_types = set()
|
|
+ ret_hint = None
|
|
+
|
|
+ # Try to use :rtype: to add explicit type annotations to return type, since PyCharm
|
|
+ # doesn't parse rtype properly (at least with split modules)
|
|
+ if ret_type is None and ':rtype:' in signature_string:
|
|
+ result = RTYPE_PATTERN.search(signature_string)
|
|
+ if result is not None:
|
|
+ type_decl = result.group(1).strip()
|
|
+ if type_decl != class_name:
|
|
+ self.add_import_types(import_types, type_decl)
|
|
+ ret_hint = NAMESPACE_PATTERN.sub(r'\2', type_decl)
|
|
+ else:
|
|
+ ret_hint = "'" + type_decl + "'"
|
|
+ self.used_typing = True
|
|
+
|
|
+ # Also use :param: to add necessary imports and fix up the parameters.
|
|
+ # PyCharm supports parsing :param: but the skeletons need the right imports
|
|
+ # and it fails for namespaced parameters (since the 'raw' parameter type
|
|
+ # foo.bar refers to a generated submodule called bar with the skeleton bar
|
|
+ # inside.
|
|
+ for p in PARAM_PATTERN.findall(signature_string):
|
|
+ type_decl = p[0]
|
|
+ if type_decl != class_name:
|
|
+ self.add_import_types(import_types, type_decl)
|
|
+ try:
|
|
+ defaults_match = [DEFAULTS.search(s) for s in seq]
|
|
+ idx = [DEFAULTS.sub('', s) for s in seq].index(p[1])
|
|
+ seq[idx] = '{}: {}'.format(p[1], NAMESPACE_PATTERN.sub(r'\2', p[0]))
|
|
+ if defaults_match[idx]:
|
|
+ seq[idx] += defaults_match[idx].group(1)
|
|
+ except ValueError:
|
|
+ note("Warning: Unrecognised parameter {} in parameter list {}".format(p, seq))
|
|
+ pass
|
|
+
|
|
+ return (seq, ret_type, doc_node, list(import_types), ret_hint)
|
|
|
|
def parse_func_doc(self, func_doc, func_id, func_name, class_name, deco=None, sip_generated=False):
|
|
"""
|
|
@@ -453,18 +553,23 @@
|
|
overloads.append(part[i + len(signature):])
|
|
if len(overloads) > 1:
|
|
docstring_results = [self.restore_by_docstring(overload, class_name, deco) for overload in overloads]
|
|
+ import_types = []
|
|
ret_types = []
|
|
for result in docstring_results:
|
|
rt = result[1]
|
|
if rt and rt not in ret_types:
|
|
ret_types.append(rt)
|
|
+ imps = result[3]
|
|
+ for imp in imps:
|
|
+ if imp and imp not in import_types:
|
|
+ import_types.append(imp)
|
|
if ret_types:
|
|
ret_literal = " or ".join(ret_types)
|
|
else:
|
|
ret_literal = None
|
|
param_lists = [result[0] for result in docstring_results]
|
|
spec = build_signature(func_name, restore_parameters_for_overloads(param_lists))
|
|
- return (spec, ret_literal, "restored from __doc__ with multiple overloads")
|
|
+ return (spec, ret_literal, "restored from __doc__ with multiple overloads", import_types)
|
|
|
|
# find the first thing to look like a definition
|
|
prefix_re = re.compile(r"\s*(?:(\w+)[ \t]+)?" + func_id + r"\s*\(") # "foo(..." or "int foo(..."
|
|
@@ -472,18 +577,21 @@
|
|
# parse the part that looks right
|
|
if match:
|
|
ret_hint = match.group(1)
|
|
- params, ret_literal, doc_note = self.restore_by_docstring(func_doc[match.end():], class_name, deco, ret_hint)
|
|
+ params, ret, doc_note, import_types, ret_hint = self.restore_by_docstring(func_doc[match.end():], class_name, deco, ret_hint)
|
|
spec = func_name + flatten(params)
|
|
- return (spec, ret_literal, doc_note)
|
|
+ # if we got a type hint, put it on the function declaration
|
|
+ if ret_hint:
|
|
+ spec = spec + ' -> ' + ret_hint
|
|
+ return (spec, ret, doc_note, import_types)
|
|
else:
|
|
- return (None, None, None)
|
|
+ return (None, None, None, [])
|
|
|
|
|
|
def is_predefined_builtin(self, module_name, class_name, func_name):
|
|
return self.doing_builtins and module_name == BUILTIN_MOD_NAME and (
|
|
class_name, func_name) in PREDEFINED_BUILTIN_SIGS
|
|
|
|
- def redo_function(self, out, p_func, p_name, indent, p_class=None, p_modname=None, classname=None, seen=None):
|
|
+ def redo_function(self, out, p_func, p_name, indent, p_class=None, p_modname=None, classname=None, seen=None, used_imports=None):
|
|
"""
|
|
Restore function argument list as best we can.
|
|
@param out output function of a Buf
|
|
@@ -531,6 +639,8 @@
|
|
deco = "classmethod"
|
|
elif type(p_func).__name__.startswith('staticmethod'):
|
|
deco = "staticmethod"
|
|
+ elif str(descriptor).startswith('<staticmethod'):
|
|
+ deco = "staticmethod"
|
|
if p_name == "__new__":
|
|
deco = "staticmethod"
|
|
deco_comment = " # known case of __new__"
|
|
@@ -580,11 +690,17 @@
|
|
sig_restored = False
|
|
action("parsing doc of func %r of class %r", p_name, p_class)
|
|
if isinstance(funcdoc, STR_TYPES):
|
|
- (spec, ret_literal, more_notes) = self.parse_func_doc(funcdoc, p_name, p_name, classname, deco,
|
|
+ (spec, ret_literal, more_notes, import_types) = self.parse_func_doc(funcdoc, p_name, p_name, classname, deco,
|
|
sip_generated)
|
|
if spec is None and p_name == '__init__' and classname:
|
|
- (spec, ret_literal, more_notes) = self.parse_func_doc(funcdoc, classname, p_name, classname, deco,
|
|
+ (spec, ret_literal, more_notes, import_types) = self.parse_func_doc(funcdoc, classname, p_name, classname, deco,
|
|
sip_generated)
|
|
+
|
|
+ # if we have some imported types, process and add them to the used imports
|
|
+ if used_imports is not None:
|
|
+ for imp in import_types:
|
|
+ self.process_import_type(used_imports, p_modname, classname, imp)
|
|
+
|
|
sig_restored = spec is not None
|
|
if more_notes:
|
|
if sig_note:
|
|
@@ -613,7 +729,7 @@
|
|
out(indent, p_name, " = ", deco, "(", p_name, ")", deco_comment)
|
|
out(0, "") # empty line after each item
|
|
|
|
- def redo_class(self, out, p_class, p_name, indent, p_modname=None, seen=None, inspect_dir=False):
|
|
+ def redo_class(self, out, p_class, p_name, indent, p_modname=None, seen=None, inspect_dir=False, used_imports=None):
|
|
"""
|
|
Restores a class definition.
|
|
@param out output function of a relevant buf
|
|
@@ -644,6 +760,11 @@
|
|
skipped_bases.append(str(base))
|
|
continue
|
|
# somehow import every base class
|
|
+ # Ignore the SwigPyObject internal class, which can't be added to KNOWN_FAKE_BASES
|
|
+ # because it is never directly accessible
|
|
+ if base.__name__ == 'SwigPyObject':
|
|
+ skipped_bases.append(str(base))
|
|
+ continue
|
|
base_name = base.__name__
|
|
qual_module_name = qualifier_of(base, skip_qualifiers)
|
|
got_existing_import = False
|
|
@@ -704,6 +825,11 @@
|
|
continue
|
|
except Exception:
|
|
continue
|
|
+
|
|
+ # Don't generate skeleton for internal enum properties
|
|
+ if isinstance(p_class, enum.EnumMeta) and (is_callable(item) or item_name[0] == '_'):
|
|
+ continue
|
|
+
|
|
if is_callable(item) and not isinstance(item, type):
|
|
methods[item_name] = item
|
|
elif is_property(item):
|
|
@@ -727,7 +853,7 @@
|
|
for item_name in sorted_no_case(methods.keys()):
|
|
item = methods[item_name]
|
|
try:
|
|
- self.redo_function(out, item, item_name, indent + 1, p_class, p_modname, classname=p_name, seen=seen_funcs)
|
|
+ self.redo_function(out, item, item_name, indent + 1, p_class, p_modname, classname=p_name, seen=seen_funcs, used_imports=used_imports)
|
|
except:
|
|
handle_error_func(item_name, out)
|
|
#
|
|
@@ -760,9 +886,41 @@
|
|
out(indent + 1, '""":type: ', prop_type, '"""')
|
|
out(0, "")
|
|
else:
|
|
- out(indent + 1, item_name, " = property(lambda self: object(), lambda self, v: None, lambda self: None) # default")
|
|
+ # for properties with docstrings put them inside the getter so that PyCharm
|
|
+ # displays them
|
|
if prop_docstring:
|
|
- out(indent + 1, '"""', prop_docstring, '"""')
|
|
+ ret = ''
|
|
+ param = ''
|
|
+
|
|
+ # Additionally if we see :type: in the docstring, add type hints
|
|
+ result = TYPE_PATTERN.search(prop_docstring)
|
|
+ if result is not None:
|
|
+ type_decl = result.group(1).strip()
|
|
+ import_types = set()
|
|
+ if type_decl == p_name or type_decl == 'List[{}]'.format(p_name):
|
|
+ type_decl = "'" + type_decl + "'"
|
|
+ else:
|
|
+ self.add_import_types(import_types, type_decl)
|
|
+ for imp in import_types:
|
|
+ self.process_import_type(used_imports, p_modname, p_name, imp)
|
|
+ type_decl = type_decl.replace('...', '__ellipses__').split('.')[-1].replace('__ellipses__', '...')
|
|
+ ret = ' -> {}'.format(type_decl)
|
|
+ param = ': {}'.format(type_decl)
|
|
+
|
|
+ out(indent + 1, "@property")
|
|
+ out(indent + 1, "def {}(self){}:".format(item_name, ret))
|
|
+ out(indent + 2, '"""', prop_docstring, '"""')
|
|
+ out(indent + 2, 'pass')
|
|
+ out(0, "")
|
|
+ out(indent + 1, "@{}.setter".format(item_name))
|
|
+ out(indent + 1, "def {}(self, value{}):".format(item_name, param))
|
|
+ out(indent + 2, 'pass')
|
|
+ out(0, "")
|
|
+
|
|
+ self.used_typing = True
|
|
+ continue
|
|
+
|
|
+ out(indent + 1, item_name, " = property(lambda self: object(), lambda self, v: None, lambda self: None) # default")
|
|
out(0, "")
|
|
if properties:
|
|
out(0, "") # empty line after the block
|
|
@@ -990,15 +1148,20 @@
|
|
out(0, "# functions")
|
|
out(0, "")
|
|
seen_funcs = {}
|
|
+ func_used_imports = emptylistdict()
|
|
for item_name in sorted_no_case(funcs.keys()):
|
|
if item_name in omitted_names:
|
|
out(0, "# definition of ", item_name, " omitted")
|
|
continue
|
|
item = funcs[item_name]
|
|
try:
|
|
- self.redo_function(out, item, item_name, 0, p_modname=p_name, seen=seen_funcs)
|
|
+ self.redo_function(out, item, item_name, 0, p_modname=p_name, seen=seen_funcs, used_imports=func_used_imports)
|
|
except:
|
|
handle_error_func(item_name, out)
|
|
+ # don't import anything from . for functions, functions are emitted in __init__
|
|
+ # and all local classes are imported by the time they're defined
|
|
+ func_used_imports['.'] = []
|
|
+ self.output_import_froms(self.func_imports_buf.out, func_used_imports)
|
|
else:
|
|
self.functions_buf.out(0, "# no functions")
|
|
#
|
|
@@ -1020,13 +1183,22 @@
|
|
self.split_modules = self.mod_filename and len(cls_list) >= 30
|
|
for item_name in [cls_item[0] for cls_item in cls_list]:
|
|
buf = ClassBuf(item_name, self)
|
|
- self.classes_buffs.append(buf)
|
|
+ imports = ClassBuf(item_name + '_imports', self)
|
|
+ self.classes_buffs.append((buf,imports))
|
|
out = buf.out
|
|
if item_name in omitted_names:
|
|
out(0, "# definition of ", item_name, " omitted")
|
|
continue
|
|
item = classes[item_name]
|
|
- self.redo_class(out, item, item_name, 0, p_modname=p_name, seen=seen_classes, inspect_dir=inspect_dir)
|
|
+ used_imports = emptylistdict()
|
|
+ self.redo_class(out, item, item_name, 0, p_modname=p_name, seen=seen_classes, inspect_dir=inspect_dir, used_imports=used_imports)
|
|
+ # if we don't have split modules we can't import dependencies, but we also
|
|
+ # have an ordering constraint - classes need to be declared after any classes
|
|
+ # they reference in type hints. This is broken either way though even with the
|
|
+ # return literals
|
|
+ if not self.split_modules:
|
|
+ func_used_imports['.'] = []
|
|
+ self.output_import_froms(imports.out, used_imports)
|
|
self._defined[item_name] = True
|
|
out(0, "") # empty line after each item
|
|
|
|
@@ -1083,19 +1255,38 @@
|
|
for value in values_to_add:
|
|
self.footer_buf.out(0, value)
|
|
# imports: last, because previous parts could alter used_imports or hidden_imports
|
|
- self.output_import_froms()
|
|
+
|
|
+ out = self.imports_buf.out
|
|
+ self.output_import_froms(out, self.used_imports)
|
|
+
|
|
+ if self.hidden_imports:
|
|
+ self.add_import_header_if_needed()
|
|
+ for mod_name in sorted_no_case(self.hidden_imports.keys()):
|
|
+ out(0, 'import ', mod_name, ' as ', self.hidden_imports[mod_name])
|
|
+ out(0, "") # empty line after group
|
|
+
|
|
+ if self.used_typing:
|
|
+ self.add_import_header_if_needed()
|
|
+ out(0, 'from typing import List, Tuple, Callable, Any')
|
|
+ out(0, "") # empty line after group
|
|
+
|
|
if self.imports_buf.isEmpty():
|
|
- self.imports_buf.out(0, "# no imports")
|
|
- self.imports_buf.out(0, "") # empty line after imports
|
|
+ out(0, "# no imports")
|
|
+ out(0, "") # empty line after imports
|
|
|
|
- def output_import_froms(self):
|
|
+ def output_import_froms(self, out, imports_list):
|
|
"""Mention all imported names known within the module, wrapping as per PEP."""
|
|
- out = self.imports_buf.out
|
|
- if self.used_imports:
|
|
+ if imports_list:
|
|
self.add_import_header_if_needed()
|
|
- for mod_name in sorted_no_case(self.used_imports.keys()):
|
|
- import_names = self.used_imports[mod_name]
|
|
- if import_names:
|
|
+ for mod_name in sorted_no_case(imports_list.keys()):
|
|
+ import_names = imports_list[mod_name]
|
|
+ if mod_name == '.':
|
|
+ # if this is a local import, we need to treat it specially to import
|
|
+ # the class inside the referenced module
|
|
+ for n in import_names:
|
|
+ out(0, "from .%s import %s" % (n, n)) # empty line after group
|
|
+ out(0, "") # empty line after group
|
|
+ elif import_names:
|
|
self._defined[mod_name] = True
|
|
right_pos = 0 # tracks width of list to fold it at right margin
|
|
import_heading = "from % s import (" % mod_name
|
|
@@ -1127,10 +1318,4 @@
|
|
|
|
out(0, "") # empty line after group
|
|
|
|
- if self.hidden_imports:
|
|
- self.add_import_header_if_needed()
|
|
- for mod_name in sorted_no_case(self.hidden_imports.keys()):
|
|
- out(0, 'import ', mod_name, ' as ', self.hidden_imports[mod_name])
|
|
- out(0, "") # empty line after group
|
|
-
|
|
|