1919 * that build-edges.ts already builds per file is the cross-module link — if
2020 * a variable aliases an imported name, resolveCallTargets follows it).
2121 */
22- import type { FnRefBinding , ParamBinding } from '../../../types.js' ;
22+ import type { ArrayCallbackBinding , ArrayElemBinding , FnRefBinding , ForOfBinding , ParamBinding , SpreadArgBinding } from '../../../types.js' ;
2323
2424export type PointsToMap = Map < string , Set < string > > ;
2525
@@ -41,18 +41,26 @@ const MAX_SOLVER_ITERATIONS = 50;
4141 * look up — either a locally-defined function name (found via byNameAndFile) or
4242 * an imported name (found via importedNames → byNameAndFile in the source file).
4343 *
44- * @param fnRefBindings - identifier/member-expr bindings from the extractor
45- * @param definitionNames - locally-defined callable names in this file
46- * @param importedNames - names imported into this file (name → resolved file)
47- * @param paramBindings - call-site arg→param bindings (Phase 8.3c)
48- * @param definitionParams - per-function ordered parameter names (Phase 8.3c)
44+ * @param fnRefBindings - identifier/member-expr bindings from the extractor
45+ * @param definitionNames - locally-defined callable names in this file
46+ * @param importedNames - names imported into this file (name → resolved file)
47+ * @param paramBindings - call-site arg→param bindings (Phase 8.3c)
48+ * @param definitionParams - per-function ordered parameter names (Phase 8.3c)
49+ * @param arrayElemBindings - array literal element bindings (Phase 8.3e)
50+ * @param spreadArgBindings - spread-argument bindings (Phase 8.3e)
51+ * @param forOfBindings - for-of iteration variable bindings (Phase 8.3e)
52+ * @param arrayCallbackBindings - Array.from/callback bindings (Phase 8.3e)
4953 */
5054export function buildPointsToMap (
5155 fnRefBindings : readonly FnRefBinding [ ] ,
5256 definitionNames : ReadonlySet < string > ,
5357 importedNames : ReadonlyMap < string , string > ,
5458 paramBindings ?: readonly ParamBinding [ ] ,
5559 definitionParams ?: ReadonlyMap < string , readonly string [ ] > ,
60+ arrayElemBindings ?: readonly ArrayElemBinding [ ] ,
61+ spreadArgBindings ?: readonly SpreadArgBinding [ ] ,
62+ forOfBindings ?: readonly ForOfBinding [ ] ,
63+ arrayCallbackBindings ?: readonly ArrayCallbackBinding [ ] ,
5664) : PointsToMap {
5765 const pts : PointsToMap = new Map ( ) ;
5866
@@ -100,6 +108,68 @@ export function buildPointsToMap(
100108 }
101109 }
102110
111+ // Phase 8.3e: array-element bindings — seed concrete elements and wildcard.
112+ // `arr[0]` etc. are seeded from literal arrays; `arr[*]` collects all elements.
113+ if ( arrayElemBindings && arrayElemBindings . length > 0 ) {
114+ for ( const { arrayName, index, elemName } of arrayElemBindings ) {
115+ const elemKey = `${ arrayName } [${ index } ]` ;
116+ const wildcardKey = `${ arrayName } [*]` ;
117+ // Seed the per-index entry if the elemName is a concrete function.
118+ if ( ! pts . has ( elemKey ) ) pts . set ( elemKey , new Set ( ) ) ;
119+ pts . get ( elemKey ) ! . add ( elemName ) ;
120+ // Wildcard: array[*] collects all element targets for imprecise spread/for-of.
121+ constraints . push ( { lhs : wildcardKey , rhsKey : elemKey } ) ;
122+ }
123+ }
124+
125+ // Phase 8.3e: spread-argument constraints.
126+ // f(...arr) → pts[f::param_i] ⊇ pts[arr[i]] for each known element.
127+ if ( spreadArgBindings && spreadArgBindings . length > 0 && definitionParams ) {
128+ // Build a per-array index count from arrayElemBindings for precise per-index constraints.
129+ const arrayMaxIndex = new Map < string , number > ( ) ;
130+ for ( const { arrayName, index } of ( arrayElemBindings ?? [ ] ) ) {
131+ const cur = arrayMaxIndex . get ( arrayName ) ?? - 1 ;
132+ if ( index > cur ) arrayMaxIndex . set ( arrayName , index ) ;
133+ }
134+
135+ for ( const { callee, arrayName, startIndex } of spreadArgBindings ) {
136+ const params = definitionParams . get ( callee ) ;
137+ if ( ! params ) continue ;
138+ const maxIdx = arrayMaxIndex . get ( arrayName ) ?? - 1 ;
139+ if ( maxIdx >= 0 ) {
140+ // Precise: per-element constraints.
141+ for ( let i = 0 ; i <= maxIdx ; i ++ ) {
142+ const paramIdx = startIndex + i ;
143+ if ( paramIdx >= params . length ) break ;
144+ constraints . push ( { lhs : `${ callee } ::${ params [ paramIdx ] } ` , rhsKey : `${ arrayName } [${ i } ]` } ) ;
145+ }
146+ } else {
147+ // Unknown array size: all params at/after startIndex get the wildcard.
148+ for ( let j = startIndex ; j < params . length ; j ++ ) {
149+ constraints . push ( { lhs : `${ callee } ::${ params [ j ] } ` , rhsKey : `${ arrayName } [*]` } ) ;
150+ }
151+ }
152+ }
153+ }
154+
155+ // Phase 8.3e: for-of iteration constraints.
156+ // `for (const x of arr)` inside `outer` → pts[outer::x] ⊇ pts[arr[*]]
157+ if ( forOfBindings ) {
158+ for ( const { varName, sourceName, enclosingFunc } of forOfBindings ) {
159+ constraints . push ( { lhs : `${ enclosingFunc } ::${ varName } ` , rhsKey : `${ sourceName } [*]` } ) ;
160+ }
161+ }
162+
163+ // Phase 8.3e: Array.from / callback constraints.
164+ // Array.from(source, cb) → pts[cb::param0] ⊇ pts[source[*]]
165+ if ( arrayCallbackBindings && definitionParams ) {
166+ for ( const { sourceName, calleeName } of arrayCallbackBindings ) {
167+ const params = definitionParams . get ( calleeName ) ;
168+ if ( ! params || params . length === 0 ) continue ;
169+ constraints . push ( { lhs : `${ calleeName } ::${ params [ 0 ] } ` , rhsKey : `${ sourceName } [*]` } ) ;
170+ }
171+ }
172+
103173 if ( constraints . length === 0 ) return pts ;
104174
105175 // Fixed-point iteration: propagate pts sets until no new information flows.
0 commit comments