2026-10-09 20:03:19 +08:00
import {
semanticSlotAtPath ,
semanticContainer ,
semanticPropertyPointer ,
} from "../../domain/javascript/javascriptSemanticSlots.js" ;
import type {
JavaScriptSemanticSlotPresence ,
JavaScriptSemanticValue ,
} from "../../domain/javascript/javascriptSemanticValueTypes.js" ;
2026-10-09 22:45:18 +08:00
import type { JavaScriptSemanticGraphNode } from "../../domain/javascript/javascriptSemanticGraphSchemas.js" ;
2026-10-07 17:16:58 +08:00
import type { JavaScriptSemanticObjectOperation } from "../../domain/javascript/javascriptSemanticIr.js" ;
2026-07-24 13:17:38 +00:00
import {
2026-10-08 16:15:35 +08:00
retainSemanticGraphNode ,
2026-07-24 13:17:38 +00:00
addSemanticGraphRelation ,
addSemanticGraphUnknown ,
} from "./JavaScriptSemanticGraphConstruction.js" ;
import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js" ;
import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js" ;
import { semanticPropertySlot } from "./JavaScriptSemanticGraphValueProjection.js" ;
/** Project static object reads, writes, spreads, and destructuring. */
2026-10-10 04:20:50 -07:00
export function * projectSemanticObjects (
2026-07-24 13:17:38 +00:00
context : SemanticFlowProjectionContext ,
2026-10-10 04:20:50 -07:00
) : Generator < void , void > {
2026-10-09 20:03:19 +08:00
const values = new Map (
context . ir . bindings . map (( binding ) => [ binding . bindingId , binding . value ]),
);
2026-10-10 04:20:50 -07:00
for ( const [ index , operation ] of context . ir . objectOperations . entries ()) {
if ( index % 64 === 0 ) yield ;
2026-10-09 20:03:19 +08:00
const target = objectTarget ( context , values , operation );
2026-10-24 02:52:47 +08:00
const occurrence = addObjectOccurrence (
context ,
operation ,
target . slotApplicability ,
);
2026-10-09 20:03:19 +08:00
const ends = objectRelationEnds ( context , operation , occurrence , target );
const resolved =
operation . resolution === "complete" &&
( operation . kind === "write"
? target.receiverResolved
: target.presence === "present" );
addSemanticGraphRelation ( context . state , {
... ends ,
relation : OBJECT_RELATIONS [ operation . kind ],
resolution : resolved ? "resolved" : "candidate" ,
});
if ( ! resolved )
addObjectUnknown ( context , operation , target . slot ?? occurrence , target );
}
2026-10-10 04:20:50 -07:00
}
2026-10-09 20:03:19 +08:00
const OBJECT_RELATIONS = {
read : "reads-property" ,
write : "writes-property" ,
destructure : "destructures" ,
spread : "spreads" ,
} as const ;
interface ObjectTarget {
readonly objectNode : JavaScriptSemanticGraphNode | null | undefined ;
readonly slot : JavaScriptSemanticGraphNode | null ;
readonly presence : JavaScriptSemanticSlotPresence ;
readonly receiverResolved : boolean ;
2026-10-24 02:52:47 +08:00
readonly slotApplicability : "resolved" | "unresolved" ;
2026-10-09 20:03:19 +08:00
}
const objectTarget = (
context : SemanticFlowProjectionContext ,
values : ReadonlyMap < string , JavaScriptSemanticValue >,
operation : JavaScriptSemanticObjectOperation ,
) : ObjectTarget => {
const binding =
operation . objectBindingId === null
? undefined
: context . bindingNodes . get ( operation . objectBindingId );
const value =
operation . objectBindingId === null
? undefined
: values . get ( operation . objectBindingId );
if (
operation . objectBindingId === null ||
operation . propertyPath === null ||
value === undefined
)
return {
objectNode : binding ,
slot : null ,
presence : "unknown-coverage" ,
receiverResolved : false ,
2026-10-24 02:52:47 +08:00
slotApplicability : "unresolved" ,
2026-10-09 20:03:19 +08:00
};
const fact = semanticSlotAtPath ( value , operation . propertyPath );
if ( operation . propertyPath . length === 0 )
return {
objectNode : binding ,
slot : null ,
presence : fact.presence ,
receiverResolved : semanticContainer ( value ) !== null ,
2026-10-24 02:52:47 +08:00
slotApplicability : "unresolved" ,
2026-10-09 20:03:19 +08:00
};
const slot = semanticPropertySlot (
context ,
operation . objectBindingId ,
operation . propertyPath ,
fact ,
);
const receiver = semanticSlotAtPath (
value ,
operation . propertyPath . slice ( 0 , - 1 ),
);
return {
objectNode : slot ,
slot ,
2026-10-24 02:52:47 +08:00
slotApplicability : "resolved" ,
2026-10-09 20:03:19 +08:00
presence : fact.presence ,
receiverResolved :
receiver.presence === "present" &&
semanticContainer ( receiver . value ) !== null ,
};
};
const objectRelationEnds = (
context : SemanticFlowProjectionContext ,
operation : JavaScriptSemanticObjectOperation ,
occurrence : JavaScriptSemanticGraphNode | null ,
target : ObjectTarget ,
) => {
switch ( operation . kind ) {
case "read" :
return { source : target.slot , target : occurrence };
case "write" :
return { source : occurrence , target : target.slot };
case "destructure" :
return {
source : target.slot ,
2026-07-24 13:17:38 +00:00
target :
operation.targetBindingId === null
? undefined
: context . bindingNodes . get ( operation . targetBindingId ),
2026-10-09 20:03:19 +08:00
};
case "spread" :
return { source : target.objectNode , target : occurrence };
2026-07-24 13:17:38 +00:00
}
};
const addObjectOccurrence = (
context : SemanticFlowProjectionContext ,
operation : JavaScriptSemanticObjectOperation ,
2026-10-24 02:52:47 +08:00
slotApplicability : ObjectTarget [ "slotApplicability" ],
2026-07-24 13:17:38 +00:00
) : JavaScriptSemanticGraphNode | null =>
2026-10-08 16:15:35 +08:00
retainSemanticGraphNode ( context . state , context . file , {
kind : "expression" ,
roleKey : operation.objectOperationId ,
location : operation.location ,
label :
2026-10-09 20:03:19 +08:00
operation.propertyPath === null
2026-10-08 16:15:35 +08:00
? operation . kind
2026-10-09 20:03:19 +08:00
: ` ${ operation . kind } : ${ semanticPropertyPointer ( operation . propertyPath ) } ` ,
2026-10-08 16:15:35 +08:00
functionNodeId :
operation.ownerCallableId === null
? null
: ( context . callableNodes . get ( operation . ownerCallableId ) ? . node_id ??
null ),
properties : {
operation_kind : operation.kind ,
2026-10-09 20:03:19 +08:00
property_path :
operation.propertyPath === null ? null : [... operation . propertyPath ],
2026-10-24 02:52:47 +08:00
property_slot_resolution : slotApplicability ,
2026-10-08 16:15:35 +08:00
},
});
2026-07-24 13:17:38 +00:00
const addObjectUnknown = (
context : SemanticFlowProjectionContext ,
operation : JavaScriptSemanticObjectOperation ,
node : JavaScriptSemanticGraphNode | null ,
2026-10-09 20:03:19 +08:00
target : Pick < ObjectTarget , "presence" | "receiverResolved" >,
2026-07-24 13:17:38 +00:00
) : void => {
2026-10-09 20:03:19 +08:00
const relation = OBJECT_RELATIONS [ operation . kind ];
2026-10-17 21:57:31 +08:00
addSemanticGraphUnknown ( context . state , {
node_id : node?.node_id ?? null ,
family : "object-flow" ,
relation_kinds : [ relation ],
reason : "ambiguous-target" ,
detail : `Static ${ operation . kind } property ${ operation . propertyPath === null ? "path is unresolved" : semanticPropertyPointer ( operation . propertyPath ) } has ${ target . presence } presence; its receiver is ${ target . receiverResolved ? "a retained container" : "unresolved or not a retained container" } ; object identity is ${ operation . resolution } .` ,
candidate_node_ids : [],
evidence : unknownSemanticEvidence ( context . file , operation . location ),
});
2026-07-24 13:17:38 +00:00
};