conversion.py 7.33 KB
Newer Older
Stelios Karozis's avatar
Stelios Karozis committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
from jedi import debug
from jedi.inference.base_value import ValueSet, \
    NO_VALUES
from jedi.inference.utils import to_list
from jedi.inference.gradual.stub_value import StubModuleValue
from jedi.inference.gradual.typeshed import try_to_load_stub_cached
from jedi.inference.value.decorator import Decoratee


def _stub_to_python_value_set(stub_value, ignore_compiled=False):
    stub_module_context = stub_value.get_root_context()
    if not stub_module_context.is_stub():
        return ValueSet([stub_value])

    decorates = None
    if isinstance(stub_value, Decoratee):
        decorates = stub_value._original_value

    was_instance = stub_value.is_instance()
    if was_instance:
        stub_value = stub_value.py__class__()

    qualified_names = stub_value.get_qualified_names()
    if qualified_names is None:
        return NO_VALUES

    was_bound_method = stub_value.is_bound_method()
    if was_bound_method:
        # Infer the object first. We can infer the method later.
        method_name = qualified_names[-1]
        qualified_names = qualified_names[:-1]
        was_instance = True

    values = _infer_from_stub(stub_module_context, qualified_names, ignore_compiled)
    if was_instance:
        values = ValueSet.from_sets(
            c.execute_with_values()
            for c in values
            if c.is_class()
        )
    if was_bound_method:
        # Now that the instance has been properly created, we can simply get
        # the method.
        values = values.py__getattribute__(method_name)
    if decorates is not None:
        values = ValueSet(Decoratee(v, decorates) for v in values)
    return values


def _infer_from_stub(stub_module_context, qualified_names, ignore_compiled):
    from jedi.inference.compiled.mixed import MixedObject
    stub_module = stub_module_context.get_value()
    assert isinstance(stub_module, (StubModuleValue, MixedObject)), stub_module_context
    non_stubs = stub_module.non_stub_value_set
    if ignore_compiled:
        non_stubs = non_stubs.filter(lambda c: not c.is_compiled())
    for name in qualified_names:
        non_stubs = non_stubs.py__getattribute__(name)
    return non_stubs


@to_list
def _try_stub_to_python_names(names, prefer_stub_to_compiled=False):
    for name in names:
        module_context = name.get_root_context()
        if not module_context.is_stub():
            yield name
            continue

        if name.api_type == 'module':
            values = convert_values(name.infer(), ignore_compiled=prefer_stub_to_compiled)
            if values:
                for v in values:
                    yield v.name
                continue
        else:
            v = name.get_defining_qualified_value()
            if v is not None:
                converted = _stub_to_python_value_set(v, ignore_compiled=prefer_stub_to_compiled)
                if converted:
                    converted_names = converted.goto(name.get_public_name())
                    if converted_names:
                        for n in converted_names:
                            if n.get_root_context().is_stub():
                                # If it's a stub again, it means we're going in
                                # a circle. Probably some imports make it a
                                # stub again.
                                yield name
                            else:
                                yield n
                        continue
        yield name


def _load_stub_module(module):
    if module.is_stub():
        return module
    return try_to_load_stub_cached(
        module.inference_state,
        import_names=module.string_names,
        python_value_set=ValueSet([module]),
        parent_module_value=None,
        sys_path=module.inference_state.get_sys_path(),
    )


@to_list
def _python_to_stub_names(names, fallback_to_python=False):
    for name in names:
        module_context = name.get_root_context()
        if module_context.is_stub():
            yield name
            continue

        if name.api_type == 'module':
            found_name = False
            for n in name.goto():
                if n.api_type == 'module':
                    values = convert_values(n.infer(), only_stubs=True)
                    for v in values:
                        yield v.name
                        found_name = True
                else:
                    for x in _python_to_stub_names([n], fallback_to_python=fallback_to_python):
                        yield x
                        found_name = True
            if found_name:
                continue
        else:
            v = name.get_defining_qualified_value()
            if v is not None:
                converted = to_stub(v)
                if converted:
                    converted_names = converted.goto(name.get_public_name())
                    if converted_names:
                        for n in converted_names:
                            yield n
                        continue
        if fallback_to_python:
            # This is the part where if we haven't found anything, just return
            # the stub name.
            yield name


def convert_names(names, only_stubs=False, prefer_stubs=False, prefer_stub_to_compiled=True):
    if only_stubs and prefer_stubs:
        raise ValueError("You cannot use both of only_stubs and prefer_stubs.")

    with debug.increase_indent_cm('convert names'):
        if only_stubs or prefer_stubs:
            return _python_to_stub_names(names, fallback_to_python=prefer_stubs)
        else:
            return _try_stub_to_python_names(
                names, prefer_stub_to_compiled=prefer_stub_to_compiled)


def convert_values(values, only_stubs=False, prefer_stubs=False, ignore_compiled=True):
    assert not (only_stubs and prefer_stubs)
    with debug.increase_indent_cm('convert values'):
        if only_stubs or prefer_stubs:
            return ValueSet.from_sets(
                to_stub(value)
                or (ValueSet({value}) if prefer_stubs else NO_VALUES)
                for value in values
            )
        else:
            return ValueSet.from_sets(
                _stub_to_python_value_set(stub_value, ignore_compiled=ignore_compiled)
                or ValueSet({stub_value})
                for stub_value in values
            )


def to_stub(value):
    if value.is_stub():
        return ValueSet([value])

    was_instance = value.is_instance()
    if was_instance:
        value = value.py__class__()

    qualified_names = value.get_qualified_names()
    stub_module = _load_stub_module(value.get_root_context().get_value())
    if stub_module is None or qualified_names is None:
        return NO_VALUES

    was_bound_method = value.is_bound_method()
    if was_bound_method:
        # Infer the object first. We can infer the method later.
        method_name = qualified_names[-1]
        qualified_names = qualified_names[:-1]
        was_instance = True

    stub_values = ValueSet([stub_module])
    for name in qualified_names:
        stub_values = stub_values.py__getattribute__(name)

    if was_instance:
        stub_values = ValueSet.from_sets(
            c.execute_with_values()
            for c in stub_values
            if c.is_class()
        )
    if was_bound_method:
        # Now that the instance has been properly created, we can simply get
        # the method.
        stub_values = stub_values.py__getattribute__(method_name)
    return stub_values