You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			221 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			221 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
This module is responsible for inferring *args and **kwargs for signatures.
 | 
						|
 | 
						|
This means for example in this case::
 | 
						|
 | 
						|
    def foo(a, b, c): ...
 | 
						|
 | 
						|
    def bar(*args):
 | 
						|
        return foo(1, *args)
 | 
						|
 | 
						|
The signature here for bar should be `bar(b, c)` instead of bar(*args).
 | 
						|
"""
 | 
						|
from inspect import Parameter
 | 
						|
 | 
						|
from parso import tree
 | 
						|
 | 
						|
from jedi.inference.utils import to_list
 | 
						|
from jedi.inference.names import ParamNameWrapper
 | 
						|
from jedi.inference.helpers import is_big_annoying_library
 | 
						|
 | 
						|
 | 
						|
def _iter_nodes_for_param(param_name):
 | 
						|
    from parso.python.tree import search_ancestor
 | 
						|
    from jedi.inference.arguments import TreeArguments
 | 
						|
 | 
						|
    execution_context = param_name.parent_context
 | 
						|
    # Walk up the parso tree to get the FunctionNode we want. We use the parso
 | 
						|
    # tree rather than going via the execution context so that we're agnostic of
 | 
						|
    # the specific scope we're evaluating within (i.e: module or function,
 | 
						|
    # etc.).
 | 
						|
    function_node = tree.search_ancestor(param_name.tree_name, 'funcdef', 'lambdef')
 | 
						|
    module_node = function_node.get_root_node()
 | 
						|
    start = function_node.children[-1].start_pos
 | 
						|
    end = function_node.children[-1].end_pos
 | 
						|
    for name in module_node.get_used_names().get(param_name.string_name):
 | 
						|
        if start <= name.start_pos < end:
 | 
						|
            # Is used in the function
 | 
						|
            argument = name.parent
 | 
						|
            if argument.type == 'argument' \
 | 
						|
                    and argument.children[0] == '*' * param_name.star_count:
 | 
						|
                trailer = search_ancestor(argument, 'trailer')
 | 
						|
                if trailer is not None:  # Make sure we're in a function
 | 
						|
                    context = execution_context.create_context(trailer)
 | 
						|
                    if _goes_to_param_name(param_name, context, name):
 | 
						|
                        values = _to_callables(context, trailer)
 | 
						|
 | 
						|
                        args = TreeArguments.create_cached(
 | 
						|
                            execution_context.inference_state,
 | 
						|
                            context=context,
 | 
						|
                            argument_node=trailer.children[1],
 | 
						|
                            trailer=trailer,
 | 
						|
                        )
 | 
						|
                        for c in values:
 | 
						|
                            yield c, args
 | 
						|
 | 
						|
 | 
						|
def _goes_to_param_name(param_name, context, potential_name):
 | 
						|
    if potential_name.type != 'name':
 | 
						|
        return False
 | 
						|
    from jedi.inference.names import TreeNameDefinition
 | 
						|
    found = TreeNameDefinition(context, potential_name).goto()
 | 
						|
    return any(param_name.parent_context == p.parent_context
 | 
						|
               and param_name.start_pos == p.start_pos
 | 
						|
               for p in found)
 | 
						|
 | 
						|
 | 
						|
def _to_callables(context, trailer):
 | 
						|
    from jedi.inference.syntax_tree import infer_trailer
 | 
						|
 | 
						|
    atom_expr = trailer.parent
 | 
						|
    index = atom_expr.children[0] == 'await'
 | 
						|
    # Infer atom first
 | 
						|
    values = context.infer_node(atom_expr.children[index])
 | 
						|
    for trailer2 in atom_expr.children[index + 1:]:
 | 
						|
        if trailer == trailer2:
 | 
						|
            break
 | 
						|
        values = infer_trailer(context, values, trailer2)
 | 
						|
    return values
 | 
						|
 | 
						|
 | 
						|
def _remove_given_params(arguments, param_names):
 | 
						|
    count = 0
 | 
						|
    used_keys = set()
 | 
						|
    for key, _ in arguments.unpack():
 | 
						|
        if key is None:
 | 
						|
            count += 1
 | 
						|
        else:
 | 
						|
            used_keys.add(key)
 | 
						|
 | 
						|
    for p in param_names:
 | 
						|
        if count and p.maybe_positional_argument():
 | 
						|
            count -= 1
 | 
						|
            continue
 | 
						|
        if p.string_name in used_keys and p.maybe_keyword_argument():
 | 
						|
            continue
 | 
						|
        yield p
 | 
						|
 | 
						|
 | 
						|
@to_list
 | 
						|
def process_params(param_names, star_count=3):  # default means both * and **
 | 
						|
    if param_names:
 | 
						|
        if is_big_annoying_library(param_names[0].parent_context):
 | 
						|
            # At first this feature can look innocent, but it does a lot of
 | 
						|
            # type inference in some cases, so we just ditch it.
 | 
						|
            yield from param_names
 | 
						|
            return
 | 
						|
 | 
						|
    used_names = set()
 | 
						|
    arg_callables = []
 | 
						|
    kwarg_callables = []
 | 
						|
 | 
						|
    kw_only_names = []
 | 
						|
    kwarg_names = []
 | 
						|
    arg_names = []
 | 
						|
    original_arg_name = None
 | 
						|
    original_kwarg_name = None
 | 
						|
    for p in param_names:
 | 
						|
        kind = p.get_kind()
 | 
						|
        if kind == Parameter.VAR_POSITIONAL:
 | 
						|
            if star_count & 1:
 | 
						|
                arg_callables = _iter_nodes_for_param(p)
 | 
						|
                original_arg_name = p
 | 
						|
        elif p.get_kind() == Parameter.VAR_KEYWORD:
 | 
						|
            if star_count & 2:
 | 
						|
                kwarg_callables = list(_iter_nodes_for_param(p))
 | 
						|
                original_kwarg_name = p
 | 
						|
        elif kind == Parameter.KEYWORD_ONLY:
 | 
						|
            if star_count & 2:
 | 
						|
                kw_only_names.append(p)
 | 
						|
        elif kind == Parameter.POSITIONAL_ONLY:
 | 
						|
            if star_count & 1:
 | 
						|
                yield p
 | 
						|
        else:
 | 
						|
            if star_count == 1:
 | 
						|
                yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
 | 
						|
            elif star_count == 2:
 | 
						|
                kw_only_names.append(ParamNameFixedKind(p, Parameter.KEYWORD_ONLY))
 | 
						|
            else:
 | 
						|
                used_names.add(p.string_name)
 | 
						|
                yield p
 | 
						|
 | 
						|
    # First process *args
 | 
						|
    longest_param_names = ()
 | 
						|
    found_arg_signature = False
 | 
						|
    found_kwarg_signature = False
 | 
						|
    for func_and_argument in arg_callables:
 | 
						|
        func, arguments = func_and_argument
 | 
						|
        new_star_count = star_count
 | 
						|
        if func_and_argument in kwarg_callables:
 | 
						|
            kwarg_callables.remove(func_and_argument)
 | 
						|
        else:
 | 
						|
            new_star_count = 1
 | 
						|
 | 
						|
        for signature in func.get_signatures():
 | 
						|
            found_arg_signature = True
 | 
						|
            if new_star_count == 3:
 | 
						|
                found_kwarg_signature = True
 | 
						|
            args_for_this_func = []
 | 
						|
            for p in process_params(
 | 
						|
                    list(_remove_given_params(
 | 
						|
                        arguments,
 | 
						|
                        signature.get_param_names(resolve_stars=False)
 | 
						|
                    )), new_star_count):
 | 
						|
                if p.get_kind() == Parameter.VAR_KEYWORD:
 | 
						|
                    kwarg_names.append(p)
 | 
						|
                elif p.get_kind() == Parameter.VAR_POSITIONAL:
 | 
						|
                    arg_names.append(p)
 | 
						|
                elif p.get_kind() == Parameter.KEYWORD_ONLY:
 | 
						|
                    kw_only_names.append(p)
 | 
						|
                else:
 | 
						|
                    args_for_this_func.append(p)
 | 
						|
            if len(args_for_this_func) > len(longest_param_names):
 | 
						|
                longest_param_names = args_for_this_func
 | 
						|
 | 
						|
    for p in longest_param_names:
 | 
						|
        if star_count == 1 and p.get_kind() != Parameter.VAR_POSITIONAL:
 | 
						|
            yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
 | 
						|
        else:
 | 
						|
            if p.get_kind() == Parameter.POSITIONAL_OR_KEYWORD:
 | 
						|
                used_names.add(p.string_name)
 | 
						|
            yield p
 | 
						|
 | 
						|
    if not found_arg_signature and original_arg_name is not None:
 | 
						|
        yield original_arg_name
 | 
						|
    elif arg_names:
 | 
						|
        yield arg_names[0]
 | 
						|
 | 
						|
    # Then process **kwargs
 | 
						|
    for func, arguments in kwarg_callables:
 | 
						|
        for signature in func.get_signatures():
 | 
						|
            found_kwarg_signature = True
 | 
						|
            for p in process_params(
 | 
						|
                    list(_remove_given_params(
 | 
						|
                        arguments,
 | 
						|
                        signature.get_param_names(resolve_stars=False)
 | 
						|
                    )), star_count=2):
 | 
						|
                if p.get_kind() == Parameter.VAR_KEYWORD:
 | 
						|
                    kwarg_names.append(p)
 | 
						|
                elif p.get_kind() == Parameter.KEYWORD_ONLY:
 | 
						|
                    kw_only_names.append(p)
 | 
						|
 | 
						|
    for p in kw_only_names:
 | 
						|
        if p.string_name in used_names:
 | 
						|
            continue
 | 
						|
        yield p
 | 
						|
        used_names.add(p.string_name)
 | 
						|
 | 
						|
    if not found_kwarg_signature and original_kwarg_name is not None:
 | 
						|
        yield original_kwarg_name
 | 
						|
    elif kwarg_names:
 | 
						|
        yield kwarg_names[0]
 | 
						|
 | 
						|
 | 
						|
class ParamNameFixedKind(ParamNameWrapper):
 | 
						|
    def __init__(self, param_name, new_kind):
 | 
						|
        super().__init__(param_name)
 | 
						|
        self._new_kind = new_kind
 | 
						|
 | 
						|
    def get_kind(self):
 | 
						|
        return self._new_kind
 |