--- "/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
-
 
