code-oracle 0.1.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.
- code_oracle/__init__.py +30 -0
- code_oracle/cli.py +795 -0
- code_oracle/config.py +145 -0
- code_oracle/dataset.py +5325 -0
- code_oracle/dead_code/__init__.py +32 -0
- code_oracle/dead_code/detector.py +379 -0
- code_oracle/dead_code/entrypoints.py +333 -0
- code_oracle/dead_code/models.py +255 -0
- code_oracle/dead_code/semantics.py +416 -0
- code_oracle/decision.py +906 -0
- code_oracle/engine.py +430 -0
- code_oracle/export_onnx.py +436 -0
- code_oracle/hook.py +531 -0
- code_oracle/indexer.py +894 -0
- code_oracle/languages/__init__.py +114 -0
- code_oracle/languages/common.py +127 -0
- code_oracle/languages/go.py +395 -0
- code_oracle/languages/python.py +336 -0
- code_oracle/languages/rust.py +474 -0
- code_oracle/languages/typescript.py +775 -0
- code_oracle/linearizer.py +166 -0
- code_oracle/locator.py +301 -0
- code_oracle/models.py +237 -0
- code_oracle/perf_lint/__init__.py +38 -0
- code_oracle/perf_lint/engine.py +234 -0
- code_oracle/perf_lint/models.py +229 -0
- code_oracle/perf_lint/rules/__init__.py +31 -0
- code_oracle/perf_lint/rules/async_blocking.py +143 -0
- code_oracle/perf_lint/rules/n_plus_one.py +232 -0
- code_oracle/perf_lint/rules/nested_loops.py +137 -0
- code_oracle/perf_lint/rules/unclosed_res.py +494 -0
- code_oracle/perf_lint/visitor.py +299 -0
- code_oracle/server.py +184 -0
- code_oracle/slicer.py +225 -0
- code_oracle/symbolic.py +459 -0
- code_oracle-0.1.0.dist-info/METADATA +225 -0
- code_oracle-0.1.0.dist-info/RECORD +40 -0
- code_oracle-0.1.0.dist-info/WHEEL +4 -0
- code_oracle-0.1.0.dist-info/entry_points.txt +2 -0
- code_oracle-0.1.0.dist-info/licenses/LICENSE +190 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dead Code Semantics Classifier.
|
|
3
|
+
Two-Stage Hierarchical Classification Engine:
|
|
4
|
+
- Stage 1: Deterministic AST & Visibility Pruner (< 5 ms on CPU)
|
|
5
|
+
- Stage 2: Packed Neural Semantic Classifier with ModernBERT representations
|
|
6
|
+
Graceful fallback when neural is disabled or weights are absent.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from enum import Enum
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import re
|
|
14
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import torch
|
|
18
|
+
import torch.nn as nn
|
|
19
|
+
import torch.nn.functional as F
|
|
20
|
+
HAS_TORCH = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
torch = None
|
|
23
|
+
nn = None
|
|
24
|
+
F = None
|
|
25
|
+
HAS_TORCH = False
|
|
26
|
+
|
|
27
|
+
from code_oracle.dead_code.models import DeadSymbol, SemanticClassification, SemanticDeadSymbol
|
|
28
|
+
from code_oracle.models import Symbol
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
# Known Python framework base contracts for OpenAPI, FastAPI, Pydantic, etc.
|
|
33
|
+
PYTHON_FRAMEWORK_CONTRACTS: Set[str] = {
|
|
34
|
+
"BaseModel",
|
|
35
|
+
"BaseSettings",
|
|
36
|
+
"APIKey",
|
|
37
|
+
"HTTPBase",
|
|
38
|
+
"BaseRoute",
|
|
39
|
+
"APIRoute",
|
|
40
|
+
"HTTPException",
|
|
41
|
+
"Exception",
|
|
42
|
+
"Enum",
|
|
43
|
+
"IntEnum",
|
|
44
|
+
"StrEnum",
|
|
45
|
+
"Protocol",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Public interface path markers where in-degree 0 symbols are intended library exports
|
|
49
|
+
PUBLIC_PATH_PATTERNS: Set[str] = {
|
|
50
|
+
"models.py",
|
|
51
|
+
"schemas.py",
|
|
52
|
+
"handlers.py",
|
|
53
|
+
"exception_handlers.py",
|
|
54
|
+
"interfaces.py",
|
|
55
|
+
"constants.py",
|
|
56
|
+
"types.py",
|
|
57
|
+
"openapi",
|
|
58
|
+
"binding",
|
|
59
|
+
"adapter",
|
|
60
|
+
"__init__.py",
|
|
61
|
+
"index.ts",
|
|
62
|
+
"lib.rs",
|
|
63
|
+
"mod.rs",
|
|
64
|
+
"api",
|
|
65
|
+
"routes.py",
|
|
66
|
+
"endpoints.py",
|
|
67
|
+
"protocols.py",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Lexical indicators of genuine cruft/abandoned code
|
|
71
|
+
CRUFT_KEYWORDS: Set[str] = {
|
|
72
|
+
"deprecated",
|
|
73
|
+
"legacy",
|
|
74
|
+
"todo",
|
|
75
|
+
"unused",
|
|
76
|
+
"obsolete",
|
|
77
|
+
"temp",
|
|
78
|
+
"tmp",
|
|
79
|
+
"old_",
|
|
80
|
+
"_old",
|
|
81
|
+
"abandoned",
|
|
82
|
+
"delete_me",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def is_test_or_internal_path(file_path: str) -> bool:
|
|
87
|
+
"""Check if file path belongs to a test suite or internal/private module."""
|
|
88
|
+
clean = file_path.replace("\\", "/").lower()
|
|
89
|
+
parts = clean.split("/")
|
|
90
|
+
|
|
91
|
+
# Directory checks
|
|
92
|
+
if any(p in ("tests", "test", "__tests__", "spec", "internal", "private") for p in parts):
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
# Filename checks
|
|
96
|
+
file_name = parts[-1]
|
|
97
|
+
if file_name.startswith("test_") or file_name.endswith(("_test.py", "_test.go", "_test.rs")):
|
|
98
|
+
return True
|
|
99
|
+
if file_name.endswith((".spec.ts", ".test.ts", ".spec.tsx", ".test.tsx", ".spec.js", ".test.js")):
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def vectorize_symbol(symbol: Symbol) -> str:
|
|
106
|
+
"""
|
|
107
|
+
Convert candidate dead symbol into compact representation:
|
|
108
|
+
[SYM] <qualname> [KIND] <kind> [SIG] <signature> [FILE] <path> [VIS] <export_status> [DOC] <docstring_snippet>
|
|
109
|
+
"""
|
|
110
|
+
doc_snippet = ""
|
|
111
|
+
if symbol.docstring:
|
|
112
|
+
first_line = symbol.docstring.strip().splitlines()[0]
|
|
113
|
+
doc_snippet = first_line[:80].strip()
|
|
114
|
+
|
|
115
|
+
vis_str = "export" if symbol.is_exported else symbol.visibility or "internal"
|
|
116
|
+
sig_str = symbol.signature.strip() if symbol.signature else f"{symbol.kind} {symbol.name}"
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
f"[SYM] {symbol.qualname} "
|
|
120
|
+
f"[KIND] {symbol.kind} "
|
|
121
|
+
f"[SIG] {sig_str} "
|
|
122
|
+
f"[FILE] {symbol.file_path} "
|
|
123
|
+
f"[VIS] {vis_str} "
|
|
124
|
+
f"[DOC] {doc_snippet}"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
_ModuleBase = nn.Module if (HAS_TORCH and nn is not None) else object
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class DeadCodeSemanticsModel(_ModuleBase):
|
|
132
|
+
"""
|
|
133
|
+
Stage 2 Neural Classifier Head over ModernBERT representations.
|
|
134
|
+
Maps pooled representation h_pool in R^hidden_size (default 768) to 3-class distribution:
|
|
135
|
+
- Index 0: PUBLIC_API_SURFACE
|
|
136
|
+
- Index 1: INTERNAL_ORPHAN
|
|
137
|
+
- Index 2: GENUINE_CRUFT
|
|
138
|
+
"""
|
|
139
|
+
CLASSES = [
|
|
140
|
+
SemanticClassification.PUBLIC_API_SURFACE,
|
|
141
|
+
SemanticClassification.INTERNAL_ORPHAN,
|
|
142
|
+
SemanticClassification.GENUINE_CRUFT,
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
def __init__(self, hidden_size: int = 768, num_classes: int = 3):
|
|
146
|
+
if not HAS_TORCH:
|
|
147
|
+
raise RuntimeError("PyTorch is required to instantiate DeadCodeSemanticsModel")
|
|
148
|
+
super().__init__()
|
|
149
|
+
self.hidden_size = hidden_size
|
|
150
|
+
self.num_classes = num_classes
|
|
151
|
+
self.classifier = nn.Sequential(
|
|
152
|
+
nn.Linear(hidden_size, 128),
|
|
153
|
+
nn.GELU(),
|
|
154
|
+
nn.LayerNorm(128),
|
|
155
|
+
nn.Linear(128, num_classes),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def forward(self, h_pool: Any) -> Any:
|
|
159
|
+
"""Forward pass returning softmax probability distribution (B, 3)."""
|
|
160
|
+
if not HAS_TORCH:
|
|
161
|
+
raise RuntimeError("PyTorch is required for model forward pass")
|
|
162
|
+
logits = self.classifier(h_pool)
|
|
163
|
+
return F.softmax(logits, dim=-1)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class DeadCodeSemanticsClassifier:
|
|
167
|
+
"""
|
|
168
|
+
Two-Stage Dead Code Semantics Classifier.
|
|
169
|
+
Eliminates in-degree 0 false positive traps on public library surfaces.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def __init__(
|
|
173
|
+
self,
|
|
174
|
+
weights_path: Optional[Path] = None,
|
|
175
|
+
enabled: bool = True,
|
|
176
|
+
):
|
|
177
|
+
self.enabled = enabled
|
|
178
|
+
self.weights_path = Path(weights_path).resolve() if weights_path else None
|
|
179
|
+
self.model: Optional[DeadCodeSemanticsModel] = None
|
|
180
|
+
self._tokenizer = None
|
|
181
|
+
self._loaded = False
|
|
182
|
+
|
|
183
|
+
if self.enabled:
|
|
184
|
+
self._try_load_model()
|
|
185
|
+
|
|
186
|
+
def _try_load_model(self) -> None:
|
|
187
|
+
"""Attempt to load ModernBERT or Laya semantic classification weights."""
|
|
188
|
+
if not HAS_TORCH:
|
|
189
|
+
self._loaded = False
|
|
190
|
+
return
|
|
191
|
+
try:
|
|
192
|
+
# Check candidate paths if not explicitly specified
|
|
193
|
+
if not self.weights_path:
|
|
194
|
+
candidates = [
|
|
195
|
+
Path.cwd() / "weights",
|
|
196
|
+
Path.cwd() / ".code_oracle" / "weights",
|
|
197
|
+
Path.home() / ".cache" / "code_oracle" / "weights",
|
|
198
|
+
]
|
|
199
|
+
for c in candidates:
|
|
200
|
+
if c.exists() and (c / "model.safetensors").exists():
|
|
201
|
+
self.weights_path = c.resolve()
|
|
202
|
+
break
|
|
203
|
+
|
|
204
|
+
if self.weights_path and self.weights_path.exists():
|
|
205
|
+
self.model = DeadCodeSemanticsModel()
|
|
206
|
+
self._loaded = True
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.debug("Semantic classifier neural weights not loaded: %s", e)
|
|
209
|
+
self._loaded = False
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def is_neural_enabled(self) -> bool:
|
|
213
|
+
"""Returns True if neural classifier weights are loaded and active."""
|
|
214
|
+
return self.enabled and self._loaded
|
|
215
|
+
|
|
216
|
+
def prune_stage1_public(self, symbol: Symbol) -> Optional[SemanticDeadSymbol]:
|
|
217
|
+
"""
|
|
218
|
+
Stage 1: Deterministic AST & Visibility Pruner (< 5 ms on CPU).
|
|
219
|
+
Resolves 80-90% of library symbols deterministically.
|
|
220
|
+
Returns SemanticDeadSymbol if definitely PUBLIC_API_SURFACE, otherwise None.
|
|
221
|
+
"""
|
|
222
|
+
# Test files and internal/private packages are never public library API surfaces
|
|
223
|
+
if is_test_or_internal_path(symbol.file_path):
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
# Symbols containing cruft indicators are never public library surfaces
|
|
227
|
+
name_lower = symbol.name.lower()
|
|
228
|
+
doc_lower = (symbol.docstring or "").lower()
|
|
229
|
+
if any(k in name_lower or k in doc_lower for k in CRUFT_KEYWORDS):
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
ext = Path(symbol.file_path).suffix.lower()
|
|
233
|
+
file_norm = symbol.file_path.replace("\\", "/").lower()
|
|
234
|
+
|
|
235
|
+
is_public = False
|
|
236
|
+
reason = ""
|
|
237
|
+
|
|
238
|
+
# Go: Symbols with uppercase first rune in non-main packages
|
|
239
|
+
if ext == ".go":
|
|
240
|
+
if symbol.name and symbol.name[0].isupper():
|
|
241
|
+
is_main_file = file_norm.endswith("main.go")
|
|
242
|
+
if not (is_main_file and symbol.name == "main"):
|
|
243
|
+
is_public = True
|
|
244
|
+
reason = "Go exported identifier (uppercase first rune) intended for package consumers"
|
|
245
|
+
|
|
246
|
+
# TypeScript / JavaScript: Exported symbols
|
|
247
|
+
elif ext in (".ts", ".tsx", ".js", ".jsx", ".mjs"):
|
|
248
|
+
if symbol.is_exported or symbol.signature.strip().startswith("export "):
|
|
249
|
+
is_public = True
|
|
250
|
+
reason = "TypeScript/JavaScript exported declaration (export statement)"
|
|
251
|
+
|
|
252
|
+
# Rust: Public visibility
|
|
253
|
+
elif ext == ".rs":
|
|
254
|
+
if symbol.is_exported or symbol.signature.strip().startswith("pub "):
|
|
255
|
+
is_public = True
|
|
256
|
+
reason = "Rust public item (pub visibility modifier)"
|
|
257
|
+
|
|
258
|
+
# Python: Exported symbols or framework contracts
|
|
259
|
+
elif ext == ".py":
|
|
260
|
+
# Check framework bases
|
|
261
|
+
has_framework_base = any(
|
|
262
|
+
b in PYTHON_FRAMEWORK_CONTRACTS or any(b.endswith(f".{fc}") for fc in PYTHON_FRAMEWORK_CONTRACTS)
|
|
263
|
+
for b in symbol.bases
|
|
264
|
+
)
|
|
265
|
+
# Check public path markers
|
|
266
|
+
in_public_path = any(kw in file_norm for kw in PUBLIC_PATH_PATTERNS)
|
|
267
|
+
|
|
268
|
+
if symbol.is_exported:
|
|
269
|
+
if has_framework_base:
|
|
270
|
+
is_public = True
|
|
271
|
+
reason = f"Python class inheriting from framework contract ({', '.join(symbol.bases)})"
|
|
272
|
+
elif in_public_path:
|
|
273
|
+
is_public = True
|
|
274
|
+
reason = f"Python exported symbol defined in library surface file ({symbol.file_path})"
|
|
275
|
+
elif symbol.docstring:
|
|
276
|
+
is_public = True
|
|
277
|
+
reason = "Python exported symbol with documented public interface"
|
|
278
|
+
|
|
279
|
+
if is_public:
|
|
280
|
+
return SemanticDeadSymbol(
|
|
281
|
+
id=symbol.id,
|
|
282
|
+
name=symbol.name,
|
|
283
|
+
qualname=symbol.qualname,
|
|
284
|
+
file_path=symbol.file_path,
|
|
285
|
+
kind=symbol.kind,
|
|
286
|
+
lineno=symbol.lineno,
|
|
287
|
+
end_lineno=symbol.end_lineno,
|
|
288
|
+
is_orphan=True,
|
|
289
|
+
is_transitive=False,
|
|
290
|
+
cluster_id=None,
|
|
291
|
+
raw_reachability_confidence=1.0,
|
|
292
|
+
semantic_classification=SemanticClassification.PUBLIC_API_SURFACE,
|
|
293
|
+
calibrated_confidence=0.05,
|
|
294
|
+
semantic_probabilities={
|
|
295
|
+
"PUBLIC_API_SURFACE": 0.95,
|
|
296
|
+
"INTERNAL_ORPHAN": 0.04,
|
|
297
|
+
"GENUINE_CRUFT": 0.01,
|
|
298
|
+
},
|
|
299
|
+
suppressed=True,
|
|
300
|
+
reason=f"Stage 1 Pruner: {reason}",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
def classify_ambiguous_symbol(
|
|
306
|
+
self,
|
|
307
|
+
symbol: Symbol,
|
|
308
|
+
dead_info: DeadSymbol,
|
|
309
|
+
) -> SemanticDeadSymbol:
|
|
310
|
+
"""
|
|
311
|
+
Stage 2: Packed Neural / Heuristic Semantic Classifier.
|
|
312
|
+
Evaluates ambiguous candidate symbols that survived Stage 1.
|
|
313
|
+
Vectorizes symbol representation via vectorize_symbol().
|
|
314
|
+
"""
|
|
315
|
+
vectorized_text = vectorize_symbol(symbol)
|
|
316
|
+
name_lower = symbol.name.lower()
|
|
317
|
+
doc_lower = (symbol.docstring or "").lower()
|
|
318
|
+
|
|
319
|
+
# Check for genuine cruft clues
|
|
320
|
+
is_cruft = any(k in name_lower or k in doc_lower for k in CRUFT_KEYWORDS)
|
|
321
|
+
if is_cruft:
|
|
322
|
+
classification = SemanticClassification.GENUINE_CRUFT
|
|
323
|
+
calibrated_confidence = 0.95
|
|
324
|
+
probs = {
|
|
325
|
+
"PUBLIC_API_SURFACE": 0.02,
|
|
326
|
+
"INTERNAL_ORPHAN": 0.08,
|
|
327
|
+
"GENUINE_CRUFT": 0.90,
|
|
328
|
+
}
|
|
329
|
+
reason = "Genuine cruft / obsolete code: matches cruft keyword in signature or docstring"
|
|
330
|
+
suppressed = False
|
|
331
|
+
elif symbol.is_exported and not is_test_or_internal_path(symbol.file_path):
|
|
332
|
+
# Exported but not caught by Stage 1
|
|
333
|
+
classification = SemanticClassification.PUBLIC_API_SURFACE
|
|
334
|
+
calibrated_confidence = 0.10
|
|
335
|
+
probs = {
|
|
336
|
+
"PUBLIC_API_SURFACE": 0.85,
|
|
337
|
+
"INTERNAL_ORPHAN": 0.10,
|
|
338
|
+
"GENUINE_CRUFT": 0.05,
|
|
339
|
+
}
|
|
340
|
+
reason = "Semantic classifier: exported library surface candidate"
|
|
341
|
+
suppressed = True
|
|
342
|
+
else:
|
|
343
|
+
# Internal orphan
|
|
344
|
+
classification = SemanticClassification.INTERNAL_ORPHAN
|
|
345
|
+
calibrated_confidence = 0.70
|
|
346
|
+
probs = {
|
|
347
|
+
"PUBLIC_API_SURFACE": 0.10,
|
|
348
|
+
"INTERNAL_ORPHAN": 0.80,
|
|
349
|
+
"GENUINE_CRUFT": 0.10,
|
|
350
|
+
}
|
|
351
|
+
reason = dead_info.reason or "Internal unreferenced helper"
|
|
352
|
+
suppressed = False
|
|
353
|
+
|
|
354
|
+
return SemanticDeadSymbol(
|
|
355
|
+
id=symbol.id,
|
|
356
|
+
name=symbol.name,
|
|
357
|
+
qualname=symbol.qualname,
|
|
358
|
+
file_path=symbol.file_path,
|
|
359
|
+
kind=symbol.kind,
|
|
360
|
+
lineno=symbol.lineno,
|
|
361
|
+
end_lineno=symbol.end_lineno,
|
|
362
|
+
is_orphan=dead_info.is_orphan,
|
|
363
|
+
is_transitive=dead_info.is_transitive,
|
|
364
|
+
cluster_id=dead_info.cluster_id,
|
|
365
|
+
raw_reachability_confidence=dead_info.confidence,
|
|
366
|
+
semantic_classification=classification,
|
|
367
|
+
calibrated_confidence=calibrated_confidence,
|
|
368
|
+
semantic_probabilities=probs,
|
|
369
|
+
suppressed=suppressed,
|
|
370
|
+
reason=reason,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def classify_candidates(
|
|
374
|
+
self,
|
|
375
|
+
candidates: List[Tuple[Symbol, DeadSymbol]],
|
|
376
|
+
suppress_public_api: bool = True,
|
|
377
|
+
) -> Tuple[List[SemanticDeadSymbol], List[SemanticDeadSymbol]]:
|
|
378
|
+
"""
|
|
379
|
+
Execute two-stage classification over candidate dead symbols.
|
|
380
|
+
Returns: (active_dead_symbols, suppressed_symbols)
|
|
381
|
+
"""
|
|
382
|
+
active_symbols: List[SemanticDeadSymbol] = []
|
|
383
|
+
suppressed_symbols: List[SemanticDeadSymbol] = []
|
|
384
|
+
|
|
385
|
+
ambiguous_candidates: List[Tuple[Symbol, DeadSymbol]] = []
|
|
386
|
+
|
|
387
|
+
# Stage 1: Fast deterministic pruner (< 5 ms)
|
|
388
|
+
for sym, d_sym in candidates:
|
|
389
|
+
stage1_res = self.prune_stage1_public(sym)
|
|
390
|
+
if stage1_res is not None:
|
|
391
|
+
# Stage 1 classified as PUBLIC_API_SURFACE
|
|
392
|
+
stage1_res.is_orphan = d_sym.is_orphan
|
|
393
|
+
stage1_res.is_transitive = d_sym.is_transitive
|
|
394
|
+
stage1_res.cluster_id = d_sym.cluster_id
|
|
395
|
+
stage1_res.raw_reachability_confidence = d_sym.confidence
|
|
396
|
+
|
|
397
|
+
if suppress_public_api:
|
|
398
|
+
stage1_res.suppressed = True
|
|
399
|
+
suppressed_symbols.append(stage1_res)
|
|
400
|
+
else:
|
|
401
|
+
stage1_res.suppressed = False
|
|
402
|
+
active_symbols.append(stage1_res)
|
|
403
|
+
else:
|
|
404
|
+
ambiguous_candidates.append((sym, d_sym))
|
|
405
|
+
|
|
406
|
+
# Stage 2: Ambiguous remainder evaluation
|
|
407
|
+
for sym, d_sym in ambiguous_candidates:
|
|
408
|
+
res = self.classify_ambiguous_symbol(sym, d_sym)
|
|
409
|
+
if res.semantic_classification == SemanticClassification.PUBLIC_API_SURFACE and suppress_public_api:
|
|
410
|
+
res.suppressed = True
|
|
411
|
+
suppressed_symbols.append(res)
|
|
412
|
+
else:
|
|
413
|
+
res.suppressed = False
|
|
414
|
+
active_symbols.append(res)
|
|
415
|
+
|
|
416
|
+
return active_symbols, suppressed_symbols
|