circle-ir 4.16.1 → 4.17.0
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/dist/analysis/constant-propagation/index.d.ts +2 -0
- package/dist/analysis/constant-propagation/index.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/index.js +10 -0
- package/dist/analysis/constant-propagation/index.js.map +1 -1
- package/dist/analysis/constant-propagation/propagator.d.ts +11 -0
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +125 -20
- package/dist/analysis/constant-propagation/propagator.js.map +1 -1
- package/dist/analysis/constant-propagation/types.d.ts +2 -0
- package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
- package/dist/analysis/constant-propagation.d.ts +1 -1
- package/dist/analysis/constant-propagation.d.ts.map +1 -1
- package/dist/analysis/constant-propagation.js +1 -1
- package/dist/analysis/constant-propagation.js.map +1 -1
- package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cross-file-pass.js +3 -1
- package/dist/analysis/passes/cross-file-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +22 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.js +17 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +167 -11
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/rust-literal-guard.d.ts.map +1 -1
- package/dist/analysis/rust-literal-guard.js +100 -1
- package/dist/analysis/rust-literal-guard.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +1 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +427 -26
- package/dist/core/circle-ir-core.cjs +149 -17
- package/dist/core/circle-ir-core.js +149 -17
- package/dist/core/extractors/types.js +53 -9
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/navigation/navigation-index.d.ts +40 -1
- package/dist/navigation/navigation-index.d.ts.map +1 -1
- package/dist/navigation/navigation-index.js +300 -45
- package/dist/navigation/navigation-index.js.map +1 -1
- package/dist/resolution/cross-file.d.ts +9 -0
- package/dist/resolution/cross-file.d.ts.map +1 -1
- package/dist/resolution/cross-file.js +134 -0
- package/dist/resolution/cross-file.js.map +1 -1
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -56,6 +56,8 @@ const REFLECTIVE_METHODS = new Set([
|
|
|
56
56
|
'getDeclaredConstructor', 'newProxyInstance', 'getAnnotation',
|
|
57
57
|
'getAnnotationsByType', 'isAnnotationPresent', 'getGenericReturnType',
|
|
58
58
|
]);
|
|
59
|
+
/** What every class inherits from `java.lang.Object`. */
|
|
60
|
+
const OBJECT_METHODS = new Set(['toString', 'hashCode', 'equals', 'getClass', 'clone', 'finalize', 'notify', 'notifyAll', 'wait']);
|
|
59
61
|
const LOMBOK_ACCESSOR = new Set(['Getter', 'Setter', 'Data', 'Value']);
|
|
60
62
|
const LOMBOK_BUILDER = new Set(['Builder', 'SuperBuilder']);
|
|
61
63
|
const PRIMITIVES = new Set(['byte', 'short', 'char', 'int', 'long', 'float', 'double', 'boolean']);
|
|
@@ -114,7 +116,7 @@ const JAVA_LANG = new Set([
|
|
|
114
116
|
'Object', 'Number', 'Class', 'StringBuilder', 'CharSequence', 'Comparable', 'Iterable', 'Enum',
|
|
115
117
|
]);
|
|
116
118
|
/** Bumped when the shape of a cached per-file record changes. */
|
|
117
|
-
const CACHE_VERSION = 'nav-
|
|
119
|
+
const CACHE_VERSION = 'nav-4';
|
|
118
120
|
const fileCache = new Map();
|
|
119
121
|
export function buildNavigationIndex(files, options = {}) {
|
|
120
122
|
return new NavigationIndex(files, options);
|
|
@@ -231,6 +233,15 @@ export class NavigationIndex {
|
|
|
231
233
|
`declared: ${declared.map(o => `${q.base}${signatureOf(o)}`).join(', ')}`;
|
|
232
234
|
return this.finish('callers', query, answers, unresolved, t0, opts, extra);
|
|
233
235
|
}
|
|
236
|
+
if (target && declared.length === 0 && q.name !== '<init>' && this.visibleOverloads(target.fqn, q.name).length === 0) {
|
|
237
|
+
// `MapIterator.toString()`: the type is in the tree, the method is
|
|
238
|
+
// not — it is inherited from outside, so no call site can bind to the
|
|
239
|
+
// query's target, and "no callers" would read as a finding.
|
|
240
|
+
extra.note =
|
|
241
|
+
`${q.owner} declares no method ${q.name}, and no supertype in the searched tree does: it is inherited from ` +
|
|
242
|
+
`${OBJECT_METHODS.has(q.name) ? 'java.lang.Object' : 'a type outside the tree'}, so no call site here binds to ${q.base}. ` +
|
|
243
|
+
`A call reached only through string conversion or library code has no call site at all.`;
|
|
244
|
+
}
|
|
234
245
|
const wantedSet = new Set(wanted);
|
|
235
246
|
const dispatchOpen = target !== undefined && q.name !== '<init>' && wanted.some(o => this.dispatchIsOpen(target, o));
|
|
236
247
|
for (const rec of this.files.values()) {
|
|
@@ -454,7 +465,7 @@ export class NavigationIndex {
|
|
|
454
465
|
const encl = this.enclosingTypeOf(call, rec);
|
|
455
466
|
const sup = encl?.extendsName ? this.resolveTypeName(encl.extendsName, rec) : undefined;
|
|
456
467
|
if (sup && this.typesByFqn.has(sup)) {
|
|
457
|
-
return this.bindInType(sup, call, rec, depth, `super call inside ${encl.fqn}`, sup);
|
|
468
|
+
return this.bindInType(sup, call, rec, depth, `super call inside ${encl.fqn}`, sup, true);
|
|
458
469
|
}
|
|
459
470
|
return undefined;
|
|
460
471
|
}
|
|
@@ -500,7 +511,7 @@ export class NavigationIndex {
|
|
|
500
511
|
return undefined;
|
|
501
512
|
}
|
|
502
513
|
/** Bind `call` to a member of `fqn` or of a supertype of it, choosing the overload. */
|
|
503
|
-
bindInType(fqn, call, rec, depth, evidence, receiver) {
|
|
514
|
+
bindInType(fqn, call, rec, depth, evidence, receiver, nonVirtual = false) {
|
|
504
515
|
const cands = this.visibleOverloads(fqn, call.method_name);
|
|
505
516
|
if (cands.length === 0)
|
|
506
517
|
return undefined;
|
|
@@ -520,8 +531,8 @@ export class NavigationIndex {
|
|
|
520
531
|
overload,
|
|
521
532
|
receiver,
|
|
522
533
|
result: overloaded
|
|
523
|
-
? this.tierFor(owner, call.method_name, overload, `${evidence}; ${sel.how}`, true)
|
|
524
|
-
: this.tierFor(owner, call.method_name, overload, evidence),
|
|
534
|
+
? this.tierFor(owner, call.method_name, overload, `${evidence}; ${sel.how}`, true, nonVirtual)
|
|
535
|
+
: this.tierFor(owner, call.method_name, overload, evidence, false, nonVirtual),
|
|
525
536
|
};
|
|
526
537
|
}
|
|
527
538
|
/**
|
|
@@ -535,11 +546,14 @@ export class NavigationIndex {
|
|
|
535
546
|
* the first; what runs there is a subclass's body or a lambda, never the
|
|
536
547
|
* member named. The target stays the declaring member, which is what a
|
|
537
548
|
* static resolver answers; only the tier changes.
|
|
549
|
+
*
|
|
550
|
+
* A `super.m()` call is not dispatched: it runs the body it names, whatever
|
|
551
|
+
* overrides it below.
|
|
538
552
|
*/
|
|
539
|
-
tierFor(owner, method, overload, evidence, overloaded = false) {
|
|
553
|
+
tierFor(owner, method, overload, evidence, overloaded = false, nonVirtual = false) {
|
|
540
554
|
const impls = this.implementorsWith(owner.fqn, overload);
|
|
541
555
|
const bodyless = this.isBodyless(owner, overload);
|
|
542
|
-
if (impls.length > 1 || bodyless) {
|
|
556
|
+
if ((impls.length > 1 && !nonVirtual) || bodyless) {
|
|
543
557
|
return {
|
|
544
558
|
target: `${owner.fqn}.${method}`,
|
|
545
559
|
tier: 'polymorphic',
|
|
@@ -652,11 +666,16 @@ export class NavigationIndex {
|
|
|
652
666
|
if (byArity.length === 0)
|
|
653
667
|
return { kind: 'none' };
|
|
654
668
|
const args = call.arguments.map(a => this.argumentType(a, call, rec, depth));
|
|
669
|
+
// An implicit, `this.` or `super.` receiver is the enclosing type itself,
|
|
670
|
+
// which fixes what the declaring type's variables are.
|
|
671
|
+
const self = call.receiver === null || call.receiver === 'this' || call.receiver === 'super'
|
|
672
|
+
? this.enclosingTypeOf(call, rec)
|
|
673
|
+
: undefined;
|
|
655
674
|
if (byArity.length === 1) {
|
|
656
675
|
// The only declaration in the tree at this arity, but the argument
|
|
657
676
|
// types certainly rule it out: what is called is an overload this
|
|
658
677
|
// index cannot see, inherited from a library type. Not ours.
|
|
659
|
-
if ([1, 2, 3].every(p => this.applicable(byArity[0], args, p) === 'no'))
|
|
678
|
+
if ([1, 2, 3].every(p => this.applicable(byArity[0], args, p, self) === 'no'))
|
|
660
679
|
return { kind: 'none' };
|
|
661
680
|
return {
|
|
662
681
|
kind: 'one',
|
|
@@ -666,7 +685,7 @@ export class NavigationIndex {
|
|
|
666
685
|
}
|
|
667
686
|
const fit = new Map();
|
|
668
687
|
for (const c of byArity)
|
|
669
|
-
fit.set(c, [1, 2, 3].map(p => this.applicable(c, args, p)));
|
|
688
|
+
fit.set(c, [1, 2, 3].map(p => this.applicable(c, args, p, self)));
|
|
670
689
|
const possible = byArity.filter(c => fit.get(c).some(f => f !== 'no'));
|
|
671
690
|
if (possible.length === 1) {
|
|
672
691
|
return {
|
|
@@ -686,7 +705,7 @@ export class NavigationIndex {
|
|
|
686
705
|
const C = P.filter(c => fit.get(c)[phase - 1] === 'yes');
|
|
687
706
|
if (C.length === 0)
|
|
688
707
|
return { kind: 'ambiguous', candidates: possible };
|
|
689
|
-
const best = C.find(m => P.every(o => o === m || this.moreSpecific(m, o, phase)));
|
|
708
|
+
const best = C.find(m => P.every(o => o === m || this.moreSpecific(m, o, phase, self)));
|
|
690
709
|
if (best) {
|
|
691
710
|
return {
|
|
692
711
|
kind: 'one',
|
|
@@ -699,10 +718,9 @@ export class NavigationIndex {
|
|
|
699
718
|
return { kind: 'ambiguous', candidates: possible };
|
|
700
719
|
}
|
|
701
720
|
/** Whether `c` applies to arguments of these types in compiler phase 1, 2 or 3. */
|
|
702
|
-
applicable(c, args, phase) {
|
|
721
|
+
applicable(c, args, phase, self) {
|
|
703
722
|
const o = c.overload;
|
|
704
|
-
const
|
|
705
|
-
const params = o.params.map(p => this.typeRef(p, declRec));
|
|
723
|
+
const params = this.paramRefsFrom(c, self);
|
|
706
724
|
let worst = 'yes';
|
|
707
725
|
const meet = (f) => {
|
|
708
726
|
if (f === 'no')
|
|
@@ -718,17 +736,16 @@ export class NavigationIndex {
|
|
|
718
736
|
}
|
|
719
737
|
if (!o.varargs || args.length < params.length - 1)
|
|
720
738
|
return 'no';
|
|
721
|
-
const
|
|
722
|
-
const elem = { ...last, dims: Math.max(0, last.dims - 1) };
|
|
739
|
+
const elem = elementOf(params[params.length - 1]);
|
|
723
740
|
args.forEach((a, i) => meet(this.fits(a, i < params.length - 1 ? params[i] : elem, 2)));
|
|
724
741
|
return worst;
|
|
725
742
|
}
|
|
726
743
|
/** `m` is more specific than `o`: each of its parameter types fits the other's. */
|
|
727
|
-
moreSpecific(m, o, phase) {
|
|
744
|
+
moreSpecific(m, o, phase, self) {
|
|
728
745
|
if (phase === 3)
|
|
729
746
|
return false;
|
|
730
|
-
const mp =
|
|
731
|
-
const op =
|
|
747
|
+
const mp = this.paramRefsFrom(m, self);
|
|
748
|
+
const op = this.paramRefsFrom(o, self);
|
|
732
749
|
if (mp.length !== op.length)
|
|
733
750
|
return false;
|
|
734
751
|
return mp.every((t, i) => this.fits(t, op[i], 1) === 'yes');
|
|
@@ -740,12 +757,42 @@ export class NavigationIndex {
|
|
|
740
757
|
fits(a, p, phase) {
|
|
741
758
|
if (!a)
|
|
742
759
|
return 'maybe';
|
|
743
|
-
if (p.
|
|
760
|
+
if (p.opaque) {
|
|
761
|
+
if (a.simple === 'null' && a.dims === 0)
|
|
762
|
+
return 'yes';
|
|
763
|
+
if (a.typeVar)
|
|
764
|
+
return a.varName !== undefined && a.varName === p.varName && a.dims === p.dims ? 'yes' : 'maybe';
|
|
765
|
+
// Nothing but the variable itself is a subtype of it.
|
|
766
|
+
return a.prim || a.fqn ? 'no' : 'maybe';
|
|
767
|
+
}
|
|
768
|
+
if (p.typeVar) {
|
|
769
|
+
// Whatever fills a type variable is a subtype of its erasure, so an
|
|
770
|
+
// argument the erasure rules out is ruled out. One it admits is
|
|
771
|
+
// certainly admitted only where the compiler infers the variable from
|
|
772
|
+
// the arguments.
|
|
773
|
+
if (p.erasure) {
|
|
774
|
+
const f = this.fits(a, p.erasure, phase);
|
|
775
|
+
return f === 'no' ? 'no' : p.certain ? f : 'maybe';
|
|
776
|
+
}
|
|
744
777
|
return a.prim && a.dims === 0 && phase === 1 ? 'no' : 'maybe';
|
|
778
|
+
}
|
|
745
779
|
if (a.simple === 'null' && a.dims === 0)
|
|
746
780
|
return p.prim && p.dims === 0 ? 'no' : 'yes';
|
|
747
|
-
if (a.typeVar)
|
|
781
|
+
if (a.typeVar) {
|
|
782
|
+
// A value of a type variable is a value of its erasure, and maybe of
|
|
783
|
+
// something narrower.
|
|
784
|
+
// A type variable's supertypes are its bound's; what a generic call
|
|
785
|
+
// infers is only known to be below the erasure.
|
|
786
|
+
if (a.erasure) {
|
|
787
|
+
// An unbounded variable's only supertype is Object.
|
|
788
|
+
if (!a.inferred && a.erasure.fqn === 'java.lang.Object' && a.erasure.dims === 0) {
|
|
789
|
+
return !p.prim && p.dims === 0 && p.simple === 'Object' && (!p.fqn || p.fqn === 'java.lang.Object') ? 'yes' : 'no';
|
|
790
|
+
}
|
|
791
|
+
const f = this.fits(a.erasure, p, phase);
|
|
792
|
+
return a.inferred && f !== 'yes' ? 'maybe' : f;
|
|
793
|
+
}
|
|
748
794
|
return 'maybe';
|
|
795
|
+
}
|
|
749
796
|
if (a.dims > 0 || p.dims > 0) {
|
|
750
797
|
if (a.dims === p.dims) {
|
|
751
798
|
const ae = { ...a, dims: 0 };
|
|
@@ -895,7 +942,7 @@ export class NavigationIndex {
|
|
|
895
942
|
*/
|
|
896
943
|
argumentType(arg, call, rec, depth) {
|
|
897
944
|
if (arg.type)
|
|
898
|
-
return this.
|
|
945
|
+
return this.callSiteRef(arg.type, call, rec);
|
|
899
946
|
if (!arg.location || depth >= 4)
|
|
900
947
|
return null;
|
|
901
948
|
const name = outerCallName(arg.expression);
|
|
@@ -908,6 +955,11 @@ export class NavigationIndex {
|
|
|
908
955
|
let returned;
|
|
909
956
|
let receiver;
|
|
910
957
|
if (b?.kind === 'bound' && b.overload?.returnType) {
|
|
958
|
+
// `<M extends Map<K, V>> M invert(…)` returns whatever the call infers
|
|
959
|
+
// for `M`: known only to be some subtype of the erasure.
|
|
960
|
+
const own = this.paramRef(b.overload.returnType, { owner: b.owner, overload: b.overload });
|
|
961
|
+
if (own.varName && b.overload.typeParams.includes(own.varName))
|
|
962
|
+
return { ...own, inferred: true };
|
|
911
963
|
returned = { text: b.overload.returnType, owner: b.owner };
|
|
912
964
|
receiver = b.receiver;
|
|
913
965
|
}
|
|
@@ -1068,37 +1120,165 @@ export class NavigationIndex {
|
|
|
1068
1120
|
signatureMatches(o, params, owner) {
|
|
1069
1121
|
if (o.params.length !== params.length)
|
|
1070
1122
|
return false;
|
|
1071
|
-
const
|
|
1072
|
-
return
|
|
1123
|
+
const have = this.paramRefs({ owner, overload: o });
|
|
1124
|
+
return have.every((h, i) => {
|
|
1073
1125
|
const want = this.typeRef(params[i], undefined);
|
|
1074
|
-
|
|
1075
|
-
if (want.dims !== have.dims)
|
|
1126
|
+
if (want.dims !== h.dims)
|
|
1076
1127
|
return false;
|
|
1077
1128
|
const wantBare = eraseType(params[i].replace(/\.\.\.$/, '').replace(/(\[\])+$/, ''));
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
return
|
|
1082
|
-
|
|
1083
|
-
return have.simple === want.simple;
|
|
1129
|
+
// A type variable is named as written (`K`) or by its erasure (`Object`,
|
|
1130
|
+
// `java.lang.Object`), the way the compiler and bytecode name it.
|
|
1131
|
+
if (h.varName && wantBare === h.varName)
|
|
1132
|
+
return true;
|
|
1133
|
+
return this.sameAsWritten(h, want, wantBare);
|
|
1084
1134
|
});
|
|
1085
1135
|
}
|
|
1086
|
-
/**
|
|
1136
|
+
/** Whether a declared parameter type is the one a query wrote. */
|
|
1137
|
+
sameAsWritten(have, want, wantBare) {
|
|
1138
|
+
if (wantBare.includes('.') && have.fqn && !have.prim)
|
|
1139
|
+
return have.fqn === wantBare;
|
|
1140
|
+
return have.simple === want.simple;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Same name and same parameter types: one overrides the other, as the
|
|
1144
|
+
* compiler decides it. Within one type that is the erasure, which the
|
|
1145
|
+
* compiler requires to differ between two declarations. Between a type and
|
|
1146
|
+
* its supertype, the supertype's parameter types are first read through the
|
|
1147
|
+
* subtype's supertype clauses — `Fn<T>.apply(T)` is `apply(String)` to a
|
|
1148
|
+
* class implementing `Fn<String>`, and `apply(T)` to one implementing
|
|
1149
|
+
* `Fn<T>`, which then does not override it with `apply(Collection<T>)` —
|
|
1150
|
+
* and the erasures compared. Between types neither of which is known to
|
|
1151
|
+
* extend the other, a type variable still matches whatever its erasure
|
|
1152
|
+
* admits.
|
|
1153
|
+
*/
|
|
1087
1154
|
sameErasure(a, b) {
|
|
1088
|
-
|
|
1089
|
-
const pb = b.overload.params;
|
|
1090
|
-
if (a.overload.name !== b.overload.name || pa.length !== pb.length)
|
|
1155
|
+
if (a.overload.name !== b.overload.name || a.overload.params.length !== b.overload.params.length)
|
|
1091
1156
|
return false;
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1157
|
+
if (a.owner !== b.owner) {
|
|
1158
|
+
const sub = this.isSubtype(a.owner.fqn, b.owner.fqn) ? a : this.isSubtype(b.owner.fqn, a.owner.fqn) ? b : undefined;
|
|
1159
|
+
if (sub)
|
|
1160
|
+
return this.overridesBySubstitution(sub, sub === a ? b : a);
|
|
1161
|
+
}
|
|
1162
|
+
const pa = this.paramRefs(a);
|
|
1163
|
+
const pb = this.paramRefs(b);
|
|
1164
|
+
const across = a.owner !== b.owner;
|
|
1165
|
+
const open = (t, c) => across && t.typeVar && (!t.varName || !c.overload.typeParams.includes(t.varName));
|
|
1166
|
+
return pa.every((x, i) => {
|
|
1167
|
+
const y = pb[i];
|
|
1168
|
+
if (x.dims !== y.dims)
|
|
1169
|
+
return false;
|
|
1170
|
+
const xo = open(x, a);
|
|
1171
|
+
const yo = open(y, b);
|
|
1172
|
+
if (xo && yo)
|
|
1173
|
+
return true;
|
|
1174
|
+
if (xo)
|
|
1175
|
+
return !x.erasure || this.fits({ ...y, dims: 0 }, { ...x.erasure, dims: 0 }, 1) !== 'no';
|
|
1176
|
+
if (yo)
|
|
1177
|
+
return !y.erasure || this.fits({ ...x, dims: 0 }, { ...y.erasure, dims: 0 }, 1) !== 'no';
|
|
1178
|
+
// A type variable no declaration in scope names: as lenient as before.
|
|
1179
|
+
if ((x.typeVar && !x.erasure) || (y.typeVar && !y.erasure))
|
|
1180
|
+
return true;
|
|
1181
|
+
return x.simple === y.simple;
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
/** Whether `sub`'s declaration has `sup`'s parameter types as `sub`'s type sees them, erased. */
|
|
1185
|
+
overridesBySubstitution(sub, sup) {
|
|
1186
|
+
const mine = this.paramRefs(sub);
|
|
1187
|
+
const scope = this.scopeOf(sub.owner, sub.overload);
|
|
1188
|
+
return sup.overload.params.every((written, i) => {
|
|
1189
|
+
const own = sup.overload.vars[i];
|
|
1190
|
+
// The supertype method's own type variable: its erasure.
|
|
1191
|
+
const s = own && sup.overload.typeParams.includes(own.name) ? undefined : this.substitute(written, sup.owner, sub.owner.fqn);
|
|
1192
|
+
// No binding read for the variable (a raw supertype, or a clause this
|
|
1193
|
+
// index cannot resolve): the supertype's own erasure.
|
|
1194
|
+
const theirs = s
|
|
1195
|
+
? this.withErasure(s.text, eraseWritten(s.text, scope), s.rec)
|
|
1196
|
+
: this.paramRef(written, sup);
|
|
1197
|
+
const m = mine[i];
|
|
1198
|
+
if (m.dims !== theirs.dims)
|
|
1199
|
+
return false;
|
|
1200
|
+
if (m.typeVar && !m.erasure)
|
|
1201
|
+
return true;
|
|
1202
|
+
if (theirs.typeVar && !theirs.erasure)
|
|
1203
|
+
return true;
|
|
1204
|
+
return m.simple === theirs.simple;
|
|
1100
1205
|
});
|
|
1101
1206
|
}
|
|
1207
|
+
/** A candidate's parameter types, each type variable carrying its erasure. */
|
|
1208
|
+
paramRefs(c) {
|
|
1209
|
+
return c.overload.params.map(p => this.paramRef(p, c));
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* `c`'s parameter types as a call on `self` — the enclosing type, as an
|
|
1213
|
+
* implicit, `this.` or `super.` receiver — sees them: a variable of the
|
|
1214
|
+
* declaring type is that of `self` when `self` declares it, and what
|
|
1215
|
+
* `self`'s supertype clauses fill it with when it is inherited
|
|
1216
|
+
* (`Lru extends Base<String>` makes `Base<K>.put(K)` a `put(String)`).
|
|
1217
|
+
*/
|
|
1218
|
+
paramRefsFrom(c, self) {
|
|
1219
|
+
const refs = this.paramRefs(c);
|
|
1220
|
+
if (!self)
|
|
1221
|
+
return refs;
|
|
1222
|
+
return refs.map((r, i) => {
|
|
1223
|
+
if (!r.typeVar || !r.varName || c.overload.typeParams.includes(r.varName))
|
|
1224
|
+
return r;
|
|
1225
|
+
if (c.owner === self)
|
|
1226
|
+
return { ...r, opaque: true };
|
|
1227
|
+
const sub = this.substitute(c.overload.params[i], c.owner, self.fqn);
|
|
1228
|
+
if (!sub)
|
|
1229
|
+
return r;
|
|
1230
|
+
const e = eraseWritten(sub.text, this.scopeOf(self, undefined));
|
|
1231
|
+
if (e)
|
|
1232
|
+
return { ...this.withErasure(sub.text, e, sub.rec), opaque: true };
|
|
1233
|
+
return this.typeRef(sub.text, sub.rec);
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* An argument's written type at a call site: a type variable of the
|
|
1238
|
+
* enclosing method or type carries its erasure.
|
|
1239
|
+
*/
|
|
1240
|
+
callSiteRef(written, call, rec) {
|
|
1241
|
+
const encl = this.enclosingTypeOf(call, rec);
|
|
1242
|
+
if (!encl)
|
|
1243
|
+
return this.typeRef(written, rec);
|
|
1244
|
+
const line = call.location.line;
|
|
1245
|
+
let method;
|
|
1246
|
+
for (const list of encl.overloads.values()) {
|
|
1247
|
+
for (const o of list) {
|
|
1248
|
+
if (line < o.startLine || line > o.endLine)
|
|
1249
|
+
continue;
|
|
1250
|
+
if (!method || o.endLine - o.startLine < method.endLine - method.startLine)
|
|
1251
|
+
method = o;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
return this.withErasure(written, eraseWritten(written, this.scopeOf(encl, method)), rec);
|
|
1255
|
+
}
|
|
1256
|
+
/** One written parameter (or return) type of `c`, from its declaring file's point of view. */
|
|
1257
|
+
paramRef(written, c) {
|
|
1258
|
+
const i = c.overload.params.indexOf(written);
|
|
1259
|
+
const e = i !== -1 && c.overload.vars[i]
|
|
1260
|
+
? { ...c.overload.vars[i], text: c.overload.erased[i] }
|
|
1261
|
+
: eraseWritten(written, this.scopeOf(c.owner, c.overload));
|
|
1262
|
+
return this.withErasure(written, e, this.files.get(c.owner.file));
|
|
1263
|
+
}
|
|
1264
|
+
/** A written type, with the erasure `eraseWritten` found for it when it is a type variable. */
|
|
1265
|
+
withErasure(written, e, rec) {
|
|
1266
|
+
if (!e)
|
|
1267
|
+
return this.typeRef(written, rec);
|
|
1268
|
+
const erased = this.typeRef(e.text, rec);
|
|
1269
|
+
return { ...erased, typeVar: true, varName: e.name, erasure: erased, certain: e.certain };
|
|
1270
|
+
}
|
|
1271
|
+
/** The type variables visible in a member of `owner` (`overload`), innermost first. */
|
|
1272
|
+
scopeOf(owner, overload) {
|
|
1273
|
+
const types = [];
|
|
1274
|
+
let t = owner;
|
|
1275
|
+
while (t) {
|
|
1276
|
+
types.push(t);
|
|
1277
|
+
const outer = this.typesByFqn.get(stripLast(t.fqn));
|
|
1278
|
+
t = outer && outer.file === owner.file ? outer : undefined;
|
|
1279
|
+
}
|
|
1280
|
+
return { method: overload ?? null, types };
|
|
1281
|
+
}
|
|
1102
1282
|
// --------------------------------------------------------------- dispatch
|
|
1103
1283
|
/**
|
|
1104
1284
|
* How a site bound to `owner.overload` reaches `target`'s overload, if it
|
|
@@ -1728,6 +1908,10 @@ function buildFileRecord(f) {
|
|
|
1728
1908
|
startLine: m.start_line,
|
|
1729
1909
|
endLine: m.end_line,
|
|
1730
1910
|
modifiers: m.modifiers ?? [],
|
|
1911
|
+
typeParams: m.type_parameters ?? [],
|
|
1912
|
+
typeBounds: m.type_parameter_bounds ?? {},
|
|
1913
|
+
erased: params,
|
|
1914
|
+
vars: params.map(() => null),
|
|
1731
1915
|
});
|
|
1732
1916
|
}
|
|
1733
1917
|
types.push({
|
|
@@ -1742,6 +1926,7 @@ function buildFileRecord(f) {
|
|
|
1742
1926
|
fieldNames: (t.fields ?? []).map(x => x.name),
|
|
1743
1927
|
fieldTypes: new Map((t.fields ?? []).filter(x => x.type).map(x => [x.name, x.type])),
|
|
1744
1928
|
typeParams: t.type_parameters ?? [],
|
|
1929
|
+
typeBounds: t.type_parameter_bounds ?? {},
|
|
1745
1930
|
getterFields: lombokFields(f.source, 'Getter'),
|
|
1746
1931
|
setterFields: lombokFields(f.source, 'Setter'),
|
|
1747
1932
|
methods,
|
|
@@ -1750,6 +1935,22 @@ function buildFileRecord(f) {
|
|
|
1750
1935
|
endLine: t.end_line,
|
|
1751
1936
|
});
|
|
1752
1937
|
}
|
|
1938
|
+
// Erase each parameter's type variables. Everything a declaration's
|
|
1939
|
+
// variables can name — the method, its type, the types enclosing it — is in
|
|
1940
|
+
// this file, so the result is per file and cached with it.
|
|
1941
|
+
for (const t of types) {
|
|
1942
|
+
const chain = [];
|
|
1943
|
+
for (let c = t; c; c = types.find(x => x.fqn === stripLast(c.fqn)))
|
|
1944
|
+
chain.push(c);
|
|
1945
|
+
for (const list of t.overloads.values()) {
|
|
1946
|
+
for (const o of list) {
|
|
1947
|
+
const scope = { method: o, types: chain };
|
|
1948
|
+
const es = o.params.map(p => eraseWritten(p, scope));
|
|
1949
|
+
o.erased = o.params.map((p, i) => es[i]?.text ?? p);
|
|
1950
|
+
o.vars = es.map(e => (e ? { name: e.name, certain: e.certain } : null));
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1753
1954
|
return {
|
|
1754
1955
|
path: f.path,
|
|
1755
1956
|
language: f.language,
|
|
@@ -1806,9 +2007,13 @@ function splitTopLevel(text) {
|
|
|
1806
2007
|
out.push(buf);
|
|
1807
2008
|
return out;
|
|
1808
2009
|
}
|
|
1809
|
-
/**
|
|
2010
|
+
/**
|
|
2011
|
+
* `(Long,String)` — simple names, generics erased, `String...` for varargs, a
|
|
2012
|
+
* type variable shown as its erasure: `getNumber(Map,Object)` for
|
|
2013
|
+
* `<K> getNumber(Map<? super K, ?>, K)`, as javadoc and bytecode name it.
|
|
2014
|
+
*/
|
|
1810
2015
|
function signatureOf(o) {
|
|
1811
|
-
return `(${o.
|
|
2016
|
+
return `(${o.erased.map(p => {
|
|
1812
2017
|
const varargs = p.endsWith('...');
|
|
1813
2018
|
let t = eraseType(p.replace(/@[\w.]+(\([^)]*\))?\s*/g, '').replace(/\bfinal\s+/g, '').replace(/\.\.\.$/, '').trim());
|
|
1814
2019
|
let dims = '';
|
|
@@ -1823,6 +2028,51 @@ function signatureOf(o) {
|
|
|
1823
2028
|
function memberName(owner, o) {
|
|
1824
2029
|
return o.isConstructor ? `${owner.fqn}.<init>` : `${owner.fqn}.${o.name}`;
|
|
1825
2030
|
}
|
|
2031
|
+
/**
|
|
2032
|
+
* What a written type that is a type variable erases to, the way the compiler
|
|
2033
|
+
* erases it: its leftmost bound, erased in turn when the bound is itself a
|
|
2034
|
+
* variable (`W extends U`), else `Object`; array and varargs suffixes kept.
|
|
2035
|
+
* Nothing when it is not a type variable in scope. The innermost declaration
|
|
2036
|
+
* of a name hides the outer ones.
|
|
2037
|
+
*
|
|
2038
|
+
* `certain` says whether an argument that fits the erasure certainly fits the
|
|
2039
|
+
* variable: so for a method's own variable, which the compiler infers from
|
|
2040
|
+
* the arguments, unless its bound names the variable itself
|
|
2041
|
+
* (`T extends Comparable<T>`); never for a type's, which the receiver fixes.
|
|
2042
|
+
*/
|
|
2043
|
+
function eraseWritten(written, scope, seen = new Set()) {
|
|
2044
|
+
let t = written.replace(/@[\w.]+(\([^)]*\))?\s*/g, '').replace(/\bfinal\s+/g, '').trim();
|
|
2045
|
+
let suffix = '';
|
|
2046
|
+
if (t.endsWith('...')) {
|
|
2047
|
+
suffix = '...';
|
|
2048
|
+
t = t.slice(0, -3).trim();
|
|
2049
|
+
}
|
|
2050
|
+
t = eraseType(t);
|
|
2051
|
+
while (t.endsWith('[]')) {
|
|
2052
|
+
suffix = `[]${suffix}`;
|
|
2053
|
+
t = t.slice(0, -2).trim();
|
|
2054
|
+
}
|
|
2055
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(t) || seen.has(t))
|
|
2056
|
+
return undefined;
|
|
2057
|
+
let v;
|
|
2058
|
+
if (scope.method?.typeParams.includes(t))
|
|
2059
|
+
v = { bound: scope.method.typeBounds[t] ?? null, kind: 'method' };
|
|
2060
|
+
else {
|
|
2061
|
+
const owner = scope.types.find(x => x.typeParams.includes(t));
|
|
2062
|
+
if (owner)
|
|
2063
|
+
v = { bound: owner.typeBounds[t] ?? null, kind: 'type' };
|
|
2064
|
+
}
|
|
2065
|
+
if (!v)
|
|
2066
|
+
return undefined;
|
|
2067
|
+
if (v.bound === null)
|
|
2068
|
+
return { name: t, text: `Object${suffix}`, certain: v.kind === 'method' };
|
|
2069
|
+
const inner = eraseWritten(v.bound, scope, new Set([...seen, t]));
|
|
2070
|
+
if (inner)
|
|
2071
|
+
return { name: t, text: `${inner.text}${suffix}`, certain: v.kind === 'method' && inner.certain };
|
|
2072
|
+
const args = v.bound.includes('<') ? v.bound.slice(v.bound.indexOf('<') + 1) : '';
|
|
2073
|
+
const selfBounded = new RegExp(`\\b${t}\\b`).test(args);
|
|
2074
|
+
return { name: t, text: `${eraseType(v.bound)}${suffix}`, certain: v.kind === 'method' && !selfBounded };
|
|
2075
|
+
}
|
|
1826
2076
|
/** Remove every `<...>` from a written type. */
|
|
1827
2077
|
function eraseType(t) {
|
|
1828
2078
|
let out = '';
|
|
@@ -1841,6 +2091,11 @@ function eraseType(t) {
|
|
|
1841
2091
|
}
|
|
1842
2092
|
return out.trim();
|
|
1843
2093
|
}
|
|
2094
|
+
/** A varargs parameter's element type. */
|
|
2095
|
+
function elementOf(t) {
|
|
2096
|
+
const dims = Math.max(0, t.dims - 1);
|
|
2097
|
+
return { ...t, dims, ...(t.erasure ? { erasure: { ...t.erasure, dims } } : {}) };
|
|
2098
|
+
}
|
|
1844
2099
|
/** Whether a call with `n` arguments can reach overload `o` at all. */
|
|
1845
2100
|
function fitsArity(o, n) {
|
|
1846
2101
|
if (o.params.length === n)
|