mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 22:14: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):
|
||||
raise NotImplementedError('Not all values need to be converted to contexts: %s', self)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -81,7 +81,8 @@ class TypingModuleName(NameWrapper):
|
||||
elif name == 'TypedDict':
|
||||
# TODO doesn't even exist in typeshed/typing.py, yet. But will be
|
||||
# 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'):
|
||||
# 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
|
||||
@@ -339,3 +340,62 @@ class CastFunction(BaseTypingValue):
|
||||
@repack_with_argument_clinic('type, object, /')
|
||||
def py__call__(self, type_value_set, object_value_set):
|
||||
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.gradual.stub_value import VersionInfo
|
||||
from jedi.inference.gradual import annotation
|
||||
from jedi.inference.gradual.typing import TypedDictClass
|
||||
from jedi.inference.names import TreeNameDefinition
|
||||
from jedi.inference.context import CompForContext
|
||||
from jedi.inference.value.decorator import Decoratee
|
||||
@@ -748,6 +749,8 @@ def _apply_decorators(context, node):
|
||||
parent_context=context,
|
||||
tree_node=node
|
||||
)
|
||||
if decoratee_value.is_typeddict():
|
||||
decoratee_value = TypedDictClass(decoratee_value)
|
||||
else:
|
||||
decoratee_value = FunctionValue.from_context(context, node)
|
||||
initial = values = ValueSet([decoratee_value])
|
||||
|
||||
@@ -38,11 +38,11 @@ py__doc__() Returns the docstring for a value.
|
||||
"""
|
||||
from jedi import debug
|
||||
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, \
|
||||
inference_state_method_generator_cache
|
||||
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.names import TreeNameDefinition, ValueName
|
||||
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')
|
||||
)]
|
||||
|
||||
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):
|
||||
from jedi.inference.gradual.base import GenericClass
|
||||
if not index_value_set:
|
||||
|
||||
@@ -293,6 +293,25 @@ def cut_value_at_position(leaf, position):
|
||||
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 wrapper(function_node):
|
||||
"""
|
||||
|
||||
@@ -493,3 +493,44 @@ def dynamic_annotation(x: int):
|
||||
|
||||
#? int()
|
||||
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