1#     Copyright 2021, Kay Hayen, mailto:kay.hayen@gmail.com
2#
3#     Part of "Nuitka", an optimizing Python compiler that is compatible and
4#     integrates with CPython, but also works on its own.
5#
6#     Licensed under the Apache License, Version 2.0 (the "License");
7#     you may not use this file except in compliance with the License.
8#     You may obtain a copy of the License at
9#
10#        http://www.apache.org/licenses/LICENSE-2.0
11#
12#     Unless required by applicable law or agreed to in writing, software
13#     distributed under the License is distributed on an "AS IS" BASIS,
14#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15#     See the License for the specific language governing permissions and
16#     limitations under the License.
17#
18""" Module/Package nodes
19
20The top of the tree. Packages are also modules. Modules are what hold a program
21together and cross-module optimizations are the most difficult to tackle.
22"""
23
24import os
25
26from nuitka import Options, Variables
27from nuitka.containers.oset import OrderedSet
28from nuitka.importing.Importing import (
29    findModule,
30    getModuleNameAndKindFromFilename,
31)
32from nuitka.importing.Recursion import decideRecursion, recurseTo
33from nuitka.ModuleRegistry import getModuleByName, getOwnerFromCodeName
34from nuitka.optimizations.TraceCollections import TraceCollectionModule
35from nuitka.PythonVersions import python_version
36from nuitka.SourceCodeReferences import fromFilename
37from nuitka.tree.SourceReading import readSourceCodeFromFilename
38from nuitka.utils.CStrings import encodePythonIdentifierToC
39from nuitka.utils.FileOperations import getFileContentByLine, relpath
40from nuitka.utils.ModuleNames import ModuleName
41
42from .Checkers import checkStatementsSequenceOrNone
43from .FutureSpecs import fromFlags
44from .IndicatorMixins import EntryPointMixin, MarkNeedsAnnotationsMixin
45from .LocalsScopes import getLocalsDictHandle
46from .NodeBases import (
47    ChildrenHavingMixin,
48    ClosureGiverNodeMixin,
49    NodeBase,
50    extractKindAndArgsFromXML,
51    fromXML,
52)
53
54
55class PythonModuleBase(NodeBase):
56    # Base classes can be abstract, pylint: disable=abstract-method
57
58    __slots__ = ("module_name",)
59
60    def __init__(self, module_name, source_ref):
61        assert type(module_name) is ModuleName, module_name
62
63        NodeBase.__init__(self, source_ref=source_ref)
64
65        self.module_name = module_name
66
67    def getDetails(self):
68        return {"module_name": self.module_name}
69
70    def getFullName(self):
71        return self.module_name
72
73    @staticmethod
74    def isMainModule():
75        return False
76
77    @staticmethod
78    def isTopModule():
79        return False
80
81    def attemptRecursion(self):
82        # Make sure the package is recursed to if any
83        package_name = self.module_name.getPackageName()
84        if package_name is None:
85            return ()
86
87        # Return the list of newly added modules.
88
89        package = getModuleByName(package_name)
90
91        if package_name is not None and package is None:
92            package_package, package_filename, finding = findModule(
93                importing=self,
94                module_name=package_name,
95                parent_package=None,
96                level=1,
97                warn=python_version < 0x300,
98            )
99
100            # TODO: Temporary, if we can't find the package for Python3.3 that
101            # is semi-OK, maybe.
102            if python_version >= 0x300 and not package_filename:
103                return ()
104
105            if package_name == "uniconvertor.app.modules":
106                return ()
107
108            assert package_filename is not None, (package_name, finding)
109
110            _package_name, package_kind = getModuleNameAndKindFromFilename(
111                package_filename
112            )
113            # assert _package_name == self.package_name, (package_filename, _package_name, self.package_name)
114
115            decision, _reason = decideRecursion(
116                module_filename=package_filename,
117                module_name=package_name,
118                module_kind=package_kind,
119            )
120
121            if decision is not None:
122                package = recurseTo(
123                    signal_change=self.trace_collection.signalChange
124                    if hasattr(self, "trace_collection")
125                    else None,
126                    module_package=package_package,
127                    module_filename=package_filename,
128                    module_relpath=relpath(package_filename),
129                    module_kind="py",
130                    reason="Containing package of '%s'." % self.getFullName(),
131                )
132
133        if package:
134            from nuitka.ModuleRegistry import addUsedModule
135
136            addUsedModule(package)
137
138    def getCodeName(self):
139        # Abstract method, pylint: disable=no-self-use
140        return None
141
142    def getCompileTimeFilename(self):
143        """The compile time filename for the module.
144
145        Returns:
146            Full path to module file at compile time.
147        Notes:
148            We are getting the absolute path here, since we do
149            not want to have to deal with resolving paths at
150            all.
151
152        """
153        return os.path.abspath(self.source_ref.getFilename())
154
155    def getCompileTimeDirectory(self):
156        """The compile time directory for the module.
157
158        Returns:
159            Full path to module directory at compile time.
160        Notes:
161            For packages, we let the package directory be
162            the result, otherwise the containing directory
163            is the result.
164        Notes:
165            Use this to find files nearby a module, mainly
166            in plugin code.
167        """
168        result = self.getCompileTimeFilename()
169        if not os.path.isdir(result):
170            result = os.path.dirname(result)
171        return result
172
173    def getRunTimeFilename(self):
174        reference_mode = Options.getFileReferenceMode()
175
176        if reference_mode == "original":
177            return self.getCompileTimeFilename()
178        elif reference_mode == "frozen":
179            return "<frozen %s>" % self.getFullName()
180        else:
181            filename = self.getCompileTimeFilename()
182
183            full_name = self.getFullName()
184
185            result = os.path.basename(filename)
186            current = filename
187
188            levels = full_name.count(".")
189            if self.isCompiledPythonPackage():
190                levels += 1
191
192            for _i in range(levels):
193                current = os.path.dirname(current)
194
195                result = os.path.join(os.path.basename(current), result)
196
197            return result
198
199
200class CompiledPythonModule(
201    ChildrenHavingMixin,
202    ClosureGiverNodeMixin,
203    MarkNeedsAnnotationsMixin,
204    EntryPointMixin,
205    PythonModuleBase,
206):
207    """Compiled Python Module"""
208
209    # This one has a few indicators, pylint: disable=too-many-instance-attributes
210
211    kind = "COMPILED_PYTHON_MODULE"
212
213    __slots__ = (
214        "is_top",
215        "name",
216        "code_prefix",
217        "code_name",
218        "uids",
219        "temp_variables",
220        "temp_scopes",
221        "preserver_id",
222        "needs_annotations_dict",
223        "trace_collection",
224        "mode",
225        "variables",
226        "active_functions",
227        "visited_functions",
228        "cross_used_functions",
229        "used_modules",
230        "future_spec",
231        "source_code",
232        "module_dict_name",
233        "locals_scope",
234    )
235
236    named_children = ("body", "functions")
237
238    checkers = {"body": checkStatementsSequenceOrNone}
239
240    def __init__(self, module_name, is_top, mode, future_spec, source_ref):
241        PythonModuleBase.__init__(self, module_name=module_name, source_ref=source_ref)
242
243        ClosureGiverNodeMixin.__init__(
244            self, name=module_name.getBasename(), code_prefix="module"
245        )
246
247        ChildrenHavingMixin.__init__(
248            self, values={"body": None, "functions": ()}  # delayed
249        )
250
251        MarkNeedsAnnotationsMixin.__init__(self)
252
253        EntryPointMixin.__init__(self)
254
255        self.is_top = is_top
256
257        self.mode = mode
258
259        self.variables = {}
260
261        # Functions that have been used.
262        self.active_functions = OrderedSet()
263
264        # Functions that should be visited again.
265        self.visited_functions = set()
266
267        self.cross_used_functions = OrderedSet()
268
269        self.used_modules = OrderedSet()
270
271        # Often "None" until tree building finishes its part.
272        self.future_spec = future_spec
273
274        # The source code of the module if changed or not from disk.
275        self.source_code = None
276
277        self.module_dict_name = "globals_%s" % (self.getCodeName(),)
278
279        self.locals_scope = getLocalsDictHandle(
280            self.module_dict_name, "module_dict", self
281        )
282
283    @staticmethod
284    def isCompiledPythonModule():
285        return True
286
287    def getDetails(self):
288        return {
289            "filename": self.source_ref.getFilename(),
290            "module_name": self.module_name,
291        }
292
293    def getDetailsForDisplay(self):
294        result = self.getDetails()
295
296        if self.future_spec is not None:
297            result["code_flags"] = ",".join(self.future_spec.asFlags())
298
299        return result
300
301    def getCompilationMode(self):
302        return self.mode
303
304    @classmethod
305    def fromXML(cls, provider, source_ref, **args):
306        # Modules are not having any provider, must not be used,
307        assert False
308
309    def getFutureSpec(self):
310        return self.future_spec
311
312    def setFutureSpec(self, future_spec):
313        self.future_spec = future_spec
314
315    def isTopModule(self):
316        return self.is_top
317
318    def asGraph(self, graph, desc):
319        graph = graph.add_subgraph(
320            name="cluster_%s" % desc, comment="Graph for %s" % self.getName()
321        )
322
323        #        graph.body.append("style=filled")
324        #        graph.body.append("color=lightgrey")
325        #        graph.body.append("label=Iteration_%d" % desc)
326
327        def makeTraceNodeName(variable, version, variable_trace):
328            return "%s/ %s %s %s" % (
329                desc,
330                variable.getName(),
331                version,
332                variable_trace.__class__.__name__,
333            )
334
335        for function_body in self.active_functions:
336            trace_collection = function_body.trace_collection
337
338            node_names = {}
339
340            for (
341                (variable, version),
342                variable_trace,
343            ) in trace_collection.getVariableTracesAll().items():
344                node_name = makeTraceNodeName(variable, version, variable_trace)
345
346                node_names[variable_trace] = node_name
347
348            for (
349                (variable, version),
350                variable_trace,
351            ) in trace_collection.getVariableTracesAll().items():
352                node_name = node_names[variable_trace]
353
354                previous = variable_trace.getPrevious()
355
356                attrs = {"style": "filled"}
357
358                if variable_trace.getUsageCount():
359                    attrs["color"] = "blue"
360                else:
361                    attrs["color"] = "red"
362
363                graph.add_node(node_name, **attrs)
364
365                if type(previous) is tuple:
366                    for prev_trace in previous:
367                        graph.add_edge(node_names[prev_trace], node_name)
368
369                        assert prev_trace is not variable_trace
370
371                elif previous is not None:
372                    assert previous is not variable_trace
373                    graph.add_edge(node_names[previous], node_name)
374
375        return graph
376
377    def getSourceCode(self):
378        if self.source_code is not None:
379            return self.source_code
380        else:
381            return readSourceCodeFromFilename(
382                module_name=self.getFullName(),
383                source_filename=self.getCompileTimeFilename(),
384            )
385
386    def setSourceCode(self, code):
387        self.source_code = code
388
389    def getParent(self):
390        # We have never have a parent
391        return None
392
393    def getParentVariableProvider(self):
394        # We have never have a provider
395        return None
396
397    def hasVariableName(self, variable_name):
398        return variable_name in self.variables or variable_name in self.temp_variables
399
400    def getProvidedVariables(self):
401        return self.variables.values()
402
403    def getFilename(self):
404        return self.source_ref.getFilename()
405
406    def getVariableForAssignment(self, variable_name):
407        return self.getProvidedVariable(variable_name)
408
409    def getVariableForReference(self, variable_name):
410        return self.getProvidedVariable(variable_name)
411
412    def getVariableForClosure(self, variable_name):
413        return self.getProvidedVariable(variable_name=variable_name)
414
415    def createProvidedVariable(self, variable_name):
416        assert variable_name not in self.variables
417
418        result = Variables.ModuleVariable(module=self, variable_name=variable_name)
419
420        self.variables[variable_name] = result
421
422        return result
423
424    @staticmethod
425    def getContainingClassDictCreation():
426        return None
427
428    @staticmethod
429    def isEarlyClosure():
430        # Modules should immediately closure variables on use.
431        return True
432
433    def getEntryPoint(self):
434        return self
435
436    def getCodeName(self):
437        # For code name of modules, we need to translate to C identifiers,
438        # removing characters illegal for that.
439
440        return encodePythonIdentifierToC(self.getFullName())
441
442    def addFunction(self, function_body):
443        functions = self.subnode_functions
444        assert function_body not in functions
445        functions += (function_body,)
446        self.setChild("functions", functions)
447
448    def startTraversal(self):
449        self.used_modules = OrderedSet()
450        self.active_functions = OrderedSet()
451
452    def restartTraversal(self):
453        self.visited_functions = set()
454
455    def addUsedModule(self, key):
456        self.used_modules.add(key)
457
458    def getUsedModules(self):
459        return self.used_modules
460
461    def addUsedFunction(self, function_body):
462        assert function_body in self.subnode_functions, function_body
463
464        assert (
465            function_body.isExpressionFunctionBody()
466            or function_body.isExpressionClassBody()
467            or function_body.isExpressionGeneratorObjectBody()
468            or function_body.isExpressionCoroutineObjectBody()
469            or function_body.isExpressionAsyncgenObjectBody()
470        )
471
472        self.active_functions.add(function_body)
473
474        result = function_body not in self.visited_functions
475        self.visited_functions.add(function_body)
476
477        return result
478
479    def getUsedFunctions(self):
480        return self.active_functions
481
482    def getUnusedFunctions(self):
483        for function in self.subnode_functions:
484            if function not in self.active_functions:
485                yield function
486
487    def addCrossUsedFunction(self, function_body):
488        if function_body not in self.cross_used_functions:
489            self.cross_used_functions.add(function_body)
490
491    def getCrossUsedFunctions(self):
492        return self.cross_used_functions
493
494    def getFunctionFromCodeName(self, code_name):
495        for function in self.subnode_functions:
496            if function.getCodeName() == code_name:
497                return function
498
499    def getOutputFilename(self):
500        main_filename = self.getFilename()
501
502        if main_filename.endswith(".py"):
503            result = main_filename[:-3]
504        elif main_filename.endswith(".pyw"):
505            result = main_filename[:-4]
506        else:
507            result = main_filename
508
509        # There are some characters that somehow are passed to shell, by
510        # Scons or unknown, so lets avoid them for now.
511        return result.replace(")", "").replace("(", "")
512
513    def computeModule(self):
514        self.restartTraversal()
515
516        old_collection = self.trace_collection
517
518        self.trace_collection = TraceCollectionModule(self)
519
520        module_body = self.subnode_body
521
522        if module_body is not None:
523            result = module_body.computeStatementsSequence(
524                trace_collection=self.trace_collection
525            )
526
527            if result is not module_body:
528                self.setChild("body", result)
529
530        self.attemptRecursion()
531
532        # Finalize locals scopes previously determined for removal in last pass.
533        self.trace_collection.updateVariablesFromCollection(
534            old_collection, self.source_ref
535        )
536
537        # Indicate if this is pass 1 for the module as return value.
538        was_complete = not self.locals_scope.complete
539
540        def markAsComplete(body, trace_collection):
541            if (
542                body.locals_scope is not None
543                and body.locals_scope.isMarkedForPropagation()
544            ):
545                body.locals_scope = None
546
547            if body.locals_scope is not None:
548                body.locals_scope.markAsComplete(trace_collection)
549
550        def markEntryPointAsComplete(body):
551            markAsComplete(body, body.trace_collection)
552
553            outline_bodies = body.trace_collection.getOutlineFunctions()
554
555            if outline_bodies is not None:
556                for outline_body in outline_bodies:
557                    markAsComplete(outline_body, body.trace_collection)
558
559            body.optimizeUnusedTempVariables()
560
561        markEntryPointAsComplete(self)
562
563        for function_body in self.getUsedFunctions():
564            markEntryPointAsComplete(function_body)
565
566            function_body.optimizeUnusedClosureVariables()
567            function_body.optimizeVariableReleases()
568
569        return was_complete
570
571    def getTraceCollections(self):
572        yield self.trace_collection
573
574        for function in self.getUsedFunctions():
575            yield function.trace_collection
576
577    def isUnoptimized(self):
578        # Modules don't do this, pylint: disable=no-self-use
579        return False
580
581    def getLocalVariables(self):
582        # Modules don't do this, pylint: disable=no-self-use
583        return ()
584
585    def getUserLocalVariables(self):
586        # Modules don't do this, pylint: disable=no-self-use
587        return ()
588
589    @staticmethod
590    def getFunctionVariablesWithAutoReleases():
591        """Return the list of function variables that should be released at exit."""
592        return ()
593
594    def getOutlineLocalVariables(self):
595        outlines = self.getTraceCollection().getOutlineFunctions()
596
597        if outlines is None:
598            return ()
599
600        result = []
601
602        for outline in outlines:
603            result.extend(outline.getUserLocalVariables())
604
605        return result
606
607    def hasClosureVariable(self, variable):
608        # Modules don't do this, pylint: disable=no-self-use,unused-argument
609        return False
610
611    def removeUserVariable(self, variable):
612        outlines = self.getTraceCollection().getOutlineFunctions()
613
614        for outline in outlines:
615            user_locals = outline.getUserLocalVariables()
616
617            if variable in user_locals:
618                outline.removeUserVariable(variable)
619                break
620
621    def getLocalsScope(self):
622        return self.locals_scope
623
624
625class CompiledPythonPackage(CompiledPythonModule):
626    kind = "COMPILED_PYTHON_PACKAGE"
627
628    def __init__(self, module_name, is_top, mode, future_spec, source_ref):
629        CompiledPythonModule.__init__(
630            self,
631            module_name=module_name,
632            is_top=is_top,
633            mode=mode,
634            future_spec=future_spec,
635            source_ref=source_ref,
636        )
637
638    def getOutputFilename(self):
639        result = self.getFilename()
640
641        if os.path.isdir(result):
642            return result
643        else:
644            return os.path.dirname(result)
645
646    @staticmethod
647    def canHaveExternalImports():
648        return True
649
650
651def makeUncompiledPythonModule(
652    module_name, filename, bytecode, is_package, user_provided, technical
653):
654    source_ref = fromFilename(filename)
655
656    if is_package:
657        return UncompiledPythonPackage(
658            module_name=module_name,
659            bytecode=bytecode,
660            filename=filename,
661            user_provided=user_provided,
662            technical=technical,
663            source_ref=source_ref,
664        )
665    else:
666        return UncompiledPythonModule(
667            module_name=module_name,
668            bytecode=bytecode,
669            filename=filename,
670            user_provided=user_provided,
671            technical=technical,
672            source_ref=source_ref,
673        )
674
675
676class UncompiledPythonModule(PythonModuleBase):
677    """Compiled Python Module"""
678
679    kind = "UNCOMPILED_PYTHON_MODULE"
680
681    __slots__ = "bytecode", "filename", "user_provided", "technical", "used_modules"
682
683    def __init__(
684        self, module_name, bytecode, filename, user_provided, technical, source_ref
685    ):
686        PythonModuleBase.__init__(self, module_name=module_name, source_ref=source_ref)
687
688        self.bytecode = bytecode
689        self.filename = filename
690
691        self.user_provided = user_provided
692        self.technical = technical
693
694        self.used_modules = ()
695
696    def finalize(self):
697        del self.used_modules
698        del self.bytecode
699
700    @staticmethod
701    def isUncompiledPythonModule():
702        return True
703
704    def isUserProvided(self):
705        return self.user_provided
706
707    def isTechnical(self):
708        """Must be bytecode as it's used in CPython library initialization."""
709        return self.technical
710
711    def getByteCode(self):
712        return self.bytecode
713
714    def getFilename(self):
715        return self.filename
716
717    def getUsedModules(self):
718        return self.used_modules
719
720    def setUsedModules(self, used_modules):
721        self.used_modules = used_modules
722
723    def startTraversal(self):
724        pass
725
726
727class UncompiledPythonPackage(UncompiledPythonModule):
728    kind = "UNCOMPILED_PYTHON_PACKAGE"
729
730
731class PythonMainModule(CompiledPythonModule):
732    kind = "PYTHON_MAIN_MODULE"
733
734    __slots__ = ("main_added", "early_modules")
735
736    def __init__(self, main_added, mode, future_spec, source_ref):
737        # Is this one from a "__main__.py" file.
738        self.main_added = main_added
739
740        CompiledPythonModule.__init__(
741            self,
742            module_name=ModuleName("__main__"),
743            is_top=True,
744            mode=mode,
745            future_spec=future_spec,
746            source_ref=source_ref,
747        )
748
749        self.early_modules = ()
750
751    def getDetails(self):
752        return {
753            "filename": self.source_ref.getFilename(),
754            "main_added": self.main_added,
755            "mode": self.mode,
756        }
757
758    @classmethod
759    def fromXML(cls, provider, source_ref, **args):
760        if "code_flags" in args:
761            future_spec = fromFlags(args["code_flags"])
762
763        result = cls(
764            main_added=args["main_added"] == "True",
765            mode=args["mode"],
766            future_spec=future_spec,
767            source_ref=source_ref,
768        )
769
770        from nuitka.ModuleRegistry import addRootModule
771
772        addRootModule(result)
773
774        function_work = []
775
776        for xml in args["functions"]:
777            _kind, node_class, func_args, source_ref = extractKindAndArgsFromXML(
778                xml, source_ref
779            )
780
781            if "provider" in func_args:
782                func_args["provider"] = getOwnerFromCodeName(func_args["provider"])
783            else:
784                func_args["provider"] = result
785
786            if "flags" in args:
787                func_args["flags"] = set(func_args["flags"].split(","))
788
789            if "doc" not in args:
790                func_args["doc"] = None
791
792            function = node_class.fromXML(source_ref=source_ref, **func_args)
793
794            # Could do more checks for look up of body here, but so what...
795            function_work.append((function, iter(iter(xml).next()).next()))
796
797        for function, xml in function_work:
798            function.setChild(
799                "body",
800                fromXML(
801                    provider=function, xml=xml, source_ref=function.getSourceReference()
802                ),
803            )
804
805        result.setChild(
806            "body", fromXML(provider=result, xml=args["body"][0], source_ref=source_ref)
807        )
808
809        return result
810
811    @staticmethod
812    def isMainModule():
813        return True
814
815    def getOutputFilename(self):
816        if self.main_added:
817            return os.path.dirname(self.getFilename())
818        else:
819            return CompiledPythonModule.getOutputFilename(self)
820
821    def setEarlyModules(self, early_modules):
822        self.early_modules = early_modules
823
824    def getEarlyModules(self):
825        return self.early_modules
826
827    def computeModule(self):
828        CompiledPythonModule.computeModule(self)
829
830        from nuitka.ModuleRegistry import addUsedModule
831
832        for early_module in self.early_modules:
833            addUsedModule(early_module)
834
835
836class PythonShlibModule(PythonModuleBase):
837    kind = "PYTHON_SHLIB_MODULE"
838
839    __slots__ = ("used_modules",)
840
841    avoid_duplicates = set()
842
843    def __init__(self, module_name, source_ref):
844        PythonModuleBase.__init__(self, module_name=module_name, source_ref=source_ref)
845
846        # That would be a mistake we just made.
847        assert os.path.basename(source_ref.getFilename()) != "<frozen>"
848
849        # That is too likely a bug.
850        assert module_name != "__main__"
851
852        # Duplicates should be avoided by us caching elsewhere before creating
853        # the object.
854        assert self.getFullName() not in self.avoid_duplicates, self.getFullName()
855        self.avoid_duplicates.add(self.getFullName())
856
857        self.used_modules = None
858
859    def finalize(self):
860        del self.used_modules
861
862    def getFilename(self):
863        return self.source_ref.getFilename()
864
865    def startTraversal(self):
866        pass
867
868    def getPyIFilename(self):
869        """Get Python type description filename."""
870
871        path = self.getFilename()
872        filename = os.path.basename(path)
873        dirname = os.path.dirname(path)
874
875        return os.path.join(dirname, filename.split(".")[0]) + ".pyi"
876
877    def _readPyPIFile(self):
878        """Read the .pyi file if present and scan for dependencies."""
879
880        # Complex stuff, pylint: disable=too-many-branches,too-many-statements
881
882        if self.used_modules is None:
883            pyi_filename = self.getPyIFilename()
884
885            if os.path.exists(pyi_filename):
886                pyi_deps = OrderedSet()
887
888                # Flag signalling multiline import handling
889                in_import = False
890                in_import_part = ""
891
892                for line in getFileContentByLine(pyi_filename):
893                    line = line.strip()
894
895                    if not in_import:
896                        if line.startswith("import "):
897                            imported = line[7:]
898
899                            pyi_deps.add(imported)
900                        elif line.startswith("from "):
901                            parts = line.split(None, 3)
902                            assert parts[0] == "from"
903                            assert parts[2] == "import"
904
905                            origin_name = parts[1]
906
907                            if origin_name == "typing":
908                                continue
909
910                            if origin_name == ".":
911                                origin_name = self.getFullName()
912
913                            # TODO: Might want to add full relative import handling.
914
915                            if origin_name != self.getFullName():
916                                pyi_deps.add(origin_name)
917
918                            imported = parts[3]
919                            if imported.startswith("("):
920                                # Handle multiline imports
921                                if not imported.endswith(")"):
922                                    in_import = True
923                                    imported = imported[1:]
924                                    in_import_part = origin_name
925                                    assert in_import_part, (
926                                        "Multiline part in file %s cannot be empty"
927                                        % pyi_filename
928                                    )
929                                else:
930                                    in_import = False
931                                    imported = imported[1:-1]
932                                    assert imported
933
934                            if imported == "*":
935                                continue
936
937                            for name in imported.split(","):
938                                if name:
939                                    name = name.strip()
940                                    pyi_deps.add(origin_name + "." + name)
941
942                    else:  # In import
943                        imported = line
944                        if imported.endswith(")"):
945                            imported = imported[0:-1]
946                            in_import = False
947
948                        for name in imported.split(","):
949                            name = name.strip()
950                            if name:
951                                pyi_deps.add(in_import_part + "." + name)
952
953                if "typing" in pyi_deps:
954                    pyi_deps.discard("typing")
955                if "__future__" in pyi_deps:
956                    pyi_deps.discard("__future__")
957
958                if self.getFullName() in pyi_deps:
959                    pyi_deps.discard(self.getFullName())
960                if self.getFullName().getPackageName() in pyi_deps:
961                    pyi_deps.discard(self.getFullName().getPackageName())
962
963                self.used_modules = tuple((pyi_dep, None) for pyi_dep in pyi_deps)
964            else:
965                self.used_modules = ()
966
967    def getUsedModules(self):
968        self._readPyPIFile()
969
970        assert "." not in self.used_modules, self
971
972        return self.used_modules
973
974    def getParentModule(self):
975        return self
976