File size: 7,601 Bytes
d1ceb73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
209
210
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:
        arguments = getattr(stub_value, '_arguments', None)
        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
        arguments = None

    values = _infer_from_stub(stub_module_context, qualified_names, ignore_compiled)
    if was_instance:
        values = ValueSet.from_sets(
            c.execute_with_values() if arguments is None else c.execute(arguments)
            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:
                        yield from converted_names
                        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