2026-10-10 15:46:24 +08:00
import * as t from "@babel/types" ;
import {
semanticStaticPropertyKey ,
unwrapJavaScriptExpression ,
} from "./javascriptAstValues.js" ;
import { semanticCallableResultExpressions } from "./javascriptSemanticCallableResults.js" ;
import {
2026-10-20 17:25:19 +08:00
javaScriptSourceInvocationContext ,
javaScriptGenericInvocationMethod ,
resolveJavaScriptGenericInvocation ,
type JavaScriptSourceInvocationContext ,
} from "./javascriptIntrinsicInvocation.js" ;
import {
semanticBindingAtRead ,
semanticEffectTiming ,
} from "./javascriptSemanticMutationInitializers.js" ;
import {
semanticReferenceSourcesInactive ,
semanticKnownArrayIteration ,
semanticKnownArrayIterationAt ,
semanticSelectedReferenceSlot ,
} from "./javascriptSemanticReferenceSources.js" ;
import { semanticArrayPropertyReferences } from "./javascriptSemanticObjectReferences.js" ;
import {
semanticNormalizeCapturedSelections ,
semanticProjectedSourcesAtRead ,
semanticShiftCapturedSelections ,
type SemanticCapturedSelection ,
} from "./javascriptSemanticProjectedSources.js" ;
import { SEMANTIC_ALIAS_SELECTION_LIMIT } from "./javascriptSemanticResourceLimits.js" ;
import {
semanticPropertyPathKeyMatches ,
2026-10-10 15:46:24 +08:00
type JavaScriptSemanticPropertyPath ,
} from "./javascriptSemanticPropertyPaths.js" ;
2026-10-20 17:25:19 +08:00
import {
SemanticAliasSelections ,
semanticAliasSelectionChoices ,
type SemanticAliasSelectionPath ,
} from "./javascriptSemanticAliasSelections.js" ;
2026-10-10 15:46:24 +08:00
import {
resolveSemanticBindingState ,
type JavaScriptSemanticAnalysisState ,
2026-10-20 17:25:19 +08:00
type JavaScriptSemanticBindingState ,
2026-10-10 15:46:24 +08:00
} from "./javascriptSemanticState.js" ;
/** A reference potentially handed to a caller or generator consumer. */
export interface SemanticReturnedReference {
readonly node : t.Node ;
readonly projection : JavaScriptSemanticPropertyPath ;
}
interface Candidate {
readonly node : t.Node ;
2026-10-19 12:26:54 +08:00
/** `undefined` means any projection depth after different projection depths merge. */
2026-10-20 17:25:19 +08:00
readonly path : SemanticAliasSelectionPath | undefined ;
/** Member selection happens at its use, while a captured function keeps its read. */
readonly readAt? : t.Node ;
readonly capturedSelections ?:
| readonly SemanticCapturedSelection []
| undefined ;
readonly invocation? : JavaScriptSourceInvocationContext ;
2026-10-16 10:09:22 +08:00
}
2026-10-19 12:26:54 +08:00
export interface SemanticReturnedReferenceResult {
readonly references : readonly SemanticReturnedReference [];
2026-10-20 17:25:19 +08:00
/** A visible invocation-method mutation prevents a source-local result proof. */
readonly unresolvedInvocation? : boolean ;
2026-10-19 12:26:54 +08:00
/** Both states close the supported source-local may-target graph. Widened joins projections without dropping represented targets; it does not claim runtime completeness. */
readonly precision : "exact" | "widened" ;
2026-10-10 15:46:24 +08:00
}
2026-10-10 18:38:59 +08:00
interface ObjectTargetIndex {
readonly propertyByName : ReadonlyMap <
string ,
t . ObjectMethod | t . ObjectProperty
> ;
readonly trailingUnknownsByProperty : ReadonlyMap <
t . ObjectMethod | t . ObjectProperty ,
ObjectTargetUnknown
> ;
}
interface ObjectTargetUnknown {
readonly node : t.ObjectMethod | t . ObjectProperty | t . SpreadElement ;
readonly previous : ObjectTargetUnknown | undefined ;
}
2026-10-10 15:46:24 +08:00
/** Resolve local result references conservatively, without claiming call execution. */
export const createSemanticReturnedReferences = (
state : JavaScriptSemanticAnalysisState ,
2026-10-20 17:25:19 +08:00
consumption :
| "potential-reference"
| "direct-call"
| "awaited-call" = "potential-reference" ,
) : ((
callee : t.Node ,
invocation? : t.CallExpression | t . OptionalCallExpression ,
) => SemanticReturnedReferenceResult ) => {
const results = new WeakMap <
t . Node ,
{
readonly result : SemanticReturnedReferenceResult ;
readonly invocation : t.Node | undefined ;
readonly iterationUnknown : boolean ;
readonly ambientWrites : number ;
readonly dependencies : readonly {
readonly binding : JavaScriptSemanticBindingState ;
readonly writes : number ;
readonly escapes : number ;
}[];
}
> ();
2026-10-10 18:38:59 +08:00
const objectTargets = new WeakMap < t.ObjectExpression , ObjectTargetIndex >();
2026-10-20 17:25:19 +08:00
return ( callee , invocation ) => {
2026-10-10 15:46:24 +08:00
const existing = results . get ( callee );
2026-10-20 17:25:19 +08:00
if (
existing !== undefined &&
existing . invocation === invocation &&
existing . iterationUnknown === state . arrayIterationUnknown &&
existing . ambientWrites === state . ambientWriteEffects . length &&
existing . dependencies . every (
({ binding , writes , escapes }) =>
binding . memberWrites . length === writes &&
binding . escapedPaths . length === escapes ,
)
)
return existing . result ;
const dependencies = new Map <
JavaScriptSemanticBindingState ,
{ readonly writes : number ; readonly escapes : number }
> ();
2026-10-19 12:26:54 +08:00
const functions = new Set < t.Function >();
2026-10-20 17:25:19 +08:00
const work : Candidate [] = [];
2026-10-19 12:26:54 +08:00
// A transition depends on the source read and current projection, not the
// initializer history used to reach it. Keeping history out of the key lets
// shared diamonds and reassignment cycles converge to the graph fixed point.
2026-10-20 17:25:19 +08:00
const projectionDepths = new WeakMap <
t . Node ,
Map < string , number | undefined >
> ();
const expanded = new WeakMap < t.Node , Set < string >>();
let additionalContexts = 0 ;
let contextsWidened = false ;
2026-10-19 12:26:54 +08:00
const result : {
references : readonly SemanticReturnedReference [];
precision : SemanticReturnedReferenceResult [ "precision" ];
2026-10-20 17:25:19 +08:00
unresolvedInvocation? : boolean ;
2026-10-19 12:26:54 +08:00
} = { references : [], precision : "exact" };
2026-10-20 17:25:19 +08:00
// The invocation owner retains the actual callable receiver. A bare alias
// of fn.call has lost that receiver and cannot execute fn by itself.
const nativeArrayIteration = ( array : t.ArrayExpression ) =>
semanticKnownArrayIterationAt ( array , state );
const sourceInvocation =
invocation === undefined
? undefined
: javaScriptSourceInvocationContext ( invocation , nativeArrayIteration );
const sourceGenericInvocation =
invocation !== undefined &&
javaScriptGenericInvocationMethod ( invocation . callee ) !== undefined ;
const inheritedInvocationChanged = ( method : "call" | "apply" ) : boolean => {
if ( invocation === undefined ) return false ;
const inheritedPath = [ "global" , "Function" , "prototype" , method ];
return state . ambientWriteEffects . some (
( effect ) =>
effect . path . length <= inheritedPath . length &&
effect . path . every (
( key , index ) =>
key === null ||
semanticPropertyPathKeyMatches ( key , inheritedPath [ index ] ?? "" ),
) &&
semanticEffectTiming ( invocation , effect . node , state ) !== "after" ,
);
};
// Exact finite alternatives retain unrelated keys and cross-path
// correlations. Growing/shrinking cycles still converge at any depth;
// selector-budget widening retains the complete may-target closure.
const selections = new SemanticAliasSelections (() => {
state . valueResourceLimits . add ( "alias-selections" );
result . precision = "widened" ;
});
const enqueue = (
node : t.Node ,
path : Candidate [ "path" ],
readAt? : t.Node ,
capturedSelections? : readonly SemanticCapturedSelection [],
invocationContext? : JavaScriptSourceInvocationContext ,
) : void => {
2026-10-19 12:26:54 +08:00
const source = unwrapJavaScriptExpression ( node ). node ;
2026-10-20 17:25:19 +08:00
if ( contextsWidened ) {
path = undefined ;
capturedSelections = undefined ;
invocationContext = undefined ;
}
const invocationKey = () =>
invocationContext === undefined
? null
: [
invocationContext . receiver ? . start ,
invocationContext . receiver ? . end ,
invocationContext . receiverUnknown ,
invocationContext . arguments ? . map (( argument ) => [
argument . start ,
argument . end ,
]),
];
let readKey = JSON . stringify ([
readAt ? . start ?? - 1 ,
readAt ? . end ?? - 1 ,
invocationKey (),
]);
const depths = projectionDepths . get ( source ) ?? new Map ();
projectionDepths . set ( source , depths );
if ( ! depths . has ( readKey )) {
depths . set ( readKey , path ? . length );
2026-10-19 12:26:54 +08:00
} else {
2026-10-20 17:25:19 +08:00
const previous = depths . get ( readKey );
if ( previous === undefined || previous !== path ? . length ) {
if ( previous !== undefined ) result . precision = "widened" ;
depths . set ( readKey , undefined );
path = undefined ;
}
}
if ( path !== undefined ) path = selections . normalize ( source , path );
capturedSelections =
path === undefined
? undefined
: semanticNormalizeCapturedSelections (
capturedSelections ,
path . length ,
);
const expansionKey = () =>
JSON . stringify ([
readKey ,
path ?? null ,
capturedSelections ? . map (({ at , depth , ownEnumerable }) => [
at . start ,
at . end ,
depth ,
ownEnumerable ,
]),
]);
let key = expansionKey ();
const paths = expanded . get ( source ) ?? new Set < string >();
if ( paths . has ( key )) return ;
// Copy/invocation contexts add selector dimensions. Bound them by the
// same query budget, then visit the complete unknown-projection closure.
if (
! contextsWidened &&
( capturedSelections !== undefined || invocationContext !== undefined ) &&
paths . size > 0 &&
++ additionalContexts > SEMANTIC_ALIAS_SELECTION_LIMIT
) {
contextsWidened = true ;
state . valueResourceLimits . add ( "alias-selections" );
2026-10-19 12:26:54 +08:00
result . precision = "widened" ;
2026-10-20 17:25:19 +08:00
// Generic transitions select only the source receiver and authored
// argument values. Keep that complete finite source closure when their
// precise positional contexts merge, including later callable receivers.
if ( sourceInvocation !== undefined ) {
if ( sourceInvocation . receiver !== undefined )
enqueue ( sourceInvocation . receiver , undefined );
for ( const argument of sourceInvocation . arguments ?? [])
if ( t . isExpression ( argument )) enqueue ( argument , undefined );
else if ( t . isSpreadElement ( argument ))
enqueue ( argument . argument , undefined );
}
2026-10-16 10:09:22 +08:00
}
2026-10-20 17:25:19 +08:00
if ( contextsWidened ) {
path = undefined ;
capturedSelections = undefined ;
invocationContext = undefined ;
readKey = JSON . stringify ([
readAt ? . start ?? - 1 ,
readAt ? . end ?? - 1 ,
null ,
]);
if ( sourceGenericInvocation ) result . unresolvedInvocation = true ;
depths . set ( readKey , undefined );
key = expansionKey ();
if ( paths . has ( key )) return ;
2026-10-16 10:09:22 +08:00
}
2026-10-20 17:25:19 +08:00
paths . add ( key );
expanded . set ( source , paths );
work . push ({
node : source ,
path ,
...( readAt === undefined ? {} : { readAt }),
...( capturedSelections === undefined ? {} : { capturedSelections }),
...( invocationContext === undefined
? {}
: { invocation : invocationContext }),
});
2026-10-19 12:26:54 +08:00
};
2026-10-20 17:25:19 +08:00
enqueue ( callee , [], undefined , undefined , sourceInvocation );
2026-10-19 12:26:54 +08:00
while ( work . length > 0 ) {
2026-10-20 17:25:19 +08:00
const current = work . pop ();
if ( current === undefined ) break ;
const { node } = current ;
// An exact empty selection with an invocation is the value being called,
// not a container whose callable children are potential escape targets.
// Ordinary objects and arrays cannot supply that callable receiver. An
// unknown projection still visits the full closure after budget widening.
if (
current . invocation !== undefined &&
current . path ? . length === 0 &&
( t . isObjectExpression ( node ) || t . isArrayExpression ( node ))
)
continue ;
2026-10-16 10:09:22 +08:00
const pending : Candidate [] = [];
2026-10-10 15:46:24 +08:00
if ( t . isIdentifier ( node )) {
const binding = resolveSemanticBindingState ( state , node , node . name );
2026-10-20 17:25:19 +08:00
if ( binding === undefined ) {
if ( sourceGenericInvocation && current . path ? . length === 0 )
result . unresolvedInvocation = true ;
continue ;
}
dependencies . set ( binding , {
writes : binding.memberWrites.length ,
escapes : binding.escapedPaths.length ,
});
if ( current . path !== undefined ) {
if (
sourceGenericInvocation &&
current . path . length > 0 &&
semanticProjectedSourcesAtRead (
binding ,
node ,
current . path ,
state ,
true ,
// The current invocation receives its callee before its own
// opaque effects; earlier captured member reads keep their site.
invocation !== undefined &&
current . readAt ===
unwrapJavaScriptExpression ( invocation . callee ). node
? invocation
: ( current . readAt ?? node ),
current . capturedSelections ,
) === undefined
)
result . unresolvedInvocation = true ;
const selectedSources = semanticProjectedSourcesAtRead (
binding ,
node ,
current . path ,
state ,
false ,
current . readAt ?? node ,
current . capturedSelections ,
);
if ( selectedSources !== undefined ) {
for ( const source of selectedSources ) {
const capturedSelections = [
...( semanticShiftCapturedSelections (
current . capturedSelections ,
source . path . length - current . path . length ,
) ?? []),
...( source . capturedSelections ?? []),
];
pending . push ({
... source ,
...( current . path . length === 0
? {}
: { readAt : current.readAt ?? node }),
...( capturedSelections . length === 0
? {}
: { capturedSelections }),
});
}
for ( let index = pending . length - 1 ; index >= 0 ; index -- ) {
const child = pending [ index ];
if ( child !== undefined )
enqueue (
child . node ,
child . path ,
child . readAt ,
child . capturedSelections ,
child . invocation ?? current . invocation ,
);
}
continue ;
}
result . precision = "widened" ;
if ( sourceGenericInvocation ) result . unresolvedInvocation = true ;
}
2026-10-16 10:09:22 +08:00
const selected = semanticBindingAtRead (
binding ,
node ,
state . parentsByNode ,
). binding ;
2026-10-10 15:46:24 +08:00
for ( const initializer of [
2026-10-16 10:09:22 +08:00
... selected . initializers ,
... selected . referenceInitializers ,
]) {
2026-10-20 17:25:19 +08:00
if ( semanticReferenceSourcesInactive ( initializer , state )) continue ;
2026-10-10 15:46:24 +08:00
pending . push ({
node : initializer.node ,
2026-10-19 12:26:54 +08:00
path :
current.path === undefined
? undefined
: [... initializer . projection , ... current . path ],
2026-10-20 17:25:19 +08:00
...( current . path ? . length === 0
? {}
: { readAt : current.readAt ?? node }),
...( current . capturedSelections === undefined
? {}
: {
capturedSelections : semanticShiftCapturedSelections (
current . capturedSelections ,
initializer . projection . length ,
),
}),
2026-10-10 15:46:24 +08:00
});
2026-10-16 10:09:22 +08:00
}
2026-10-20 17:25:19 +08:00
if ( current . path === undefined )
for ( const effect of binding . memberWrites )
for ( const source of effect . assignedSources ?? [])
if ( ! semanticReferenceSourcesInactive ( source , state ))
pending . push ({ node : source.node , path : undefined });
2026-10-10 15:46:24 +08:00
} else if (
t . isMemberExpression ( node ) ||
t . isOptionalMemberExpression ( node )
) {
const path : ( string | number | null )[] = [];
let receiver : t.Node = node ;
while (
t . isMemberExpression ( receiver ) ||
t . isOptionalMemberExpression ( receiver )
) {
path . push (
semanticStaticPropertyKey ( receiver . property , receiver . computed ),
);
receiver = unwrapJavaScriptExpression ( receiver . object ). node ;
}
pending . push ({
2026-10-19 12:26:54 +08:00
... current ,
2026-10-10 15:46:24 +08:00
node : receiver ,
2026-10-20 17:25:19 +08:00
readAt : current.readAt ?? node ,
...( current . capturedSelections === undefined
? {}
: {
capturedSelections : semanticShiftCapturedSelections (
current . capturedSelections ,
path . length ,
),
}),
2026-10-19 12:26:54 +08:00
path :
current.path === undefined
? undefined
: [... path . reverse (), ... current . path ],
2026-10-10 15:46:24 +08:00
});
} else if (
t . isConditionalExpression ( node ) ||
t . isLogicalExpression ( node )
) {
pending . push (
{
2026-10-19 12:26:54 +08:00
... current ,
2026-10-10 15:46:24 +08:00
node : t.isConditionalExpression ( node ) ? node.consequent : node.left ,
},
{
2026-10-19 12:26:54 +08:00
... current ,
2026-10-10 15:46:24 +08:00
node : t.isConditionalExpression ( node ) ? node.alternate : node.right ,
},
);
} else if ( t . isSequenceExpression ( node )) {
const last = node . expressions . at ( - 1 );
2026-10-19 12:26:54 +08:00
if ( last !== undefined ) pending . push ({ ... current , node : last });
2026-10-20 17:25:19 +08:00
} else if ( t . isFunction ( node )) {
if ( current . path === undefined || current . path . length === 0 )
functions . add ( node );
else if (
current . invocation !== undefined &&
current . path . every (( key ) =>
semanticAliasSelectionChoices ( key ). every (
( choice ) => choice === "call" || choice === "apply" ,
),
)
) {
// Own member writes have already selected their actual RHS above.
// Only a remaining inherited invocation member reaches this branch.
const methods = semanticAliasSelectionChoices (
current . path . at ( - 1 ) ! ,
). filter (
( method ) : method is "call" | "apply" =>
method === "call" || method === "apply" ,
);
for ( const method of methods ) {
const selected = resolveJavaScriptGenericInvocation (
method ,
current . invocation ,
nativeArrayIteration ,
);
if ( selected !== undefined )
pending . push ({
node : selected.callable ,
path : [],
invocation : selected.context ,
});
else if ( current . invocation . receiverUnknown )
result . unresolvedInvocation = true ;
if ( inheritedInvocationChanged ( method ))
result . unresolvedInvocation = true ;
}
}
} else if ( t . isObjectExpression ( node ))
2026-10-19 12:26:54 +08:00
selectObjectTargets ( node , current , pending , objectTargets );
2026-10-10 15:46:24 +08:00
else if ( t . isArrayExpression ( node )) {
2026-10-19 12:26:54 +08:00
const [ key , ... path ] = current . path ?? [];
2026-10-20 17:25:19 +08:00
if ( current . path !== undefined && key !== undefined ) {
for ( const choice of semanticAliasSelectionChoices ( key ))
for ( const source of semanticArrayPropertyReferences ( node , [
choice ,
])) {
let projection = source . path ;
if (
source . capturedSelectionAt !== undefined &&
! semanticKnownArrayIteration (
semanticSelectedReferenceSlot (
{ node : source.node , projection : [] },
state ,
source . capturedSelectionAt ,
) ? . value ,
state ,
)
)
projection = [ null , ... source . path . slice ( 1 )];
const selectedPath = [... projection , ... path ];
pending . push ({
... current ,
node : source.node ,
path : selectedPath ,
capturedSelections : [
...( semanticShiftCapturedSelections (
current . capturedSelections ,
selectedPath . length - current . path . length ,
) ?? []),
...( source . capturedSelectionAt === undefined
? []
: [{ at : source.capturedSelectionAt , depth : 1 }]),
],
});
}
for ( let index = pending . length - 1 ; index >= 0 ; index -- ) {
const child = pending [ index ];
if ( child !== undefined )
enqueue (
child . node ,
child . path ,
child . readAt ,
child . capturedSelections ,
child . invocation ?? current . invocation ,
);
}
continue ;
}
2026-10-10 15:46:24 +08:00
let uncertainOffset = false ;
node . elements . forEach (( element , offset ) => {
if ( element === null ) return ;
if ( t . isSpreadElement ( element )) {
uncertainOffset = true ;
pending . push ({
2026-10-19 12:26:54 +08:00
... current ,
2026-10-10 15:46:24 +08:00
node : element.argument ,
2026-10-19 12:26:54 +08:00
path : current.path === undefined ? undefined : [ null , ... path ],
2026-10-20 17:25:19 +08:00
capturedSelections : [
...( current . capturedSelections ?? []),
{ at : element.argument , depth : 1 },
],
2026-10-10 15:46:24 +08:00
});
2026-10-20 17:25:19 +08:00
} else if (
current . path === undefined ||
key === undefined ||
uncertainOffset ||
semanticAliasSelectionChoices ( key ). some (( choice ) =>
semanticPropertyPathKeyMatches ( choice , String ( offset )),
)
)
2026-10-19 12:26:54 +08:00
pending . push ({
... current ,
node : element ,
path : current.path === undefined ? undefined : path ,
2026-10-20 17:25:19 +08:00
capturedSelections :
semanticShiftCapturedSelections (
current . capturedSelections ,
- 1 ,
) ?? [],
2026-10-19 12:26:54 +08:00
});
2026-10-10 15:46:24 +08:00
});
} else if ( t . isNewExpression ( node ))
2026-10-19 12:26:54 +08:00
pending . push ({ ... current , node : node.callee });
2026-10-10 15:46:24 +08:00
else if ( t . isClass ( node )) {
2026-10-19 12:26:54 +08:00
const [ key , ... path ] = current . path ?? [];
2026-10-10 15:46:24 +08:00
for ( const member of node . body . body ) {
if ( t . isClassMethod ( member )) {
if (
2026-10-19 12:26:54 +08:00
(( current . path === undefined || current . path . length === 0 ) &&
member . kind === "constructor" ) ||
current . path === undefined ||
2026-10-10 15:46:24 +08:00
( key !== undefined &&
2026-10-20 17:25:19 +08:00
semanticAliasSelectionChoices ( key ). some (( choice ) => {
const name = semanticStaticPropertyKey (
member . key ,
member . computed ,
);
return (
name === null ||
semanticPropertyPathKeyMatches ( choice , name )
);
}))
2026-10-10 15:46:24 +08:00
)
2026-10-19 12:26:54 +08:00
pending . push ({
... current ,
node : member ,
path : current.path === undefined ? undefined : path ,
2026-10-20 17:25:19 +08:00
capturedSelections : semanticShiftCapturedSelections (
current . capturedSelections ,
- 1 ,
),
2026-10-19 12:26:54 +08:00
});
2026-10-10 15:46:24 +08:00
} else if (
t . isClassProperty ( member ) &&
member . value !== null &&
member . value !== undefined &&
2026-10-19 12:26:54 +08:00
( key !== undefined || current . path === undefined ) &&
( current . path === undefined ||
2026-10-20 17:25:19 +08:00
( key !== undefined &&
semanticAliasSelectionChoices ( key ). some (( choice ) => {
const name = semanticStaticPropertyKey (
member . key ,
member . computed ,
);
return (
name === null ||
semanticPropertyPathKeyMatches ( choice , name )
);
})))
2026-10-10 15:46:24 +08:00
)
2026-10-19 12:26:54 +08:00
pending . push ({
... current ,
node : member.value ,
path : current.path === undefined ? undefined : path ,
2026-10-20 17:25:19 +08:00
capturedSelections : semanticShiftCapturedSelections (
current . capturedSelections ,
- 1 ,
),
2026-10-19 12:26:54 +08:00
});
2026-10-10 15:46:24 +08:00
}
if ( node . superClass !== null && node . superClass !== undefined )
2026-10-19 12:26:54 +08:00
pending . push ({ ... current , node : node.superClass });
2026-10-10 15:46:24 +08:00
}
2026-10-16 10:09:22 +08:00
for ( let index = pending . length - 1 ; index >= 0 ; index -- ) {
const child = pending [ index ];
2026-10-20 17:25:19 +08:00
if ( child !== undefined )
enqueue (
child . node ,
child . path ,
child . readAt ,
child . capturedSelections ,
child . invocation ?? current . invocation ,
);
2026-10-16 10:09:22 +08:00
}
2026-10-10 15:46:24 +08:00
}
2026-10-20 17:25:19 +08:00
const output = [... functions ]. flatMap (( callable ) => {
// A direct generator/async call produces a fresh iterator/Promise, not
// the yielded/resolved contents. Escape analysis still retains those
// potential references for their eventual consumers.
if (
consumption !== "potential-reference" &&
( callable . generator ||
( consumption === "direct-call" && callable . async ))
)
return [];
return semanticCallableResultExpressions ( callable ). flatMap (
({ node , delegated , kind }) =>
node === null ||
( consumption !== "potential-reference" && kind !== "return" )
? []
: [{ node , projection : delegated ? [ null ] : [] }],
);
});
2026-10-19 12:26:54 +08:00
result . references = output ;
2026-10-20 17:25:19 +08:00
results . set ( callee , {
result ,
invocation ,
iterationUnknown : state.arrayIterationUnknown ,
ambientWrites : state.ambientWriteEffects.length ,
dependencies : [... dependencies ]. map (([ binding , counts ]) => ({
binding ,
... counts ,
})),
});
2026-10-19 12:26:54 +08:00
if ( result . precision === "widened" )
state . returnedReferenceWidenings . add ( callee );
return result ;
2026-10-10 15:46:24 +08:00
};
};
const selectObjectTargets = (
node : t.ObjectExpression ,
item : Candidate ,
pending : Candidate [],
2026-10-10 18:38:59 +08:00
indexes : WeakMap < t.ObjectExpression , ObjectTargetIndex >,
2026-10-10 15:46:24 +08:00
) : void => {
2026-10-19 12:26:54 +08:00
const [ key , ... path ] = item . path ?? [];
const childPath = item . path === undefined ? undefined : path ;
2026-10-20 17:25:19 +08:00
if ( key !== null && typeof key === "object" && "alternatives" in key ) {
for ( const choice of semanticAliasSelectionChoices ( key ))
selectObjectTargets (
node ,
{ ... item , path : [ choice , ... path ] },
pending ,
indexes ,
);
return ;
}
if ( key !== null && key !== undefined && typeof key !== "object" ) {
2026-10-10 18:38:59 +08:00
const index = objectTargetIndex ( node , indexes );
const property = index . propertyByName . get ( String ( key ));
if ( property !== undefined ) {
const unknowns : ObjectTargetUnknown [] = [];
for (
let unknown = index . trailingUnknownsByProperty . get ( property );
unknown !== undefined ;
unknown = unknown . previous
)
unknowns . push ( unknown );
for ( const { node : unknown } of unknowns . reverse ())
pending . push ({
... item ,
node : t.isSpreadElement ( unknown )
? unknown . argument
: t.isObjectProperty ( unknown )
? unknown . value
: unknown ,
2026-10-19 12:26:54 +08:00
...( t . isSpreadElement ( unknown ) ? {} : { path : childPath }),
2026-10-20 17:25:19 +08:00
capturedSelections : t.isSpreadElement ( unknown )
? [
...( item . capturedSelections ?? []),
{ at : unknown.argument , depth : 1 , ownEnumerable : true },
]
: ( semanticShiftCapturedSelections ( item . capturedSelections , - 1 ) ??
[]),
2026-10-10 18:38:59 +08:00
});
pending . push ({
... item ,
node : t.isObjectProperty ( property ) ? property.value : property ,
2026-10-19 12:26:54 +08:00
path : childPath ,
2026-10-20 17:25:19 +08:00
capturedSelections :
semanticShiftCapturedSelections ( item . capturedSelections , - 1 ) ?? [],
2026-10-10 18:38:59 +08:00
});
return ;
}
}
2026-10-10 15:46:24 +08:00
let replaced = false ;
for ( const property of [... node . properties ]. reverse ()) {
if ( replaced ) break ;
if ( t . isSpreadElement ( property )) {
2026-10-20 17:25:19 +08:00
pending . push ({
... item ,
node : property.argument ,
capturedSelections : [
...( item . capturedSelections ?? []),
{ at : property.argument , depth : 1 , ownEnumerable : true },
],
});
2026-10-10 15:46:24 +08:00
continue ;
}
const name = semanticStaticPropertyKey ( property . key , property . computed );
if (
key !== null &&
key !== undefined &&
name !== null &&
2026-10-20 17:25:19 +08:00
! semanticPropertyPathKeyMatches ( key , name )
2026-10-10 15:46:24 +08:00
)
continue ;
if ( t . isObjectProperty ( property ))
2026-10-20 17:25:19 +08:00
pending . push ({
... item ,
node : property.value ,
path : childPath ,
capturedSelections :
semanticShiftCapturedSelections ( item . capturedSelections , - 1 ) ?? [],
});
else
pending . push ({
... item ,
node : property ,
path : childPath ,
capturedSelections :
semanticShiftCapturedSelections ( item . capturedSelections , - 1 ) ?? [],
});
if (
key !== null &&
key !== undefined &&
typeof key !== "object" &&
name === String ( key )
)
2026-10-10 15:46:24 +08:00
replaced = true ;
}
};
2026-10-10 18:38:59 +08:00
const objectTargetIndex = (
node : t.ObjectExpression ,
indexes : WeakMap < t.ObjectExpression , ObjectTargetIndex >,
) : ObjectTargetIndex => {
const existing = indexes . get ( node );
if ( existing !== undefined ) return existing ;
const propertyByName = new Map < string , t.ObjectMethod | t.ObjectProperty >();
const trailingUnknownsByProperty = new Map <
t . ObjectMethod | t . ObjectProperty ,
ObjectTargetUnknown
> ();
let trailingUnknown : ObjectTargetUnknown | undefined ;
for ( const property of [... node . properties ]. reverse ()) {
if ( t . isSpreadElement ( property )) {
trailingUnknown = { node : property , previous : trailingUnknown };
continue ;
}
const name = semanticStaticPropertyKey ( property . key , property . computed );
if ( name === null ) {
trailingUnknown = { node : property , previous : trailingUnknown };
continue ;
}
if ( propertyByName . has ( name )) continue ;
propertyByName . set ( name , property );
if ( trailingUnknown !== undefined )
trailingUnknownsByProperty . set ( property , trailingUnknown );
}
const created = { propertyByName , trailingUnknownsByProperty };
indexes . set ( node , created );
return created ;
};