adl-toolkit 0.0.0-stage → 0.1.1
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.
- package/INSTALL_TOOLKIT.md +163 -0
- package/README.md +269 -2
- package/adl_drift/__init__.py +6 -0
- package/adl_drift/cli/__init__.py +1 -0
- package/adl_drift/cli/commands.py +993 -0
- package/adl_drift/cli/main.py +744 -0
- package/adl_drift/cortex/__init__.py +1 -0
- package/adl_drift/cortex/embeddings.py +96 -0
- package/adl_drift/cortex/memory.py +1749 -0
- package/adl_drift/cortex/patterns.py +89 -0
- package/adl_drift/cortex/telemetry.py +192 -0
- package/adl_drift/detectors/__init__.py +1 -0
- package/adl_drift/detectors/async_patterns.py +115 -0
- package/adl_drift/detectors/base.py +121 -0
- package/adl_drift/detectors/config.py +122 -0
- package/adl_drift/detectors/data_access.py +128 -0
- package/adl_drift/detectors/decorators.py +122 -0
- package/adl_drift/detectors/dynamic_patterns.py +260 -0
- package/adl_drift/detectors/errors.py +134 -0
- package/adl_drift/detectors/imports.py +132 -0
- package/adl_drift/detectors/logging_patterns.py +108 -0
- package/adl_drift/detectors/routes.py +257 -0
- package/adl_drift/detectors/security.py +236 -0
- package/adl_drift/detectors/taint.py +406 -0
- package/adl_drift/detectors/tests.py +95 -0
- package/adl_drift/detectors/type_conventions.py +81 -0
- package/adl_drift/graph/__init__.py +1 -0
- package/adl_drift/graph/builder.py +2670 -0
- package/adl_drift/graph/cfg.py +461 -0
- package/adl_drift/graph/cluster.py +40 -0
- package/adl_drift/graph/edges.py +86 -0
- package/adl_drift/graph/node.py +90 -0
- package/adl_drift/harness/__init__.py +10 -0
- package/adl_drift/harness/setup.py +832 -0
- package/adl_drift/harness/skills/drift-antigravity.md +19 -0
- package/adl_drift/harness/skills/drift-generic.md +19 -0
- package/adl_drift/harness/skills/drift.md +25 -0
- package/adl_drift/harness/watch.py +84 -0
- package/adl_drift/lang/__init__.py +8 -0
- package/adl_drift/lang/base.py +464 -0
- package/adl_drift/lang/java/__init__.py +6 -0
- package/adl_drift/lang/java/backend.py +97 -0
- package/adl_drift/lang/java/cfg.py +1424 -0
- package/adl_drift/lang/java/config.py +707 -0
- package/adl_drift/lang/java/detectors/__init__.py +29 -0
- package/adl_drift/lang/java/detectors/additional.py +445 -0
- package/adl_drift/lang/java/detectors/base.py +154 -0
- package/adl_drift/lang/java/detectors/data_access.py +148 -0
- package/adl_drift/lang/java/detectors/imports.py +53 -0
- package/adl_drift/lang/java/detectors/routes.py +159 -0
- package/adl_drift/lang/java/detectors/security.py +386 -0
- package/adl_drift/lang/java/detectors/tests.py +74 -0
- package/adl_drift/lang/java/extractor.py +582 -0
- package/adl_drift/lang/java/ids.py +99 -0
- package/adl_drift/lang/java/parser.py +49 -0
- package/adl_drift/lang/java/resolution/__init__.py +1 -0
- package/adl_drift/lang/java/resolution/constants.py +1619 -0
- package/adl_drift/lang/java/resolution/constructors.py +278 -0
- package/adl_drift/lang/java/resolution/context.py +305 -0
- package/adl_drift/lang/java/resolution/dispatch.py +540 -0
- package/adl_drift/lang/java/resolution/functional.py +1318 -0
- package/adl_drift/lang/java/resolution/generated.py +95 -0
- package/adl_drift/lang/java/resolution/model.py +153 -0
- package/adl_drift/lang/java/resolution/orchestration.py +866 -0
- package/adl_drift/lang/java/resolution/overloads.py +1275 -0
- package/adl_drift/lang/java/resolution/scopes.py +1111 -0
- package/adl_drift/lang/java/resolution/types.py +2947 -0
- package/adl_drift/lang/java/resolver.py +1401 -0
- package/adl_drift/lang/java/scopes.py +69 -0
- package/adl_drift/lang/java/taint.py +500 -0
- package/adl_drift/lang/java/type_provenance.py +342 -0
- package/adl_drift/lang/python/__init__.py +8 -0
- package/adl_drift/lang/python/backend.py +94 -0
- package/adl_drift/lang/python/cfg.py +537 -0
- package/adl_drift/lang/python/extractor.py +454 -0
- package/adl_drift/lang/python/parser.py +41 -0
- package/adl_drift/lang/python/resolver.py +1789 -0
- package/adl_drift/lang/python/taint.py +365 -0
- package/adl_drift/lang/registry.py +117 -0
- package/adl_drift/parser/__init__.py +1 -0
- package/adl_drift/parser/cache.py +38 -0
- package/adl_drift/parser/core.py +33 -0
- package/adl_drift/parser/extractor.py +17 -0
- package/adl_drift/parser/literal_eval.py +326 -0
- package/adl_drift/scanner/__init__.py +1 -0
- package/adl_drift/scanner/discovery.py +338 -0
- package/adl_drift/scanner/incremental.py +261 -0
- package/adl_drift/scanner/parallel.py +224 -0
- package/adl_drift/server/__init__.py +1 -0
- package/adl_drift/server/handlers.py +2849 -0
- package/adl_drift/server/mcp_server.py +127 -0
- package/adl_drift/server/mcp_stdio.py +335 -0
- package/adl_drift/server/protocol.py +71 -0
- package/adl_drift/server/static/index.html +696 -0
- package/adl_drift/server/web.py +350 -0
- package/adl_drift/utils/__init__.py +1 -0
- package/adl_drift/utils/config.py +77 -0
- package/adl_drift/utils/fswalk.py +52 -0
- package/adl_drift/utils/types.py +345 -0
- package/bin/adl-drift.mjs +35 -0
- package/bin/adl-toolkit.mjs +488 -0
- package/package.json +29 -4
- package/pyproject.toml +49 -0
- package/scripts/install_toolkit.py +3584 -0
|
@@ -0,0 +1,1275 @@
|
|
|
1
|
+
"""Java overload selection: member lookup, applicability, and ranking."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import deque
|
|
6
|
+
from typing import Callable, Dict, FrozenSet, List, Optional, Set, Tuple, Union
|
|
7
|
+
|
|
8
|
+
from adl_drift.graph.edges import EdgeType
|
|
9
|
+
from adl_drift.graph.node import NodeType
|
|
10
|
+
from adl_drift.lang.java.ids import erased_type
|
|
11
|
+
from adl_drift.lang.java.resolution.constants import (
|
|
12
|
+
JAVA_EXTERNAL_SUBTYPE_STUBS,
|
|
13
|
+
JAVA_EXTERNAL_TYPES,
|
|
14
|
+
JAVA_LANG_SIMPLE_TYPES,
|
|
15
|
+
_JAVA_FUNCTIONAL_INTERFACE_SHAPE,
|
|
16
|
+
_JAVA_FUNCTIONAL_INTERFACES,
|
|
17
|
+
_LAMBDA_ARG_TYPE,
|
|
18
|
+
_NULL_ARG_TYPE,
|
|
19
|
+
_UNKNOWN_ARG_TYPE,
|
|
20
|
+
argument_nodes,
|
|
21
|
+
)
|
|
22
|
+
from adl_drift.lang.java.resolution.model import (
|
|
23
|
+
ImportContext as _ImportContext,
|
|
24
|
+
LocalScopes as _LocalScopes,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OverloadResolutionMixin:
|
|
29
|
+
def _lexical_outer_class_ids(self, class_id: str) -> List[str]:
|
|
30
|
+
"""Nearest-to-farthest source classes lexically enclosing `class_id`."""
|
|
31
|
+
if ":" not in class_id:
|
|
32
|
+
return []
|
|
33
|
+
module, qualname = class_id.rsplit(":", 1)
|
|
34
|
+
parts = qualname.split(".")
|
|
35
|
+
out: List[str] = []
|
|
36
|
+
for end in range(len(parts) - 1, 0, -1):
|
|
37
|
+
candidate = f"{module}:{'.'.join(parts[:end])}"
|
|
38
|
+
node = self.builder._nodes.get(candidate)
|
|
39
|
+
if node is not None and node.node_type == NodeType.CLASS:
|
|
40
|
+
out.append(candidate)
|
|
41
|
+
return out
|
|
42
|
+
|
|
43
|
+
def _top_level_class_id(self, class_id: str) -> str:
|
|
44
|
+
"""The outermost lexically-enclosing class id for `class_id`
|
|
45
|
+
(itself, if already top-level). JLS 6.6.1: a `private` member is
|
|
46
|
+
accessible anywhere within the body of the *top level* class that
|
|
47
|
+
encloses its declaration — not merely from its own literal
|
|
48
|
+
declaring class — so a nested class may freely call a `private`
|
|
49
|
+
method of its enclosing class (and vice versa, and sibling nested
|
|
50
|
+
classes of one another) as long as both share the same top-level
|
|
51
|
+
ancestor. `_lexical_outer_class_ids` is nearest-to-farthest, so the
|
|
52
|
+
last entry is the farthest (top-level) one."""
|
|
53
|
+
outers = self._lexical_outer_class_ids(class_id)
|
|
54
|
+
return outers[-1] if outers else class_id
|
|
55
|
+
|
|
56
|
+
def _contains_children(self, node_id: str) -> List[str]:
|
|
57
|
+
"""Every node directly `CONTAINS`-ed by `node_id` (class members,
|
|
58
|
+
module classes, ...). Memoized per `node_id` (`_contains_children_cache`,
|
|
59
|
+
reset alongside `_declared_direct_ancestors`/`_class_ancestors_cache`
|
|
60
|
+
at the start of every resolution pass): every CONTAINS edge, including
|
|
61
|
+
generated member stubs, is fully established before call-site
|
|
62
|
+
resolution starts (`_build_resolution_indexes`) and never changes
|
|
63
|
+
mid-pass, yet this was called 480K times on JUnit5 with a fresh
|
|
64
|
+
NetworkX `out_edges` traversal every time — fix #5, same shape as
|
|
65
|
+
fix #4's `_direct_ancestors` (§8 bước 7 profiling: this was the
|
|
66
|
+
largest remaining self-time entry after fix #3+#4 landed)."""
|
|
67
|
+
cached = self._contains_children_cache.get(node_id)
|
|
68
|
+
if cached is not None:
|
|
69
|
+
return cached
|
|
70
|
+
if node_id not in self.builder.graph:
|
|
71
|
+
result: List[str] = []
|
|
72
|
+
else:
|
|
73
|
+
result = [
|
|
74
|
+
t for _, t, d in self.builder.graph.out_edges(node_id, data=True)
|
|
75
|
+
if d.get("type") == EdgeType.CONTAINS.name
|
|
76
|
+
]
|
|
77
|
+
self._contains_children_cache[node_id] = result
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
def _declaring_class_id(self, member_id: str) -> Optional[str]:
|
|
81
|
+
"""The fully-qualified id of `member_id`'s immediate declaring
|
|
82
|
+
class, via the reverse CONTAINS edge `graph/builder.py` wires from
|
|
83
|
+
`Symbol.scope_chain` (`f"{module}:{'.'.join(chain)}"`).
|
|
84
|
+
|
|
85
|
+
Unlike `GraphNode.contained_in` — which `extractor.py::
|
|
86
|
+
_find_parent_class` deliberately keeps as the coarser *nearest
|
|
87
|
+
enclosing simple name* ("Box"), by its own docstring a separate,
|
|
88
|
+
coarser field than the scope-chain uniqueness key — this is
|
|
89
|
+
qualified even for a nested class ("Outer.Box"), so it actually
|
|
90
|
+
matches how `_class_type_params`/INHERITS/IMPLEMENTS class ids are
|
|
91
|
+
keyed (both built off the same scope-chain-qualified node ids).
|
|
92
|
+
A bare `f"{module}:{contained_in}"` lookup silently misses for any
|
|
93
|
+
nested class member (F-01)."""
|
|
94
|
+
cache = self._declaring_class_cache
|
|
95
|
+
if cache is not None and member_id in cache:
|
|
96
|
+
return cache[member_id]
|
|
97
|
+
result = self._declaring_class_id_uncached(member_id)
|
|
98
|
+
if cache is not None:
|
|
99
|
+
cache[member_id] = result
|
|
100
|
+
return result
|
|
101
|
+
|
|
102
|
+
def _declaring_class_id_uncached(self, member_id: str) -> Optional[str]:
|
|
103
|
+
if member_id not in self.builder.graph:
|
|
104
|
+
return None
|
|
105
|
+
for source, _, data in self.builder.graph.in_edges(member_id, data=True):
|
|
106
|
+
if data.get("type") != EdgeType.CONTAINS.name:
|
|
107
|
+
continue
|
|
108
|
+
source_node = self.builder._nodes.get(source)
|
|
109
|
+
if source_node is not None and source_node.node_type == NodeType.CLASS:
|
|
110
|
+
return source
|
|
111
|
+
# Methods inside an anonymous class have a synthetic scope segment
|
|
112
|
+
# (`Outer.$1.method()`), but no standalone CLASS node for `$1`, so the
|
|
113
|
+
# shared builder cannot attach a CONTAINS edge to that immediate
|
|
114
|
+
# scope. Recover the nearest real lexical type from the qualified
|
|
115
|
+
# member id. This is also what Java uses to resolve an unqualified
|
|
116
|
+
# nested type such as `Keys` inside an enum-constant body.
|
|
117
|
+
member = self.builder._nodes.get(member_id)
|
|
118
|
+
if member is None:
|
|
119
|
+
return None
|
|
120
|
+
prefix = f"{member.module}:"
|
|
121
|
+
if not member_id.startswith(prefix) or "(" not in member_id:
|
|
122
|
+
return None
|
|
123
|
+
method_head = member_id[len(prefix):].rsplit("(", 1)[0]
|
|
124
|
+
if "." not in method_head:
|
|
125
|
+
return None
|
|
126
|
+
owner = method_head.rsplit(".", 1)[0]
|
|
127
|
+
while owner:
|
|
128
|
+
candidate = f"{member.module}:{owner}"
|
|
129
|
+
node = self.builder._nodes.get(candidate)
|
|
130
|
+
if node is not None and node.node_type == NodeType.CLASS:
|
|
131
|
+
return candidate
|
|
132
|
+
if "." not in owner:
|
|
133
|
+
break
|
|
134
|
+
owner = owner.rsplit(".", 1)[0]
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
def _direct_ancestors(self, class_id: str, edge_type: EdgeType) -> List[str]:
|
|
138
|
+
# `_declared_direct_ancestors` is fully populated before any call site
|
|
139
|
+
# resolution runs (orchestration.py's `_build_resolution_indexes`), so
|
|
140
|
+
# checking it first never changes which branch below is taken — it
|
|
141
|
+
# only skips the expensive NetworkX out_edges traversal on every one
|
|
142
|
+
# of the millions of calls this makes where the cache already has the
|
|
143
|
+
# answer (P0 profiling: this line was ~22% of JUnit5 resolve_calls
|
|
144
|
+
# time before the reorder, almost entirely wasted work on cache hits).
|
|
145
|
+
key = (class_id, edge_type)
|
|
146
|
+
if key in self._declared_direct_ancestors:
|
|
147
|
+
return self._declared_direct_ancestors[key]
|
|
148
|
+
# Graph-only fallback supports partial/incremental graphs whose
|
|
149
|
+
# declaring AnalysisResult was unavailable to this resolver pass —
|
|
150
|
+
# in practice this is every external/JDK class_id, since only
|
|
151
|
+
# project source classes get an entry above. Without a supporting
|
|
152
|
+
# AnalysisResult these edges cannot change mid-pass, so the result is
|
|
153
|
+
# written back into the same dict once computed: bottleneck #4
|
|
154
|
+
# profiling (JAVA_PERFORMANCE_OPTIMIZATION_PLAN.md §8 bước 7) found
|
|
155
|
+
# ~79% of all _direct_ancestors calls on JUnit5 missed the cache and
|
|
156
|
+
# re-walked NetworkX's out_edges for the same handful of common
|
|
157
|
+
# external ancestors (Object, Comparable, ...) millions of times.
|
|
158
|
+
if class_id not in self.builder.graph:
|
|
159
|
+
result: List[str] = []
|
|
160
|
+
else:
|
|
161
|
+
edge_type_name = edge_type.name
|
|
162
|
+
result = [
|
|
163
|
+
t for _, t, d in self.builder.graph.out_edges(class_id, data=True)
|
|
164
|
+
if d.get("type") == edge_type_name
|
|
165
|
+
]
|
|
166
|
+
self._declared_direct_ancestors[key] = result
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
def _class_ancestors(self, class_id: str) -> List[str]:
|
|
170
|
+
"""BFS over INHERITS ∪ IMPLEMENTS, nearest first — a class's own
|
|
171
|
+
superclass and interfaces before their own ancestors in turn.
|
|
172
|
+
|
|
173
|
+
Memoized per class_id (`_class_ancestors_cache`, reset alongside
|
|
174
|
+
`_declared_direct_ancestors` at the start of every resolution pass in
|
|
175
|
+
`_build_resolution_indexes`): the INHERITS/IMPLEMENTS edges this BFS
|
|
176
|
+
walks are fully established before call-site resolution starts and
|
|
177
|
+
never change mid-pass, yet this was called 748K times on JUnit5 for a
|
|
178
|
+
graph of only ~3.7K classes — the same class's ancestor chain
|
|
179
|
+
recomputed from scratch on almost every call (§8 bước 7 profiling).
|
|
180
|
+
"""
|
|
181
|
+
cached = self._class_ancestors_cache.get(class_id)
|
|
182
|
+
if cached is not None:
|
|
183
|
+
return cached
|
|
184
|
+
seen: Set[str] = {class_id}
|
|
185
|
+
q: deque = deque(
|
|
186
|
+
self._direct_ancestors(class_id, EdgeType.INHERITS)
|
|
187
|
+
+ self._direct_ancestors(class_id, EdgeType.IMPLEMENTS)
|
|
188
|
+
)
|
|
189
|
+
out: List[str] = []
|
|
190
|
+
while q:
|
|
191
|
+
cur = q.popleft()
|
|
192
|
+
if cur in seen:
|
|
193
|
+
continue
|
|
194
|
+
seen.add(cur)
|
|
195
|
+
out.append(cur)
|
|
196
|
+
q.extend(self._direct_ancestors(cur, EdgeType.INHERITS))
|
|
197
|
+
q.extend(self._direct_ancestors(cur, EdgeType.IMPLEMENTS))
|
|
198
|
+
self._class_ancestors_cache[class_id] = out
|
|
199
|
+
return out
|
|
200
|
+
|
|
201
|
+
def _all_field_types(self, class_id: str) -> Dict[str, str]:
|
|
202
|
+
"""`{field_name: declared_type_text}` for `class_id`, merged with
|
|
203
|
+
every INHERITS/IMPLEMENTS ancestor's fields not shadowed by a
|
|
204
|
+
closer declaration — the field analogue of `_members_named`'s
|
|
205
|
+
own+ancestor merge, needed because `self._field_types` is keyed per
|
|
206
|
+
declaring class only (built once in `resolve_calls()` from each
|
|
207
|
+
field's own `contained_in`), so a field declared on an ancestor was
|
|
208
|
+
otherwise invisible to a subclass's `this.field.method()` chain."""
|
|
209
|
+
def declared_fields(owner_id: str) -> Dict[str, str]:
|
|
210
|
+
fields = dict(self._field_types.get(owner_id, {}))
|
|
211
|
+
owner = self.builder._nodes.get(owner_id)
|
|
212
|
+
if owner is not None:
|
|
213
|
+
# A record component declares a private final field even
|
|
214
|
+
# though there is no `field_declaration` CST node for the
|
|
215
|
+
# extractor to emit. Treat compiler-mandated component
|
|
216
|
+
# fields exactly like explicit fields for bare/`this` and
|
|
217
|
+
# pattern-variable receiver chains.
|
|
218
|
+
fields.update(dict(owner.metadata.get("record_components", {})))
|
|
219
|
+
return fields
|
|
220
|
+
|
|
221
|
+
merged: Dict[str, str] = {}
|
|
222
|
+
for anc in reversed(self._class_ancestors(class_id)):
|
|
223
|
+
fields = declared_fields(anc)
|
|
224
|
+
# A field declared as `T ctx` on `Base<T>` is a `ClassCtx` on
|
|
225
|
+
# `Sub extends Base<ClassCtx>` (JLS 8.1.4 / 4.5): substitute the
|
|
226
|
+
# ancestor's class variables through the inheritance edge the
|
|
227
|
+
# same way method return types already are.
|
|
228
|
+
bindings = self._compose_type_var_bindings(class_id, [], anc)
|
|
229
|
+
if bindings:
|
|
230
|
+
fields = {
|
|
231
|
+
name: self._replace_type_variables(text, bindings)
|
|
232
|
+
for name, text in fields.items()
|
|
233
|
+
}
|
|
234
|
+
merged.update(fields)
|
|
235
|
+
merged.update(declared_fields(class_id))
|
|
236
|
+
return merged
|
|
237
|
+
|
|
238
|
+
def _visible_bare_field_types(self, class_id: str) -> Dict[str, str]:
|
|
239
|
+
"""Fields visible to an unqualified name inside ``class_id``.
|
|
240
|
+
|
|
241
|
+
A non-static nested/local class may refer to fields of its lexical
|
|
242
|
+
enclosing instances without an explicit ``Outer.this`` qualifier.
|
|
243
|
+
Merge farthest-to-nearest lexical owners, then the current class, so
|
|
244
|
+
normal Java shadowing keeps the closest declaration. Explicit
|
|
245
|
+
``this.field`` resolution deliberately continues to use
|
|
246
|
+
``_all_field_types(class_id)`` and therefore cannot accidentally bind
|
|
247
|
+
an outer-instance field.
|
|
248
|
+
"""
|
|
249
|
+
current = self.builder._nodes.get(class_id)
|
|
250
|
+
if current is not None and "static" in current.metadata.get("modifiers", ()):
|
|
251
|
+
return self._all_field_types(class_id)
|
|
252
|
+
accessible_outers: List[str] = []
|
|
253
|
+
for outer_id in self._lexical_outer_class_ids(class_id):
|
|
254
|
+
accessible_outers.append(outer_id)
|
|
255
|
+
outer = self.builder._nodes.get(outer_id)
|
|
256
|
+
if outer is not None and "static" in outer.metadata.get("modifiers", ()):
|
|
257
|
+
break
|
|
258
|
+
merged: Dict[str, str] = {}
|
|
259
|
+
for outer_id in reversed(accessible_outers):
|
|
260
|
+
merged.update(self._all_field_types(outer_id))
|
|
261
|
+
merged.update(self._all_field_types(class_id))
|
|
262
|
+
return merged
|
|
263
|
+
|
|
264
|
+
def _erased_signature(self, func_id: str) -> Tuple[str, ...]:
|
|
265
|
+
"""Erased positional parameter-type tuple, used to tell a true
|
|
266
|
+
override (same name + same erased signature) apart from an overload
|
|
267
|
+
(same name, different signature) when merging inherited members.
|
|
268
|
+
|
|
269
|
+
Memoized per `func_id` (`_erased_signature_cache`, reset alongside
|
|
270
|
+
the other per-run caches in `_build_resolution_indexes`): purely a
|
|
271
|
+
function of `_param_type_lists[func_id]`, which is fixed for the
|
|
272
|
+
whole resolution pass, yet `_members_named` recomputes it for every
|
|
273
|
+
candidate on every lookup — a large overload family (e.g. a
|
|
274
|
+
project's own `assertEquals`-shaped API with dozens of siblings)
|
|
275
|
+
gets looked up at every call site sharing that name, so the same
|
|
276
|
+
handful of func_ids' signatures were being re-erased tens of
|
|
277
|
+
thousands of times before this cache."""
|
|
278
|
+
cache = getattr(self, "_erased_signature_cache", None)
|
|
279
|
+
if cache is not None:
|
|
280
|
+
cached = cache.get(func_id)
|
|
281
|
+
if cached is not None:
|
|
282
|
+
return cached
|
|
283
|
+
sig = tuple(erased_type(t) for t in self._param_type_lists.get(func_id, []))
|
|
284
|
+
if cache is not None:
|
|
285
|
+
cache[func_id] = sig
|
|
286
|
+
return sig
|
|
287
|
+
|
|
288
|
+
def _members_named(
|
|
289
|
+
self, class_id: str, name: str, search_ancestors: bool, search_self: bool = True,
|
|
290
|
+
exclude_constructors: bool = False, caller_class_id: Optional[str] = None,
|
|
291
|
+
) -> List[str]:
|
|
292
|
+
"""Every FUNCTION child of `class_id` named `name`, plus — iff
|
|
293
|
+
`search_ancestors` — every same-named overload across the *entire*
|
|
294
|
+
INHERITS/IMPLEMENTS ancestor chain, not just the nearest ancestor
|
|
295
|
+
tier that declares one: a name declared two-or-more levels up
|
|
296
|
+
(`Child extends Parent extends GrandParent`) stays a candidate as
|
|
297
|
+
long as no *closer* class (own class, or a nearer ancestor already
|
|
298
|
+
walked) overrides it with an identical erased signature — that's a
|
|
299
|
+
true override and hides it; a differently-signed overload does not,
|
|
300
|
+
and both must stay candidates for `_select_overload`. When
|
|
301
|
+
`search_self` is False, the own class is never consulted, not even
|
|
302
|
+
for override exclusion — this is `super.m()`'s path, which must
|
|
303
|
+
always reach an ancestor's method regardless of the child's own
|
|
304
|
+
override.
|
|
305
|
+
|
|
306
|
+
A Java constructor's node is named after its class's own simple
|
|
307
|
+
name, so a plain `method_invocation` lookup for a method that
|
|
308
|
+
happens to share the class's name (e.g. implementing an interface
|
|
309
|
+
method named `Foo` inside a class also named `Foo`) would otherwise
|
|
310
|
+
be indistinguishable from the constructor. `exclude_constructors`
|
|
311
|
+
drops any candidate flagged `metadata["constructor"]`; callers that
|
|
312
|
+
deliberately look up a class's own name to find its constructor(s)
|
|
313
|
+
(`new T(...)`, `this(...)`, `super(...)`) leave it False."""
|
|
314
|
+
def is_visible(candidate_id: str) -> bool:
|
|
315
|
+
if caller_class_id is None:
|
|
316
|
+
return True
|
|
317
|
+
node = self.builder._nodes.get(candidate_id)
|
|
318
|
+
if node is None or "private" not in node.metadata.get("modifiers", ()):
|
|
319
|
+
return True
|
|
320
|
+
declaring = self._declaring_class_id(candidate_id)
|
|
321
|
+
if declaring is None:
|
|
322
|
+
return True
|
|
323
|
+
return self._top_level_class_id(declaring) == self._top_level_class_id(
|
|
324
|
+
caller_class_id,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
own = [
|
|
328
|
+
c for c in self._function_members_named(class_id, name)
|
|
329
|
+
if not (exclude_constructors and self.builder._nodes[c].metadata.get("constructor"))
|
|
330
|
+
and is_visible(c)
|
|
331
|
+
]
|
|
332
|
+
if not search_ancestors:
|
|
333
|
+
return own if search_self else []
|
|
334
|
+
collected: List[str] = list(own) if search_self else []
|
|
335
|
+
shadowed = {self._erased_signature(c) for c in collected}
|
|
336
|
+
for anc in self._class_ancestors(class_id):
|
|
337
|
+
for c in self._function_members_named(anc, name):
|
|
338
|
+
n = self.builder._nodes[c]
|
|
339
|
+
if exclude_constructors and n.metadata.get("constructor"):
|
|
340
|
+
continue
|
|
341
|
+
if not is_visible(c):
|
|
342
|
+
continue
|
|
343
|
+
sig = self._erased_signature(c)
|
|
344
|
+
if sig in shadowed:
|
|
345
|
+
continue
|
|
346
|
+
collected.append(c)
|
|
347
|
+
shadowed.add(sig)
|
|
348
|
+
return collected
|
|
349
|
+
|
|
350
|
+
def _function_members_named(self, class_id: str, name: str) -> List[str]:
|
|
351
|
+
"""FUNCTION children of `class_id` named `name`, in `_contains_children`
|
|
352
|
+
order. Built once per class from `_contains_children` (same source of
|
|
353
|
+
truth `_members_named` scanned linearly on every call) and memoized in
|
|
354
|
+
`_members_by_name_cache`, reset with the other per-run caches."""
|
|
355
|
+
cache = self._members_by_name_cache
|
|
356
|
+
index = cache.get(class_id) if cache is not None else None
|
|
357
|
+
if index is None:
|
|
358
|
+
index = {}
|
|
359
|
+
for c in self._contains_children(class_id):
|
|
360
|
+
n = self.builder._nodes.get(c)
|
|
361
|
+
if n is not None and n.node_type == NodeType.FUNCTION:
|
|
362
|
+
index.setdefault(n.name, []).append(c)
|
|
363
|
+
if cache is not None:
|
|
364
|
+
cache[class_id] = index
|
|
365
|
+
return index.get(name, ())
|
|
366
|
+
|
|
367
|
+
def _arity_matches(self, func_id: str, arg_count: int) -> bool:
|
|
368
|
+
types = self._param_type_lists.get(func_id, [])
|
|
369
|
+
if len(types) == arg_count:
|
|
370
|
+
return True
|
|
371
|
+
if types and self._is_vararg_param(types[-1]):
|
|
372
|
+
# varargs-shaped last param accepts >= the fixed-arity prefix.
|
|
373
|
+
return arg_count >= len(types) - 1
|
|
374
|
+
return False
|
|
375
|
+
|
|
376
|
+
def _is_vararg_candidate(self, func_id: str) -> bool:
|
|
377
|
+
"""True iff `func_id`'s *last* declared parameter is a true vararg
|
|
378
|
+
(`T...`), i.e. this candidate can only be reached in JLS's phase-3
|
|
379
|
+
"variable arity invocation" — never in phase 1/2 fixed-arity form
|
|
380
|
+
except the single "array form" case `_param_types_match`/
|
|
381
|
+
`_param_types_compatible` already special-case. Used by
|
|
382
|
+
`_select_overload` to keep phase-3-only candidates from competing
|
|
383
|
+
with a phase-1/2-applicable fixed-arity sibling — see there."""
|
|
384
|
+
raw_types = self._param_type_lists.get(func_id, [])
|
|
385
|
+
return bool(raw_types) and self._is_vararg_param(raw_types[-1])
|
|
386
|
+
|
|
387
|
+
def _is_phase3_only(
|
|
388
|
+
self, func_id: str, arg_types: List[str], arg_ctx: "_ImportContext",
|
|
389
|
+
) -> bool:
|
|
390
|
+
"""True iff, *for this particular call*, `func_id` is reachable only
|
|
391
|
+
through JLS §15.12.2 phase 3 (variable-arity, element-wise vararg
|
|
392
|
+
expansion) — and so must not compete with a phase-1/2-applicable
|
|
393
|
+
sibling. Unlike `_is_vararg_candidate`, which classifies purely by
|
|
394
|
+
declaration shape (`T...`), this is invocation-form-aware: a
|
|
395
|
+
vararg-declaring method still has a phase-1/2 *array form* (JLS
|
|
396
|
+
§15.12.2.1) when the caller passes exactly the declared arity with a
|
|
397
|
+
final argument assignable to the vararg parameter's array type
|
|
398
|
+
(`m(String[] arr)` against `m(String...)`), and in that form it is a
|
|
399
|
+
legitimate phase-1/2 candidate, not a phase-3-only one.
|
|
400
|
+
|
|
401
|
+
A fixed-arity candidate is never phase-3-only. A vararg candidate is
|
|
402
|
+
phase-3-only unless the call matches its array form — i.e. the arg
|
|
403
|
+
count differs from the declared parameter count (element-wise
|
|
404
|
+
expansion is the only fit), or the single trailing argument is not
|
|
405
|
+
`_types_compatible` with the vararg array type (`save(1)` against
|
|
406
|
+
`save(int...)`: `int` is not assignable to `int[]`, so array form
|
|
407
|
+
fails and this stays phase 3, preserving the P4.4 regression fix that
|
|
408
|
+
lets a `save(long)` sibling win). The array-form gate mirrors the
|
|
409
|
+
array-form branch `_param_types_match`/`_param_types_compatible`
|
|
410
|
+
already special-case, using the loosest phase (`allow_boxing=True`)
|
|
411
|
+
so classification never *under*-includes a candidate those two
|
|
412
|
+
methods would still accept in a tier."""
|
|
413
|
+
if not self._is_vararg_candidate(func_id):
|
|
414
|
+
return False
|
|
415
|
+
types = [erased_type(t) for t in self._param_type_lists.get(func_id, [])]
|
|
416
|
+
if len(arg_types) != len(types):
|
|
417
|
+
return True
|
|
418
|
+
param_ctx = self._param_ctx_for(func_id)
|
|
419
|
+
vararg_type = types[-1]
|
|
420
|
+
trailing = arg_types[len(types) - 1:]
|
|
421
|
+
if len(trailing) == 1 and self._types_compatible(
|
|
422
|
+
vararg_type, trailing[0], param_ctx, arg_ctx, allow_boxing=True,
|
|
423
|
+
):
|
|
424
|
+
return False # array form: a phase-1/2 candidate for this call.
|
|
425
|
+
return True
|
|
426
|
+
|
|
427
|
+
@staticmethod
|
|
428
|
+
def _is_vararg_param(raw_type_text: str) -> bool:
|
|
429
|
+
"""True iff the *source* declaration used `T...`, not a plain `T[]`.
|
|
430
|
+
|
|
431
|
+
Must check the raw (pre-erasure) text: `erased_type()` normalizes
|
|
432
|
+
`...` to `[]` for type-identity purposes, which would otherwise make
|
|
433
|
+
a plain array-typed last parameter (`Object[] rows`, exactly one
|
|
434
|
+
argument required) indistinguishable from true varargs
|
|
435
|
+
(`Object... rows`, zero-or-more arguments) — the two accept
|
|
436
|
+
different call-site arities and must not be conflated here.
|
|
437
|
+
"""
|
|
438
|
+
return raw_type_text.strip().endswith("...")
|
|
439
|
+
|
|
440
|
+
# JLS 5.1.2 widening primitive conversion: a value of the key type may be
|
|
441
|
+
# passed where any type in the value set is declared.
|
|
442
|
+
_PRIMITIVE_WIDENING: Dict[str, FrozenSet[str]] = {
|
|
443
|
+
"byte": frozenset({"short", "int", "long", "float", "double"}),
|
|
444
|
+
"short": frozenset({"int", "long", "float", "double"}),
|
|
445
|
+
"char": frozenset({"int", "long", "float", "double"}),
|
|
446
|
+
"int": frozenset({"long", "float", "double"}),
|
|
447
|
+
"long": frozenset({"float", "double"}),
|
|
448
|
+
"float": frozenset({"double"}),
|
|
449
|
+
}
|
|
450
|
+
# JLS 5.1.7/5.1.8 boxing/unboxing conversion, checked in both directions.
|
|
451
|
+
_BOXED_PRIMITIVE: Dict[str, str] = {
|
|
452
|
+
"int": "Integer", "long": "Long", "double": "Double", "float": "Float",
|
|
453
|
+
"boolean": "Boolean", "char": "Character", "byte": "Byte", "short": "Short",
|
|
454
|
+
}
|
|
455
|
+
# Reverse of `_BOXED_PRIMITIVE` (wrapper -> primitive), used to unbox an
|
|
456
|
+
# arg type before checking primitive widening — see `_types_compatible`.
|
|
457
|
+
_UNBOXED_PRIMITIVE: Dict[str, str] = {v: k for k, v in _BOXED_PRIMITIVE.items()}
|
|
458
|
+
|
|
459
|
+
@staticmethod
|
|
460
|
+
def _external_type_name(type_name: str, ctx: "_ImportContext") -> Optional[str]:
|
|
461
|
+
"""Exact FQN of an external (JDK) type spelled in `ctx`, or None.
|
|
462
|
+
|
|
463
|
+
An explicit import, a wildcard import matching an exact-FQN hierarchy
|
|
464
|
+
fact, and the implicit `java.lang` names qualify; an already dotted
|
|
465
|
+
spelling is kept as written. A name that resolves to nothing here is
|
|
466
|
+
*not* guessed."""
|
|
467
|
+
wildcard_matches = {
|
|
468
|
+
f"{package}.{type_name}" for package in ctx.wildcard_packages
|
|
469
|
+
if f"{package}.{type_name}" in JAVA_EXTERNAL_TYPES
|
|
470
|
+
}
|
|
471
|
+
if len(wildcard_matches) == 1:
|
|
472
|
+
return next(iter(wildcard_matches))
|
|
473
|
+
if "." in type_name:
|
|
474
|
+
head, tail = type_name.split(".", 1)
|
|
475
|
+
imported_head = ctx.import_map.get(head)
|
|
476
|
+
if imported_head is not None:
|
|
477
|
+
return f"{imported_head}.{tail}"
|
|
478
|
+
return type_name
|
|
479
|
+
imported = ctx.import_map.get(type_name)
|
|
480
|
+
if imported is not None:
|
|
481
|
+
return imported
|
|
482
|
+
if "java.util" in ctx.wildcard_packages and type_name in {
|
|
483
|
+
"ArrayList", "Collection", "HashMap", "HashSet", "List", "Map", "Set", "TreeMap",
|
|
484
|
+
}:
|
|
485
|
+
return f"java.util.{type_name}"
|
|
486
|
+
if type_name in JAVA_LANG_SIMPLE_TYPES:
|
|
487
|
+
return f"java.lang.{type_name}"
|
|
488
|
+
return None
|
|
489
|
+
|
|
490
|
+
def _types_compatible(
|
|
491
|
+
self, param_type: str, arg_type: str,
|
|
492
|
+
param_ctx: "_ImportContext", arg_ctx: "_ImportContext",
|
|
493
|
+
allow_boxing: bool = True,
|
|
494
|
+
) -> bool:
|
|
495
|
+
"""True iff a value statically typed `arg_type` (erased, per
|
|
496
|
+
`_infer_arg_types`) may be passed where `param_type` (also erased)
|
|
497
|
+
is declared: exact match, `Object` (every reference/boxed-primitive
|
|
498
|
+
type is Object-compatible), JLS 5.1.2 primitive widening, JLS
|
|
499
|
+
5.1.7/5.1.8 boxing/unboxing, or — for two types that resolve to
|
|
500
|
+
in-repo CLASS nodes — an INHERITS/IMPLEMENTS subtype relationship
|
|
501
|
+
(a subclass argument passed where a superclass/interface parameter
|
|
502
|
+
is declared, the single most common source of "known type, but not
|
|
503
|
+
an exact match" in real Java). Reference arrays follow JLS widening
|
|
504
|
+
reference conversion component-wise (`String[]` -> `Object[]`, and
|
|
505
|
+
source `Child[]` -> `Parent[]`). Array-ness must agree before that
|
|
506
|
+
recursion; primitive array components never box or widen into a
|
|
507
|
+
different component type (`int[]` is not `Object[]` or `long[]`).
|
|
508
|
+
|
|
509
|
+
`param_type` and `arg_type` are resolved against *separate*
|
|
510
|
+
contexts on purpose: `param_type` was written in the candidate
|
|
511
|
+
method's own declaring file and must resolve against that file's
|
|
512
|
+
imports/wildcard-imports (`param_ctx`), while `arg_type` was
|
|
513
|
+
written at the call site and resolves against the caller's
|
|
514
|
+
(`arg_ctx`) — using the caller's context for both, or dropping
|
|
515
|
+
wildcard-imported packages entirely, silently fails to resolve a
|
|
516
|
+
wildcard-imported or cross-file-imported type and drops an
|
|
517
|
+
otherwise-valid candidate (P4.4 regression fix).
|
|
518
|
+
|
|
519
|
+
`allow_boxing` splits JLS §15.12.2's phases: with it False (phase 1,
|
|
520
|
+
strict invocation) only identity + primitive widening + reference
|
|
521
|
+
widening/subtyping apply — *no* boxing/unboxing; with it True (phase
|
|
522
|
+
2, loose invocation) boxing/unboxing is additionally allowed.
|
|
523
|
+
`_select_overload` tries the strict phase across all candidates before
|
|
524
|
+
the loose one, so an `int` argument picks a `save(long)` (widening,
|
|
525
|
+
phase 1) over a `save(Integer)` (boxing, phase 2) sibling the way
|
|
526
|
+
real javac does, instead of fanning out to both.
|
|
527
|
+
|
|
528
|
+
The `null` literal (`_NULL_ARG_TYPE`) is compatible with every
|
|
529
|
+
*reference* parameter type (class, interface, array, boxed
|
|
530
|
+
primitive) and no primitive — independent of `allow_boxing`, since
|
|
531
|
+
`null` -> reference is a widening reference conversion (phase 1).
|
|
532
|
+
|
|
533
|
+
JLS 5.1.8 unboxing, optionally followed by 5.1.2 widening, is also
|
|
534
|
+
modeled: a wrapper arg (`Integer`) unboxes to its primitive
|
|
535
|
+
(`int`) and, if that alone doesn't match `param_type`, is checked
|
|
536
|
+
against `_PRIMITIVE_WIDENING` from there — `Integer` -> `long`
|
|
537
|
+
(F-05 fix). The reverse chain (box, then widen as a *primitive*) is
|
|
538
|
+
not attempted because JLS doesn't allow it: once boxed, only
|
|
539
|
+
widening *reference* conversion applies, which the
|
|
540
|
+
`_resolve_declared_type`/`_class_ancestors` path below already
|
|
541
|
+
covers.
|
|
542
|
+
|
|
543
|
+
Still not modeled (documented simplification): external/JDK
|
|
544
|
+
supertypes (`Comparable` param + `String` arg needs a JDK hierarchy
|
|
545
|
+
this issue doesn't fabricate). A pair neither rule proves compatible
|
|
546
|
+
is treated as a real mismatch,
|
|
547
|
+
not an unknown — the caller drops the candidate. When more than one
|
|
548
|
+
candidate is compatible, `_select_overload` calls `_most_specific`
|
|
549
|
+
to rank them (JLS §15.12.2.5) before falling back to a fan-out —
|
|
550
|
+
see that method."""
|
|
551
|
+
if arg_type == _NULL_ARG_TYPE:
|
|
552
|
+
# null type: assignable to any reference (anything not a
|
|
553
|
+
# primitive), never to a primitive parameter.
|
|
554
|
+
return param_type not in self._BOXED_PRIMITIVE
|
|
555
|
+
if arg_type.startswith(_LAMBDA_ARG_TYPE):
|
|
556
|
+
simple = erased_type(param_type).split(".")[-1]
|
|
557
|
+
if simple in _JAVA_FUNCTIONAL_INTERFACES:
|
|
558
|
+
shape = _JAVA_FUNCTIONAL_INTERFACE_SHAPE.get(simple)
|
|
559
|
+
lambda_shape = arg_type[len(_LAMBDA_ARG_TYPE) + 1:] if (
|
|
560
|
+
len(arg_type) > len(_LAMBDA_ARG_TYPE)
|
|
561
|
+
) else None
|
|
562
|
+
if shape is not None and lambda_shape is not None:
|
|
563
|
+
count_text, _, value_shape = lambda_shape.partition(":")
|
|
564
|
+
if count_text.isdigit() and int(count_text) != shape[0]:
|
|
565
|
+
# JLS 15.27.3: a lambda's declared parameter count
|
|
566
|
+
# must exactly equal its target SAM's arity — not
|
|
567
|
+
# merely "compatible" the way widening/boxing works
|
|
568
|
+
# for an ordinary value argument.
|
|
569
|
+
return False
|
|
570
|
+
if value_shape == "value" and shape[1]:
|
|
571
|
+
# A block body proven to contain a value `return` is
|
|
572
|
+
# not void-compatible (JLS 15.27.2): it cannot
|
|
573
|
+
# target a void-returning SAM like Runnable/Consumer.
|
|
574
|
+
return False
|
|
575
|
+
return True
|
|
576
|
+
# Project-declared SAM interfaces are valid lambda targets too
|
|
577
|
+
# (`Converter<T>` in Commons CLI). Resolve strictly in the
|
|
578
|
+
# candidate parameter's declaring context and require exactly
|
|
579
|
+
# one abstract instance-method signature across its hierarchy.
|
|
580
|
+
param_id = self._resolve_declared_type(
|
|
581
|
+
param_type, param_ctx.module, param_ctx.import_map,
|
|
582
|
+
param_ctx.wildcard_packages, param_ctx.enclosing_class_id,
|
|
583
|
+
)
|
|
584
|
+
if param_id is None:
|
|
585
|
+
return False
|
|
586
|
+
param_node = self.builder._nodes.get(param_id)
|
|
587
|
+
if param_node is None or param_node.metadata.get("java_type_kind") != "interface":
|
|
588
|
+
return False
|
|
589
|
+
abstract_signatures: Set[Tuple[str, str]] = set()
|
|
590
|
+
for owner_id in (param_id, *self._class_ancestors(param_id)):
|
|
591
|
+
if owner_id not in self.builder.graph:
|
|
592
|
+
continue
|
|
593
|
+
for _, member_id, data in self.builder.graph.out_edges(owner_id, data=True):
|
|
594
|
+
if data.get("type") != EdgeType.CONTAINS.name:
|
|
595
|
+
continue
|
|
596
|
+
member = self.builder._nodes.get(member_id)
|
|
597
|
+
if (
|
|
598
|
+
member is not None
|
|
599
|
+
and member.node_type == NodeType.FUNCTION
|
|
600
|
+
and member.metadata.get("java_is_abstract")
|
|
601
|
+
and not member.metadata.get("java_is_static")
|
|
602
|
+
):
|
|
603
|
+
abstract_signatures.add((member.name, member.signature or ""))
|
|
604
|
+
if len(abstract_signatures) == 1:
|
|
605
|
+
return True
|
|
606
|
+
if abstract_signatures:
|
|
607
|
+
return False
|
|
608
|
+
# `interface TimeoutProvider extends Function<Config, R> {}`
|
|
609
|
+
# declares nothing itself; it is functional exactly when its
|
|
610
|
+
# single external super-interface is a JDK functional one.
|
|
611
|
+
return self._external_functional_super(param_id) is not None
|
|
612
|
+
if param_type == arg_type:
|
|
613
|
+
return True
|
|
614
|
+
arg_is_primitive = arg_type in self._BOXED_PRIMITIVE
|
|
615
|
+
if param_type == "Object":
|
|
616
|
+
# `String` -> `Object` is a widening reference conversion
|
|
617
|
+
# (phase 1); `int` -> `Object` needs boxing first (phase 2).
|
|
618
|
+
return (not arg_is_primitive) or allow_boxing
|
|
619
|
+
param_is_array = param_type.endswith("[]")
|
|
620
|
+
arg_is_array = arg_type.endswith("[]")
|
|
621
|
+
if param_is_array or arg_is_array:
|
|
622
|
+
if not (param_is_array and arg_is_array):
|
|
623
|
+
return False
|
|
624
|
+
param_component = param_type[:-2]
|
|
625
|
+
arg_component = arg_type[:-2]
|
|
626
|
+
primitive_types = set(self._BOXED_PRIMITIVE)
|
|
627
|
+
if param_component in primitive_types or arg_component in primitive_types:
|
|
628
|
+
return param_component == arg_component
|
|
629
|
+
return self._types_compatible(
|
|
630
|
+
param_component, arg_component, param_ctx, arg_ctx,
|
|
631
|
+
allow_boxing=False,
|
|
632
|
+
)
|
|
633
|
+
if param_type in self._PRIMITIVE_WIDENING.get(arg_type, ()):
|
|
634
|
+
return True
|
|
635
|
+
if allow_boxing:
|
|
636
|
+
boxed_arg = self._BOXED_PRIMITIVE.get(arg_type)
|
|
637
|
+
boxed_param = self._BOXED_PRIMITIVE.get(param_type)
|
|
638
|
+
if boxed_arg == param_type or boxed_param == arg_type:
|
|
639
|
+
return True
|
|
640
|
+
unboxed_arg = self._UNBOXED_PRIMITIVE.get(arg_type)
|
|
641
|
+
if unboxed_arg is not None and param_type in self._PRIMITIVE_WIDENING.get(unboxed_arg, ()):
|
|
642
|
+
return True # unboxing (5.1.8) then widening (5.1.2): Integer -> int -> long.
|
|
643
|
+
external_name = self._external_type_name
|
|
644
|
+
|
|
645
|
+
param_external = external_name(param_type, param_ctx)
|
|
646
|
+
arg_external = external_name(arg_type, arg_ctx)
|
|
647
|
+
if param_external is not None and param_external == arg_external:
|
|
648
|
+
return True
|
|
649
|
+
if (
|
|
650
|
+
param_external is not None
|
|
651
|
+
and arg_external is not None
|
|
652
|
+
and (arg_external, param_external) in JAVA_EXTERNAL_SUBTYPE_STUBS
|
|
653
|
+
):
|
|
654
|
+
return True
|
|
655
|
+
param_id = self._resolve_declared_type(
|
|
656
|
+
param_type, param_ctx.module, param_ctx.import_map, param_ctx.wildcard_packages,
|
|
657
|
+
param_ctx.enclosing_class_id,
|
|
658
|
+
)
|
|
659
|
+
arg_id = self._resolve_declared_type(
|
|
660
|
+
arg_type, arg_ctx.module, arg_ctx.import_map, arg_ctx.wildcard_packages,
|
|
661
|
+
arg_ctx.enclosing_class_id,
|
|
662
|
+
)
|
|
663
|
+
if param_id is not None and arg_id is not None:
|
|
664
|
+
return param_id == arg_id or param_id in self._class_ancestors(arg_id)
|
|
665
|
+
if param_external is not None and arg_id is not None:
|
|
666
|
+
# A source class reaches an external supertype through its own
|
|
667
|
+
# `extends` clause or through any source ancestor's: the exact
|
|
668
|
+
# JDK facts apply to the nearest external superclass on that
|
|
669
|
+
# chain (`NoTestsDiscoveredException extends JUnitException
|
|
670
|
+
# extends RuntimeException` may be passed as `Throwable`). Each
|
|
671
|
+
# `extends` spelling resolves in its own declaring file.
|
|
672
|
+
for owner_id in (arg_id, *self._class_ancestors(arg_id)):
|
|
673
|
+
owner_node = self.builder._nodes.get(owner_id)
|
|
674
|
+
owner_ctx = (
|
|
675
|
+
self._file_ctx.get(owner_node.file_path)
|
|
676
|
+
if owner_node is not None else None
|
|
677
|
+
)
|
|
678
|
+
if owner_node is None or owner_ctx is None:
|
|
679
|
+
continue
|
|
680
|
+
declared_supers = (
|
|
681
|
+
*owner_node.metadata.get("java_superclass_args", ()),
|
|
682
|
+
*owner_node.metadata.get("java_implements_args", ()),
|
|
683
|
+
)
|
|
684
|
+
for declared_super in declared_supers:
|
|
685
|
+
super_name = external_name(erased_type(declared_super), owner_ctx)
|
|
686
|
+
if super_name is None:
|
|
687
|
+
continue
|
|
688
|
+
if (
|
|
689
|
+
super_name == param_external
|
|
690
|
+
or (super_name, param_external) in JAVA_EXTERNAL_SUBTYPE_STUBS
|
|
691
|
+
):
|
|
692
|
+
return True
|
|
693
|
+
return False
|
|
694
|
+
|
|
695
|
+
def _external_functional_super(self, interface_id: str) -> Optional[Tuple[str, str]]:
|
|
696
|
+
"""`(owner_id, extends_text)` when `interface_id` (or a source ancestor)
|
|
697
|
+
extends exactly one external JDK functional interface, else None.
|
|
698
|
+
|
|
699
|
+
The extends clause is resolved in its own declaring file: the name
|
|
700
|
+
must not be a source class there, and its exact FQN must be the JDK
|
|
701
|
+
one (`java.util.function.Function`, `java.lang.Runnable`, ...). A
|
|
702
|
+
project interface that declares its own abstract method is handled
|
|
703
|
+
by the caller before this is consulted."""
|
|
704
|
+
found: List[Tuple[str, str]] = []
|
|
705
|
+
for owner_id in (interface_id, *self._class_ancestors(interface_id)):
|
|
706
|
+
owner_node = self.builder._nodes.get(owner_id)
|
|
707
|
+
owner_ctx = (
|
|
708
|
+
self._file_ctx.get(owner_node.file_path)
|
|
709
|
+
if owner_node is not None else None
|
|
710
|
+
)
|
|
711
|
+
if owner_node is None or owner_ctx is None:
|
|
712
|
+
continue
|
|
713
|
+
if owner_node.metadata.get("java_type_kind") != "interface":
|
|
714
|
+
continue
|
|
715
|
+
for declared_super in owner_node.metadata.get("java_superclass_args", ()):
|
|
716
|
+
raw = erased_type(declared_super).strip()
|
|
717
|
+
if raw.rsplit(".", 1)[-1] not in _JAVA_FUNCTIONAL_INTERFACES:
|
|
718
|
+
continue
|
|
719
|
+
if self._resolve_declared_type(
|
|
720
|
+
raw, owner_ctx.module, owner_ctx.import_map,
|
|
721
|
+
owner_ctx.wildcard_packages,
|
|
722
|
+
) is not None:
|
|
723
|
+
continue
|
|
724
|
+
external = self._external_type_name(raw, owner_ctx)
|
|
725
|
+
if external is None or not external.startswith("java."):
|
|
726
|
+
continue
|
|
727
|
+
found.append((owner_id, declared_super))
|
|
728
|
+
return found[0] if len(found) == 1 else None
|
|
729
|
+
|
|
730
|
+
def _narrower_or_equal(
|
|
731
|
+
self, a_type: str, b_type: str,
|
|
732
|
+
a_ctx: "_ImportContext", b_ctx: "_ImportContext",
|
|
733
|
+
) -> bool:
|
|
734
|
+
"""True iff every value accepted where `a_type` is declared is also
|
|
735
|
+
accepted where `b_type` is declared — i.e. `a_type` is at least as
|
|
736
|
+
specific as `b_type`. Used only to rank *pairs of already-compatible
|
|
737
|
+
candidates'* declared parameter types against each other (see
|
|
738
|
+
`_most_specific`), not to re-derive compatibility with a concrete
|
|
739
|
+
argument — that is `_types_compatible`'s job. Primitive widening
|
|
740
|
+
via the existing `_PRIMITIVE_WIDENING` lattice; reference types via
|
|
741
|
+
an INHERITS/IMPLEMENTS subtype check, each side resolved against
|
|
742
|
+
its *own* declaring context (`a_ctx`/`b_ctx` — two overloads being
|
|
743
|
+
ranked against each other may be declared in two different files,
|
|
744
|
+
same reasoning as `_types_compatible`'s `param_ctx`/`arg_ctx`
|
|
745
|
+
split). Array types and primitive/reference mixed pairs are
|
|
746
|
+
deliberately not ordered (same "not modeled" boundary
|
|
747
|
+
`_types_compatible` documents for boxing/unboxing chains) — an
|
|
748
|
+
unordered pair returns `False` in both directions, which only
|
|
749
|
+
costs a tie-break (both stay fan-out candidates), never a wrong
|
|
750
|
+
pick."""
|
|
751
|
+
if a_type == b_type:
|
|
752
|
+
return True
|
|
753
|
+
if a_type.endswith("[]") or b_type.endswith("[]"):
|
|
754
|
+
return False
|
|
755
|
+
a_is_prim = a_type in self._BOXED_PRIMITIVE
|
|
756
|
+
b_is_prim = b_type in self._BOXED_PRIMITIVE
|
|
757
|
+
if a_is_prim and b_is_prim:
|
|
758
|
+
return b_type in self._PRIMITIVE_WIDENING.get(a_type, ())
|
|
759
|
+
if a_is_prim or b_is_prim:
|
|
760
|
+
return False
|
|
761
|
+
if b_type == "Object":
|
|
762
|
+
# Mirrors `_types_compatible`'s own `Object`-is-universal-
|
|
763
|
+
# supertype shortcut: `Object` is a JDK type this resolver
|
|
764
|
+
# never fabricates a CLASS node for, so `_resolve_declared_type`
|
|
765
|
+
# below would always return `None` for it and this domination
|
|
766
|
+
# would incorrectly read as "unorderable" instead of "any
|
|
767
|
+
# reference type is narrower than Object".
|
|
768
|
+
return True
|
|
769
|
+
a_id = self._resolve_declared_type(
|
|
770
|
+
a_type, a_ctx.module, a_ctx.import_map, a_ctx.wildcard_packages,
|
|
771
|
+
)
|
|
772
|
+
b_id = self._resolve_declared_type(
|
|
773
|
+
b_type, b_ctx.module, b_ctx.import_map, b_ctx.wildcard_packages,
|
|
774
|
+
)
|
|
775
|
+
if a_id is not None and b_id is not None:
|
|
776
|
+
return a_id == b_id or b_id in self._class_ancestors(a_id)
|
|
777
|
+
if a_id is None and b_id is None:
|
|
778
|
+
# Two external JDK parameter types are ordered by the same
|
|
779
|
+
# exact-FQN facts `_types_compatible` accepts, so a nested
|
|
780
|
+
# source return canonicalized to `java.util.Set` still prefers
|
|
781
|
+
# `m(Set)` over `m(Collection)`.
|
|
782
|
+
a_external = self._external_type_name(a_type, a_ctx)
|
|
783
|
+
b_external = self._external_type_name(b_type, b_ctx)
|
|
784
|
+
if a_external is not None and b_external is not None:
|
|
785
|
+
return (
|
|
786
|
+
a_external == b_external
|
|
787
|
+
or (a_external, b_external) in JAVA_EXTERNAL_SUBTYPE_STUBS
|
|
788
|
+
)
|
|
789
|
+
return False
|
|
790
|
+
|
|
791
|
+
def _dominates(self, c1: str, c2: str) -> bool:
|
|
792
|
+
"""True iff `c1` is strictly more specific than `c2` — every one of
|
|
793
|
+
`c1`'s declared parameter types is narrower-or-equal to `c2`'s at
|
|
794
|
+
the same position, and at least one position is strictly narrower
|
|
795
|
+
(not just equal). Positional only: candidates with differing
|
|
796
|
+
parameter-list lengths (a fixed-arity/vararg shape mismatch between
|
|
797
|
+
two same-arg-count-compatible overloads) are treated as
|
|
798
|
+
incomparable rather than guessed at."""
|
|
799
|
+
types1 = [erased_type(t) for t in self._param_type_lists.get(c1, [])]
|
|
800
|
+
types2 = [erased_type(t) for t in self._param_type_lists.get(c2, [])]
|
|
801
|
+
if len(types1) != len(types2):
|
|
802
|
+
return False
|
|
803
|
+
ctx1 = self._param_ctx_for(c1)
|
|
804
|
+
ctx2 = self._param_ctx_for(c2)
|
|
805
|
+
strictly_narrower_somewhere = False
|
|
806
|
+
for t1, t2 in zip(types1, types2):
|
|
807
|
+
if not self._narrower_or_equal(t1, t2, ctx1, ctx2):
|
|
808
|
+
return False
|
|
809
|
+
if not self._narrower_or_equal(t2, t1, ctx2, ctx1):
|
|
810
|
+
strictly_narrower_somewhere = True
|
|
811
|
+
return strictly_narrower_somewhere
|
|
812
|
+
|
|
813
|
+
def _most_specific(self, candidates: List[str]) -> List[str]:
|
|
814
|
+
"""Narrow a `compatible`-tier candidate set down to the maximally
|
|
815
|
+
specific ones — JLS §15.12.2.5's "most specific method" tie-break,
|
|
816
|
+
run only among candidates `_param_types_compatible` already
|
|
817
|
+
accepted (this never promotes an otherwise-incompatible candidate,
|
|
818
|
+
it only prunes redundant *compatible* ones). A candidate dominated
|
|
819
|
+
by another (see `_dominates`) is dropped; a pair `_dominates` can't
|
|
820
|
+
order — two interfaces neither extending the other, or a type this
|
|
821
|
+
resolver can't resolve — is a genuine tie and both survive, exactly
|
|
822
|
+
the fan-out behavior `_select_overload` had before this method
|
|
823
|
+
existed. `save(long)`/`save(float)` both declared, called with a
|
|
824
|
+
statically-known `int`: `save(long)` dominates `save(float)`
|
|
825
|
+
(`int` widens to both, but `long` also widens to `float` while
|
|
826
|
+
`float` does not widen to `long` — `long` is strictly narrower) and
|
|
827
|
+
survives alone."""
|
|
828
|
+
dominated: Set[str] = set()
|
|
829
|
+
for c1 in candidates:
|
|
830
|
+
for c2 in candidates:
|
|
831
|
+
if c1 != c2 and self._dominates(c1, c2):
|
|
832
|
+
dominated.add(c2)
|
|
833
|
+
return [c for c in candidates if c not in dominated]
|
|
834
|
+
|
|
835
|
+
@staticmethod
|
|
836
|
+
def _vararg_component_type(vararg_array_type: str) -> str:
|
|
837
|
+
"""`int[]` -> `int`, `String[]` -> `String` — the type each
|
|
838
|
+
variable-arity *element* must match, as opposed to the array type
|
|
839
|
+
itself (which only a caller passing the array directly, "array
|
|
840
|
+
form", needs to match — see `_param_types_match`/
|
|
841
|
+
`_param_types_compatible`)."""
|
|
842
|
+
return (
|
|
843
|
+
vararg_array_type[:-2]
|
|
844
|
+
if vararg_array_type.endswith("[]")
|
|
845
|
+
else vararg_array_type
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
def _param_types_match(self, func_id: str, arg_types: List[str]) -> bool:
|
|
849
|
+
"""Exact erased-type match, positionally — JLS §15.12.2 "phase 1,
|
|
850
|
+
strict invocation" (no widening/boxing/subtyping). Preferred over
|
|
851
|
+
`_param_types_compatible` whenever it has any survivor: an argument
|
|
852
|
+
that exactly matches one overload's declared parameter type must
|
|
853
|
+
pick that overload even when a *different* overload would also
|
|
854
|
+
accept it via widening (`save(int)` and `save(long)` both declared,
|
|
855
|
+
`save(1)` must resolve only to `save(int)`, never fan out to
|
|
856
|
+
`save(long)` too) — mirrors javac's phase ordering, where a phase-1
|
|
857
|
+
applicable method always wins over a phase-2-only one.
|
|
858
|
+
|
|
859
|
+
A vararg-declaring candidate is checked as vararg-or-nothing, never
|
|
860
|
+
as a fixed-arity candidate first (P4.4 regression fix): `void
|
|
861
|
+
save(int... values)` erases to a single `int[]` parameter, so
|
|
862
|
+
checking `len(types) == len(arg_types)` first and comparing
|
|
863
|
+
`save(1)`'s `int` argument against the *array* type `int[]` fails
|
|
864
|
+
and used to `return` immediately — the vararg branch, which must
|
|
865
|
+
compare against the *component* type `int` instead, never ran.
|
|
866
|
+
Separately, the old vararg branch only checked the fixed prefix
|
|
867
|
+
length/content and never compared the vararg elements' types at
|
|
868
|
+
all, so any trailing argument type — `String...` called with two
|
|
869
|
+
`int`s — was silently accepted."""
|
|
870
|
+
raw_types = self._param_type_lists.get(func_id, [])
|
|
871
|
+
types = [erased_type(t) for t in raw_types]
|
|
872
|
+
method_type_params = self._method_type_param_bounds.get(func_id, {})
|
|
873
|
+
|
|
874
|
+
def exact_or_generic_array(param_type: str, arg_type: str) -> bool:
|
|
875
|
+
if param_type == arg_type:
|
|
876
|
+
return True
|
|
877
|
+
dimensions = 0
|
|
878
|
+
component = param_type
|
|
879
|
+
while component.endswith("[]"):
|
|
880
|
+
dimensions += 1
|
|
881
|
+
component = component[:-2]
|
|
882
|
+
if not dimensions or component not in method_type_params:
|
|
883
|
+
return False
|
|
884
|
+
arg_component = arg_type
|
|
885
|
+
for _ in range(dimensions):
|
|
886
|
+
if not arg_component.endswith("[]"):
|
|
887
|
+
return False
|
|
888
|
+
arg_component = arg_component[:-2]
|
|
889
|
+
# `T` cannot be a primitive, so `T[]` never matches `int[]`.
|
|
890
|
+
return arg_component not in self._BOXED_PRIMITIVE
|
|
891
|
+
|
|
892
|
+
is_vararg = bool(raw_types) and self._is_vararg_param(raw_types[-1])
|
|
893
|
+
if not is_vararg:
|
|
894
|
+
return len(types) == len(arg_types) and all(
|
|
895
|
+
exact_or_generic_array(p, a) for p, a in zip(types, arg_types)
|
|
896
|
+
)
|
|
897
|
+
fixed = types[:-1]
|
|
898
|
+
if len(arg_types) < len(fixed) or arg_types[: len(fixed)] != fixed:
|
|
899
|
+
return False
|
|
900
|
+
trailing = arg_types[len(fixed):]
|
|
901
|
+
vararg_type = types[-1]
|
|
902
|
+
if len(trailing) == 1 and trailing[0] == vararg_type:
|
|
903
|
+
return True # array form: caller passed the array itself.
|
|
904
|
+
component_type = self._vararg_component_type(vararg_type)
|
|
905
|
+
return all(a == component_type for a in trailing)
|
|
906
|
+
|
|
907
|
+
def _param_ctx_for(self, func_id: str) -> "_ImportContext":
|
|
908
|
+
"""The `_ImportContext` of the file that declares `func_id` — its
|
|
909
|
+
parameter types were written against *that* file's imports and
|
|
910
|
+
wildcard-imports, not whichever call site is currently resolving
|
|
911
|
+
against it. Keyed by `GraphNode.file_path`, not `.module`: several
|
|
912
|
+
files commonly share one Java module (module id = declared
|
|
913
|
+
`package`), each with its own import list, so a module-keyed lookup
|
|
914
|
+
would silently pick an arbitrary same-package file's imports instead
|
|
915
|
+
of the declaring file's own. Falls back to an empty context (no
|
|
916
|
+
import binds, no wildcard packages, `module` still correct so
|
|
917
|
+
same-package resolution still works) if the file is somehow missing
|
|
918
|
+
from `self._file_ctx` — e.g. a single-file incremental rescan that
|
|
919
|
+
doesn't re-pass every file `results` normally covers — which only
|
|
920
|
+
drops cross-package import resolution for that one lookup, never
|
|
921
|
+
raises, matching this resolver's "missing edge over wrong edge"
|
|
922
|
+
precision stance.
|
|
923
|
+
|
|
924
|
+
Memoized per `func_id` (`_param_ctx_for_cache`, reset alongside the
|
|
925
|
+
other per-run caches): every input (`self._file_ctx`, `node.module`,
|
|
926
|
+
`_declaring_class_id(func_id)`) is fixed for the whole resolution
|
|
927
|
+
pass, but this is called once per candidate on every overload
|
|
928
|
+
applicability check, so the same heavily-overloaded method gets its
|
|
929
|
+
context rebuilt on every call site that names it."""
|
|
930
|
+
cache = getattr(self, "_param_ctx_for_cache", None)
|
|
931
|
+
if cache is not None:
|
|
932
|
+
cached = cache.get(func_id)
|
|
933
|
+
if cached is not None:
|
|
934
|
+
return cached
|
|
935
|
+
node = self.builder._nodes.get(func_id)
|
|
936
|
+
if node is None:
|
|
937
|
+
ctx = _ImportContext("", {}, [])
|
|
938
|
+
else:
|
|
939
|
+
ctx = self._file_ctx.get(node.file_path, _ImportContext(node.module, {}, []))
|
|
940
|
+
ctx = ctx._replace(enclosing_class_id=self._declaring_class_id(func_id))
|
|
941
|
+
if cache is not None:
|
|
942
|
+
cache[func_id] = ctx
|
|
943
|
+
return ctx
|
|
944
|
+
|
|
945
|
+
def _param_types_compatible(
|
|
946
|
+
self, func_id: str, arg_types: List[str], arg_ctx: "_ImportContext",
|
|
947
|
+
allow_boxing: bool = True, allow_method_type_inference: bool = False,
|
|
948
|
+
) -> bool:
|
|
949
|
+
"""Positional argument/parameter compatibility via
|
|
950
|
+
`_types_compatible`. `allow_boxing` selects the JLS §15.12.2 phase:
|
|
951
|
+
False = "phase 1, strict invocation" (identity, primitive widening,
|
|
952
|
+
reference subtyping — no boxing); True = "phase 2, loose invocation"
|
|
953
|
+
(adds boxing/unboxing). `_select_overload` tries the strict phase
|
|
954
|
+
across all candidates before the loose one — see there for why phase 1
|
|
955
|
+
must be exhausted before phase 2 is attempted.
|
|
956
|
+
|
|
957
|
+
`func_id`'s own declaring-file context (`_param_ctx_for`) resolves
|
|
958
|
+
its parameter types; `arg_ctx` (the call site's context) resolves
|
|
959
|
+
the argument types — see `_types_compatible` for why these must not
|
|
960
|
+
be conflated.
|
|
961
|
+
|
|
962
|
+
Vararg handling mirrors `_param_types_match` (P4.4 regression fix,
|
|
963
|
+
same bug, same shape): checked as vararg-or-nothing rather than
|
|
964
|
+
trying fixed-arity first, and every trailing ("variadic") argument
|
|
965
|
+
is checked against the vararg's *component* type via
|
|
966
|
+
`_types_compatible` — not silently accepted, and not compared
|
|
967
|
+
against the array type unless the caller actually passed exactly
|
|
968
|
+
one argument that is itself array-compatible (array form)."""
|
|
969
|
+
param_ctx = self._param_ctx_for(func_id)
|
|
970
|
+
raw_types = self._param_type_lists.get(func_id, [])
|
|
971
|
+
types = [erased_type(t) for t in raw_types]
|
|
972
|
+
method_type_params = self._method_type_param_bounds.get(func_id, {})
|
|
973
|
+
|
|
974
|
+
def compatible(param_type: str, arg_type: str) -> bool:
|
|
975
|
+
# `_UNKNOWN_ARG_TYPE` means "no information about this argument,"
|
|
976
|
+
# not a real type — it must never rule a candidate in or out, on
|
|
977
|
+
# any parameter shape (array, generic, primitive). Checked before
|
|
978
|
+
# every other branch below, each of which assumes `arg_type` is a
|
|
979
|
+
# real (possibly sentinel-but-known) type.
|
|
980
|
+
if arg_type == _UNKNOWN_ARG_TYPE:
|
|
981
|
+
return True
|
|
982
|
+
# A method inference variable accepts a reference argument without
|
|
983
|
+
# treating its short name as a source class. Primitive arguments
|
|
984
|
+
# require the loose-invocation (boxing) phase. This remains scoped
|
|
985
|
+
# to an already-found own/inherited candidate; it adds no global
|
|
986
|
+
# name lookup or candidate fan-out.
|
|
987
|
+
if allow_method_type_inference and param_type in method_type_params:
|
|
988
|
+
# A lambda needs a functional-interface target type. An
|
|
989
|
+
# unbounded inference variable (`<T> ... T value`) has the
|
|
990
|
+
# implicit bound Object and is not itself a lambda target.
|
|
991
|
+
# A variable explicitly bounded by a functional interface is
|
|
992
|
+
# valid; reuse the existing JDK/project SAM check for it.
|
|
993
|
+
if arg_type.startswith(_LAMBDA_ARG_TYPE):
|
|
994
|
+
bound = method_type_params[param_type]
|
|
995
|
+
if erased_type(bound) == "Object":
|
|
996
|
+
return False
|
|
997
|
+
return self._types_compatible(
|
|
998
|
+
bound, arg_type, param_ctx, arg_ctx, allow_boxing,
|
|
999
|
+
)
|
|
1000
|
+
return arg_type not in self._BOXED_PRIMITIVE or allow_boxing
|
|
1001
|
+
# Class-scoped variables are instantiated by the receiver
|
|
1002
|
+
# (`Store<String>` makes `N` a reference type). The resolver does
|
|
1003
|
+
# not yet carry that substitution into this overload helper, but
|
|
1004
|
+
# treating the declared class variable as a reference slot is
|
|
1005
|
+
# sound for applicability and lets generic field calls inside a
|
|
1006
|
+
# lambda resolve without guessing a concrete source class.
|
|
1007
|
+
owner = self._declaring_class_id(func_id)
|
|
1008
|
+
if (
|
|
1009
|
+
owner is not None
|
|
1010
|
+
and param_type in self._class_type_params.get(owner, [])
|
|
1011
|
+
):
|
|
1012
|
+
return arg_type not in self._BOXED_PRIMITIVE or allow_boxing
|
|
1013
|
+
# Method type variables can occur inside an array type (`T[]`),
|
|
1014
|
+
# not only as the complete parameter spelling. Treat an array
|
|
1015
|
+
# argument as an instantiation of that variable only when its
|
|
1016
|
+
# dimensions agree and its component is a reference type. This
|
|
1017
|
+
# lets `T[] copy(T[] value)` win over a sibling `T copy(T value)`
|
|
1018
|
+
# for a `Widget[]` argument; accepting the latter alone loses a
|
|
1019
|
+
# real Java overload edge. Primitive arrays are excluded because
|
|
1020
|
+
# a Java generic variable cannot be instantiated with `int`.
|
|
1021
|
+
array_dims = 0
|
|
1022
|
+
component = param_type
|
|
1023
|
+
while component.endswith("[]"):
|
|
1024
|
+
array_dims += 1
|
|
1025
|
+
component = component[:-2]
|
|
1026
|
+
if allow_method_type_inference and array_dims and component in method_type_params:
|
|
1027
|
+
arg_component = arg_type
|
|
1028
|
+
for _ in range(array_dims):
|
|
1029
|
+
if not arg_component.endswith("[]"):
|
|
1030
|
+
return False
|
|
1031
|
+
arg_component = arg_component[:-2]
|
|
1032
|
+
if arg_component in self._BOXED_PRIMITIVE:
|
|
1033
|
+
return False
|
|
1034
|
+
bound = method_type_params[component]
|
|
1035
|
+
return self._types_compatible(
|
|
1036
|
+
bound, arg_component, param_ctx, arg_ctx, allow_boxing,
|
|
1037
|
+
)
|
|
1038
|
+
return self._types_compatible(
|
|
1039
|
+
param_type, arg_type, param_ctx, arg_ctx, allow_boxing,
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
is_vararg = bool(raw_types) and self._is_vararg_param(raw_types[-1])
|
|
1043
|
+
if not is_vararg:
|
|
1044
|
+
if len(types) != len(arg_types):
|
|
1045
|
+
return False
|
|
1046
|
+
return all(
|
|
1047
|
+
compatible(p, a)
|
|
1048
|
+
for p, a in zip(types, arg_types)
|
|
1049
|
+
)
|
|
1050
|
+
fixed = types[:-1]
|
|
1051
|
+
if len(arg_types) < len(fixed):
|
|
1052
|
+
return False
|
|
1053
|
+
if not all(
|
|
1054
|
+
compatible(p, a)
|
|
1055
|
+
for p, a in zip(fixed, arg_types[: len(fixed)])
|
|
1056
|
+
):
|
|
1057
|
+
return False
|
|
1058
|
+
trailing = arg_types[len(fixed):]
|
|
1059
|
+
vararg_type = types[-1]
|
|
1060
|
+
if len(trailing) == 1 and compatible(vararg_type, trailing[0]):
|
|
1061
|
+
return True # array form: caller passed the array itself.
|
|
1062
|
+
component_type = self._vararg_component_type(vararg_type)
|
|
1063
|
+
return all(
|
|
1064
|
+
compatible(component_type, a)
|
|
1065
|
+
for a in trailing
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
def _narrow_candidates_by_known_arg_types(
|
|
1069
|
+
self, candidates: List[str], arg_count: int, args_node,
|
|
1070
|
+
source: bytes, local_types: "_LocalScopes", param_types: Dict[str, str],
|
|
1071
|
+
field_types: Dict[str, str], arg_ctx: "_ImportContext",
|
|
1072
|
+
) -> List[str]:
|
|
1073
|
+
"""Shrink an otherwise-total arity-only fan-out using whichever
|
|
1074
|
+
argument positions *are* statically known, leaving a genuinely
|
|
1075
|
+
unresolvable position as a wildcard rather than guessing it.
|
|
1076
|
+
|
|
1077
|
+
`_infer_arg_types`'s all-or-nothing contract (by design: never guess
|
|
1078
|
+
an unknown argument) means one unresolvable position — commonly an
|
|
1079
|
+
external/JDK method-call result with no exact-return fact, such as
|
|
1080
|
+
`exception.getMessage()` — makes `_select_overload` fan out across
|
|
1081
|
+
*every* same-arity candidate, even ones a known sibling argument
|
|
1082
|
+
rules out entirely. `assertEquals(expectedMessage, exception.getMessage())`
|
|
1083
|
+
(`expectedMessage` a plain `String`) is a real example: no
|
|
1084
|
+
`assertEquals(byte,byte)`/`(int,int)`/…/`(Double,Double)` sibling can
|
|
1085
|
+
ever accept a `String` first argument, so fanning out to all of
|
|
1086
|
+
JUnit's ~20 primitive/boxed overloads is not "unresolved" for any of
|
|
1087
|
+
them — it is a wrong edge for every one but the `(Object,Object)`
|
|
1088
|
+
family. This reuses `_param_types_compatible`'s own vararg/generic/
|
|
1089
|
+
boxing-aware applicability check with `_UNKNOWN_ARG_TYPE` standing in
|
|
1090
|
+
for each position this function cannot determine, so an unresolvable
|
|
1091
|
+
position can never disqualify a candidate — only a position with a
|
|
1092
|
+
*real* known type can.
|
|
1093
|
+
|
|
1094
|
+
Per-position typing first tries `_infer_arg_type` (literals/
|
|
1095
|
+
declarations only) and, only when that is None, falls back to
|
|
1096
|
+
`_infer_known_arg_type_for_narrowing` — the same source/JDK
|
|
1097
|
+
return-type propagation the `var`/fluent-chain typing already
|
|
1098
|
+
trusts (batch 16 P5 finding #1: `exception.getMessage()` itself
|
|
1099
|
+
stayed unknown before this, so a *known* sibling argument's type
|
|
1100
|
+
could never exclude anything). This asymmetry with `_infer_arg_type`
|
|
1101
|
+
is safe specifically because narrowing can only ever *exclude* a
|
|
1102
|
+
same-arity sibling a resolved type provably cannot reach — it can
|
|
1103
|
+
never fabricate an edge or select a sole winner from a chain-typed
|
|
1104
|
+
guess the way using it for `_infer_arg_types`' exact-match path
|
|
1105
|
+
could.
|
|
1106
|
+
|
|
1107
|
+
Returns `candidates` unchanged whenever no position is statically
|
|
1108
|
+
known, arity is inconsistent with the call, or narrowing would
|
|
1109
|
+
otherwise discard every candidate (a correctness backstop, not
|
|
1110
|
+
expected in practice): this never changes behavior for a call this
|
|
1111
|
+
helper cannot actually inform."""
|
|
1112
|
+
if len(candidates) <= 1:
|
|
1113
|
+
return candidates
|
|
1114
|
+
arg_nodes = argument_nodes(args_node)
|
|
1115
|
+
if len(arg_nodes) != arg_count:
|
|
1116
|
+
return candidates
|
|
1117
|
+
partial = [
|
|
1118
|
+
self._infer_arg_type(node, source, local_types, param_types, field_types)
|
|
1119
|
+
or self._infer_known_arg_type_for_narrowing(
|
|
1120
|
+
node, source, arg_ctx, local_types, param_types, field_types,
|
|
1121
|
+
)
|
|
1122
|
+
for node in arg_nodes
|
|
1123
|
+
]
|
|
1124
|
+
if all(t is None for t in partial):
|
|
1125
|
+
return candidates
|
|
1126
|
+
known_types = [
|
|
1127
|
+
erased_type(t) if t is not None else _UNKNOWN_ARG_TYPE for t in partial
|
|
1128
|
+
]
|
|
1129
|
+
survivors = [
|
|
1130
|
+
candidate for candidate in candidates
|
|
1131
|
+
if not self._arity_matches(candidate, arg_count)
|
|
1132
|
+
or self._param_types_compatible(
|
|
1133
|
+
candidate, known_types, arg_ctx, allow_method_type_inference=True,
|
|
1134
|
+
)
|
|
1135
|
+
]
|
|
1136
|
+
return survivors if survivors else candidates
|
|
1137
|
+
|
|
1138
|
+
def _select_overload(
|
|
1139
|
+
self, candidates: List[str], arg_count: int,
|
|
1140
|
+
arg_types: Optional[List[str]], conf_for: Callable[[str], float],
|
|
1141
|
+
arg_ctx: "_ImportContext", allow_method_type_inference: bool = False,
|
|
1142
|
+
) -> Tuple[Union[str, List[str], None], float]:
|
|
1143
|
+
"""P4.4 overload selection: arg count first — arity is a hard Java
|
|
1144
|
+
constraint, checked even for a single candidate, never skipped. When
|
|
1145
|
+
arg types are statically known, arity-matching candidates are then
|
|
1146
|
+
ranked in two tiers mirroring JLS §15.12.2's phases: exact erased-type
|
|
1147
|
+
match (`_param_types_match`) first, and only when *no* candidate
|
|
1148
|
+
exact-matches does the wider widening/boxing/subtype-aware
|
|
1149
|
+
`_param_types_compatible` apply — and when more than one candidate
|
|
1150
|
+
passes that check, `_most_specific` (JLS §15.12.2.5) prunes it down
|
|
1151
|
+
to whichever candidate's parameter types are strictly narrower than
|
|
1152
|
+
every other survivor's before falling back to a fan-out, so e.g.
|
|
1153
|
+
`save(long)`/`save(float)` called with a statically-known `int`
|
|
1154
|
+
resolves to `save(long)` alone rather than both. Each tier applies
|
|
1155
|
+
the same rule after that ranking — one survivor resolves at full
|
|
1156
|
+
confidence, more than one (a genuine tie `_most_specific` couldn't
|
|
1157
|
+
order) fans out at reduced confidence, per-candidate confidence
|
|
1158
|
+
still coming from `conf_for` — but a lone *exact* match is never
|
|
1159
|
+
diluted by a widening-only sibling overload, and a lone *arity*
|
|
1160
|
+
match is no longer accepted unconditionally the way it was before this fix: a
|
|
1161
|
+
single candidate whose declared parameter types are provably
|
|
1162
|
+
incompatible with the call's statically-known argument types (e.g.
|
|
1163
|
+
`repo.save(1)` against a sole `Repo.save(String)`) is a real
|
|
1164
|
+
mismatch, not an unresolved ambiguity, and resolves to nothing
|
|
1165
|
+
rather than a wrong edge. No candidate matching the call's arg count
|
|
1166
|
+
(or, when types are known, matching neither tier) is a resolution
|
|
1167
|
+
failure: it must never fall back to a broader candidate set, which
|
|
1168
|
+
would emit an edge to a same-name overload the call site cannot
|
|
1169
|
+
actually reach. `conf_for` looks up the confidence tied to *where* a
|
|
1170
|
+
given candidate was declared (own class vs. inherited), so a merged
|
|
1171
|
+
own+ancestor candidate set (see `_members_named`) reports the right
|
|
1172
|
+
confidence for whichever tier the winner actually came from.
|
|
1173
|
+
|
|
1174
|
+
**Non-vararg candidates are ranked, and given a chance to resolve
|
|
1175
|
+
alone, before any vararg-declaring candidate is even considered**
|
|
1176
|
+
(P4.4 regression fix): JLS §15.12.2 tries phase 1 (strict, no
|
|
1177
|
+
boxing, no varargs) and phase 2 (loose — boxing/widening allowed,
|
|
1178
|
+
still no varargs) across *every* applicable method — fixed-arity
|
|
1179
|
+
and vararg-declared alike, the vararg one only in its unexpanded
|
|
1180
|
+
"array form" — before ever attempting phase 3 (variable arity,
|
|
1181
|
+
element-wise vararg expansion). A vararg-declared candidate reached
|
|
1182
|
+
only via expansion is a phase-3-only candidate, and phase 3 must
|
|
1183
|
+
never even be attempted while a phase-1/2-applicable method exists.
|
|
1184
|
+
The two-tier exact-then-compatible ranking above conflated this:
|
|
1185
|
+
`_param_types_match`'s vararg branch matches a *component* type
|
|
1186
|
+
exactly (`save(int... v)` vs. `save(1)` matches `int` == `int`),
|
|
1187
|
+
landing it in the same "exact" tier as a true fixed-arity exact
|
|
1188
|
+
match, while a fixed-arity sibling reachable only through widening
|
|
1189
|
+
(`save(long)` vs. the same `save(1)`) is relegated to the
|
|
1190
|
+
"compatible" tier — `exact`'s existing short-circuit then resolved
|
|
1191
|
+
to the vararg candidate alone, or fanned out to both, never letting
|
|
1192
|
+
`save(long)` win the way real javac does. **Fix:** partition
|
|
1193
|
+
`by_arity` into a phase-1/2 pool and a phase-3-only pool first, keyed
|
|
1194
|
+
on `_is_phase3_only` — which is *invocation-form-aware*, not a
|
|
1195
|
+
declaration-shape test: a vararg-declaring method reached in its
|
|
1196
|
+
phase-1/2 *array form* for this call (`m(String[])` against
|
|
1197
|
+
`m(String...)`) stays in the phase-1/2 pool and competes with a
|
|
1198
|
+
fixed-arity sibling, while one reachable only through element-wise
|
|
1199
|
+
expansion (`save(1)` against `save(int...)`, `int` not assignable to
|
|
1200
|
+
`int[]`) is deferred to phase 3. This corrects the earlier
|
|
1201
|
+
declaration-only `_is_vararg_candidate` partition, which demoted
|
|
1202
|
+
*every* vararg candidate — array form and all — out of phase 1/2,
|
|
1203
|
+
so an array-form call could never beat a widening-compatible
|
|
1204
|
+
fixed-arity overload (`m(Object)`). Run the existing
|
|
1205
|
+
exact-then-compatible-then-`_most_specific` ranking against the
|
|
1206
|
+
phase-1/2 pool alone, and only fall through to ranking the
|
|
1207
|
+
phase-3-only pool the same way if the first produced zero survivors
|
|
1208
|
+
(nothing phase-1/2-applicable at all) — never merging the two pools'
|
|
1209
|
+
results, and never touching `_param_types_match`/
|
|
1210
|
+
`_param_types_compatible`/`_most_specific` themselves, which stay
|
|
1211
|
+
correct within each pool."""
|
|
1212
|
+
if not candidates:
|
|
1213
|
+
return None, 0.0
|
|
1214
|
+
by_arity = [c for c in candidates if self._arity_matches(c, arg_count)]
|
|
1215
|
+
if not by_arity:
|
|
1216
|
+
return None, 0.0
|
|
1217
|
+
if arg_types is None:
|
|
1218
|
+
if len(by_arity) == 1:
|
|
1219
|
+
return by_arity[0], conf_for(by_arity[0])
|
|
1220
|
+
return by_arity, min(min(conf_for(c) for c in by_arity), 0.55)
|
|
1221
|
+
|
|
1222
|
+
def _resolve_tier(hits: List[str]) -> Tuple[Union[str, List[str], None], float]:
|
|
1223
|
+
"""One survivor -> full confidence; >1 (a genuine tie
|
|
1224
|
+
`_most_specific` couldn't order) -> fan out at reduced
|
|
1225
|
+
confidence; 0 -> unresolved. Applied per phase tier below."""
|
|
1226
|
+
if not hits:
|
|
1227
|
+
return None, 0.0
|
|
1228
|
+
if len(hits) > 1:
|
|
1229
|
+
hits = self._most_specific(hits)
|
|
1230
|
+
if len(hits) == 1:
|
|
1231
|
+
return hits[0], conf_for(hits[0])
|
|
1232
|
+
return hits, min(min(conf_for(c) for c in hits), 0.55)
|
|
1233
|
+
|
|
1234
|
+
def _rank(pool: List[str]) -> Tuple[Union[str, List[str], None], float]:
|
|
1235
|
+
# JLS §15.12.2 phase ordering, stopping at the first phase with
|
|
1236
|
+
# any applicable candidate: exact (identity) first, then phase 1
|
|
1237
|
+
# strict (widening/subtyping, no boxing), then phase 2 loose
|
|
1238
|
+
# (adds boxing/unboxing). A widening-applicable candidate thus
|
|
1239
|
+
# always wins over a boxing-only sibling — javac's behavior —
|
|
1240
|
+
# instead of both surviving one merged `compatible` tier.
|
|
1241
|
+
exact = [c for c in pool if self._param_types_match(c, arg_types)]
|
|
1242
|
+
if exact:
|
|
1243
|
+
target, conf = _resolve_tier(exact)
|
|
1244
|
+
if target is not None:
|
|
1245
|
+
return target, conf
|
|
1246
|
+
strict = [
|
|
1247
|
+
c for c in pool
|
|
1248
|
+
if self._param_types_compatible(
|
|
1249
|
+
c, arg_types, arg_ctx, allow_boxing=False,
|
|
1250
|
+
allow_method_type_inference=allow_method_type_inference,
|
|
1251
|
+
)
|
|
1252
|
+
]
|
|
1253
|
+
if strict:
|
|
1254
|
+
target, conf = _resolve_tier(strict)
|
|
1255
|
+
if target is not None:
|
|
1256
|
+
return target, conf
|
|
1257
|
+
loose = [
|
|
1258
|
+
c for c in pool
|
|
1259
|
+
if self._param_types_compatible(
|
|
1260
|
+
c, arg_types, arg_ctx,
|
|
1261
|
+
allow_method_type_inference=allow_method_type_inference,
|
|
1262
|
+
)
|
|
1263
|
+
]
|
|
1264
|
+
return _resolve_tier(loose)
|
|
1265
|
+
|
|
1266
|
+
non_vararg = [
|
|
1267
|
+
c for c in by_arity if not self._is_phase3_only(c, arg_types, arg_ctx)
|
|
1268
|
+
]
|
|
1269
|
+
phase3 = [
|
|
1270
|
+
c for c in by_arity if self._is_phase3_only(c, arg_types, arg_ctx)
|
|
1271
|
+
]
|
|
1272
|
+
target, conf = _rank(non_vararg)
|
|
1273
|
+
if target is not None:
|
|
1274
|
+
return target, conf
|
|
1275
|
+
return _rank(phase3)
|