gyomu-python-analysis 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. gyomu_python_analysis/__init__.py +0 -0
  2. gyomu_python_analysis/analysis/__init__.py +0 -0
  3. gyomu_python_analysis/analysis/analyzers/__init__.py +0 -0
  4. gyomu_python_analysis/analysis/analyzers/cls.py +343 -0
  5. gyomu_python_analysis/analysis/analyzers/context.py +66 -0
  6. gyomu_python_analysis/analysis/analyzers/decorator.py +88 -0
  7. gyomu_python_analysis/analysis/analyzers/dependency.py +134 -0
  8. gyomu_python_analysis/analysis/analyzers/docstring.py +288 -0
  9. gyomu_python_analysis/analysis/analyzers/expression/__init__.py +0 -0
  10. gyomu_python_analysis/analysis/analyzers/expression/expr.py +400 -0
  11. gyomu_python_analysis/analysis/analyzers/expression/name.py +38 -0
  12. gyomu_python_analysis/analysis/analyzers/functions.py +53 -0
  13. gyomu_python_analysis/analysis/analyzers/imports.py +21 -0
  14. gyomu_python_analysis/analysis/analyzers/internal/__init__.py +0 -0
  15. gyomu_python_analysis/analysis/analyzers/internal/common.py +87 -0
  16. gyomu_python_analysis/analysis/analyzers/internal/location.py +68 -0
  17. gyomu_python_analysis/analysis/analyzers/internal/visibility.py +27 -0
  18. gyomu_python_analysis/analysis/analyzers/pydantic.py +62 -0
  19. gyomu_python_analysis/analysis/analyzers/type_alias.py +24 -0
  20. gyomu_python_analysis/analysis/analyzers/types.py +29 -0
  21. gyomu_python_analysis/analysis/analyzers/variables.py +22 -0
  22. gyomu_python_analysis/analysis/extract/__init__.py +0 -0
  23. gyomu_python_analysis/analysis/extract/symbols.py +118 -0
  24. gyomu_python_analysis/analysis/file/__init__.py +0 -0
  25. gyomu_python_analysis/analysis/file/source_file_context.py +10 -0
  26. gyomu_python_analysis/analysis/get_module.py +55 -0
  27. gyomu_python_analysis/analysis/load.py +42 -0
  28. gyomu_python_analysis/analysis/load_file_context.py +27 -0
  29. gyomu_python_analysis/analysis/load_module.py +65 -0
  30. gyomu_python_analysis/analysis/metadata.py +49 -0
  31. gyomu_python_analysis/analysis/parser/__init__.py +0 -0
  32. gyomu_python_analysis/analysis/parser/docstring.py +7 -0
  33. gyomu_python_analysis/error/__init__.py +0 -0
  34. gyomu_python_analysis/error/analysis.py +36 -0
  35. gyomu_python_analysis/error/update.py +32 -0
  36. gyomu_python_analysis/path/__init__.py +0 -0
  37. gyomu_python_analysis/path/conversion.py +25 -0
  38. gyomu_python_analysis/project/__init__.py +0 -0
  39. gyomu_python_analysis/project/context.py +18 -0
  40. gyomu_python_analysis-0.2.0.dist-info/METADATA +15 -0
  41. gyomu_python_analysis-0.2.0.dist-info/RECORD +42 -0
  42. gyomu_python_analysis-0.2.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,288 @@
