mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-07 06:24:27 +08:00
Create the basics to work with TypedDict in the future
This commit is contained in:
@@ -258,6 +258,7 @@ class Value(HelperValueMixin, BaseValue):
|
|||||||
def _as_context(self):
|
def _as_context(self):
|
||||||
raise NotImplementedError('Not all values need to be converted to contexts: %s', self)
|
raise NotImplementedError('Not all values need to be converted to contexts: %s', self)
|
||||||
|
|
||||||
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ class TypingModuleName(NameWrapper):
|
|||||||
elif name == 'TypedDict':
|
elif name == 'TypedDict':
|
||||||
# TODO doesn't even exist in typeshed/typing.py, yet. But will be
|
# TODO doesn't even exist in typeshed/typing.py, yet. But will be
|
||||||
# added soon.
|
# added soon.
|
||||||
pass
|
yield TypedDictBase.create_cached(
|
||||||
|
inference_state, self.parent_context, self.tree_name)
|
||||||
elif name in ('no_type_check', 'no_type_check_decorator'):
|
elif name in ('no_type_check', 'no_type_check_decorator'):
|
||||||
# This is not necessary, as long as we are not doing type checking.
|
# This is not necessary, as long as we are not doing type checking.
|
||||||
for c in self._wrapped_name.infer(): # Fuck my life Python 2
|
for c in self._wrapped_name.infer(): # Fuck my life Python 2
|
||||||
@@ -339,3 +340,62 @@ class CastFunction(BaseTypingValue):
|
|||||||
@repack_with_argument_clinic('type, object, /')
|
@repack_with_argument_clinic('type, object, /')
|
||||||
def py__call__(self, type_value_set, object_value_set):
|
def py__call__(self, type_value_set, object_value_set):
|
||||||
return type_value_set.execute_annotation()
|
return type_value_set.execute_annotation()
|
||||||
|
|
||||||
|
|
||||||
|
class TypedDictBase(BaseTypingValue):
|
||||||
|
"""
|
||||||
|
This class has no responsibilities and is just here to make sure that typed
|
||||||
|
dicts can be identified.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TypedDictClass(Value):
|
||||||
|
"""
|
||||||
|
This represents a class defined like:
|
||||||
|
|
||||||
|
class Foo(TypedDict):
|
||||||
|
bar: str
|
||||||
|
"""
|
||||||
|
def __init__(self, definition_class):
|
||||||
|
super().__init__(definition_class.inference_state, definition_class.parent_context)
|
||||||
|
self.tree_node = definition_class.tree_node
|
||||||
|
self._definition_class = definition_class
|
||||||
|
|
||||||
|
def get_filters(self, origin_scope=None):
|
||||||
|
"""
|
||||||
|
A TypedDict doesn't have attributes.
|
||||||
|
"""
|
||||||
|
o, = self.inference_state.builtins_module.py__getattribute__('object')
|
||||||
|
return o.get_filters()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return ValueName(self, self.tree_node.name)
|
||||||
|
|
||||||
|
def py__call__(self, arguments):
|
||||||
|
return ValueSet({TypedDict(self._definition_class)})
|
||||||
|
|
||||||
|
|
||||||
|
class TypedDict(LazyValueWrapper):
|
||||||
|
"""Represents the instance version of ``TypedDictClass``."""
|
||||||
|
def __init__(self, definition_class):
|
||||||
|
self.inference_state = definition_class.inference_state
|
||||||
|
self.parent_context = definition_class.parent_context
|
||||||
|
self.tree_node = definition_class.tree_node
|
||||||
|
self._definition_class = definition_class
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return ValueName(self, self.tree_node.name)
|
||||||
|
|
||||||
|
def py__simple_getitem__(self, index):
|
||||||
|
return ValueSet({self.inference_state.builtins_module})
|
||||||
|
|
||||||
|
def get_key_values(self):
|
||||||
|
from jedi.inference.compiled import create_simple_object
|
||||||
|
return ValueSet({create_simple_object(self.inference_state, 'baz')})
|
||||||
|
|
||||||
|
def _get_wrapped_value(self):
|
||||||
|
d, = self.inference_state.builtins_module.py__getattribute__('dict')
|
||||||
|
result, = d.execute_with_values()
|
||||||
|
return result
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from jedi.inference.compiled.access import COMPARISON_OPERATORS
|
|||||||
from jedi.inference.cache import inference_state_method_cache
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
from jedi.inference.gradual.stub_value import VersionInfo
|
from jedi.inference.gradual.stub_value import VersionInfo
|
||||||
from jedi.inference.gradual import annotation
|
from jedi.inference.gradual import annotation
|
||||||
|
from jedi.inference.gradual.typing import TypedDictClass
|
||||||
from jedi.inference.names import TreeNameDefinition
|
from jedi.inference.names import TreeNameDefinition
|
||||||
from jedi.inference.context import CompForContext
|
from jedi.inference.context import CompForContext
|
||||||
from jedi.inference.value.decorator import Decoratee
|
from jedi.inference.value.decorator import Decoratee
|
||||||
@@ -748,6 +749,8 @@ def _apply_decorators(context, node):
|
|||||||
parent_context=context,
|
parent_context=context,
|
||||||
tree_node=node
|
tree_node=node
|
||||||
)
|
)
|
||||||
|
if decoratee_value.is_typeddict():
|
||||||
|
decoratee_value = TypedDictClass(decoratee_value)
|
||||||
else:
|
else:
|
||||||
decoratee_value = FunctionValue.from_context(context, node)
|
decoratee_value = FunctionValue.from_context(context, node)
|
||||||
initial = values = ValueSet([decoratee_value])
|
initial = values = ValueSet([decoratee_value])
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ py__doc__() Returns the docstring for a value.
|
|||||||
"""
|
"""
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi._compatibility import use_metaclass
|
from jedi._compatibility import use_metaclass
|
||||||
from jedi.parser_utils import get_cached_parent_scope
|
from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted
|
||||||
from jedi.inference.cache import inference_state_method_cache, CachedMetaClass, \
|
from jedi.inference.cache import inference_state_method_cache, CachedMetaClass, \
|
||||||
inference_state_method_generator_cache
|
inference_state_method_generator_cache
|
||||||
from jedi.inference import compiled
|
from jedi.inference import compiled
|
||||||
from jedi.inference.lazy_value import LazyKnownValues
|
from jedi.inference.lazy_value import LazyKnownValues, LazyTreeValue
|
||||||
from jedi.inference.filters import ParserTreeFilter
|
from jedi.inference.filters import ParserTreeFilter
|
||||||
from jedi.inference.names import TreeNameDefinition, ValueName
|
from jedi.inference.names import TreeNameDefinition, ValueName
|
||||||
from jedi.inference.arguments import unpack_arglist, ValuesArguments
|
from jedi.inference.arguments import unpack_arglist, ValuesArguments
|
||||||
@@ -265,6 +265,35 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase
|
|||||||
self.inference_state.builtins_module.py__getattribute__('object')
|
self.inference_state.builtins_module.py__getattribute__('object')
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
def is_typeddict(self):
|
||||||
|
# TODO Do a proper mro resolution. Currently we are just listing
|
||||||
|
# classes. However, it's a complicated algorithm.
|
||||||
|
from jedi.inference.gradual.typing import TypedDictBase
|
||||||
|
for lazy_cls in self.py__bases__():
|
||||||
|
if not isinstance(lazy_cls, LazyTreeValue):
|
||||||
|
return False
|
||||||
|
tree_node = lazy_cls.data
|
||||||
|
# Only resolve simple classes, stuff like Iterable[str] are more
|
||||||
|
# intensive to resolve and if generics are involved, we know it's
|
||||||
|
# not a TypedDict.
|
||||||
|
if not expr_is_dotted(tree_node):
|
||||||
|
return False
|
||||||
|
|
||||||
|
for cls in lazy_cls.infer():
|
||||||
|
if isinstance(cls, TypedDictBase):
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
method = cls.is_typeddict
|
||||||
|
except AttributeError:
|
||||||
|
# We're only dealing with simple classes, so just returning
|
||||||
|
# here should be fine. This only happens with e.g. compiled
|
||||||
|
# classes.
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if method():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def py__getitem__(self, index_value_set, contextualized_node):
|
def py__getitem__(self, index_value_set, contextualized_node):
|
||||||
from jedi.inference.gradual.base import GenericClass
|
from jedi.inference.gradual.base import GenericClass
|
||||||
if not index_value_set:
|
if not index_value_set:
|
||||||
|
|||||||
@@ -293,6 +293,25 @@ def cut_value_at_position(leaf, position):
|
|||||||
return ''.join(lines)
|
return ''.join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def expr_is_dotted(node):
|
||||||
|
"""
|
||||||
|
Checks if a path looks like `name` or `name.foo.bar` and not `name()`.
|
||||||
|
"""
|
||||||
|
if node.type == 'atom':
|
||||||
|
if len(node.children) == 3 and node.children[0] == '(':
|
||||||
|
return expr_is_dotted(node.children[1])
|
||||||
|
return False
|
||||||
|
if node.type == 'atom_expr':
|
||||||
|
children = node.children
|
||||||
|
if children[0] == 'await':
|
||||||
|
return False
|
||||||
|
if not expr_is_dotted(children[0]):
|
||||||
|
return False
|
||||||
|
# Check trailers
|
||||||
|
return all(c.children[0] == '.' for c in children[1:])
|
||||||
|
return node.type == 'name'
|
||||||
|
|
||||||
|
|
||||||
def _function_is_x_method(method_name):
|
def _function_is_x_method(method_name):
|
||||||
def wrapper(function_node):
|
def wrapper(function_node):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -493,3 +493,44 @@ def dynamic_annotation(x: int):
|
|||||||
|
|
||||||
#? int()
|
#? int()
|
||||||
dynamic_annotation('')
|
dynamic_annotation('')
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# TypeDict
|
||||||
|
# -------------------------
|
||||||
|
|
||||||
|
# python >= 3.8
|
||||||
|
|
||||||
|
class Foo(typing.TypedDict):
|
||||||
|
foo: str
|
||||||
|
bar: List[int]
|
||||||
|
foo
|
||||||
|
#! ['foo: str']
|
||||||
|
foo
|
||||||
|
#? str()
|
||||||
|
foo
|
||||||
|
|
||||||
|
#! ['class Foo']
|
||||||
|
d: Foo
|
||||||
|
#? str()
|
||||||
|
d['foo']
|
||||||
|
#? str()
|
||||||
|
d['bar'][0]
|
||||||
|
#?
|
||||||
|
d['baz']
|
||||||
|
|
||||||
|
#?
|
||||||
|
d.foo
|
||||||
|
#?
|
||||||
|
d.bar
|
||||||
|
#! []
|
||||||
|
d.foo
|
||||||
|
|
||||||
|
#? []
|
||||||
|
Foo.set
|
||||||
|
#? ['setdefault']
|
||||||
|
d.setdefaul
|
||||||
|
|
||||||
|
#? 5 ["'foo'"]
|
||||||
|
d['fo']
|
||||||
|
#? 5 ['"bar"']
|
||||||
|
d["bar"]
|
||||||
|
|||||||
Reference in New Issue
Block a user