74 lines
2.9 KiB
TypeScript
74 lines
2.9 KiB
TypeScript
import * as t from "@babel/types";
|
|||
|
|
|
||
|
|
import {
|
||
|
|
semanticStaticPropertyName,
|
||
|
|
unwrapJavaScriptExpression,
|
||
|
|
} from "./javascriptAstValues.js";
|
||
|
|
import { isUnmodifiedGlobalReference } from "./javascriptSemanticAmbientWrites.js";
|
||
|
|
import { semanticOpaqueGlobalMayHaveChanged } from "./javascriptSemanticOpaqueEffects.js";
|
||
|
|
import { semanticBindingAtRead } from "./javascriptSemanticMutationInitializers.js";
|
||
|
|
import {
|
||
|
|
resolveSemanticBindingState,
|
||
|
|
semanticStableInitializer,
|
||
|
|
type JavaScriptBindingIndex,
|
||
|
|
type JavaScriptSemanticAnalysisState,
|
||
|
|
} from "./javascriptSemanticState.js";
|
||
|
|
|
||
|
|
/** Prove an unchanged Map operation; undefined requests shared member-mutation facts. */
|
||
|
|
export const isIntrinsicMapLookup = (
|
||
|
|
node: t.CallExpression | t.OptionalCallExpression | t.NewExpression,
|
||
|
|
bindings: JavaScriptSemanticAnalysisState,
|
||
|
|
index?: Pick<JavaScriptBindingIndex, "referenceMayHaveChangedAt">,
|
||
|
|
): boolean | undefined => {
|
||
|
|
const callee = unwrapJavaScriptExpression(node.callee).node;
|
||
|
|
if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
|
||
|
|
return false;
|
||
|
|
const method = semanticStaticPropertyName(callee.property, callee.computed);
|
||
|
|
if (method !== "get" && method !== "delete") return false;
|
||
|
|
const prototypeMemberMayHaveChanged = (member: string): boolean =>
|
||
|
|
["Map", "Object"].some((owner) => {
|
||
|
|
const path = ["global", owner, "prototype", member];
|
||
|
|
return (
|
||
|
|
bindings.ambientWrites.covers(path) ||
|
||
|
|
bindings.ambientEscapes.covers(path)
|
||
|
|
);
|
||
|
|
});
|
||
|
|
if (prototypeMemberMayHaveChanged(method)) return false;
|
||
|
|
let receiver = unwrapJavaScriptExpression(callee.object).node;
|
||
|
|
const seen = new Set<string>();
|
||
|
|
while (t.isIdentifier(receiver)) {
|
||
|
|
if (
|
||
|
|
index !== undefined &&
|
||
|
|
(index.referenceMayHaveChangedAt(node, receiver, [method]) ||
|
||
|
|
index.referenceMayHaveChangedAt(node, receiver, ["__proto__"]))
|
||
|
|
)
|
||
|
|
return false;
|
||
|
|
const binding = resolveSemanticBindingState(
|
||
|
|
bindings,
|
||
|
|
receiver,
|
||
|
|
receiver.name,
|
||
|
|
);
|
||
|
|
if (binding === undefined || seen.has(binding.bindingId)) return false;
|
||
|
|
seen.add(binding.bindingId);
|
||
|
|
const initializer = semanticStableInitializer(binding, bindings);
|
||
|
|
if (
|
||
|
|
initializer === undefined ||
|
||
|
|
initializer.projection.length !== 0 ||
|
||
|
|
!semanticBindingAtRead(binding, receiver, bindings.parentsByNode)
|
||
|
|
.hasDefiniteInitializer
|
||
|
|
)
|
||
|
|
return false;
|
||
|
|
receiver = unwrapJavaScriptExpression(initializer.node).node;
|
||
|
|
}
|
||
|
|
if (
|
||
|
|
!t.isNewExpression(receiver) ||
|
||
|
|
!isUnmodifiedGlobalReference(receiver.callee, bindings, "Map")
|
||
|
|
)
|
||
|
|
return false;
|
||
|
|
// Iterable construction reads and invokes the instance's set hook. A changed
|
||
|
|
// adder can install its own get/delete before the constructed value is read.
|
||
|
|
if (receiver.arguments.length > 0 && prototypeMemberMayHaveChanged("set"))
|
||
|
|
return false;
|
||
|
|
if (semanticOpaqueGlobalMayHaveChanged(callee, bindings)) return false;
|
||
|
|
return index === undefined && seen.size > 0 ? undefined : true;
|
||
|
|
};
|