cseq 0.0.1__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.
cseq/model.py ADDED
@@ -0,0 +1,367 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from array import array
5
+ from enum import Enum
6
+ from pathlib import Path
7
+ from typing import Iterable, Any
8
+
9
+
10
+ class ParseStatus(str, Enum):
11
+ OK = "OK"
12
+ PARTIAL = "PARTIAL"
13
+ ERROR_RECOVERED = "ERROR_RECOVERED"
14
+ OPAQUE_REGION = "OPAQUE_REGION"
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class SourceFile:
19
+ path: Path
20
+ project_relative_path: str
21
+ suffix: str
22
+ size_bytes: int
23
+ content_hash: str
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class TranslationUnit:
28
+ source: SourceFile
29
+ arguments: tuple[str, ...] = ()
30
+ working_directory: Path | None = None
31
+ configuration_name: str | None = None
32
+ # Ephemeral per-TU source text. Project may populate this so dependency
33
+ # fingerprinting and the fast parser share one file read. It is never part
34
+ # of persistent IR/cache identity and is released with the parse artifact.
35
+ source_text: str | None = None
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class SourceRange:
40
+ start_line: int | None = None
41
+ start_column: int | None = None
42
+ end_line: int | None = None
43
+ end_column: int | None = None
44
+
45
+
46
+ @dataclass(frozen=True, slots=True)
47
+ class FunctionRecord:
48
+ name: str
49
+ qualified_id: str
50
+ source_path: str
51
+ source_range: SourceRange
52
+ storage_class: str | None = None
53
+ type_qualifier: str | None = None
54
+ parameters: tuple[str, ...] = ()
55
+
56
+
57
+
58
+
59
+ @dataclass(frozen=True, slots=True)
60
+ class CompactFunctionRecord:
61
+ """Memory-lean FunctionRecord-compatible view for conservative fast parsing.
62
+
63
+ The stable qualified id and SourceRange are derived lazily instead of being
64
+ stored for every function. This materially reduces peak memory on
65
+ million-function projects while keeping the same public attribute contract.
66
+ """
67
+ name: str
68
+ source_path: str
69
+ start_line: int
70
+ start_column: int
71
+ end_line: int
72
+ end_column: int
73
+ storage_class: str | None = None
74
+ type_qualifier: str | None = None
75
+ parameters: tuple[str, ...] = ()
76
+
77
+ @property
78
+ def source_range(self) -> SourceRange:
79
+ return SourceRange(self.start_line, self.start_column, self.end_line, self.end_column)
80
+
81
+ @property
82
+ def qualified_id(self) -> str:
83
+ return (
84
+ f"{self.source_path}:{self.start_line}:{self.start_column}:"
85
+ f"{self.storage_class or 'external'}:{self.name}:{self.type_qualifier or '?'}"
86
+ )
87
+
88
+
89
+ class CompactFunctionTable:
90
+ """Columnar storage for fast-parser function metadata.
91
+
92
+ Records are materialized only when a caller iterates/addresses them. The
93
+ table avoids retaining one Python object and one SourceRange per function.
94
+ """
95
+ __slots__ = (
96
+ "source_path", "names", "start_lines", "start_columns",
97
+ "end_lines", "end_columns", "meta_ids", "meta", "_meta_index"
98
+ )
99
+
100
+ def __init__(self, source_path: str) -> None:
101
+ self.source_path = source_path
102
+ self.names: list[str] = []
103
+ self.start_lines = array("I")
104
+ self.start_columns = array("I")
105
+ self.end_lines = array("I")
106
+ self.end_columns = array("I")
107
+ self.meta_ids = array("I")
108
+ self.meta: list[tuple[str | None, str | None, tuple[str, ...]]] = []
109
+ self._meta_index: dict[tuple[str | None, str | None, tuple[str, ...]], int] = {}
110
+
111
+ def append_fields(
112
+ self, name: str, start_line: int, start_column: int, end_line: int, end_column: int,
113
+ storage_class: str | None, type_qualifier: str | None, parameters: tuple[str, ...],
114
+ ) -> None:
115
+ key = (storage_class, type_qualifier, parameters)
116
+ meta_id = self._meta_index.get(key)
117
+ if meta_id is None:
118
+ meta_id = len(self.meta)
119
+ self.meta.append(key)
120
+ self._meta_index[key] = meta_id
121
+ self.names.append(name)
122
+ self.start_lines.append(start_line or 0)
123
+ self.start_columns.append(start_column or 0)
124
+ self.end_lines.append(end_line or 0)
125
+ self.end_columns.append(end_column or 0)
126
+ self.meta_ids.append(meta_id)
127
+
128
+ def __len__(self) -> int:
129
+ return len(self.names)
130
+
131
+ def __getitem__(self, index: int) -> CompactFunctionRecord:
132
+ if index < 0:
133
+ index += len(self.names)
134
+ storage, type_qualifier, parameters = self.meta[self.meta_ids[index]]
135
+ return CompactFunctionRecord(
136
+ self.names[index], self.source_path,
137
+ self.start_lines[index], self.start_columns[index],
138
+ self.end_lines[index], self.end_columns[index],
139
+ storage, type_qualifier, parameters,
140
+ )
141
+
142
+ def __iter__(self):
143
+ for i in range(len(self.names)):
144
+ yield self[i]
145
+
146
+ def iter_named(self, names: set[str]):
147
+ if not names:
148
+ return
149
+ for i, name in enumerate(self.names):
150
+ if name in names:
151
+ yield self[i]
152
+
153
+ def index_of_qid(self, qid: str) -> int:
154
+ # Artifacts are intentionally small enough (normally hundreds of
155
+ # functions/TU) that a local scan is cheaper than retaining a second
156
+ # project-wide qualified-id dictionary.
157
+ for i in range(len(self.names)):
158
+ if self[i].qualified_id == qid:
159
+ return i
160
+ return -1
161
+
162
+
163
+ @dataclass(frozen=True, slots=True)
164
+ class Diagnostic:
165
+ severity: str
166
+ message: str
167
+ raw: str | None = None
168
+
169
+
170
+ class ValueSlotKind(str, Enum):
171
+ FUNCTION = "FUNCTION"
172
+ LOCAL = "LOCAL"
173
+ PARAMETER = "PARAMETER"
174
+ STRUCT_FIELD = "STRUCT_FIELD"
175
+ GLOBAL = "GLOBAL"
176
+ GLOBAL_ARRAY_ELEMENT = "GLOBAL_ARRAY_ELEMENT"
177
+ GLOBAL_ARRAY_SUMMARY = "GLOBAL_ARRAY_SUMMARY"
178
+ HEAP_ALLOCATION = "HEAP_ALLOCATION"
179
+ UNION_SUMMARY = "UNION_SUMMARY"
180
+ POINTER_MEMORY = "POINTER_MEMORY"
181
+ UNKNOWN = "UNKNOWN"
182
+
183
+
184
+ @dataclass(frozen=True, slots=True)
185
+ class ValueSlot:
186
+ key: str
187
+ kind: ValueSlotKind
188
+ name: str
189
+ function_id: str | None = None
190
+ field_name: str | None = None
191
+ index: int | None = None
192
+
193
+
194
+ class FlowEdgeKind(str, Enum):
195
+ ASSIGN = "ASSIGN"
196
+ INIT = "INIT"
197
+ ARG_TO_PARAM = "ARG_TO_PARAM"
198
+
199
+
200
+ @dataclass(frozen=True, slots=True)
201
+ class FlowEdge:
202
+ source_key: str
203
+ target_key: str
204
+ kind: FlowEdgeKind
205
+ source_path: str | None = None
206
+ source_range: SourceRange = SourceRange()
207
+
208
+
209
+ class CallTargetKind(str, Enum):
210
+ INTERNAL_EXACT = "INTERNAL_EXACT"
211
+ INTERNAL_POSSIBLE = "INTERNAL_POSSIBLE"
212
+ INDIRECT_EXACT = "INDIRECT_EXACT"
213
+ INDIRECT_POSSIBLE = "INDIRECT_POSSIBLE"
214
+ EXTERNAL_DECLARED = "EXTERNAL_DECLARED"
215
+ UNKNOWN = "UNKNOWN"
216
+
217
+
218
+ @dataclass(frozen=True, slots=True)
219
+ class CallSiteRecord:
220
+ caller_id: str
221
+ caller_name: str
222
+ callee_name: str | None
223
+ raw_text: str
224
+ source_path: str
225
+ source_range: SourceRange
226
+ target_kind: CallTargetKind = CallTargetKind.UNKNOWN
227
+ target_function_id: str | None = None
228
+ target_function_ids: tuple[str, ...] = ()
229
+ unknown_possible: bool = False
230
+ callee_slot_key: str | None = None
231
+ argument_slot_keys: tuple[str | None, ...] = ()
232
+ control_context: tuple[str, ...] = ()
233
+ macro_expanded: bool = False
234
+ spelling_range: SourceRange | None = None
235
+ expansion_range: SourceRange | None = None
236
+ guard_expression: str | None = None
237
+
238
+
239
+ class CpuEvidenceLayer(str, Enum):
240
+ STATIC_AFFINITY = "STATIC_AFFINITY"
241
+ BUILD_DOMAIN = "BUILD_DOMAIN"
242
+ OBSERVED_RUNTIME_DOMAIN = "OBSERVED_RUNTIME_DOMAIN"
243
+
244
+
245
+ class CpuValueKind(str, Enum):
246
+ CPU = "CPU"
247
+ CPU_GROUP = "CPU_GROUP"
248
+ UNKNOWN = "UNKNOWN"
249
+
250
+
251
+ @dataclass(frozen=True, slots=True)
252
+ class CpuEvidenceRecord:
253
+ target_function_id: str
254
+ layer: CpuEvidenceLayer
255
+ value_kind: CpuValueKind
256
+ value: str
257
+ provenance: str
258
+ explicit_override: bool = False
259
+
260
+
261
+ @dataclass(frozen=True, slots=True)
262
+ class LinkerEvidenceRecord:
263
+ symbol_name: str
264
+ address: int
265
+ object_file: str | None
266
+ section: str | None
267
+ matched_function_ids: tuple[str, ...] = ()
268
+ provenance: str = "linker-map"
269
+
270
+
271
+ @dataclass(frozen=True, slots=True)
272
+ class BinaryEvidenceRecord:
273
+ symbol_name: str
274
+ address: int
275
+ size: int
276
+ symbol_kind: str
277
+ matched_function_ids: tuple[str, ...] = ()
278
+ binary_hash: str = ""
279
+ provenance: str = "binary"
280
+
281
+
282
+ @dataclass(frozen=True, slots=True)
283
+ class DwarfEvidenceRecord:
284
+ function_id: str
285
+ source_path: str
286
+ line: int
287
+ address: int
288
+ dwarf_file: str
289
+ provenance: str = "dwarf"
290
+
291
+
292
+ @dataclass(frozen=True, slots=True)
293
+ class GuardedSourceRegion:
294
+ source_path: str
295
+ source_range: SourceRange
296
+ guard_expression: str
297
+
298
+
299
+ @dataclass(slots=True)
300
+ class FileParseArtifact:
301
+ translation_unit: TranslationUnit
302
+ status: ParseStatus
303
+ functions: list[FunctionRecord | CompactFunctionRecord] | CompactFunctionTable = field(default_factory=list)
304
+ callsites: list[CallSiteRecord] = field(default_factory=list)
305
+ value_slots: list[ValueSlot] = field(default_factory=list)
306
+ flow_edges: list[FlowEdge] = field(default_factory=list)
307
+ diagnostics: list[Diagnostic] = field(default_factory=list)
308
+ opaque_regions: list[SourceRange] = field(default_factory=list)
309
+ guarded_regions: list[GuardedSourceRegion] = field(default_factory=list)
310
+ parser_backend: str = "unknown"
311
+
312
+ @property
313
+ def has_errors(self) -> bool:
314
+ return any(d.severity in {"error", "fatal"} for d in self.diagnostics)
315
+
316
+
317
+ @dataclass(slots=True)
318
+ class ProjectIndex:
319
+ root: Path
320
+ source_files: list[SourceFile] = field(default_factory=list)
321
+ parse_artifacts: list[FileParseArtifact] = field(default_factory=list)
322
+ callsites: list[CallSiteRecord] = field(default_factory=list)
323
+ value_slots: list[ValueSlot] = field(default_factory=list)
324
+ flow_edges: list[FlowEdge] = field(default_factory=list)
325
+ cpu_evidence: list[CpuEvidenceRecord] = field(default_factory=list)
326
+ linker_evidence: list[LinkerEvidenceRecord] = field(default_factory=list)
327
+ binary_evidence: list[BinaryEvidenceRecord] = field(default_factory=list)
328
+ dwarf_evidence: list[DwarfEvidenceRecord] = field(default_factory=list)
329
+ guarded_regions: list[GuardedSourceRegion] = field(default_factory=list)
330
+ static_symbol_store: Any | None = None
331
+
332
+ def functions(self) -> Iterable[FunctionRecord | CompactFunctionRecord]:
333
+ if self.static_symbol_store is not None:
334
+ yield from self.static_symbol_store.iter_functions()
335
+ return
336
+ for artifact in self.parse_artifacts:
337
+ yield from artifact.functions
338
+
339
+ def functions_named(self, names: set[str]) -> Iterable[FunctionRecord | CompactFunctionRecord]:
340
+ if not names:
341
+ return
342
+ if self.static_symbol_store is not None:
343
+ yield from self.static_symbol_store.iter_named(names)
344
+ return
345
+ for artifact in self.parse_artifacts:
346
+ funcs = artifact.functions
347
+ if isinstance(funcs, CompactFunctionTable):
348
+ yield from funcs.iter_named(names)
349
+ else:
350
+ for fn in funcs:
351
+ if fn.name in names:
352
+ yield fn
353
+
354
+ def functions_by_ids(self, qids: set[str]) -> Iterable[FunctionRecord | CompactFunctionRecord]:
355
+ if not qids:
356
+ return
357
+ if self.static_symbol_store is not None:
358
+ yield from self.static_symbol_store.iter_ids(qids)
359
+ return
360
+ for fn in self.functions():
361
+ if fn.qualified_id in qids:
362
+ yield fn
363
+
364
+ def function_count(self) -> int:
365
+ if self.static_symbol_store is not None:
366
+ return self.static_symbol_store.count()
367
+ return sum(len(a.functions) for a in self.parse_artifacts)