Resolve derivation references into DERIVED_FROM relationships. For each derivation ref: - identifier → scope-chain variable lookup - call → name_registry / file_registry function lookup - attribute → scope-chain for self.field (skip local scope), import-based otherwise
(
derivation_infos: list[DerivationInfo],
registries: Registries,
)
| 264 | |
| 265 | |
| 266 | def resolve_derivations( |
| 267 | derivation_infos: list[DerivationInfo], |
| 268 | registries: Registries, |
| 269 | ) -> list[GraphRelationship]: |
| 270 | """Resolve derivation references into DERIVED_FROM relationships. |
| 271 | |
| 272 | For each derivation ref: |
| 273 | - identifier → scope-chain variable lookup |
| 274 | - call → name_registry / file_registry function lookup |
| 275 | - attribute → scope-chain for self.field (skip local scope), import-based otherwise |
| 276 | """ |
| 277 | results: list[GraphRelationship] = [] |
| 278 | |
| 279 | for di in derivation_infos: |
| 280 | for name, receiver, kind in di.refs: |
| 281 | target_id: str | None = None |
| 282 | transform = kind |
| 283 | |
| 284 | if kind == "identifier": |
| 285 | target_id = _find_variable_in_scopes(name, di.scope_id, registries.variable_registry) |
| 286 | # Fallback: imported symbol (e.g., from other import CONSTANT) |
| 287 | if target_id is None: |
| 288 | file_imports = registries.import_registry.get(di.file_id, {}) |
| 289 | target_file_id = file_imports.get(name) |
| 290 | if target_file_id: |
| 291 | target_sym = registries.file_registry.get(target_file_id, {}).get(name) |
| 292 | if target_sym: |
| 293 | target_id = target_sym.node_id |
| 294 | |
| 295 | elif kind == "call": |
| 296 | if receiver: |
| 297 | # Attribute call: receiver.name() — try import-based |
| 298 | file_imports = registries.import_registry.get(di.file_id, {}) |
| 299 | target_file_id = file_imports.get(receiver) |
| 300 | if target_file_id: |
| 301 | target_names = registries.file_registry.get(target_file_id, {}) |
| 302 | target_sym = target_names.get(name) |
| 303 | if target_sym: |
| 304 | target_id = target_sym.node_id |
| 305 | else: |
| 306 | # Bare call: name() — try file_registry, import_registry, then name_registry |
| 307 | file_names = registries.file_registry.get(di.file_id, {}) |
| 308 | target_sym = file_names.get(name) |
| 309 | if target_sym: |
| 310 | target_id = target_sym.node_id |
| 311 | if target_id is None: |
| 312 | # Import-based: from X import func → func() |
| 313 | file_imports = registries.import_registry.get(di.file_id, {}) |
| 314 | target_file_id = file_imports.get(name) |
| 315 | if target_file_id: |
| 316 | target_sym = registries.file_registry.get(target_file_id, {}).get(name) |
| 317 | if target_sym: |
| 318 | target_id = target_sym.node_id |
| 319 | if target_id is None: |
| 320 | candidates = registries.name_registry.get(name, []) |
| 321 | if len(candidates) == 1: |
| 322 | target_id = candidates[0].node_id |
| 323 |