diff --git a/jedi/inference/base_value.py b/jedi/inference/base_value.py index dfa2c230..075de49a 100644 --- a/jedi/inference/base_value.py +++ b/jedi/inference/base_value.py @@ -174,6 +174,9 @@ class Value(HelperValueMixin): def is_class(self): return False + def is_class_mixin(self): + return False + def is_instance(self): return False diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index e7940800..44564f65 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -190,7 +190,7 @@ class GenericClass(ClassMixin, DefineGenericBaseClass): @to_list def py__bases__(self): for base in self._wrapped_value.py__bases__(): - yield _LazyGenericBaseClass(self, base) + yield _LazyGenericBaseClass(self, base, self._generics_manager) def _create_instance_with_generics(self, generics_manager): return GenericClass(self._class_value, generics_manager) @@ -241,9 +241,10 @@ class GenericClass(ClassMixin, DefineGenericBaseClass): class _LazyGenericBaseClass(object): - def __init__(self, class_value, lazy_base_class): + def __init__(self, class_value, lazy_base_class, generics_manager): self._class_value = class_value self._lazy_base_class = lazy_base_class + self._generics_manager = generics_manager @iterator_to_value_set def infer(self): @@ -256,7 +257,17 @@ class _LazyGenericBaseClass(object): TupleGenericManager(tuple(self._remap_type_vars(base))), ) else: - yield base + if base.is_class_mixin(): + # This case basically allows classes like `class Foo(List)` + # to be used like `Foo[int]`. The generics are not + # necessary and can be used later. + yield GenericClass.create_cached( + base.inference_state, + base, + self._generics_manager, + ) + else: + yield base def _remap_type_vars(self, base): from jedi.inference.gradual.type_var import TypeVar diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index ec51b24a..4a47da0e 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -135,6 +135,9 @@ class ClassMixin(object): def is_class(self): return True + def is_class_mixin(self): + return True + def py__call__(self, arguments=None): from jedi.inference.value import TreeInstance @@ -314,6 +317,7 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase def py__getitem__(self, index_value_set, contextualized_node): from jedi.inference.gradual.base import GenericClass if not index_value_set: + debug.warning('Class indexes inferred to nothing. Returning class instead') return ValueSet([self]) return ValueSet( GenericClass( diff --git a/jedi/plugins/django.py b/jedi/plugins/django.py index 43cbaf01..89bc38c6 100644 --- a/jedi/plugins/django.py +++ b/jedi/plugins/django.py @@ -102,7 +102,7 @@ def _create_manager_for(cls, manager_cls='BaseManager'): ('django', 'db', 'models', 'manager') ).py__getattribute__(manager_cls) for m in managers: - if m.is_class() and not m.is_compiled(): + if m.is_class_mixin(): generics_manager = TupleGenericManager((ValueSet([cls]),)) for c in GenericClass(m, generics_manager).execute_annotation(): return c diff --git a/test/completion/pep0484_generic_passthroughs.py b/test/completion/pep0484_generic_passthroughs.py index 3ccdae7a..4fccc5ed 100644 --- a/test/completion/pep0484_generic_passthroughs.py +++ b/test/completion/pep0484_generic_passthroughs.py @@ -126,3 +126,19 @@ for p in typed_bound_generic_passthrough(untyped_list_str): for q in typed_bound_generic_passthrough(typed_list_str): #? str() q + + +class CustomList(List): + def get_first(self): + return self[0] + + +#? str() +CustomList[str]()[0] +#? str() +CustomList[str]().get_first() + +#? str() +typed_fully_generic_passthrough(CustomList[str]())[0] +#? +typed_list_generic_passthrough(CustomList[str])[0]