mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 22:14:27 +08:00
Start putting the signature matching onto the ExecutedParam class
This commit is contained in:
@@ -110,8 +110,10 @@ class FunctionContext(use_metaclass(CachedMetaClass, AbstractFunction)):
|
||||
)
|
||||
return function
|
||||
|
||||
def py__call__(self, arguments):
|
||||
def py__call__(self, arguments, need_param_match=False):
|
||||
function_execution = self.get_function_execution(arguments)
|
||||
if need_param_match and function_execution.matches_signature():
|
||||
return NO_CONTEXTS
|
||||
return function_execution.infer()
|
||||
|
||||
def get_function_execution(self, arguments=None):
|
||||
@@ -274,6 +276,19 @@ class FunctionExecutionContext(TreeContext):
|
||||
def get_executed_params(self):
|
||||
return self.var_args.get_executed_params(self)
|
||||
|
||||
def matches_signature(self):
|
||||
matches = all(executed_param.matches_signature()
|
||||
for executed_param in self.get_executed_params())
|
||||
if debug.enable_notice:
|
||||
signature = parser_utils.get_call_signature(self.tree_node)
|
||||
if matches:
|
||||
debug.dbg("Overloading match: %s@%s",
|
||||
signature, self.tree_node.start_pos[0], color='BLUE')
|
||||
else:
|
||||
debug.dbg("Overloading no match: %s@%s (%s)",
|
||||
signature, self.tree_node.start_pos[0], self.var_args, color='BLUE')
|
||||
return matches
|
||||
|
||||
def infer(self):
|
||||
"""
|
||||
Created to be used by inheritance.
|
||||
@@ -301,17 +316,17 @@ class FunctionExecutionContext(TreeContext):
|
||||
class OverloadedFunctionContext(ContextWrapper):
|
||||
def __init__(self, function, overloaded_functions):
|
||||
super(OverloadedFunctionContext, self).__init__(function)
|
||||
self._overloaded_functions = overloaded_functions
|
||||
self.overloaded_functions = overloaded_functions
|
||||
|
||||
def py__call__(self, arguments):
|
||||
debug.dbg("Execute overloaded function %s", self._wrapped_context, color='BLUE')
|
||||
return ContextSet.from_sets(
|
||||
matching_function.py__call__(arguments=arguments)
|
||||
for matching_function in self.get_matching_functions(arguments)
|
||||
f.py__call__(arguments=arguments, need_param_match=True)
|
||||
for f in self.overloaded_functions
|
||||
)
|
||||
|
||||
def get_matching_functions(self, arguments):
|
||||
for f in self._overloaded_functions:
|
||||
for f in self.overloaded_functions:
|
||||
signature = parser_utils.get_call_signature(f.tree_node)
|
||||
if signature_matches(f, arguments):
|
||||
debug.dbg("Overloading match: %s@%s",
|
||||
@@ -324,15 +339,18 @@ class OverloadedFunctionContext(ContextWrapper):
|
||||
|
||||
def signature_matches(function_context, arguments):
|
||||
unpacked_arguments = arguments.unpack()
|
||||
key_args = {}
|
||||
for param_node in function_context.tree_node.get_params():
|
||||
while True:
|
||||
key, argument = next(unpacked_arguments, (None, None))
|
||||
if key is None or argument is None:
|
||||
break
|
||||
key_args[key] = argument
|
||||
if argument is None:
|
||||
argument = key_args.pop(param_node.name.value, None)
|
||||
if argument is None:
|
||||
# This signature has an parameter more than arguments were given.
|
||||
return bool(param_node.star_count)
|
||||
if key is not None:
|
||||
# TODO this is obviously wrong, we cannot just ignore keyword
|
||||
# arguments, but it's easier for now.
|
||||
return False
|
||||
return bool(param_node.star_count == 1)
|
||||
|
||||
if param_node.annotation is not None:
|
||||
if param_node.star_count == 2:
|
||||
|
||||
@@ -68,6 +68,9 @@ class AbstractInstanceContext(Context):
|
||||
def is_class(self):
|
||||
return False
|
||||
|
||||
def get_annotated_class_object(self):
|
||||
return self.class_context # This is the default.
|
||||
|
||||
@property
|
||||
def py__call__(self):
|
||||
names = self.get_function_slot_names(u'__call__')
|
||||
@@ -118,8 +121,9 @@ class AbstractInstanceContext(Context):
|
||||
|
||||
def get_filters(self, search_global=None, until_position=None,
|
||||
origin_scope=None, include_self_names=True):
|
||||
class_context = self.get_annotated_class_object()
|
||||
if include_self_names:
|
||||
for cls in py__mro__(self.class_context):
|
||||
for cls in py__mro__(class_context):
|
||||
if not isinstance(cls, compiled.CompiledObject) \
|
||||
or cls.tree_node is not None:
|
||||
# In this case we're excluding compiled objects that are
|
||||
@@ -127,7 +131,7 @@ class AbstractInstanceContext(Context):
|
||||
# compiled objects to search for self variables.
|
||||
yield SelfAttributeFilter(self.evaluator, self, cls, origin_scope)
|
||||
|
||||
for cls in py__mro__(self.class_context):
|
||||
for cls in py__mro__(class_context):
|
||||
if isinstance(cls, compiled.CompiledObject):
|
||||
yield CompiledInstanceClassFilter(self.evaluator, self, cls)
|
||||
else:
|
||||
@@ -170,18 +174,16 @@ class AbstractInstanceContext(Context):
|
||||
def name(self):
|
||||
pass
|
||||
|
||||
def _create_init_execution(self, class_context, bound_method):
|
||||
return bound_method.get_function_execution(self.var_args)
|
||||
|
||||
def create_init_executions(self):
|
||||
for name in self.get_function_slot_names(u'__init__'):
|
||||
# TODO is this correct? I think we need to check for functions.
|
||||
if isinstance(name, LazyInstanceClassName):
|
||||
function = FunctionContext.from_context(
|
||||
self.parent_context,
|
||||
name.tree_name.parent
|
||||
)
|
||||
bound_method = BoundMethod(self, name.class_context, function)
|
||||
yield self._create_init_execution(name.class_context, bound_method)
|
||||
yield bound_method.get_function_execution(self.var_args)
|
||||
|
||||
@evaluator_method_cache()
|
||||
def create_instance_context(self, class_context, node):
|
||||
@@ -199,7 +201,7 @@ class AbstractInstanceContext(Context):
|
||||
)
|
||||
bound_method = BoundMethod(self, class_context, func)
|
||||
if scope.name.value == '__init__' and parent_context == class_context:
|
||||
return self._create_init_execution(class_context, bound_method)
|
||||
return bound_method.get_function_execution(self.var_args)
|
||||
else:
|
||||
return bound_method.get_function_execution()
|
||||
elif scope.type == 'classdef':
|
||||
@@ -259,6 +261,38 @@ class TreeInstance(AbstractInstanceContext):
|
||||
def name(self):
|
||||
return filters.ContextName(self, self.class_context.name.tree_name)
|
||||
|
||||
@evaluator_method_cache()
|
||||
def get_annotated_class_object(self):
|
||||
from jedi.evaluate.pep0484 import define_type_vars_for_execution
|
||||
|
||||
for func in self._get_annotation_init_functions():
|
||||
# Just take the first result, it should always be one, because we
|
||||
# control the typeshed code.
|
||||
bound = BoundMethod(self, self.class_context, func)
|
||||
execution = bound.get_function_execution(self.var_args)
|
||||
if not execution.matches_signature():
|
||||
# First check if the signature even matches, if not we don't
|
||||
# need to infer anything.
|
||||
print('no m', bound)
|
||||
continue
|
||||
print(bound)
|
||||
context_set = define_type_vars_for_execution(
|
||||
ContextSet(self.class_context),
|
||||
execution,
|
||||
self.class_context.list_type_vars()
|
||||
)
|
||||
if context_set:
|
||||
return next(iter(context_set))
|
||||
return self.class_context
|
||||
|
||||
def _get_annotation_init_functions(self):
|
||||
for init in self.class_context.py__getattribute__('__init__'):
|
||||
if isinstance(init, OverloadedFunctionContext):
|
||||
for func in init.overloaded_functions:
|
||||
yield func
|
||||
elif isinstance(init, FunctionContext):
|
||||
yield init
|
||||
|
||||
|
||||
class AnonymousInstance(TreeInstance):
|
||||
def __init__(self, evaluator, parent_context, class_context):
|
||||
|
||||
@@ -188,37 +188,8 @@ class Sequence(BuiltinOverwrite, IterableMixin):
|
||||
@memoize_method
|
||||
def get_object(self):
|
||||
klass = compiled.builtin_from_name(self.evaluator, self.array_type)
|
||||
annotated_instance, = self._annotate_class(klass).execute_evaluated()
|
||||
return annotated_instance
|
||||
|
||||
def _annotate_class(self, klass):
|
||||
from jedi.evaluate.pep0484 import define_type_vars_for_execution
|
||||
|
||||
instance, = klass.execute(self)
|
||||
|
||||
for execution_context in self._get_init_executions(instance):
|
||||
# Just take the first result, it should always be one, because we
|
||||
# control the typeshed code.
|
||||
return define_type_vars_for_execution(
|
||||
ContextSet(klass),
|
||||
execution_context,
|
||||
klass.list_type_vars()
|
||||
)
|
||||
assert "Should never land here, probably an issue with typeshed changes"
|
||||
|
||||
def _get_init_executions(self, instance):
|
||||
from jedi.evaluate import arguments
|
||||
from jedi.evaluate.context.instance import InstanceArguments
|
||||
for init in instance.py__getattribute__('__init__'):
|
||||
try:
|
||||
method = init.get_matching_functions
|
||||
except AttributeError:
|
||||
continue
|
||||
else:
|
||||
base_args = arguments.ValuesArguments([ContextSet(self)])
|
||||
arguments = InstanceArguments(instance, base_args)
|
||||
for func in method(arguments):
|
||||
yield func.get_function_execution(base_args)
|
||||
instance, = klass.execute_evaluated(self)
|
||||
return instance
|
||||
|
||||
def py__bool__(self):
|
||||
return None # We don't know the length, because of appends.
|
||||
|
||||
@@ -24,16 +24,32 @@ class ExecutedParam(object):
|
||||
self._lazy_context = lazy_context
|
||||
self.string_name = param_node.name.value
|
||||
|
||||
def infer_annotations(self):
|
||||
from jedi.evaluate import pep0484
|
||||
return pep0484.infer_param(self._execution_context, self._param_node)
|
||||
|
||||
def infer(self, use_hints=True):
|
||||
if use_hints:
|
||||
from jedi.evaluate import pep0484
|
||||
pep0484_hints = pep0484.infer_param(self._execution_context, self._param_node)
|
||||
doc_params = docstrings.infer_param(self._execution_context, self._param_node)
|
||||
if pep0484_hints or doc_params:
|
||||
return pep0484_hints | doc_params
|
||||
ann = self.infer_annotations().execute_annotation()
|
||||
if ann or doc_params:
|
||||
return ann | doc_params
|
||||
|
||||
return self._lazy_context.infer()
|
||||
|
||||
def matches_signature(self):
|
||||
argument_contexts = self.infer(use_hints=False).py__class__()
|
||||
if self._param_node.star_count:
|
||||
return True
|
||||
annotations = self.infer_annotations()
|
||||
if not annotations:
|
||||
# If we cannot infer annotations - or there aren't any - pretend
|
||||
# that the signature matches.
|
||||
return True
|
||||
return any(c1.is_sub_class_of(c2)
|
||||
for c1 in argument_contexts
|
||||
for c2 in annotations)
|
||||
|
||||
@property
|
||||
def var_args(self):
|
||||
return self._execution_context.var_args
|
||||
|
||||
@@ -58,7 +58,7 @@ def _evaluate_annotation_string(context, string, index=None):
|
||||
lambda context: context.array_type == u'tuple' # noqa
|
||||
and len(list(context.py__iter__())) >= index
|
||||
).py__simple_getitem__(index)
|
||||
return context_set.execute_annotation()
|
||||
return context_set
|
||||
|
||||
|
||||
def _fix_forward_reference(context, node):
|
||||
@@ -174,7 +174,7 @@ def infer_param(execution_context, param):
|
||||
)
|
||||
# Annotations are like default params and resolve in the same way.
|
||||
context = execution_context.function_context.get_default_param_context()
|
||||
return _evaluate_for_annotation(context, annotation).execute_annotation()
|
||||
return _evaluate_for_annotation(context, annotation)
|
||||
|
||||
|
||||
def py__annotations__(funcdef):
|
||||
@@ -212,7 +212,7 @@ def infer_return_types(function_execution_context):
|
||||
return _evaluate_annotation_string(
|
||||
function_execution_context.function_context.get_default_param_context(),
|
||||
match.group(1).strip()
|
||||
)
|
||||
).execute_annotation()
|
||||
if annotation is None:
|
||||
return NO_CONTEXTS
|
||||
|
||||
@@ -255,6 +255,8 @@ def _infer_type_vars_for_execution(execution_context, annotation_dict):
|
||||
annotation_node = annotation_dict[executed_param.string_name]
|
||||
except KeyError:
|
||||
continue
|
||||
if executed_param._param_node.star_count: # TODO remove this.
|
||||
continue
|
||||
|
||||
annotation_variables = find_unknown_type_vars(context, annotation_node)
|
||||
if annotation_variables:
|
||||
@@ -478,7 +480,9 @@ def _find_type_from_comment_hint(context, node, varlist, name):
|
||||
match = re.match(r"^#\s*type:\s*([^#]*)", comment)
|
||||
if match is None:
|
||||
return []
|
||||
return _evaluate_annotation_string(context, match.group(1).strip(), index)
|
||||
return _evaluate_annotation_string(
|
||||
context, match.group(1).strip(), index
|
||||
).execute_annotation()
|
||||
|
||||
|
||||
def find_unknown_type_vars(context, node):
|
||||
|
||||
@@ -209,7 +209,6 @@ g
|
||||
dic2 = {'asdf': 3, 'b': 'str'}
|
||||
#? int()
|
||||
dic2['asdf']
|
||||
# TODO for now get doesn't work properly when used with a literal.
|
||||
#? None int() str()
|
||||
dic2.get('asdf')
|
||||
|
||||
@@ -268,8 +267,12 @@ for x in {1: 3.0, '': 1j}:
|
||||
dict().values().__iter__
|
||||
|
||||
d = dict(a=3, b='')
|
||||
#? int() str()
|
||||
# Indexing is not supported
|
||||
#?
|
||||
d.values()[0]
|
||||
x, = d.values()
|
||||
#? int() str()
|
||||
x
|
||||
#? int()
|
||||
d['a']
|
||||
#? int() None
|
||||
|
||||
Reference in New Issue
Block a user