1
+ import ast
2
+ import re
3
+ from textwrap import dedent
4
+
5
+ from griffe import (
6
+ Docstring,
7
+ DocstringNamedElement,
8
+ DocstringSectionAdmonition,
9
+ DocstringSectionKind,
10
+ )
11
+ from griffe import DocstringSection as GriffeSection
12
+ from griffe import DocstringSectionExamples as GriffeSectionExamples
13
+ from griffe import DocstringSectionParameters as GriffeSectionParameters
14
+ from griffe import DocstringSectionRaises as GriffeSectionRaises
15
+ from griffe import DocstringSectionReturns as GriffeSectionReturns
16
+ from griffe import (
17
+ DocstringSectionText as GriffeSectionText,
18
+ )
19
+ from gyomu_infra.logger import logger
20
+ from gyomu_schema.schemas.python.docstring import (
21
+ DocstringAnalysis,
22
+ DocstringCommon,
23
+ DocstringCustomSection,
24
+ DocstringExamplesSection,
25
+ DocstringExamplesSectionItem,
26
+ DocstringGyomuContextSection,
27
+ DocstringNotesSection,
28
+ DocstringParametersSection,
29
+ DocstringParametersSectionItem,
30
+ DocstringRaisesSection,
31
+ DocstringRaisesSectionItem,
32
+ DocstringReturnsSection,
33
+ DocstringReturnsSectionItem,
34
+ DocstringSection,
35
+ DocstringStyle,
36
+ DocstringTextSection,
37
+ )
38
+
39
+ from gyomu_python_analysis.analysis.analyzers.context import SymbolContext
40
+ from gyomu_python_analysis.analysis.analyzers.types import analyze_type
41
+
42
+ _CUSTOM_SECTION_PATTERN = re.compile(r"^(?P<name>[A-Za-z][A-Za-z0-9 _-]*):\s*$")
43
+
44
+
45
+ def _analyze_parameters(
46
+ section: GriffeSectionParameters, context: SymbolContext
47
+ ) -> DocstringParametersSection:
48
+ parameters: list[DocstringParametersSectionItem] = []
49
+ for parameter in section.value:
50
+ param_type = analyze_type(parameter.annotation, context)
51
+ parameters.append(
52
+ DocstringParametersSectionItem(
53
+ name=parameter.name,
54
+ description=parameter.description,
55
+ type=param_type if param_type is None else param_type.text,
56
+ )
57
+ )
58
+ return DocstringParametersSection(items=tuple(parameters))
59
+
60
+
61
+ def _analyze_raises(
62
+ section: GriffeSectionRaises, context: SymbolContext
63
+ ) -> DocstringRaisesSection:
64
+ raises: list[DocstringRaisesSectionItem] = []
65
+ for raiseItem in section.value:
66
+ raise_type = analyze_type(raiseItem.annotation, context)
67
+ raises.append(
68
+ DocstringRaisesSectionItem(
69
+ description=raiseItem.description,
70
+ type="" if raise_type is None else raise_type.text,
71
+ )
72
+ )
73
+ return DocstringRaisesSection(items=tuple(raises))
74
+
75
+
76
+ def _analyze_examples(section: GriffeSectionExamples) -> DocstringExamplesSection:
77
+ examples: list[DocstringExamplesSectionItem] = []
78
+ for exampleItem in section.value:
79
+ for item in exampleItem:
80
+ item_text: str
81
+ if (
82
+ isinstance(item, str)
83
+ and item is not DocstringSectionKind.text
84
+ and item is not DocstringSectionKind.examples
85
+ ):
86
+ item_text = item
87
+ examples.append(DocstringExamplesSectionItem(value=item_text))
88
+ return DocstringExamplesSection(items=tuple(examples))
89
+
90
+
91
+ def _analyze_returns(
92
+ section: GriffeSectionReturns, context: SymbolContext
93
+ ) -> DocstringReturnsSection:
94
+ # Gyomu models a Returns section as a single item.
95
+ # Google-style docstrings are expected to contain at most one return item.
96
+ if len(section.value) > 1:
97
+ logger.warning(
98
+ "Multiple return items are not supported; using the first item: %r",
99
+ section.value,
100
+ )
101
+
102
+ return_item = section.value[0]
103
+ print(return_item.as_dict())
104
+ return_type = analyze_type(return_item.annotation, context)
105
+
106
+ return DocstringReturnsSection(
107
+ item=DocstringReturnsSectionItem(
108
+ description=_extract_return_description(return_item.description),
109
+ type=return_type.text if return_type is not None else None,
110
+ )
111
+ )
112
+
113
+
114
+ def _extract_return_description(description: str) -> str:
115
+ description = description.strip()
116
+
117
+ if ":" not in description:
118
+ return description
119
+
120
+ type_text, return_description = description.split(":", 1)
121
+ type_text = type_text.strip()
122
+ return_description = return_description.strip()
123
+
124
+ if not _looks_like_type(type_text):
125
+ return description
126
+
127
+ return return_description
128
+
129
+
130
+ def _looks_like_type(text: str) -> bool:
131
+ try:
132
+ ast.parse(text, mode="eval")
133
+ return True
134
+ except SyntaxError:
135
+ return False
136
+
137
+
138
+ def _analyze_admonition(section: DocstringSectionAdmonition) -> DocstringSection:
139
+ return _parse_custom_section(title=section.title, value=section.value.description)
140
+
141
+
142
+ def _parse_custom_section(title: str | None, value: str) -> DocstringSection:
143
+ if title == "Notes":
144
+ return DocstringNotesSection(value=value)
145
+ elif title == "Gyomu Context":
146
+ return DocstringGyomuContextSection(value=value)
147
+ else:
148
+ return DocstringCustomSection(
149
+ title=title if title is not None else "",
150
+ value=value,
151
+ )
152
+
153
+
154
+ def _is_indented(line: str) -> bool:
155
+ return bool(line) and line[0].isspace()
156
+
157
+
158
+ def parse_text_section(
159
+ text: str,
160
+ ) -> tuple[str, str, list[DocstringSection]]:
161
+ lines = text.splitlines()
162
+
163
+ while lines and not lines[0].strip():
164
+ lines.pop(0)
165
+
166
+ while lines and not lines[-1].strip():
167
+ lines.pop()
168
+
169
+ if not lines:
170
+ return "", "", []
171
+
172
+ # First paragraph = summary.
173
+ summary_lines: list[str] = []
174
+
175
+ index = 0
176
+ while index < len(lines) and lines[index].strip():
177
+ summary_lines.append(lines[index])
178
+ index += 1
179
+
180
+ summary = "\n".join(summary_lines).strip()
181
+
182
+ # Skip blank lines between summary and the rest.
183
+ while index < len(lines) and not lines[index].strip():
184
+ index += 1
185
+
186
+ description_lines: list[str] = []
187
+ custom_sections: list[DocstringSection] = []
188
+
189
+ while index < len(lines):
190
+ match = _CUSTOM_SECTION_PATTERN.match(lines[index])
191
+
192
+ if match and index + 1 < len(lines) and _is_indented(lines[index + 1]):
193
+ name = match.group("name")
194
+ index += 1
195
+
196
+ section_lines: list[str] = []
197
+
198
+ while index < len(lines):
199
+ if (
200
+ _CUSTOM_SECTION_PATTERN.match(lines[index])
201
+ and index + 1 < len(lines)
202
+ and _is_indented(lines[index + 1])
203
+ ):
204
+ break
205
+
206
+ section_lines.append(lines[index])
207
+ index += 1
208
+
209
+ section_text = "\n".join(section_lines)
210
+ section_text = dedent(section_text).strip()
211
+
212
+ custom_sections.append(
213
+ _parse_custom_section(
214
+ title=name,
215
+ value=section_text,
216
+ )
217
+ )
218
+ continue
219
+
220
+ description_lines.append(lines[index])
221
+ index += 1
222
+
223
+ description = "\n".join(description_lines).strip()
224
+
225
+ return summary, description, (custom_sections)
226
+
227
+
228
+ def analyze_docstring(
229
+ doc: Docstring | None,
230
+ doc_common: DocstringCommon,
231
+ context: SymbolContext,
232
+ ) -> DocstringAnalysis | None:
233
+ if doc is None:
234
+ return None
235
+
236
+ # print(doc.source)
237
+ sections = doc.parse(parser="auto")
238
+
239
+ text_section: DocstringTextSection | None = None
240
+ parsed_sections: list[DocstringSection] = []
241
+ for section in sections:
242
+ if isinstance(section, GriffeSectionText):
243
+ text_section = DocstringTextSection(value=section.value)
244
+ elif isinstance(section, GriffeSectionParameters):
245
+ parsed_sections.append(_analyze_parameters(section, context))
246
+ elif isinstance(section, GriffeSectionRaises):
247
+ parsed_sections.append(_analyze_raises(section, context))
248
+ elif isinstance(section, GriffeSectionExamples):
249
+ parsed_sections.append(_analyze_examples(section))
250
+ elif isinstance(section, GriffeSectionReturns):
251
+ parsed_sections.append(_analyze_returns(section, context))
252
+ elif isinstance(section, DocstringSectionAdmonition):
253
+ parsed_sections.append(_analyze_admonition(section))
254
+ else:
255
+ print("Unknown Section in Docstring")
256
+ value = section.value
257
+ if isinstance(value, str):
258
+ print(section.as_dict())
259
+ elif isinstance(value, list):
260
+ for item in value:
261
+ print(f"Kind: {section.kind}, item is list")
262
+ if isinstance(
263
+ item,
264
+ (GriffeSection, DocstringNamedElement, GriffeSectionRaises),
265
+ ):
266
+ print(item.as_dict())
267
+ else:
268
+ print(item)
269
+ else:
270
+ print(section.as_dict())
271
+ if text_section:
272
+ summary, description, sections2 = parse_text_section(text_section.value)
273
+ return DocstringAnalysis(
274
+ **doc_common,
275
+ raw=doc.source,
276
+ summary=None if summary == "" else summary,
277
+ description=None if description == "" else description,
278
+ sections=tuple(parsed_sections if len(parsed_sections) > 0 else sections2),
279
+ style=DocstringStyle.GOOGLE,
280
+ )
281
+ return DocstringAnalysis(
282
+ **doc_common,
283
+ raw=doc.source,
284
+ summary=None,
285
+ description=None,
286
+ sections=tuple(parsed_sections),
287
+ style=DocstringStyle.GOOGLE,
288
+ )
@@ -0,0 +1,400 @@
1
+ import ast
2
+
3
+ from griffe import (
4
+ Expr,
5
+ ExprAttribute,
6
+ ExprBinOp,
7
+ ExprCall,
8
+ ExprConstant,
9
+ ExprDict,
10
+ ExprKeyword,
11
+ ExprList,
12
+ ExprName,
13
+ ExprSet,
14
+ ExprSubscript,
15
+ ExprTuple,
16
+ )
17
+ from gyomu_schema.schemas.python.type.structure import (
18
+ EllipsisStructureAnalysis,
19
+ LiteralValue,
20
+ NameStructureAnalysis,
21
+ NoneStructureAnalysis,
22
+ TypeStructureKind,
23
+ UnknownStructureAnalysis,
24
+ )
25
+ from gyomu_schema.schemas.python.type.type_analysis import (
26
+ ArrayStructureAnalysis,
27
+ AttributeStructureAnalysis,
28
+ CallableStructureAnalysis,
29
+ CallStructureAnalysis,
30
+ DictionaryStructureAnalysis,
31
+ ExpressionAnalysis,
32
+ GenericsStructureAnalysis,
33
+ KeywordStructureAnalysis,
34
+ LiteralStructureAnalysis,
35
+ SetStructureAnalysis,
36
+ TupleStructureAnalysis,
37
+ TypeExpression,
38
+ UnionStructureAnalysis,
39
+ )
40
+
41
+ from gyomu_python_analysis.analysis.analyzers.context import SymbolContext
42
+ from gyomu_python_analysis.analysis.analyzers.dependency import register_dependency
43
+
44
+
45
+ def analyze_expression(
46
+ expression: Expr, context: SymbolContext, need_registration_dependency: bool = True
47
+ ) -> ExpressionAnalysis:
48
+ if isinstance(expression, ExprName):
49
+ return analyze_expression_name(
50
+ expression, context, need_registration_dependency
51
+ )
52
+ if isinstance(expression, ExprBinOp):
53
+ return _analyze_expression_binary_operation(expression, context)
54
+ if isinstance(expression, ExprSubscript):
55
+ return analyze_subscript(expression, context)
56
+ if isinstance(expression, ExprAttribute):
57
+ return _analyze_expression_attribute(expression, context)
58
+ if isinstance(expression, ExprTuple):
59
+ return analyze_tuple(expression, context)
60
+ if isinstance(expression, ExprList):
61
+ return analyze_array(expression, context)
62
+ if isinstance(expression, ExprDict):
63
+ return analyze_dictionary(expression, context)
64
+ if isinstance(expression, ExprSet):
65
+ return analyze_set(expression, context)
66
+ if isinstance(expression, ExprKeyword):
67
+ return _analyze_keyword(expression, context)
68
+ if isinstance(expression, ExprCall):
69
+ return _analyze_call(expression, context)
70
+
71
+ else:
72
+ print(f"Unsupported expression type: {type(expression)}")
73
+ print(expression.as_dict())
74
+ return UnknownStructureAnalysis()
75
+ # elif isinstance(expression, ExprSubscript):
76
+ # return analyze_expression_subscript(expression)
77
+ # else:
78
+ # raise ValueError(f"Unsupported expression type: {type(expression)}")
79
+
80
+
81
+ def analyze_expression_constant(
82
+ expression: ExprConstant, context: SymbolContext
83
+ ) -> TypeExpression:
84
+ return analyze_type_expression(expression.value, context)
85
+
86
+
87
+ def _analyze_expression_attribute(
88
+ expression: ExprAttribute, context: SymbolContext
89
+ ) -> AttributeStructureAnalysis:
90
+ # print(
91
+ # dict(
92
+ # canonical_path=expression.canonical_path,
93
+ # values=expression.values,
94
+ # path=expression.path,
95
+ # canonical_name=expression.canonical_name,
96
+ # is_classvar=expression.is_classvar,
97
+ # is_generator=expression.is_generator,
98
+ # is_iterator=expression.is_iterator,
99
+ # is_tuple=expression.is_tuple,
100
+ # )
101
+ # )
102
+ return AttributeStructureAnalysis(
103
+ values=tuple(
104
+ analyzed
105
+ for index, value in enumerate(expression.values)
106
+ if (
107
+ analyzed := analyze_type_expression(
108
+ value,
109
+ context,
110
+ need_registration_dependency=index == 0,
111
+ )
112
+ )
113
+ is not None
114
+ )
115
+ )
116
+
117
+
118
+ def _analyze_expression_binary_operation(
119
+ expression: ExprBinOp, context: SymbolContext
120
+ ) -> UnionStructureAnalysis | UnknownStructureAnalysis:
121
+ # print(
122
+ # dict(
123
+ # name=expression.name,
124
+ # member=expression.member,
125
+ # path=expression.path,
126
+ # canonical_name=expression.canonical_name,
127
+ # is_enum_class=expression.is_enum_class,
128
+ # is_enum_instance=expression.is_enum_instance,
129
+ # is_enum_value=expression.is_enum_value,
130
+ # is_type_parameter=expression.is_type_parameter,
131
+ # )
132
+ # )
133
+ match expression.operator:
134
+ case "|":
135
+ return _analyze_union(expression, context)
136
+ case _:
137
+ print(f"Unsupported operation: {type(expression)}")
138
+ print(expression.as_dict())
139
+ return UnknownStructureAnalysis()
140
+
141
+
142
+ def _analyze_union(
143
+ expression: ExprBinOp, context: SymbolContext
144
+ ) -> UnionStructureAnalysis:
145
+ types: list[TypeExpression] = []
146
+
147
+ def append_union_types(value: str | Expr) -> None:
148
+ if isinstance(value, ExprBinOp) and value.operator == "|":
149
+ append_union_types(value.left)
150
+ append_union_types(value.right)
151
+ return
152
+
153
+ analyzed = analyze_type_expression(value, context)
154
+ types.append(analyzed)
155
+
156
+ append_union_types(expression)
157
+
158
+ return UnionStructureAnalysis(
159
+ types=tuple(types),
160
+ )
161
+
162
+
163
+ # def _analyze_type_internal(annotation: str | Expr) -> TypeAnalysis:
164
+ # if isinstance(annotation, str):
165
+ # print(annotation)
166
+ # if annotation == "None":
167
+ # return TypeAnalysis(text=annotation, structure=NoneStructureAnalysis())
168
+ # return TypeAnalysis(text=annotation)
169
+ # if isinstance(annotation, Expr):
170
+ # text = str(annotation)
171
+ # print(annotation.as_dict())
172
+ # return TypeAnalysis(text=text, structure=analyze_expression(annotation))
173
+
174
+
175
+ def analyze_subscript(
176
+ expression: ExprSubscript, context: SymbolContext
177
+ ) -> ExpressionAnalysis:
178
+ # print(expression.as_dict())
179
+ # print(f"canonical_name : {expression.canonical_name}")
180
+ # print(f"canonical_path : {expression.canonical_path}")
181
+ # print(f"classname : {expression.classname}")
182
+ # print(f"is_classvar: {expression.is_classvar}")
183
+ # print(f"is_generator: {expression.is_generator}")
184
+ # print(f"is_iterator: {expression.is_iterator}")
185
+ # print(f"is_tuple : {expression.is_tuple}")
186
+
187
+ left = expression.left
188
+ slice = expression.slice
189
+ if isinstance(left, ExprName):
190
+ if left.name == "Literal":
191
+ return analyze_literal(slice, context)
192
+ elif left.name == "list":
193
+ return _analyze_array_from_subscript(slice, context)
194
+ elif left.name == "dict":
195
+ return _analyze_dictionary_from_subscript(slice, context)
196
+ elif left.name == "Callable":
197
+ return _analyze_callable_from_subscript(slice, context)
198
+ elif left.name == "tuple":
199
+ return _analyze_tuple_from_subscript(slice, context)
200
+ elif left.name == "set":
201
+ return _analyze_set_from_subscript(slice, context)
202
+
203
+ param = analyze_type_expression(slice, context)
204
+ if not isinstance(param, LiteralValue):
205
+ parameters: list[TypeExpression] = []
206
+ if isinstance(param, TupleStructureAnalysis):
207
+ parameters = list(param.elements)
208
+ elif isinstance(param, NameStructureAnalysis):
209
+ parameters.append(param)
210
+ else:
211
+ parameters.append(param)
212
+ return GenericsStructureAnalysis(
213
+ base=analyze_type_expression(left, context), parameters=tuple(parameters)
214
+ )
215
+ print(f"Unsupported expression type in subscript: {type(expression)}")
216
+ print(expression.as_dict())
217
+ return UnknownStructureAnalysis()
218
+
219
+
220
+ def analyze_dictionary(
221
+ expression: ExprDict, context: SymbolContext
222
+ ) -> DictionaryStructureAnalysis:
223
+ assert len(expression.keys) == 1
224
+ assert expression.keys[0]
225
+ assert len(expression.values) == 1
226
+ return DictionaryStructureAnalysis(
227
+ keys=analyze_type_expression(expression.keys[0], context),
228
+ values=analyze_type_expression(expression.values[0], context),
229
+ )
230
+
231
+
232
+ def _analyze_dictionary_from_subscript(
233
+ slice: str | Expr, context: SymbolContext
234
+ ) -> DictionaryStructureAnalysis:
235
+ assert isinstance(slice, ExprTuple)
236
+ assert len(slice.elements) == 2
237
+ return DictionaryStructureAnalysis(
238
+ keys=analyze_type_expression(slice.elements[0], context),
239
+ values=analyze_type_expression(slice.elements[1], context),
240
+ )
241
+
242
+
243
+ def analyze_array(
244
+ expression: ExprList, context: SymbolContext
245
+ ) -> ArrayStructureAnalysis:
246
+ return ArrayStructureAnalysis(
247
+ element=analyze_type_expression(expression.elements[0], context)
248
+ )
249
+
250
+
251
+ def _analyze_callable_from_subscript(
252
+ slice: str | Expr, context: SymbolContext
253
+ ) -> CallableStructureAnalysis:
254
+ assert isinstance(slice, ExprTuple)
255
+ assert len(slice.elements) == 2
256
+ parameters_expression = slice.elements[0]
257
+ if isinstance(parameters_expression, ExprList):
258
+ parameters: list[TypeExpression] = []
259
+ for expression in parameters_expression.elements:
260
+ analyzed = analyze_type_expression(expression, context)
261
+ parameters.append(analyzed)
262
+
263
+ return CallableStructureAnalysis(
264
+ parameters=tuple(parameters),
265
+ return_type=analyze_type_expression(slice.elements[1], context),
266
+ )
267
+ assert isinstance(parameters_expression, str)
268
+ assert parameters_expression == "..."
269
+ return CallableStructureAnalysis(
270
+ parameters=None,
271
+ return_type=analyze_type_expression(slice.elements[1], context),
272
+ )
273
+
274
+
275
+ def _analyze_array_from_subscript(
276
+ slice: str | Expr, context: SymbolContext
277
+ ) -> ArrayStructureAnalysis:
278
+ return ArrayStructureAnalysis(element=analyze_type_expression(slice, context))
279
+
280
+
281
+ def analyze_literal(
282
+ slice: str | Expr, context: SymbolContext
283
+ ) -> LiteralStructureAnalysis:
284
+
285
+ return LiteralStructureAnalysis(value=analyze_type_expression(slice, context))
286
+
287
+
288
+ def analyze_type_expression(
289
+ value: str | Expr, context: SymbolContext, need_registration_dependency: bool = True
290
+ ) -> TypeExpression:
291
+ if isinstance(value, str):
292
+ parsed = ast.literal_eval(value)
293
+ if parsed is None:
294
+ return NoneStructureAnalysis()
295
+ if parsed is Ellipsis:
296
+ return EllipsisStructureAnalysis()
297
+ return LiteralValue(value=parse_literal_value(value))
298
+ return analyze_expression(value, context, need_registration_dependency)
299
+
300
+
301
+ def parse_literal_value(value: str) -> str | int | bool:
302
+ parsed = ast.literal_eval(value)
303
+ if isinstance(parsed, bool):
304
+ return parsed
305
+
306
+ if isinstance(parsed, int):
307
+ return parsed
308
+
309
+ if isinstance(parsed, str):
310
+ return parsed
311
+
312
+ raise ValueError(f"Unsupported literal value: {value}")
313
+
314
+
315
+ def _analyze_tuple_from_subscript(
316
+ slice: str | Expr, context: SymbolContext
317
+ ) -> TupleStructureAnalysis:
318
+ assert isinstance(slice, ExprTuple)
319
+
320
+ return analyze_tuple(slice, context)
321
+
322
+
323
+ def analyze_tuple(
324
+ expression: ExprTuple, context: SymbolContext
325
+ ) -> TupleStructureAnalysis:
326
+ variable_length = False
327
+ elements: list[TypeExpression] = []
328
+
329
+ for value in expression.elements:
330
+ if value == "...":
331
+ variable_length = True
332
+ continue
333
+
334
+ analyzed = analyze_type_expression(value, context)
335
+ if analyzed is not None:
336
+ elements.append(analyzed)
337
+
338
+ return TupleStructureAnalysis(
339
+ elements=tuple(elements),
340
+ variable_length=variable_length,
341
+ )
342
+
343
+
344
+ def _analyze_set_from_subscript(
345
+ slice: str | Expr, context: SymbolContext
346
+ ) -> SetStructureAnalysis:
347
+ return SetStructureAnalysis(element_type=analyze_type_expression(slice, context))
348
+
349
+
350
+ def analyze_set(expression: ExprSet, context: SymbolContext) -> SetStructureAnalysis:
351
+ return SetStructureAnalysis(
352
+ element_type=analyze_type_expression(expression.elements[0], context)
353
+ )
354
+
355
+
356
+ def analyze_expression_name(
357
+ expression: ExprName, context: SymbolContext, need_registration_dependency: bool
358
+ ) -> NameStructureAnalysis | NoneStructureAnalysis:
359
+ # print(
360
+ # dict(
361
+ # name=expression.name,
362
+ # member=expression.member,
363
+ # path=expression.path,
364
+ # canonical_name=expression.canonical_name,
365
+ # is_enum_class=expression.is_enum_class,
366
+ # is_enum_instance=expression.is_enum_instance,
367
+ # is_enum_value=expression.is_enum_value,
368
+ # is_type_parameter=expression.is_type_parameter,
369
+ # )
370
+ # )
371
+ if expression.name == "None":
372
+ return NoneStructureAnalysis(
373
+ kind=TypeStructureKind.NONE,
374
+ )
375
+ if need_registration_dependency:
376
+ register_dependency(context.declaration, expression.name, context)
377
+ return NameStructureAnalysis(
378
+ name=expression.name,
379
+ )
380
+
381
+
382
+ def _analyze_keyword(
383
+ expression: ExprKeyword, context: SymbolContext
384
+ ) -> KeywordStructureAnalysis:
385
+ name = expression.name
386
+ value = analyze_type_expression(expression.value, context)
387
+ return KeywordStructureAnalysis(name=name, value=value)
388
+
389
+
390
+ def _analyze_call(
391
+ expression: ExprCall, context: SymbolContext
392
+ ) -> CallStructureAnalysis:
393
+ func = analyze_expression(expression.function, context)
394
+ arguments: list[TypeExpression] = []
395
+
396
+ for value in expression.arguments:
397
+ analyzed = analyze_type_expression(value, context)
398
+ arguments.append(analyzed)
399
+
400
+ return CallStructureAnalysis(function=func, arguments=tuple(arguments))