Start putting the signature matching onto the ExecutedParam class

This commit is contained in:
Dave Halter
2018-09-20 21:14:07 +02:00
parent 9807a7f038
commit 5fda4a2f8b
6 changed files with 106 additions and 60 deletions

View File

@@ -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:

View File

@@ -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):

View File

@@ -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.

View File

@@ -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

View File

@@ -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):

View File

@@ -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