python-delphi-lsp 3.6.0__cp310-abi3-win_amd64.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.
- delphi_lsp/__init__.py +184 -0
- delphi_lsp/_version.py +1 -0
- delphi_lsp/agent_cache.py +1574 -0
- delphi_lsp/agent_cli.py +902 -0
- delphi_lsp/agent_context.py +3907 -0
- delphi_lsp/agent_cpg.py +349 -0
- delphi_lsp/agent_cpg_builder.py +666 -0
- delphi_lsp/agent_layers.py +1410 -0
- delphi_lsp/agent_metrics.py +526 -0
- delphi_lsp/agent_protocol.py +445 -0
- delphi_lsp/agent_relations.py +1881 -0
- delphi_lsp/agent_templates.py +614 -0
- delphi_lsp/agent_wiki.py +2853 -0
- delphi_lsp/agent_workspace.py +860 -0
- delphi_lsp/ast_serialize.py +126 -0
- delphi_lsp/binary.py +250 -0
- delphi_lsp/comment_builder.py +28 -0
- delphi_lsp/consts.py +345 -0
- delphi_lsp/delphi_lsp_native.pyd +0 -0
- delphi_lsp/delphiast_lexer.py +399 -0
- delphi_lsp/delphiast_parser.py +2189 -0
- delphi_lsp/delphiast_tokens.py +41 -0
- delphi_lsp/grammar.py +459 -0
- delphi_lsp/incremental.py +1139 -0
- delphi_lsp/lark_builder.py +2684 -0
- delphi_lsp/lark_tokens.py +236 -0
- delphi_lsp/lexical_scanner.py +30 -0
- delphi_lsp/lsp_server.py +6698 -0
- delphi_lsp/metrics.py +1452 -0
- delphi_lsp/native_bridge.py +300 -0
- delphi_lsp/navigation_cache.py +189 -0
- delphi_lsp/nodes.py +462 -0
- delphi_lsp/parallel_outline.py +662 -0
- delphi_lsp/parse_artifacts.py +969 -0
- delphi_lsp/parser.py +342 -0
- delphi_lsp/parser_backend.py +139 -0
- delphi_lsp/preprocessor.py +1122 -0
- delphi_lsp/progress.py +26 -0
- delphi_lsp/project_config.py +285 -0
- delphi_lsp/project_discovery.py +932 -0
- delphi_lsp/project_indexer.py +470 -0
- delphi_lsp/py.typed +1 -0
- delphi_lsp/response_cache.py +465 -0
- delphi_lsp/semantic.py +394 -0
- delphi_lsp/semantic_builder.py +2302 -0
- delphi_lsp/source_reader.py +17 -0
- delphi_lsp/wiki_external_sort.py +392 -0
- delphi_lsp/wiki_memory.py +548 -0
- delphi_lsp/workspace.py +83 -0
- delphi_lsp/writer.py +73 -0
- python_delphi_lsp-3.6.0.dist-info/METADATA +766 -0
- python_delphi_lsp-3.6.0.dist-info/RECORD +56 -0
- python_delphi_lsp-3.6.0.dist-info/WHEEL +4 -0
- python_delphi_lsp-3.6.0.dist-info/entry_points.txt +3 -0
- python_delphi_lsp-3.6.0.dist-info/licenses/LICENSE +373 -0
- python_delphi_lsp-3.6.0.dist-info/sboms/python-delphi-lsp.cyclonedx.json +770 -0
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import stat
|
|
7
|
+
import tempfile
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from time import time_ns
|
|
12
|
+
|
|
13
|
+
from .preprocessor import Preprocessor
|
|
14
|
+
from .project_config import ProjectPathConfig
|
|
15
|
+
from .semantic import (
|
|
16
|
+
ArrayTypeRef,
|
|
17
|
+
ClassOfTypeRef,
|
|
18
|
+
ClassTypeRef,
|
|
19
|
+
EnumTypeRef,
|
|
20
|
+
FileTypeRef,
|
|
21
|
+
GenericInstanceTypeRef,
|
|
22
|
+
InterfaceTypeRef,
|
|
23
|
+
Modifier,
|
|
24
|
+
NamedTypeRef,
|
|
25
|
+
PointerTypeRef,
|
|
26
|
+
ProcTypeRef,
|
|
27
|
+
RecordTypeRef,
|
|
28
|
+
ReferenceTypeRef,
|
|
29
|
+
Scope,
|
|
30
|
+
ScopeKind,
|
|
31
|
+
SetTypeRef,
|
|
32
|
+
SourceRange,
|
|
33
|
+
SubrangeTypeRef,
|
|
34
|
+
Symbol,
|
|
35
|
+
SymbolIndex,
|
|
36
|
+
SymbolKind,
|
|
37
|
+
TypeParameterRef,
|
|
38
|
+
TypeRef,
|
|
39
|
+
UnknownTypeRef,
|
|
40
|
+
Visibility,
|
|
41
|
+
)
|
|
42
|
+
from .semantic_builder import SemanticModel, SemanticProblem
|
|
43
|
+
from .source_reader import read_source_text
|
|
44
|
+
|
|
45
|
+
ARTIFACT_SCHEMA_VERSION = 1
|
|
46
|
+
OUTLINE_PRODUCER_VERSION = "outline-model-v3"
|
|
47
|
+
DEFAULT_ARTIFACT_CACHE_BYTES = 2 * 1024**3
|
|
48
|
+
|
|
49
|
+
_MAX_ARTIFACT_BYTES = 256 * 1024**2
|
|
50
|
+
_MAX_SYMBOLS_PER_ARTIFACT = 1_000_000
|
|
51
|
+
_STALE_SHARD_GRACE_NS = 5 * 60 * 1_000_000_000
|
|
52
|
+
_KEY_PREFIX = b"python-delphi-lsp-outline-artifact-v1\0"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ArtifactFormatError(ValueError):
|
|
56
|
+
"""Raised internally when an untrusted artifact has an invalid shape."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class ArtifactDependency:
|
|
61
|
+
relative_path: str
|
|
62
|
+
content_hash: str
|
|
63
|
+
|
|
64
|
+
def to_mapping(self) -> dict[str, str]:
|
|
65
|
+
return {
|
|
66
|
+
"relative_path": self.relative_path,
|
|
67
|
+
"content_hash": self.content_hash,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True)
|
|
72
|
+
class ArtifactKey:
|
|
73
|
+
cache_key: str
|
|
74
|
+
relative_path: str
|
|
75
|
+
content_hash: str
|
|
76
|
+
effective_defines: tuple[str, ...]
|
|
77
|
+
effective_defines_hash: str
|
|
78
|
+
includes: tuple[ArtifactDependency, ...]
|
|
79
|
+
producer_version: str
|
|
80
|
+
schema_version: int = ARTIFACT_SCHEMA_VERSION
|
|
81
|
+
|
|
82
|
+
def to_mapping(self) -> dict[str, object]:
|
|
83
|
+
return {
|
|
84
|
+
"relative_path": self.relative_path,
|
|
85
|
+
"content_hash": self.content_hash,
|
|
86
|
+
"effective_defines": list(self.effective_defines),
|
|
87
|
+
"effective_defines_hash": self.effective_defines_hash,
|
|
88
|
+
"includes": [item.to_mapping() for item in self.includes],
|
|
89
|
+
"producer_version": self.producer_version,
|
|
90
|
+
"schema_version": self.schema_version,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class CachedOutlineArtifact:
|
|
96
|
+
model: SemanticModel
|
|
97
|
+
lines_processed: int
|
|
98
|
+
symbols_discovered: int
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def artifact_cache_disabled() -> bool:
|
|
102
|
+
return os.environ.get("DELPHI_LSP_NO_ARTIFACT_CACHE") == "1"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def artifact_key(
|
|
106
|
+
workspace_root: str | Path,
|
|
107
|
+
source_path: str | Path,
|
|
108
|
+
text: str,
|
|
109
|
+
defines: tuple[str, ...],
|
|
110
|
+
*,
|
|
111
|
+
include_paths: tuple[str, ...] = (),
|
|
112
|
+
project_config: ProjectPathConfig | None = None,
|
|
113
|
+
producer_version: str = OUTLINE_PRODUCER_VERSION,
|
|
114
|
+
) -> ArtifactKey:
|
|
115
|
+
root = Path(workspace_root).expanduser().resolve()
|
|
116
|
+
source = Path(source_path).expanduser().resolve()
|
|
117
|
+
dependencies: dict[str, str] = {}
|
|
118
|
+
preprocessor = Preprocessor(
|
|
119
|
+
defines=defines,
|
|
120
|
+
include_paths=include_paths,
|
|
121
|
+
include_loader=_recording_include_loader(
|
|
122
|
+
root,
|
|
123
|
+
include_paths,
|
|
124
|
+
project_config,
|
|
125
|
+
dependencies,
|
|
126
|
+
),
|
|
127
|
+
track_source_map=False,
|
|
128
|
+
collect_comments=False,
|
|
129
|
+
)
|
|
130
|
+
effective_defines = tuple(sorted(preprocessor.defines))
|
|
131
|
+
if "{$" in text or "(*$" in text:
|
|
132
|
+
preprocessor.process(text, str(source))
|
|
133
|
+
|
|
134
|
+
define_data = _canonical_json(list(effective_defines))
|
|
135
|
+
define_hash = hashlib.sha256(define_data).hexdigest()
|
|
136
|
+
dependency_items = tuple(
|
|
137
|
+
ArtifactDependency(relative_path=relative_path, content_hash=content_hash)
|
|
138
|
+
for relative_path, content_hash in sorted(dependencies.items())
|
|
139
|
+
)
|
|
140
|
+
relative_path = _relative_path(root, source)
|
|
141
|
+
content_hash = _text_hash(text)
|
|
142
|
+
material = {
|
|
143
|
+
"relative_path": relative_path,
|
|
144
|
+
"content_hash": content_hash,
|
|
145
|
+
"effective_defines_hash": define_hash,
|
|
146
|
+
"includes": [item.to_mapping() for item in dependency_items],
|
|
147
|
+
"producer_version": producer_version,
|
|
148
|
+
"schema_version": ARTIFACT_SCHEMA_VERSION,
|
|
149
|
+
}
|
|
150
|
+
digest = hashlib.sha256()
|
|
151
|
+
digest.update(_KEY_PREFIX)
|
|
152
|
+
digest.update(_canonical_json(material))
|
|
153
|
+
return ArtifactKey(
|
|
154
|
+
cache_key=digest.hexdigest(),
|
|
155
|
+
relative_path=relative_path,
|
|
156
|
+
content_hash=content_hash,
|
|
157
|
+
effective_defines=effective_defines,
|
|
158
|
+
effective_defines_hash=define_hash,
|
|
159
|
+
includes=dependency_items,
|
|
160
|
+
producer_version=producer_version,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _recording_include_loader(
|
|
165
|
+
root: Path,
|
|
166
|
+
include_paths: tuple[str, ...],
|
|
167
|
+
project_config: ProjectPathConfig | None,
|
|
168
|
+
dependencies: dict[str, str],
|
|
169
|
+
):
|
|
170
|
+
search_paths = tuple(Path(path).expanduser().resolve() for path in include_paths)
|
|
171
|
+
|
|
172
|
+
def load(parent_file: str, include_name: str) -> tuple[str, str] | None:
|
|
173
|
+
include_path = Path(include_name.replace("\\", "/"))
|
|
174
|
+
parent = Path(parent_file).expanduser().resolve().parent
|
|
175
|
+
for base in (parent, *search_paths):
|
|
176
|
+
candidate = (base / include_path).resolve()
|
|
177
|
+
if (
|
|
178
|
+
project_config is not None
|
|
179
|
+
and project_config.excludes_workspace_path(candidate)
|
|
180
|
+
):
|
|
181
|
+
continue
|
|
182
|
+
try:
|
|
183
|
+
if not candidate.is_file():
|
|
184
|
+
continue
|
|
185
|
+
content = read_source_text(candidate)
|
|
186
|
+
except (OSError, UnicodeError):
|
|
187
|
+
continue
|
|
188
|
+
dependencies[_relative_path(root, candidate)] = _text_hash(content)
|
|
189
|
+
return content, str(candidate)
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
return load
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class ArtifactStore:
|
|
196
|
+
"""Sharded, bounded JSON storage for untrusted outline artifacts."""
|
|
197
|
+
|
|
198
|
+
def __init__(
|
|
199
|
+
self,
|
|
200
|
+
root: str | Path,
|
|
201
|
+
*,
|
|
202
|
+
max_bytes: int = DEFAULT_ARTIFACT_CACHE_BYTES,
|
|
203
|
+
) -> None:
|
|
204
|
+
if isinstance(max_bytes, bool) or not isinstance(max_bytes, int) or max_bytes < 0:
|
|
205
|
+
raise ValueError("artifact cache byte budget must be non-negative")
|
|
206
|
+
self.root = Path(root)
|
|
207
|
+
self.max_bytes = max_bytes
|
|
208
|
+
|
|
209
|
+
def load(self, cache_key: str) -> Mapping[str, object] | None:
|
|
210
|
+
path = self.path_for_key(cache_key)
|
|
211
|
+
if self._has_unsafe_directory(path.parent):
|
|
212
|
+
return None
|
|
213
|
+
try:
|
|
214
|
+
descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0))
|
|
215
|
+
try:
|
|
216
|
+
info = os.fstat(descriptor)
|
|
217
|
+
if (
|
|
218
|
+
not stat.S_ISREG(info.st_mode)
|
|
219
|
+
or info.st_size > _MAX_ARTIFACT_BYTES
|
|
220
|
+
or (
|
|
221
|
+
os.name != "nt"
|
|
222
|
+
and (info.st_uid != os.getuid() or info.st_mode & 0o077)
|
|
223
|
+
)
|
|
224
|
+
):
|
|
225
|
+
return None
|
|
226
|
+
with os.fdopen(descriptor, "rb", closefd=False) as stream:
|
|
227
|
+
raw = stream.read(_MAX_ARTIFACT_BYTES + 1)
|
|
228
|
+
if len(raw) > _MAX_ARTIFACT_BYTES:
|
|
229
|
+
return None
|
|
230
|
+
finally:
|
|
231
|
+
os.close(descriptor)
|
|
232
|
+
payload = json.loads(raw.decode("utf-8"))
|
|
233
|
+
except (OSError, UnicodeError, ValueError, RecursionError):
|
|
234
|
+
return None
|
|
235
|
+
if (
|
|
236
|
+
not isinstance(payload, dict)
|
|
237
|
+
or payload.get("schema_version") != ARTIFACT_SCHEMA_VERSION
|
|
238
|
+
or payload.get("cache_key") != cache_key
|
|
239
|
+
):
|
|
240
|
+
return None
|
|
241
|
+
return payload
|
|
242
|
+
|
|
243
|
+
def store(self, cache_key: str, payload: Mapping[str, object]) -> None:
|
|
244
|
+
path = self.path_for_key(cache_key)
|
|
245
|
+
self._ensure_directory(path.parent)
|
|
246
|
+
record = {
|
|
247
|
+
**dict(payload),
|
|
248
|
+
"schema_version": ARTIFACT_SCHEMA_VERSION,
|
|
249
|
+
"cache_key": cache_key,
|
|
250
|
+
}
|
|
251
|
+
data = _canonical_json(record)
|
|
252
|
+
if len(data) > _MAX_ARTIFACT_BYTES:
|
|
253
|
+
return
|
|
254
|
+
descriptor, temporary = tempfile.mkstemp(prefix=".artifact-", dir=path.parent)
|
|
255
|
+
try:
|
|
256
|
+
if os.name != "nt":
|
|
257
|
+
os.fchmod(descriptor, 0o600)
|
|
258
|
+
with os.fdopen(descriptor, "wb") as stream:
|
|
259
|
+
stream.write(data)
|
|
260
|
+
stream.flush()
|
|
261
|
+
os.fsync(stream.fileno())
|
|
262
|
+
os.replace(temporary, path)
|
|
263
|
+
if os.name != "nt":
|
|
264
|
+
os.chmod(path, 0o600)
|
|
265
|
+
finally:
|
|
266
|
+
try:
|
|
267
|
+
os.unlink(temporary)
|
|
268
|
+
except FileNotFoundError:
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
def prune(self, live_keys: set[str]) -> None:
|
|
272
|
+
if not self.root.exists() or self._has_unsafe_directory(self.root):
|
|
273
|
+
return
|
|
274
|
+
retained: list[tuple[int, str, Path, int]] = []
|
|
275
|
+
observed_at = time_ns()
|
|
276
|
+
bucket_paths: list[Path] = []
|
|
277
|
+
try:
|
|
278
|
+
buckets = tuple(self.root.iterdir())
|
|
279
|
+
except OSError:
|
|
280
|
+
return
|
|
281
|
+
for bucket in buckets:
|
|
282
|
+
if bucket.is_symlink() or not bucket.is_dir():
|
|
283
|
+
continue
|
|
284
|
+
bucket_paths.append(bucket)
|
|
285
|
+
try:
|
|
286
|
+
shards = tuple(bucket.iterdir())
|
|
287
|
+
except OSError:
|
|
288
|
+
continue
|
|
289
|
+
for shard in shards:
|
|
290
|
+
if shard.is_symlink() or not shard.is_file() or shard.suffix != ".json":
|
|
291
|
+
continue
|
|
292
|
+
cache_key = shard.stem
|
|
293
|
+
try:
|
|
294
|
+
info = shard.stat(follow_symlinks=False)
|
|
295
|
+
except OSError:
|
|
296
|
+
continue
|
|
297
|
+
if (
|
|
298
|
+
cache_key not in live_keys
|
|
299
|
+
and observed_at - info.st_mtime_ns >= _STALE_SHARD_GRACE_NS
|
|
300
|
+
):
|
|
301
|
+
try:
|
|
302
|
+
shard.unlink()
|
|
303
|
+
except OSError:
|
|
304
|
+
pass
|
|
305
|
+
continue
|
|
306
|
+
retained.append((info.st_mtime_ns, str(shard), shard, info.st_size))
|
|
307
|
+
|
|
308
|
+
retained_bytes = sum(item[3] for item in retained)
|
|
309
|
+
for _mtime, _name, shard, size in sorted(retained):
|
|
310
|
+
if retained_bytes <= self.max_bytes:
|
|
311
|
+
break
|
|
312
|
+
try:
|
|
313
|
+
shard.unlink()
|
|
314
|
+
except OSError:
|
|
315
|
+
continue
|
|
316
|
+
retained_bytes -= size
|
|
317
|
+
for bucket in bucket_paths:
|
|
318
|
+
try:
|
|
319
|
+
bucket.rmdir()
|
|
320
|
+
except OSError:
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
def path_for_key(self, cache_key: str) -> Path:
|
|
324
|
+
_validate_cache_key(cache_key)
|
|
325
|
+
return self.root / cache_key[:2] / f"{cache_key}.json"
|
|
326
|
+
|
|
327
|
+
def _has_unsafe_directory(self, directory: Path) -> bool:
|
|
328
|
+
return any(path.is_symlink() for path in (self.root.parent, self.root, directory))
|
|
329
|
+
|
|
330
|
+
def _ensure_directory(self, directory: Path) -> None:
|
|
331
|
+
if self._has_unsafe_directory(directory):
|
|
332
|
+
raise OSError("artifact cache path is unsafe")
|
|
333
|
+
self.root.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
334
|
+
for path in (self.root, directory):
|
|
335
|
+
if path.exists() and path.is_symlink():
|
|
336
|
+
raise OSError("artifact cache path is unsafe")
|
|
337
|
+
path.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
338
|
+
if os.name != "nt":
|
|
339
|
+
os.chmod(path, 0o700)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def load_outline_artifact(
|
|
343
|
+
store: ArtifactStore,
|
|
344
|
+
key: ArtifactKey,
|
|
345
|
+
source_path: str,
|
|
346
|
+
) -> CachedOutlineArtifact | None:
|
|
347
|
+
payload = store.load(key.cache_key)
|
|
348
|
+
if payload is None:
|
|
349
|
+
return None
|
|
350
|
+
try:
|
|
351
|
+
if (
|
|
352
|
+
payload.get("producer_version") != key.producer_version
|
|
353
|
+
or payload.get("key") != key.to_mapping()
|
|
354
|
+
or payload.get("source_path") != source_path
|
|
355
|
+
):
|
|
356
|
+
return None
|
|
357
|
+
lines_processed = _non_negative_int(payload.get("lines_processed"))
|
|
358
|
+
symbols_discovered = _non_negative_int(payload.get("symbols_discovered"))
|
|
359
|
+
model = semantic_model_from_mapping(_mapping(payload.get("model")))
|
|
360
|
+
except (ArtifactFormatError, KeyError, TypeError, ValueError, RecursionError):
|
|
361
|
+
return None
|
|
362
|
+
return CachedOutlineArtifact(model, lines_processed, symbols_discovered)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def store_outline_artifact(
|
|
366
|
+
store: ArtifactStore,
|
|
367
|
+
key: ArtifactKey,
|
|
368
|
+
source_path: str,
|
|
369
|
+
model: SemanticModel,
|
|
370
|
+
*,
|
|
371
|
+
lines_processed: int,
|
|
372
|
+
symbols_discovered: int,
|
|
373
|
+
) -> None:
|
|
374
|
+
store.store(
|
|
375
|
+
key.cache_key,
|
|
376
|
+
{
|
|
377
|
+
"producer_version": key.producer_version,
|
|
378
|
+
"key": key.to_mapping(),
|
|
379
|
+
"source_path": source_path,
|
|
380
|
+
"lines_processed": lines_processed,
|
|
381
|
+
"symbols_discovered": symbols_discovered,
|
|
382
|
+
"model": semantic_model_to_mapping(model),
|
|
383
|
+
},
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def semantic_model_to_mapping(model: SemanticModel) -> dict[str, object]:
|
|
388
|
+
if model.references:
|
|
389
|
+
raise ValueError("outline artifacts do not support semantic references")
|
|
390
|
+
payload: dict[str, object] = {"unit_scope": _scope_to_mapping(model.unit_scope)}
|
|
391
|
+
if model.problems:
|
|
392
|
+
payload["problems"] = [
|
|
393
|
+
{
|
|
394
|
+
"message": problem.message,
|
|
395
|
+
"range": _range_to_mapping(problem.range),
|
|
396
|
+
}
|
|
397
|
+
for problem in model.problems
|
|
398
|
+
]
|
|
399
|
+
return payload
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def semantic_model_from_mapping(payload: Mapping[str, object]) -> SemanticModel:
|
|
403
|
+
count = [0]
|
|
404
|
+
unit_scope = _scope_from_mapping(
|
|
405
|
+
_mapping(payload.get("unit_scope")),
|
|
406
|
+
parent=None,
|
|
407
|
+
inherited_owner=None,
|
|
408
|
+
symbol_count=count,
|
|
409
|
+
)
|
|
410
|
+
problem_payloads = _optional_list(payload.get("problems"))
|
|
411
|
+
if _optional_list(payload.get("references")):
|
|
412
|
+
raise ArtifactFormatError("outline artifact references must be empty")
|
|
413
|
+
problems = [
|
|
414
|
+
SemanticProblem(
|
|
415
|
+
message=_string(_mapping(item).get("message")),
|
|
416
|
+
range=_range_from_value(_mapping(item).get("range")),
|
|
417
|
+
)
|
|
418
|
+
for item in problem_payloads
|
|
419
|
+
]
|
|
420
|
+
index = SymbolIndex()
|
|
421
|
+
index.register_unit(unit_scope.name, unit_scope)
|
|
422
|
+
return SemanticModel(unit_scope=unit_scope, index=index, problems=problems)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _scope_to_mapping(scope: Scope) -> dict[str, object]:
|
|
426
|
+
symbols = [symbol for items in scope.symbols.values() for symbol in items]
|
|
427
|
+
owner_index = next(
|
|
428
|
+
(index for index, symbol in enumerate(symbols) if symbol is scope.owner),
|
|
429
|
+
None,
|
|
430
|
+
)
|
|
431
|
+
payload: dict[str, object] = {
|
|
432
|
+
"kind": scope.kind.value,
|
|
433
|
+
"name": scope.name,
|
|
434
|
+
"symbols": [_symbol_to_mapping(symbol) for symbol in symbols],
|
|
435
|
+
}
|
|
436
|
+
if owner_index is not None:
|
|
437
|
+
payload["owner_index"] = owner_index
|
|
438
|
+
return payload
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _scope_from_mapping(
|
|
442
|
+
payload: Mapping[str, object],
|
|
443
|
+
*,
|
|
444
|
+
parent: Scope | None,
|
|
445
|
+
inherited_owner: Symbol | None,
|
|
446
|
+
symbol_count: list[int],
|
|
447
|
+
) -> Scope:
|
|
448
|
+
scope = Scope(
|
|
449
|
+
kind=ScopeKind(_string(payload.get("kind"))),
|
|
450
|
+
name=_string(payload.get("name")),
|
|
451
|
+
parent=parent,
|
|
452
|
+
owner=inherited_owner,
|
|
453
|
+
)
|
|
454
|
+
symbol_payloads = [_mapping(item) for item in _list(payload.get("symbols"))]
|
|
455
|
+
symbol_count[0] += len(symbol_payloads)
|
|
456
|
+
if symbol_count[0] > _MAX_SYMBOLS_PER_ARTIFACT:
|
|
457
|
+
raise ArtifactFormatError("outline artifact has too many symbols")
|
|
458
|
+
symbols: list[Symbol] = []
|
|
459
|
+
for item in symbol_payloads:
|
|
460
|
+
symbol = Symbol(
|
|
461
|
+
name=_string(item.get("name")),
|
|
462
|
+
kind=SymbolKind(_string(item.get("kind"))),
|
|
463
|
+
decl_range=(decl_range := _range_from_value(item.get("range"))),
|
|
464
|
+
name_range=(
|
|
465
|
+
decl_range
|
|
466
|
+
if item.get("name_range") is None
|
|
467
|
+
else _range_from_value(item.get("name_range"))
|
|
468
|
+
),
|
|
469
|
+
scope=scope,
|
|
470
|
+
visibility=Visibility(
|
|
471
|
+
_string(item.get("visibility", Visibility.UNKNOWN.value))
|
|
472
|
+
),
|
|
473
|
+
type_ref=(
|
|
474
|
+
UnknownTypeRef()
|
|
475
|
+
if item.get("type_ref") is None
|
|
476
|
+
else _type_ref_from_mapping(_mapping(item.get("type_ref")))
|
|
477
|
+
),
|
|
478
|
+
modifiers={
|
|
479
|
+
Modifier(_string(value))
|
|
480
|
+
for value in _optional_list(item.get("modifiers"))
|
|
481
|
+
},
|
|
482
|
+
attributes=_string_mapping(item.get("attributes")),
|
|
483
|
+
doc=_string(item.get("doc", "")),
|
|
484
|
+
base_types=tuple(
|
|
485
|
+
_type_ref_from_mapping(_mapping(value))
|
|
486
|
+
for value in _optional_list(item.get("base_types"))
|
|
487
|
+
),
|
|
488
|
+
)
|
|
489
|
+
scope.define(symbol)
|
|
490
|
+
symbols.append(symbol)
|
|
491
|
+
|
|
492
|
+
owner_index = payload.get("owner_index")
|
|
493
|
+
if owner_index is not None:
|
|
494
|
+
owner_ordinal = _integer(owner_index)
|
|
495
|
+
if owner_ordinal < 0 or owner_ordinal >= len(symbols):
|
|
496
|
+
raise ArtifactFormatError("outline scope owner is invalid")
|
|
497
|
+
scope.owner = symbols[owner_ordinal]
|
|
498
|
+
|
|
499
|
+
for symbol, item in zip(symbols, symbol_payloads, strict=True):
|
|
500
|
+
member_scope = item.get("member_scope")
|
|
501
|
+
if member_scope is not None:
|
|
502
|
+
symbol.member_scope = _scope_from_mapping(
|
|
503
|
+
_mapping(member_scope),
|
|
504
|
+
parent=scope,
|
|
505
|
+
inherited_owner=symbol,
|
|
506
|
+
symbol_count=symbol_count,
|
|
507
|
+
)
|
|
508
|
+
type_ref = item.get("type_ref")
|
|
509
|
+
if type_ref is not None:
|
|
510
|
+
symbol.type_ref = _type_ref_from_mapping(
|
|
511
|
+
_mapping(type_ref),
|
|
512
|
+
structured_scope=symbol.member_scope,
|
|
513
|
+
)
|
|
514
|
+
symbol.overloads = [
|
|
515
|
+
_standalone_symbol_from_mapping(
|
|
516
|
+
_mapping(overload),
|
|
517
|
+
scope,
|
|
518
|
+
symbol_count,
|
|
519
|
+
)
|
|
520
|
+
for overload in _optional_list(item.get("overloads"))
|
|
521
|
+
]
|
|
522
|
+
return scope
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _standalone_symbol_from_mapping(
|
|
526
|
+
payload: Mapping[str, object],
|
|
527
|
+
scope: Scope,
|
|
528
|
+
symbol_count: list[int],
|
|
529
|
+
) -> Symbol:
|
|
530
|
+
symbol_count[0] += 1
|
|
531
|
+
if symbol_count[0] > _MAX_SYMBOLS_PER_ARTIFACT:
|
|
532
|
+
raise ArtifactFormatError("outline artifact has too many symbols")
|
|
533
|
+
symbol = Symbol(
|
|
534
|
+
name=_string(payload.get("name")),
|
|
535
|
+
kind=SymbolKind(_string(payload.get("kind"))),
|
|
536
|
+
decl_range=(decl_range := _range_from_value(payload.get("range"))),
|
|
537
|
+
name_range=(
|
|
538
|
+
decl_range
|
|
539
|
+
if payload.get("name_range") is None
|
|
540
|
+
else _range_from_value(payload.get("name_range"))
|
|
541
|
+
),
|
|
542
|
+
scope=scope,
|
|
543
|
+
visibility=Visibility(
|
|
544
|
+
_string(payload.get("visibility", Visibility.UNKNOWN.value))
|
|
545
|
+
),
|
|
546
|
+
type_ref=(
|
|
547
|
+
UnknownTypeRef()
|
|
548
|
+
if payload.get("type_ref") is None
|
|
549
|
+
else _type_ref_from_mapping(_mapping(payload.get("type_ref")))
|
|
550
|
+
),
|
|
551
|
+
modifiers={
|
|
552
|
+
Modifier(_string(value))
|
|
553
|
+
for value in _optional_list(payload.get("modifiers"))
|
|
554
|
+
},
|
|
555
|
+
attributes=_string_mapping(payload.get("attributes")),
|
|
556
|
+
doc=_string(payload.get("doc", "")),
|
|
557
|
+
base_types=tuple(
|
|
558
|
+
_type_ref_from_mapping(_mapping(value))
|
|
559
|
+
for value in _optional_list(payload.get("base_types"))
|
|
560
|
+
),
|
|
561
|
+
)
|
|
562
|
+
member_scope = payload.get("member_scope")
|
|
563
|
+
if member_scope is not None:
|
|
564
|
+
symbol.member_scope = _scope_from_mapping(
|
|
565
|
+
_mapping(member_scope),
|
|
566
|
+
parent=scope,
|
|
567
|
+
inherited_owner=symbol,
|
|
568
|
+
symbol_count=symbol_count,
|
|
569
|
+
)
|
|
570
|
+
type_ref = payload.get("type_ref")
|
|
571
|
+
if type_ref is not None:
|
|
572
|
+
symbol.type_ref = _type_ref_from_mapping(
|
|
573
|
+
_mapping(type_ref),
|
|
574
|
+
structured_scope=symbol.member_scope,
|
|
575
|
+
)
|
|
576
|
+
symbol.overloads = [
|
|
577
|
+
_standalone_symbol_from_mapping(_mapping(value), scope, symbol_count)
|
|
578
|
+
for value in _optional_list(payload.get("overloads"))
|
|
579
|
+
]
|
|
580
|
+
return symbol
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def _symbol_to_mapping(symbol: Symbol) -> dict[str, object]:
|
|
584
|
+
payload: dict[str, object] = {
|
|
585
|
+
"name": symbol.name,
|
|
586
|
+
"kind": symbol.kind.value,
|
|
587
|
+
"range": _range_to_mapping(symbol.decl_range),
|
|
588
|
+
}
|
|
589
|
+
if symbol.name_range != symbol.decl_range:
|
|
590
|
+
payload["name_range"] = _range_to_mapping(symbol.name_range)
|
|
591
|
+
if symbol.visibility is not Visibility.UNKNOWN:
|
|
592
|
+
payload["visibility"] = symbol.visibility.value
|
|
593
|
+
if not isinstance(symbol.type_ref, UnknownTypeRef) or symbol.type_ref.reason:
|
|
594
|
+
payload["type_ref"] = _type_ref_to_mapping(symbol.type_ref)
|
|
595
|
+
if symbol.modifiers:
|
|
596
|
+
payload["modifiers"] = sorted(modifier.value for modifier in symbol.modifiers)
|
|
597
|
+
if symbol.attributes:
|
|
598
|
+
payload["attributes"] = dict(sorted(symbol.attributes.items()))
|
|
599
|
+
if symbol.doc:
|
|
600
|
+
payload["doc"] = symbol.doc
|
|
601
|
+
if symbol.overloads:
|
|
602
|
+
payload["overloads"] = [_symbol_to_mapping(item) for item in symbol.overloads]
|
|
603
|
+
if symbol.member_scope is not None:
|
|
604
|
+
payload["member_scope"] = _scope_to_mapping(symbol.member_scope)
|
|
605
|
+
if symbol.base_types:
|
|
606
|
+
payload["base_types"] = [
|
|
607
|
+
_type_ref_to_mapping(item) for item in symbol.base_types
|
|
608
|
+
]
|
|
609
|
+
return payload
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def _range_to_mapping(source_range: SourceRange) -> list[object]:
|
|
613
|
+
return [
|
|
614
|
+
source_range.file_name,
|
|
615
|
+
source_range.start_line,
|
|
616
|
+
source_range.start_col,
|
|
617
|
+
source_range.end_line,
|
|
618
|
+
source_range.end_col,
|
|
619
|
+
]
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _range_from_value(value: object) -> SourceRange:
|
|
623
|
+
payload = _list(value)
|
|
624
|
+
if len(payload) != 5:
|
|
625
|
+
raise ArtifactFormatError("outline source range must have five values")
|
|
626
|
+
return SourceRange(
|
|
627
|
+
file_name=_string(payload[0]),
|
|
628
|
+
start_line=_positive_int(payload[1]),
|
|
629
|
+
start_col=_positive_int(payload[2]),
|
|
630
|
+
end_line=_positive_int(payload[3]),
|
|
631
|
+
end_col=_positive_int(payload[4]),
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def _type_ref_to_mapping(type_ref: TypeRef) -> dict[str, object]:
|
|
636
|
+
if isinstance(type_ref, UnknownTypeRef):
|
|
637
|
+
return {"kind": "unknown", "reason": type_ref.reason}
|
|
638
|
+
if isinstance(type_ref, NamedTypeRef):
|
|
639
|
+
return {"kind": "named", "name": type_ref.name, "unit_name": type_ref.unit_name}
|
|
640
|
+
if isinstance(type_ref, PointerTypeRef):
|
|
641
|
+
return {"kind": "pointer", "target": _type_ref_to_mapping(type_ref.target)}
|
|
642
|
+
if isinstance(type_ref, ClassOfTypeRef):
|
|
643
|
+
return {"kind": "class_of", "target": _type_ref_to_mapping(type_ref.target)}
|
|
644
|
+
if isinstance(type_ref, ReferenceTypeRef):
|
|
645
|
+
return {"kind": "reference", "target": _type_ref_to_mapping(type_ref.target)}
|
|
646
|
+
if isinstance(type_ref, FileTypeRef):
|
|
647
|
+
return {
|
|
648
|
+
"kind": "file",
|
|
649
|
+
"element_type": (
|
|
650
|
+
None
|
|
651
|
+
if type_ref.element_type is None
|
|
652
|
+
else _type_ref_to_mapping(type_ref.element_type)
|
|
653
|
+
),
|
|
654
|
+
}
|
|
655
|
+
if isinstance(type_ref, GenericInstanceTypeRef):
|
|
656
|
+
return {
|
|
657
|
+
"kind": "generic",
|
|
658
|
+
"base": _type_ref_to_mapping(type_ref.base),
|
|
659
|
+
"args": [_type_ref_to_mapping(item) for item in type_ref.args],
|
|
660
|
+
}
|
|
661
|
+
if isinstance(type_ref, ArrayTypeRef):
|
|
662
|
+
return {
|
|
663
|
+
"kind": "array",
|
|
664
|
+
"element_type": _type_ref_to_mapping(type_ref.element_type),
|
|
665
|
+
"dimensions": [_type_ref_to_mapping(item) for item in type_ref.dimensions],
|
|
666
|
+
"is_dynamic": type_ref.is_dynamic,
|
|
667
|
+
}
|
|
668
|
+
if isinstance(type_ref, SetTypeRef):
|
|
669
|
+
return {"kind": "set", "element_type": _type_ref_to_mapping(type_ref.element_type)}
|
|
670
|
+
if isinstance(type_ref, SubrangeTypeRef):
|
|
671
|
+
return {"kind": "subrange", "lower": type_ref.lower, "upper": type_ref.upper}
|
|
672
|
+
if isinstance(type_ref, EnumTypeRef):
|
|
673
|
+
return {"kind": "enum", "name": type_ref.name, "members": list(type_ref.members)}
|
|
674
|
+
if isinstance(type_ref, RecordTypeRef):
|
|
675
|
+
return {
|
|
676
|
+
"kind": "record",
|
|
677
|
+
"name": type_ref.name,
|
|
678
|
+
"packed": type_ref.packed,
|
|
679
|
+
"has_scope": type_ref.scope is not None,
|
|
680
|
+
}
|
|
681
|
+
if isinstance(type_ref, ClassTypeRef):
|
|
682
|
+
return {
|
|
683
|
+
"kind": "class",
|
|
684
|
+
"name": type_ref.name,
|
|
685
|
+
"bases": [_type_ref_to_mapping(item) for item in type_ref.bases],
|
|
686
|
+
"is_abstract": type_ref.is_abstract,
|
|
687
|
+
"has_scope": type_ref.scope is not None,
|
|
688
|
+
}
|
|
689
|
+
if isinstance(type_ref, InterfaceTypeRef):
|
|
690
|
+
return {
|
|
691
|
+
"kind": "interface",
|
|
692
|
+
"name": type_ref.name,
|
|
693
|
+
"bases": [_type_ref_to_mapping(item) for item in type_ref.bases],
|
|
694
|
+
"has_scope": type_ref.scope is not None,
|
|
695
|
+
}
|
|
696
|
+
if isinstance(type_ref, TypeParameterRef):
|
|
697
|
+
return {"kind": "type_parameter", "name": type_ref.name}
|
|
698
|
+
if isinstance(type_ref, ProcTypeRef):
|
|
699
|
+
return {
|
|
700
|
+
"kind": "proc",
|
|
701
|
+
"name": type_ref.name,
|
|
702
|
+
"params": [_type_ref_to_mapping(item) for item in type_ref.params],
|
|
703
|
+
"return_type": (
|
|
704
|
+
None
|
|
705
|
+
if type_ref.return_type is None
|
|
706
|
+
else _type_ref_to_mapping(type_ref.return_type)
|
|
707
|
+
),
|
|
708
|
+
}
|
|
709
|
+
raise ValueError(f"unsupported outline type reference: {type(type_ref).__name__}")
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def _type_ref_from_mapping(
|
|
713
|
+
payload: Mapping[str, object],
|
|
714
|
+
*,
|
|
715
|
+
structured_scope: Scope | None = None,
|
|
716
|
+
) -> TypeRef:
|
|
717
|
+
kind = _string(payload.get("kind"))
|
|
718
|
+
if kind == "unknown":
|
|
719
|
+
return UnknownTypeRef(_string(payload.get("reason")))
|
|
720
|
+
if kind == "named":
|
|
721
|
+
unit_name = payload.get("unit_name")
|
|
722
|
+
return NamedTypeRef(
|
|
723
|
+
_string(payload.get("name")),
|
|
724
|
+
None if unit_name is None else _string(unit_name),
|
|
725
|
+
)
|
|
726
|
+
if kind == "pointer":
|
|
727
|
+
return PointerTypeRef(
|
|
728
|
+
_type_ref_from_mapping(
|
|
729
|
+
_mapping(payload.get("target")),
|
|
730
|
+
structured_scope=structured_scope,
|
|
731
|
+
)
|
|
732
|
+
)
|
|
733
|
+
if kind == "class_of":
|
|
734
|
+
return ClassOfTypeRef(
|
|
735
|
+
_type_ref_from_mapping(
|
|
736
|
+
_mapping(payload.get("target")),
|
|
737
|
+
structured_scope=structured_scope,
|
|
738
|
+
)
|
|
739
|
+
)
|
|
740
|
+
if kind == "reference":
|
|
741
|
+
return ReferenceTypeRef(
|
|
742
|
+
_type_ref_from_mapping(
|
|
743
|
+
_mapping(payload.get("target")),
|
|
744
|
+
structured_scope=structured_scope,
|
|
745
|
+
)
|
|
746
|
+
)
|
|
747
|
+
if kind == "file":
|
|
748
|
+
element = payload.get("element_type")
|
|
749
|
+
return FileTypeRef(
|
|
750
|
+
None
|
|
751
|
+
if element is None
|
|
752
|
+
else _type_ref_from_mapping(
|
|
753
|
+
_mapping(element),
|
|
754
|
+
structured_scope=structured_scope,
|
|
755
|
+
)
|
|
756
|
+
)
|
|
757
|
+
if kind == "generic":
|
|
758
|
+
base = _type_ref_from_mapping(
|
|
759
|
+
_mapping(payload.get("base")),
|
|
760
|
+
structured_scope=structured_scope,
|
|
761
|
+
)
|
|
762
|
+
if not isinstance(base, NamedTypeRef):
|
|
763
|
+
raise ArtifactFormatError("generic base must be a named type")
|
|
764
|
+
return GenericInstanceTypeRef(
|
|
765
|
+
base,
|
|
766
|
+
tuple(
|
|
767
|
+
_type_ref_from_mapping(
|
|
768
|
+
_mapping(item),
|
|
769
|
+
structured_scope=structured_scope,
|
|
770
|
+
)
|
|
771
|
+
for item in _list(payload.get("args"))
|
|
772
|
+
),
|
|
773
|
+
)
|
|
774
|
+
if kind == "array":
|
|
775
|
+
return ArrayTypeRef(
|
|
776
|
+
element_type=_type_ref_from_mapping(
|
|
777
|
+
_mapping(payload.get("element_type")),
|
|
778
|
+
structured_scope=structured_scope,
|
|
779
|
+
),
|
|
780
|
+
dimensions=tuple(
|
|
781
|
+
_type_ref_from_mapping(
|
|
782
|
+
_mapping(item),
|
|
783
|
+
structured_scope=structured_scope,
|
|
784
|
+
)
|
|
785
|
+
for item in _list(payload.get("dimensions"))
|
|
786
|
+
),
|
|
787
|
+
is_dynamic=_boolean(payload.get("is_dynamic")),
|
|
788
|
+
)
|
|
789
|
+
if kind == "set":
|
|
790
|
+
return SetTypeRef(
|
|
791
|
+
_type_ref_from_mapping(
|
|
792
|
+
_mapping(payload.get("element_type")),
|
|
793
|
+
structured_scope=structured_scope,
|
|
794
|
+
)
|
|
795
|
+
)
|
|
796
|
+
if kind == "subrange":
|
|
797
|
+
return SubrangeTypeRef(_string(payload.get("lower")), _string(payload.get("upper")))
|
|
798
|
+
if kind == "enum":
|
|
799
|
+
return EnumTypeRef(
|
|
800
|
+
_string(payload.get("name")),
|
|
801
|
+
tuple(_string(item) for item in _list(payload.get("members"))),
|
|
802
|
+
)
|
|
803
|
+
if kind == "record":
|
|
804
|
+
return RecordTypeRef(
|
|
805
|
+
_string(payload.get("name")),
|
|
806
|
+
scope=(
|
|
807
|
+
structured_scope
|
|
808
|
+
if _boolean(payload.get("has_scope", False))
|
|
809
|
+
else None
|
|
810
|
+
),
|
|
811
|
+
packed=_boolean(payload.get("packed")),
|
|
812
|
+
)
|
|
813
|
+
if kind == "class":
|
|
814
|
+
return ClassTypeRef(
|
|
815
|
+
_string(payload.get("name")),
|
|
816
|
+
scope=(
|
|
817
|
+
structured_scope
|
|
818
|
+
if _boolean(payload.get("has_scope", False))
|
|
819
|
+
else None
|
|
820
|
+
),
|
|
821
|
+
bases=tuple(
|
|
822
|
+
_type_ref_from_mapping(
|
|
823
|
+
_mapping(item),
|
|
824
|
+
structured_scope=structured_scope,
|
|
825
|
+
)
|
|
826
|
+
for item in _list(payload.get("bases"))
|
|
827
|
+
),
|
|
828
|
+
is_abstract=_boolean(payload.get("is_abstract")),
|
|
829
|
+
)
|
|
830
|
+
if kind == "interface":
|
|
831
|
+
return InterfaceTypeRef(
|
|
832
|
+
_string(payload.get("name")),
|
|
833
|
+
scope=(
|
|
834
|
+
structured_scope
|
|
835
|
+
if _boolean(payload.get("has_scope", False))
|
|
836
|
+
else None
|
|
837
|
+
),
|
|
838
|
+
bases=tuple(
|
|
839
|
+
_type_ref_from_mapping(
|
|
840
|
+
_mapping(item),
|
|
841
|
+
structured_scope=structured_scope,
|
|
842
|
+
)
|
|
843
|
+
for item in _list(payload.get("bases"))
|
|
844
|
+
),
|
|
845
|
+
)
|
|
846
|
+
if kind == "type_parameter":
|
|
847
|
+
return TypeParameterRef(_string(payload.get("name")))
|
|
848
|
+
if kind == "proc":
|
|
849
|
+
result = payload.get("return_type")
|
|
850
|
+
return ProcTypeRef(
|
|
851
|
+
_string(payload.get("name")),
|
|
852
|
+
params=tuple(
|
|
853
|
+
_type_ref_from_mapping(
|
|
854
|
+
_mapping(item),
|
|
855
|
+
structured_scope=structured_scope,
|
|
856
|
+
)
|
|
857
|
+
for item in _list(payload.get("params"))
|
|
858
|
+
),
|
|
859
|
+
return_type=(
|
|
860
|
+
None
|
|
861
|
+
if result is None
|
|
862
|
+
else _type_ref_from_mapping(
|
|
863
|
+
_mapping(result),
|
|
864
|
+
structured_scope=structured_scope,
|
|
865
|
+
)
|
|
866
|
+
),
|
|
867
|
+
)
|
|
868
|
+
raise ArtifactFormatError("outline type reference kind is invalid")
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def _relative_path(root: Path, path: Path) -> str:
|
|
872
|
+
try:
|
|
873
|
+
return path.relative_to(root).as_posix()
|
|
874
|
+
except ValueError:
|
|
875
|
+
return Path(os.path.relpath(path, root)).as_posix()
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def _text_hash(text: str) -> str:
|
|
879
|
+
return hashlib.sha256(text.encode("utf-8", errors="surrogatepass")).hexdigest()
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def _canonical_json(payload: object) -> bytes:
|
|
883
|
+
return json.dumps(
|
|
884
|
+
payload,
|
|
885
|
+
ensure_ascii=False,
|
|
886
|
+
sort_keys=True,
|
|
887
|
+
separators=(",", ":"),
|
|
888
|
+
allow_nan=False,
|
|
889
|
+
).encode("utf-8", errors="surrogatepass")
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
def _validate_cache_key(cache_key: str) -> None:
|
|
893
|
+
if (
|
|
894
|
+
len(cache_key) != 64
|
|
895
|
+
or any(character not in "0123456789abcdef" for character in cache_key)
|
|
896
|
+
):
|
|
897
|
+
raise ValueError("artifact cache key is malformed")
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def _mapping(value: object) -> Mapping[str, object]:
|
|
901
|
+
if not isinstance(value, Mapping):
|
|
902
|
+
raise ArtifactFormatError("outline artifact value must be an object")
|
|
903
|
+
return value
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
def _list(value: object) -> list[object]:
|
|
907
|
+
if not isinstance(value, list):
|
|
908
|
+
raise ArtifactFormatError("outline artifact value must be an array")
|
|
909
|
+
return value
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
def _optional_list(value: object) -> list[object]:
|
|
913
|
+
return [] if value is None else _list(value)
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def _string(value: object) -> str:
|
|
917
|
+
if not isinstance(value, str):
|
|
918
|
+
raise ArtifactFormatError("outline artifact value must be a string")
|
|
919
|
+
return value
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
def _string_mapping(value: object) -> dict[str, str]:
|
|
923
|
+
if value is None:
|
|
924
|
+
return {}
|
|
925
|
+
mapping = _mapping(value)
|
|
926
|
+
return {_string(key): _string(item) for key, item in mapping.items()}
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def _integer(value: object) -> int:
|
|
930
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
931
|
+
raise ArtifactFormatError("outline artifact value must be an integer")
|
|
932
|
+
return value
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
def _positive_int(value: object) -> int:
|
|
936
|
+
result = _integer(value)
|
|
937
|
+
if result < 1:
|
|
938
|
+
raise ArtifactFormatError("outline artifact position must be positive")
|
|
939
|
+
return result
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
def _non_negative_int(value: object) -> int:
|
|
943
|
+
result = _integer(value)
|
|
944
|
+
if result < 0:
|
|
945
|
+
raise ArtifactFormatError("outline artifact count must be non-negative")
|
|
946
|
+
return result
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
def _boolean(value: object) -> bool:
|
|
950
|
+
if not isinstance(value, bool):
|
|
951
|
+
raise ArtifactFormatError("outline artifact value must be a boolean")
|
|
952
|
+
return value
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
__all__ = [
|
|
956
|
+
"ARTIFACT_SCHEMA_VERSION",
|
|
957
|
+
"DEFAULT_ARTIFACT_CACHE_BYTES",
|
|
958
|
+
"OUTLINE_PRODUCER_VERSION",
|
|
959
|
+
"ArtifactDependency",
|
|
960
|
+
"ArtifactKey",
|
|
961
|
+
"ArtifactStore",
|
|
962
|
+
"CachedOutlineArtifact",
|
|
963
|
+
"artifact_cache_disabled",
|
|
964
|
+
"artifact_key",
|
|
965
|
+
"load_outline_artifact",
|
|
966
|
+
"semantic_model_from_mapping",
|
|
967
|
+
"semantic_model_to_mapping",
|
|
968
|
+
"store_outline_artifact",
|
|
969
|
+
]
|