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""" Nodes for functions and their creations.
19
20Lambdas are functions too. The functions are at the core of the language and
21have their complexities.
22
23Creating a CPython function object is an optional thing. Some things might
24only be used to be called directly, while knowing exactly what it is. So
25the "ExpressionFunctionCreation" might be used to provide that kind of
26CPython reference, and may escape.
27
28Coroutines and generators live in their dedicated module and share base
29classes.
30"""
31
32import inspect
33
34from nuitka import Options, Variables
35from nuitka.Constants import isMutable
36from nuitka.optimizations.TraceCollections import (
37    TraceCollectionPureFunction,
38    withChangeIndicationsTo,
39)
40from nuitka.PythonVersions import python_version
41from nuitka.specs.ParameterSpecs import (
42    ParameterSpec,
43    TooManyArguments,
44    matchCall,
45)
46from nuitka.Tracing import optimization_logger
47from nuitka.tree.Extractions import updateVariableUsage
48from nuitka.tree.TreeHelpers import makeDictCreationOrConstant2
49
50from .Checkers import checkStatementsSequenceOrNone
51from .CodeObjectSpecs import CodeObjectSpec
52from .ExpressionBases import (
53    ExpressionBase,
54    ExpressionChildHavingBase,
55    ExpressionChildrenHavingBase,
56)
57from .FutureSpecs import fromFlags
58from .IndicatorMixins import (
59    EntryPointMixin,
60    MarkUnoptimizedFunctionIndicatorMixin,
61)
62from .LocalsScopes import getLocalsDictHandle
63from .NodeBases import (
64    ClosureGiverNodeMixin,
65    ClosureTakerMixin,
66    SideEffectsFromChildrenMixin,
67)
68from .NodeMakingHelpers import (
69    makeRaiseExceptionReplacementExpressionFromInstance,
70    wrapExpressionWithSideEffects,
71)
72from .shapes.BuiltinTypeShapes import tshape_function
73
74_is_verbose = Options.isVerbose()
75
76
77class MaybeLocalVariableUsage(Exception):
78    pass
79
80
81class ExpressionFunctionBodyBase(
82    ClosureTakerMixin, ClosureGiverNodeMixin, ExpressionChildHavingBase
83):
84    # TODO: The code_prefix should be a class attribute instead.
85    __slots__ = (
86        "provider",
87        "taken",
88        "name",
89        "code_prefix",
90        "code_name",
91        "uids",
92        "temp_variables",
93        "temp_scopes",
94        "preserver_id",
95        "flags",
96    )
97
98    if python_version >= 0x340:
99        __slots__ += ("qualname_provider",)
100
101    if python_version >= 0x300:
102        __slots__ += ("non_local_declarations",)
103
104    named_child = "body"
105
106    checker = checkStatementsSequenceOrNone
107
108    def __init__(self, provider, name, body, code_prefix, flags, source_ref):
109        while provider.isExpressionOutlineBody():
110            provider = provider.getParentVariableProvider()
111
112        ExpressionChildHavingBase.__init__(
113            self,
114            value=body,  # Might be None initially in some cases.
115            source_ref=source_ref,
116        )
117
118        ClosureTakerMixin.__init__(self, provider=provider)
119
120        ClosureGiverNodeMixin.__init__(self, name=name, code_prefix=code_prefix)
121
122        # Special things, "has_super" indicates presence of "super" in variable
123        # usage, which modifies some behaviors.
124        self.flags = flags or None
125
126        # Hack: This allows some APIs to work although this is not yet
127        # officially a child yet. Important during building.
128        self.parent = provider
129
130        # Python3.4: Might be overridden by global statement on the class name.
131        # TODO: Make this class only code.
132        if python_version >= 0x340:
133            self.qualname_provider = provider
134
135        # Non-local declarations.
136        if python_version >= 0x300:
137            self.non_local_declarations = None
138
139    @staticmethod
140    def isExpressionFunctionBodyBase():
141        return True
142
143    def getEntryPoint(self):
144        """Entry point for code.
145
146        Normally ourselves. Only outlines will refer to their parent which
147        technically owns them.
148
149        """
150
151        return self
152
153    def getContainingClassDictCreation(self):
154        current = self
155
156        while not current.isCompiledPythonModule():
157            if current.isExpressionClassBody():
158                return current
159
160            current = current.getParentVariableProvider()
161
162        return None
163
164    def hasFlag(self, flag):
165        return self.flags is not None and flag in self.flags
166
167    def discardFlag(self, flag):
168        if self.flags is not None:
169            self.flags.discard(flag)
170
171    @staticmethod
172    def isEarlyClosure():
173        """Early closure taking means immediate binding of references.
174
175        Normally it's good to lookup name references immediately, but not for
176        functions. In case of a function body it is not allowed to do that,
177        because a later assignment needs to be queried first. Nodes need to
178        indicate via this if they would like to resolve references at the same
179        time as assignments.
180        """
181
182        return False
183
184    def getLocalsScope(self):
185        return self.locals_scope
186
187    # TODO: Dubious function doing to distinct things, should be moved to users.
188    def hasVariableName(self, variable_name):
189        return (
190            self.locals_scope.hasProvidedVariable(variable_name)
191            or variable_name in self.temp_variables
192        )
193
194    def getProvidedVariables(self):
195        if self.locals_scope is not None:
196            return self.locals_scope.getProvidedVariables()
197        else:
198            return ()
199
200    def getLocalVariables(self):
201        return [
202            variable
203            for variable in self.getProvidedVariables()
204            if variable.isLocalVariable()
205        ]
206
207    def getUserLocalVariables(self):
208        return [
209            variable
210            for variable in self.getProvidedVariables()
211            if variable.isLocalVariable() and not variable.isParameterVariable()
212            if variable.getOwner() is self
213        ]
214
215    def getOutlineLocalVariables(self):
216        result = []
217
218        outlines = self.getTraceCollection().getOutlineFunctions()
219
220        if outlines is None:
221            return result
222
223        for outline in outlines:
224            result.extend(outline.getUserLocalVariables())
225
226        return result
227
228    def removeClosureVariable(self, variable):
229        # Do not remove parameter variables of ours.
230        assert not variable.isParameterVariable() or variable.getOwner() is not self
231
232        self.locals_scope.unregisterClosureVariable(variable)
233
234        self.taken.remove(variable)
235
236        self.code_object.removeFreeVarname(variable.getName())
237
238    def demoteClosureVariable(self, variable):
239        assert variable.isLocalVariable()
240
241        self.taken.remove(variable)
242
243        assert variable.getOwner() is not self
244
245        new_variable = Variables.LocalVariable(
246            owner=self, variable_name=variable.getName()
247        )
248        for variable_trace in variable.traces:
249            if variable_trace.getOwner() is self:
250                new_variable.addTrace(variable_trace)
251        new_variable.updateUsageState()
252
253        self.locals_scope.unregisterClosureVariable(variable)
254        self.locals_scope.registerProvidedVariable(new_variable)
255
256        updateVariableUsage(
257            provider=self, old_variable=variable, new_variable=new_variable
258        )
259
260    def hasClosureVariable(self, variable):
261        return variable in self.taken
262
263    def getVariableForAssignment(self, variable_name):
264        # print("ASS func", self, variable_name)
265
266        if self.hasTakenVariable(variable_name):
267            result = self.getTakenVariable(variable_name)
268        else:
269            result = self.getProvidedVariable(variable_name)
270
271        return result
272
273    def getVariableForReference(self, variable_name):
274        # print( "REF func", self, variable_name )
275
276        if self.hasProvidedVariable(variable_name):
277            result = self.getProvidedVariable(variable_name)
278        else:
279            result = self.getClosureVariable(variable_name=variable_name)
280
281            # Remember that we need that closure variable for something, so
282            # we don't create it again all the time.
283            if not result.isModuleVariable():
284                self.locals_scope.registerClosureVariable(result)
285
286            entry_point = self.getEntryPoint()
287
288            # For "exec" containing/star import containing, we raise this exception to indicate
289            # that instead of merely a variable, to be assigned, we need to replace with locals
290            # dict access.
291            if (
292                python_version < 0x300
293                and not entry_point.isExpressionClassBody()
294                and not entry_point.isPythonMainModule()
295                and result.isModuleVariable()
296                and entry_point.isUnoptimized()
297            ):
298                raise MaybeLocalVariableUsage
299
300        return result
301
302    def getVariableForClosure(self, variable_name):
303        # print( "getVariableForClosure", self.getCodeName(), variable_name, self.isUnoptimized() )
304
305        if self.hasProvidedVariable(variable_name):
306            return self.getProvidedVariable(variable_name)
307
308        return self.takeVariableForClosure(variable_name)
309
310    def takeVariableForClosure(self, variable_name):
311        result = self.provider.getVariableForClosure(variable_name)
312        self.taken.add(result)
313        return result
314
315    def createProvidedVariable(self, variable_name):
316        # print("createProvidedVariable", self, variable_name)
317
318        assert self.locals_scope, self
319
320        return self.locals_scope.getLocalVariable(
321            variable_name=variable_name, owner=self
322        )
323
324    def addNonlocalsDeclaration(self, names, user_provided, source_ref):
325        """Add a nonlocal declared name.
326
327        This happens during tree building, and is a Python3 only
328        feature. We remember the names for later use through the
329        function @consumeNonlocalDeclarations
330        """
331        if self.non_local_declarations is None:
332            self.non_local_declarations = []
333
334        self.non_local_declarations.append((names, user_provided, source_ref))
335
336    def consumeNonlocalDeclarations(self):
337        """Return the nonlocal declared names for this function.
338
339        There may not be any, which is why we assigned it to
340        None originally and now check and return empty tuple
341        in that case.
342        """
343
344        result = self.non_local_declarations or ()
345        self.non_local_declarations = None
346        return result
347
348    def getFunctionName(self):
349        return self.name
350
351    def getFunctionQualname(self):
352        """Function __qualname__ new in CPython3.3
353
354        Should contain some kind of full name descriptions for the closure to
355        recognize and will be used for outputs.
356        """
357
358        function_name = self.getFunctionName()
359
360        if python_version < 0x340:
361            provider = self.getParentVariableProvider()
362        else:
363            provider = self.qualname_provider
364
365        if provider.isCompiledPythonModule():
366            return function_name
367        elif provider.isExpressionClassBody():
368            return provider.getFunctionQualname() + "." + function_name
369        else:
370            return provider.getFunctionQualname() + ".<locals>." + function_name
371
372    def computeExpression(self, trace_collection):
373        assert False
374
375        # Function body is quite irreplaceable.
376        return self, None, None
377
378    def mayRaiseException(self, exception_type):
379        body = self.subnode_body
380
381        if body is None:
382            return False
383        else:
384            return self.subnode_body.mayRaiseException(exception_type)
385
386    def getFunctionInlineCost(self, values):
387        """Cost of inlining this function with given arguments
388
389        Returns: None or integer values, None means don't do it.
390        """
391
392        # For overload, pylint: disable=no-self-use,unused-argument
393        return None
394
395    def optimizeUnusedClosureVariables(self):
396        """Gets called once module is complete, to consider giving up on closure variables."""
397
398        changed = False
399
400        for closure_variable in self.getClosureVariables():
401
402            # Need to take closure of those either way
403            if (
404                closure_variable.isParameterVariable()
405                and self.isExpressionGeneratorObjectBody()
406            ):
407                continue
408
409            empty = self.trace_collection.hasEmptyTraces(closure_variable)
410
411            if empty:
412                changed = True
413
414                self.trace_collection.signalChange(
415                    "var_usage",
416                    self.source_ref,
417                    message="Remove unused closure variable '%s'."
418                    % closure_variable.getName(),
419                )
420
421                self.removeClosureVariable(closure_variable)
422
423        return changed
424
425    def optimizeVariableReleases(self):
426        for parameter_variable in self.getParameterVariablesWithManualRelease():
427            read_only = self.trace_collection.hasReadOnlyTraces(parameter_variable)
428
429            if read_only:
430                self.trace_collection.signalChange(
431                    "var_usage",
432                    self.source_ref,
433                    message="Schedule removal releases of unassigned parameter variable '%s'."
434                    % parameter_variable.getName(),
435                )
436
437                self.removeVariableReleases(parameter_variable)
438
439
440class ExpressionFunctionEntryPointBase(EntryPointMixin, ExpressionFunctionBodyBase):
441    __slots__ = ("trace_collection", "code_object", "locals_scope", "auto_release")
442
443    def __init__(
444        self, provider, name, code_object, code_prefix, flags, auto_release, source_ref
445    ):
446        ExpressionFunctionBodyBase.__init__(
447            self,
448            provider=provider,
449            name=name,
450            code_prefix=code_prefix,
451            flags=flags,
452            body=None,
453            source_ref=source_ref,
454        )
455
456        EntryPointMixin.__init__(self)
457
458        self.code_object = code_object
459
460        provider.getParentModule().addFunction(self)
461
462        if flags is not None and "has_exec" in flags:
463            locals_kind = "python2_function_exec"
464        else:
465            locals_kind = "python_function"
466
467        self.locals_scope = getLocalsDictHandle(
468            "locals_%s" % self.getCodeName(), locals_kind, self
469        )
470
471        # Automatic parameter variable releases.
472        self.auto_release = auto_release or None
473
474    def getDetails(self):
475        result = ExpressionFunctionBodyBase.getDetails(self)
476
477        result["auto_release"] = tuple(sorted(self.auto_release or ()))
478
479        return result
480
481    def getCodeObject(self):
482        return self.code_object
483
484    def computeFunctionRaw(self, trace_collection):
485        from nuitka.optimizations.TraceCollections import (
486            TraceCollectionFunction,
487        )
488
489        trace_collection = TraceCollectionFunction(
490            parent=trace_collection, function_body=self
491        )
492        old_collection = self.setTraceCollection(trace_collection)
493
494        self.computeFunction(trace_collection)
495
496        trace_collection.updateVariablesFromCollection(old_collection, self.source_ref)
497
498    def computeFunction(self, trace_collection):
499        statements_sequence = self.subnode_body
500
501        # TODO: Lift this restriction to only functions here and it code generation.
502        if statements_sequence is not None and self.isExpressionFunctionBody():
503            if statements_sequence.subnode_statements[0].isStatementReturnNone():
504                self.clearChild("body")
505                statements_sequence.finalize()
506                statements_sequence = None
507
508        if statements_sequence is not None:
509            result = statements_sequence.computeStatementsSequence(
510                trace_collection=trace_collection
511            )
512
513            if result is not statements_sequence:
514                self.setChild("body", result)
515
516    def removeVariableReleases(self, variable):
517        assert variable in self.locals_scope.providing.values(), (self, variable)
518
519        if self.auto_release is None:
520            self.auto_release = set()
521
522        self.auto_release.add(variable)
523
524    def getParameterVariablesWithManualRelease(self):
525        """Return the list of parameter variables that have release statements.
526
527        These are for consideration if these can be dropped, and if so, they
528        are releases automatically by function code.
529        """
530        return tuple(
531            variable
532            for variable in self.locals_scope.getProvidedVariables()
533            if not self.auto_release or variable not in self.auto_release
534            if variable.isParameterVariable()
535            if variable.getOwner() is self
536        )
537
538    def isAutoReleaseVariable(self, variable):
539        """Is this variable to be automatically released."""
540        return self.auto_release is not None and variable in self.auto_release
541
542    def getFunctionVariablesWithAutoReleases(self):
543        """Return the list of function variables that should be released at exit."""
544        if self.auto_release is None:
545            return ()
546
547        return tuple(
548            variable
549            for variable in self.locals_scope.getProvidedVariables()
550            if variable in self.auto_release
551        )
552
553    @staticmethod
554    def getConstantReturnValue():
555        """Special function that checks if code generation allows to use common C code.
556
557        Notes:
558            This is only done for standard functions.
559
560        """
561        return False, False
562
563
564class ExpressionFunctionBody(
565    MarkUnoptimizedFunctionIndicatorMixin, ExpressionFunctionEntryPointBase
566):
567    kind = "EXPRESSION_FUNCTION_BODY"
568
569    # TODO: These should be more special than the general type in order to not cover exec ones.
570    __slots__ = (
571        "unoptimized_locals",
572        "unqualified_exec",
573        "doc",
574        "return_exception",
575        "needs_creation",
576        "needs_direct",
577        "cross_module_use",
578        "parameters",
579    )
580
581    if python_version >= 0x340:
582        __slots__ += ("qualname_setup",)
583
584    checkers = {
585        # TODO: Is "None" really an allowed value.
586        "body": checkStatementsSequenceOrNone
587    }
588
589    def __init__(
590        self,
591        provider,
592        name,
593        code_object,
594        doc,
595        parameters,
596        flags,
597        auto_release,
598        source_ref,
599    ):
600        ExpressionFunctionEntryPointBase.__init__(
601            self,
602            provider=provider,
603            name=name,
604            code_object=code_object,
605            code_prefix="function",
606            flags=flags,
607            auto_release=auto_release,
608            source_ref=source_ref,
609        )
610
611        MarkUnoptimizedFunctionIndicatorMixin.__init__(self, flags)
612
613        self.doc = doc
614
615        # Indicator if the return value exception might be required.
616        self.return_exception = False
617
618        # Indicator if the function needs to be created as a function object.
619        self.needs_creation = False
620
621        # Indicator if the function is called directly.
622        self.needs_direct = False
623
624        # Indicator if the function is used outside of where it's defined.
625        self.cross_module_use = False
626
627        if python_version >= 0x340:
628            self.qualname_setup = None
629
630        self.parameters = parameters
631        self.parameters.setOwner(self)
632
633        for variable in self.parameters.getAllVariables():
634            self.locals_scope.registerProvidedVariable(variable)
635
636    def getDetails(self):
637        return {
638            "name": self.getFunctionName(),
639            "ref_name": self.getCodeName(),
640            "parameters": self.getParameters(),
641            "code_object": self.code_object,
642            "provider": self.provider.getCodeName(),
643            "doc": self.doc,
644            "flags": self.flags,
645        }
646
647    def getDetailsForDisplay(self):
648        result = {
649            "name": self.getFunctionName(),
650            "provider": self.provider.getCodeName(),
651            "flags": self.flags,
652        }
653
654        result.update(self.parameters.getDetails())
655
656        if self.code_object:
657            result.update(self.code_object.getDetails())
658
659        if self.doc is not None:
660            result["doc"] = self.doc
661
662        return result
663
664    @classmethod
665    def fromXML(cls, provider, source_ref, **args):
666        assert provider is not None
667
668        parameter_spec_args = {}
669        code_object_args = {}
670        other_args = {}
671
672        for key, value in args.items():
673            if key.startswith("ps_"):
674                parameter_spec_args[key] = value
675            elif key.startswith("co_"):
676                code_object_args[key] = value
677            elif key == "code_flags":
678                code_object_args["future_spec"] = fromFlags(args["code_flags"])
679            else:
680                other_args[key] = value
681
682        parameters = ParameterSpec(**parameter_spec_args)
683        code_object = CodeObjectSpec(**code_object_args)
684
685        # The empty doc string and no doc string are distinguished by presence. The
686        # most common case is going to be not present.
687        if "doc" not in other_args:
688            other_args["doc"] = None
689
690        return cls(
691            provider=provider,
692            parameters=parameters,
693            code_object=code_object,
694            source_ref=source_ref,
695            **other_args
696        )
697
698    @staticmethod
699    def isExpressionFunctionBody():
700        return True
701
702    def getParent(self):
703        assert False
704
705    def getDoc(self):
706        return self.doc
707
708    def getParameters(self):
709        return self.parameters
710
711    def needsCreation(self):
712        return self.needs_creation
713
714    def markAsNeedsCreation(self):
715        self.needs_creation = True
716
717    def needsDirectCall(self):
718        return self.needs_direct
719
720    def markAsDirectlyCalled(self):
721        self.needs_direct = True
722
723    def isCrossModuleUsed(self):
724        return self.cross_module_use
725
726    def markAsCrossModuleUsed(self):
727        self.cross_module_use = True
728
729    def computeExpressionCall(self, call_node, call_args, call_kw, trace_collection):
730        # TODO: Until we have something to re-order the arguments, we need to
731        # skip this. For the immediate need, we avoid this complexity, as a
732        # re-ordering will be needed.
733
734        assert False, self
735
736    @staticmethod
737    def isCompileTimeConstant():
738        # TODO: It's actually pretty much compile time accessible maybe, but that
739        # would require extra effort.
740        return False
741
742    @staticmethod
743    def mayHaveSideEffects():
744        # The function definition has no side effects, calculating the defaults
745        # would be, but that is done outside of this.
746        return False
747
748    def mayRaiseException(self, exception_type):
749        body = self.subnode_body
750
751        return body is not None and body.mayRaiseException(exception_type)
752
753    def markAsExceptionReturnValue(self):
754        self.return_exception = True
755
756    def needsExceptionReturnValue(self):
757        return self.return_exception
758
759    def getConstantReturnValue(self):
760        """Special function that checks if code generation allows to use common C code."""
761        body = self.subnode_body
762
763        if body is None:
764            return True, None
765
766        first_statement = body.subnode_statements[0]
767
768        if first_statement.isStatementReturnConstant():
769            constant_value = first_statement.getConstant()
770
771            # TODO: For mutable constants, we could also have something, but it would require an indicator
772            # flag to make a deep copy.
773            if not isMutable(constant_value):
774                return True, constant_value
775            else:
776                return False, False
777        else:
778            return False, False
779
780
781class ExpressionFunctionPureBody(ExpressionFunctionBody):
782    kind = "EXPRESSION_FUNCTION_PURE_BODY"
783
784    __slots__ = (
785        # These need only one optimization ever.
786        "optimization_done",
787    )
788
789    def __init__(
790        self,
791        provider,
792        name,
793        code_object,
794        doc,
795        parameters,
796        flags,
797        auto_release,
798        source_ref,
799    ):
800        ExpressionFunctionBody.__init__(
801            self,
802            provider=provider,
803            name=name,
804            code_object=code_object,
805            doc=doc,
806            parameters=parameters,
807            flags=flags,
808            auto_release=auto_release,
809            source_ref=source_ref,
810        )
811
812        self.optimization_done = False
813
814    def computeFunctionRaw(self, trace_collection):
815        if self.optimization_done:
816            for function_body in self.trace_collection.getUsedFunctions():
817                trace_collection.onUsedFunction(function_body)
818
819        def mySignal(tag, source_ref, change_desc):
820            if _is_verbose:
821                optimization_logger.info(
822                    "{source_ref} : {tags} : {message}".format(
823                        source_ref=source_ref.getAsString(),
824                        tags=tags,
825                        message=change_desc()
826                        if inspect.isfunction(change_desc)
827                        else change_desc,
828                    )
829                )
830
831            tags.add(tag)
832
833        tags = set()
834
835        while 1:
836            trace_collection = TraceCollectionPureFunction(function_body=self)
837            old_collection = self.setTraceCollection(trace_collection)
838
839            with withChangeIndicationsTo(mySignal):
840                self.computeFunction(trace_collection)
841
842            trace_collection.updateVariablesFromCollection(
843                old_collection, self.source_ref
844            )
845
846            if tags:
847                tags.clear()
848            else:
849                break
850
851        self.optimization_done = True
852
853
854class ExpressionFunctionPureInlineConstBody(ExpressionFunctionBody):
855    kind = "EXPRESSION_FUNCTION_PURE_INLINE_CONST_BODY"
856
857    def getFunctionInlineCost(self, values):
858        return 0
859
860
861def _convertNoneConstantOrEmptyDictToNone(node):
862    if node is None:
863        return None
864    elif node.isExpressionConstantNoneRef():
865        return None
866    elif node.isExpressionConstantDictEmptyRef():
867        return None
868    else:
869        return node
870
871
872# TODO: Function direct call node ought to be here too.
873
874
875class ExpressionFunctionCreation(
876    SideEffectsFromChildrenMixin, ExpressionChildrenHavingBase
877):
878    kind = "EXPRESSION_FUNCTION_CREATION"
879
880    __slots__ = ("variable_closure_traces",)
881
882    # Note: The order of evaluation for these is a bit unexpected, but
883    # true. Keyword defaults go first, then normal defaults, and annotations of
884    # all kinds go last.
885
886    # A bug of CPython3.x not fixed before version 3.4, see bugs.python.org/issue16967
887    kw_defaults_before_defaults = python_version < 0x340
888
889    if kw_defaults_before_defaults:
890        named_children = ("kw_defaults", "defaults", "annotations", "function_ref")
891    else:
892        named_children = ("defaults", "kw_defaults", "annotations", "function_ref")
893
894    checkers = {"kw_defaults": _convertNoneConstantOrEmptyDictToNone}
895
896    def __init__(self, function_ref, defaults, kw_defaults, annotations, source_ref):
897        assert kw_defaults is None or kw_defaults.isExpression()
898        assert annotations is None or annotations.isExpression()
899        assert function_ref.isExpressionFunctionRef()
900
901        ExpressionChildrenHavingBase.__init__(
902            self,
903            values={
904                "function_ref": function_ref,
905                "defaults": tuple(defaults),
906                "kw_defaults": kw_defaults,
907                "annotations": annotations,
908            },
909            source_ref=source_ref,
910        )
911
912        self.variable_closure_traces = None
913
914    def getName(self):
915        return self.subnode_function_ref.getName()
916
917    @staticmethod
918    def getTypeShape():
919        return tshape_function
920
921    def computeExpression(self, trace_collection):
922        self.variable_closure_traces = []
923
924        for (
925            closure_variable
926        ) in self.subnode_function_ref.getFunctionBody().getClosureVariables():
927            trace = trace_collection.getVariableCurrentTrace(closure_variable)
928            trace.addNameUsage()
929
930            self.variable_closure_traces.append((closure_variable, trace))
931
932        kw_defaults = self.subnode_kw_defaults
933        if kw_defaults is not None:
934            kw_defaults.onContentEscapes(trace_collection)
935
936        for default in self.subnode_defaults:
937            default.onContentEscapes(trace_collection)
938
939        # TODO: Function body may know something too.
940        return self, None, None
941
942    def mayRaiseException(self, exception_type):
943        for default in self.subnode_defaults:
944            if default.mayRaiseException(exception_type):
945                return True
946
947        kw_defaults = self.subnode_kw_defaults
948
949        if kw_defaults is not None and kw_defaults.mayRaiseException(exception_type):
950            return True
951
952        annotations = self.subnode_annotations
953
954        if annotations is not None and annotations.mayRaiseException(exception_type):
955            return True
956
957        return False
958
959    def computeExpressionCall(self, call_node, call_args, call_kw, trace_collection):
960
961        trace_collection.onExceptionRaiseExit(BaseException)
962
963        # TODO: Until we have something to re-order the keyword arguments, we
964        # need to skip this. For the immediate need, we avoid this complexity,
965        # as a re-ordering will be needed.
966        if call_kw is not None and not call_kw.isExpressionConstantDictEmptyRef():
967            return call_node, None, None
968
969        if call_args is None:
970            args_tuple = ()
971        else:
972            assert (
973                call_args.isExpressionConstantTupleRef()
974                or call_args.isExpressionMakeTuple()
975            )
976
977            args_tuple = call_args.getIterationValues()
978
979        function_body = self.subnode_function_ref.getFunctionBody()
980
981        # TODO: Actually the above disables it entirely, as it is at least
982        # the empty dictionary node in any case. We will need some enhanced
983        # interfaces for "matchCall" to work on.
984
985        call_spec = function_body.getParameters()
986
987        try:
988            args_dict = matchCall(
989                func_name=self.getName(),
990                args=call_spec.getArgumentNames(),
991                kw_only_args=call_spec.getKwOnlyParameterNames(),
992                star_list_arg=call_spec.getStarListArgumentName(),
993                star_dict_arg=call_spec.getStarDictArgumentName(),
994                num_defaults=call_spec.getDefaultCount(),
995                num_posonly=call_spec.getPosOnlyParameterCount(),
996                positional=args_tuple,
997                pairs=(),
998            )
999
1000            values = [args_dict[name] for name in call_spec.getParameterNames()]
1001
1002            # TODO: Not handling default values either yet.
1003            if None in values:
1004                return call_node, None, None
1005
1006            # TODO: This is probably something that the matchCall ought to do
1007            # for us, but that will need cleanups. Also these functions and
1008            # nodes ought to work with # ordered dictionaries maybe.
1009            if call_spec.getStarDictArgumentName():
1010                values[-1] = makeDictCreationOrConstant2(
1011                    keys=[value[0] for value in values[-1]],
1012                    values=[value[1] for value in values[-1]],
1013                    source_ref=call_node.source_ref,
1014                )
1015
1016            result = ExpressionFunctionCall(
1017                function=self, values=values, source_ref=call_node.source_ref
1018            )
1019
1020            return (
1021                result,
1022                "new_statements",  # TODO: More appropriate tag maybe.
1023                """\
1024Replaced call to created function body '%s' with direct \
1025function call."""
1026                % self.getName(),
1027            )
1028
1029        except TooManyArguments as e:
1030            result = wrapExpressionWithSideEffects(
1031                new_node=makeRaiseExceptionReplacementExpressionFromInstance(
1032                    expression=call_node, exception=e.getRealException()
1033                ),
1034                old_node=call_node,
1035                side_effects=call_node.extractSideEffectsPreCall(),
1036            )
1037
1038            return (
1039                result,
1040                "new_raise",  # TODO: More appropriate tag maybe.
1041                """Replaced call to created function body '%s' to argument \
1042error"""
1043                % self.getName(),
1044            )
1045
1046    def getClosureVariableVersions(self):
1047        return self.variable_closure_traces
1048
1049
1050class ExpressionFunctionRef(ExpressionBase):
1051    kind = "EXPRESSION_FUNCTION_REF"
1052
1053    __slots__ = "function_body", "code_name"
1054
1055    def __init__(self, source_ref, function_body=None, code_name=None):
1056        assert function_body is not None or code_name is not None
1057        assert code_name != "None"
1058
1059        ExpressionBase.__init__(self, source_ref=source_ref)
1060
1061        self.function_body = function_body
1062        self.code_name = code_name
1063
1064    def finalize(self):
1065        del self.parent
1066        del self.function_body
1067
1068    def getName(self):
1069        return self.function_body.getName()
1070
1071    def getDetails(self):
1072        return {"function_body": self.function_body}
1073
1074    def getDetailsForDisplay(self):
1075        return {"code_name": self.getFunctionBody().getCodeName()}
1076
1077    def getFunctionBody(self):
1078        if self.function_body is None:
1079            module_code_name, _ = self.code_name.split("$$$", 1)
1080
1081            from nuitka.ModuleRegistry import getModuleFromCodeName
1082
1083            module = getModuleFromCodeName(module_code_name)
1084
1085            self.function_body = module.getFunctionFromCodeName(self.code_name)
1086
1087        return self.function_body
1088
1089    def computeExpressionRaw(self, trace_collection):
1090        trace_collection.onUsedFunction(self.getFunctionBody())
1091
1092        # TODO: Function after collection may now know something.
1093        return self, None, None
1094
1095    @staticmethod
1096    def mayHaveSideEffects():
1097        # Using a function has no side effects, the use might, but this is not it.
1098        return False
1099
1100    @staticmethod
1101    def mayRaiseException(exception_type):
1102        return False
1103
1104
1105class ExpressionFunctionCall(ExpressionChildrenHavingBase):
1106    """Shared function call.
1107
1108    This is for calling created function bodies with multiple users. Not
1109    clear if such a thing should exist. But what this will do is to have
1110    respect for the fact that there are multiple such calls.
1111    """
1112
1113    kind = "EXPRESSION_FUNCTION_CALL"
1114
1115    __slots__ = ("variable_closure_traces",)
1116
1117    named_children = ("function", "values")
1118
1119    def __init__(self, function, values, source_ref):
1120        assert function.isExpressionFunctionCreation()
1121
1122        ExpressionChildrenHavingBase.__init__(
1123            self,
1124            values={"function": function, "values": tuple(values)},
1125            source_ref=source_ref,
1126        )
1127
1128        self.variable_closure_traces = None
1129
1130    def computeExpression(self, trace_collection):
1131        function = self.subnode_function
1132        function_body = function.subnode_function_ref.getFunctionBody()
1133
1134        if function_body.mayRaiseException(BaseException):
1135            trace_collection.onExceptionRaiseExit(BaseException)
1136
1137        values = self.subnode_values
1138
1139        # Ask for function for its cost.
1140        cost = function_body.getFunctionInlineCost(values)
1141
1142        if cost is not None and cost < 50:
1143            from nuitka.optimizations.FunctionInlining import (
1144                convertFunctionCallToOutline,
1145            )
1146
1147            result = convertFunctionCallToOutline(
1148                provider=self.getParentVariableProvider(),
1149                function_body=function_body,
1150                values=values,
1151                call_source_ref=self.source_ref,
1152            )
1153
1154            return (
1155                result,
1156                "new_statements",
1157                lambda: "Function call to '%s' in-lined." % function_body.getCodeName(),
1158            )
1159
1160        self.variable_closure_traces = []
1161
1162        for closure_variable in function_body.getClosureVariables():
1163            trace = trace_collection.getVariableCurrentTrace(closure_variable)
1164            trace.addNameUsage()
1165
1166            self.variable_closure_traces.append((closure_variable, trace))
1167
1168        return self, None, None
1169
1170    def mayRaiseException(self, exception_type):
1171        function = self.subnode_function
1172
1173        if function.subnode_function_ref.getFunctionBody().mayRaiseException(
1174            exception_type
1175        ):
1176            return True
1177
1178        values = self.subnode_values
1179
1180        for value in values:
1181            if value.mayRaiseException(exception_type):
1182                return True
1183
1184        return False
1185
1186    def getClosureVariableVersions(self):
1187        return self.variable_closure_traces
1188