Try to resolve a single call reference. Returns (target_node_id, confidence).
(
*,
name: str,
receiver: str | None,
kind: str,
caller_id: str,
file_id: str,
caller_receiver_var: str | None,
caller_receiver_type: str | None,
caller_param_types: dict[str, str] | None,
registries: Registries,
)
| 145 | |
| 146 | |
| 147 | def _resolve_single_call( |
| 148 | *, |
| 149 | name: str, |
| 150 | receiver: str | None, |
| 151 | kind: str, |
| 152 | caller_id: str, |
| 153 | file_id: str, |
| 154 | caller_receiver_var: str | None, |
| 155 | caller_receiver_type: str | None, |
| 156 | caller_param_types: dict[str, str] | None, |
| 157 | registries: Registries, |
| 158 | ) -> tuple[str | None, float]: |
| 159 | """Try to resolve a single call reference. Returns (target_node_id, confidence).""" |
| 160 | |
| 161 | # Strategy 1: self/this → enclosing class children |
| 162 | if kind == "attribute" and receiver in ("self", "this"): |
| 163 | # The caller's parent in the ID is the class: file_id::ClassName::method |
| 164 | parts = caller_id.rsplit("::", 2) |
| 165 | if len(parts) >= 3: |
| 166 | class_name = parts[-2] |
| 167 | class_candidates = registries.class_registry.get(class_name, []) |
| 168 | for cls in class_candidates: |
| 169 | if cls.file_id == file_id: |
| 170 | for child in cls.children: |
| 171 | if child.name == name: |
| 172 | return child.node_id, 1.0 |
| 173 | return None, 0.0 |
| 174 | |
| 175 | # Strategy 2: Go receiver variable resolution |
| 176 | if kind == "attribute": |
| 177 | if caller_receiver_var and receiver == caller_receiver_var and caller_receiver_type: |
| 178 | candidates = registries.name_registry.get(name, []) |
| 179 | for c in candidates: |
| 180 | if c.receiver_type == caller_receiver_type: |
| 181 | return c.node_id, 1.0 |
| 182 | |
| 183 | # Strategy 2.5: Parameter type hint resolution |
| 184 | if kind == "attribute" and receiver: |
| 185 | if caller_param_types: |
| 186 | type_name = caller_param_types.get(receiver) |
| 187 | if type_name and type_name in registries.class_registry: |
| 188 | for cls in registries.class_registry[type_name]: |
| 189 | for child in cls.children: |
| 190 | if child.name == name: |
| 191 | return child.node_id, 0.7 |
| 192 | |
| 193 | # Strategy 3: ClassName.method() resolution |
| 194 | if kind == "attribute" and receiver in registries.class_registry: |
| 195 | class_candidates = registries.class_registry[receiver] |
| 196 | sorted_classes = sorted(class_candidates, key=lambda c: 0 if c.file_id == file_id else 1) |
| 197 | for cls in sorted_classes: |
| 198 | for child in cls.children: |
| 199 | if child.name == name: |
| 200 | conf = 1.0 if child.file_id == file_id else 0.9 |
| 201 | return child.node_id, conf |
| 202 | # Go-style methods with matching receiver_type |
| 203 | candidates = registries.name_registry.get(name, []) |
| 204 | for c in candidates: |