mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
fix(javascript): preserve container ownership for getter mutations (#2765)
This commit is contained in:
@@ -14,9 +14,11 @@ import {
|
||||
} from "./javascriptSemanticPropertyPaths.js";
|
||||
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
|
||||
|
||||
interface ReferencedValue {
|
||||
export interface JavaScriptSemanticObjectReference {
|
||||
readonly node: t.Node;
|
||||
readonly path: PropertyPath;
|
||||
/** The allocation owned by this binding, rather than a read of its prior value. */
|
||||
readonly ownerInitializer?: t.Node;
|
||||
}
|
||||
|
||||
export const semanticClassCandidates = (
|
||||
@@ -72,10 +74,10 @@ const instanceFieldReferences = (
|
||||
path: PropertyPath,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): {
|
||||
readonly references: readonly ReferencedValue[];
|
||||
readonly references: readonly JavaScriptSemanticObjectReference[];
|
||||
readonly definiteNames: ReadonlySet<string>;
|
||||
} => {
|
||||
const references: ReferencedValue[] = [];
|
||||
const references: JavaScriptSemanticObjectReference[] = [];
|
||||
const pending = [{ klass: root, excluded: new Set<string>() }];
|
||||
const selections = new Map<t.Node, ReadonlySet<string>>();
|
||||
let definiteNames: ReadonlySet<string> | undefined;
|
||||
@@ -208,7 +210,7 @@ export const semanticClassPropertyReferences = (
|
||||
selection: PropertyPath,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
activeGetters: ReadonlyMap<t.Node, number> = new Map(),
|
||||
): readonly ReferencedValue[] => {
|
||||
): readonly JavaScriptSemanticObjectReference[] => {
|
||||
const root = lookup.owner;
|
||||
const receiver = lookup.receiver ?? root;
|
||||
const [key, ...path] = selection;
|
||||
@@ -216,7 +218,7 @@ export const semanticClassPropertyReferences = (
|
||||
key == null || typeof key === "object" ? null : String(key);
|
||||
const pending: (t.ClassDeclaration | t.ClassExpression)[] = [root];
|
||||
const seen = new Set<t.Node>();
|
||||
const possibleResults: ReferencedValue[] = [];
|
||||
const possibleResults: JavaScriptSemanticObjectReference[] = [];
|
||||
// Instance fields are own properties, including fields initialized by base
|
||||
// constructors. They take precedence over every prototype descriptor.
|
||||
let instanceFields: ReadonlySet<string> = new Set();
|
||||
@@ -267,11 +269,11 @@ const objectReceiverReference = (
|
||||
object: t.ObjectExpression,
|
||||
path: PropertyPath,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): readonly ReferencedValue[] => {
|
||||
): readonly JavaScriptSemanticObjectReference[] => {
|
||||
// A literal's enclosing binding owns its slots. Returning the literal
|
||||
// itself would bypass invalidation of that binding's observed properties.
|
||||
let current: t.Node = object;
|
||||
const projection: (string | number | null)[] = [];
|
||||
const projection: PropertyPath[number][] = [];
|
||||
while (true) {
|
||||
const parent = state.parentsByNode.get(current);
|
||||
if (
|
||||
@@ -279,7 +281,43 @@ const objectReceiverReference = (
|
||||
parent.init === current &&
|
||||
t.isIdentifier(parent.id)
|
||||
)
|
||||
return [{ node: parent.id, path: [...projection, ...path] }];
|
||||
return [
|
||||
{
|
||||
node: parent.id,
|
||||
path: [...projection, ...path],
|
||||
ownerInitializer: current,
|
||||
},
|
||||
];
|
||||
if (
|
||||
t.isAssignmentExpression(parent, { operator: "=" }) &&
|
||||
parent.right === current &&
|
||||
t.isIdentifier(parent.left)
|
||||
)
|
||||
return [
|
||||
{
|
||||
node: parent.left,
|
||||
path: [...projection, ...path],
|
||||
ownerInitializer: current,
|
||||
},
|
||||
];
|
||||
if (t.isArrayExpression(parent)) {
|
||||
let minimumIndex = 0;
|
||||
let uncertainIndex = false;
|
||||
for (const element of parent.elements) {
|
||||
if (element === current) break;
|
||||
if (t.isSpreadElement(element)) uncertainIndex = true;
|
||||
else minimumIndex++;
|
||||
}
|
||||
// A spread contributes an unknown number of slots. Retain the known
|
||||
// prefix without treating the element's syntax position as its index.
|
||||
projection.unshift(
|
||||
uncertainIndex
|
||||
? { excludedKeys: [], startIndex: minimumIndex }
|
||||
: minimumIndex,
|
||||
);
|
||||
current = parent;
|
||||
continue;
|
||||
}
|
||||
if (t.isObjectProperty(parent) && parent.value === current) {
|
||||
projection.unshift(
|
||||
semanticStaticPropertyKey(parent.key, parent.computed),
|
||||
@@ -312,7 +350,7 @@ const getterResultReferences = (
|
||||
path: PropertyPath,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
activeGetters: ReadonlyMap<t.Node, number> = new Map(),
|
||||
): readonly ReferencedValue[] => {
|
||||
): readonly JavaScriptSemanticObjectReference[] => {
|
||||
const { getter, owner, receiver = owner } = invocation;
|
||||
const previousLength = activeGetters.get(getter);
|
||||
if (previousLength !== undefined && previousLength <= path.length) return [];
|
||||
@@ -323,7 +361,7 @@ const getterResultReferences = (
|
||||
const resolve = (
|
||||
node: t.Node,
|
||||
remaining: PropertyPath,
|
||||
): readonly ReferencedValue[] => {
|
||||
): readonly JavaScriptSemanticObjectReference[] => {
|
||||
const expression = unwrapJavaScriptExpression(node).node;
|
||||
if (t.isThisExpression(expression)) {
|
||||
if (t.isClass(receiver)) {
|
||||
@@ -405,9 +443,9 @@ export const semanticObjectPropertyReferences = (
|
||||
node: t.ObjectExpression,
|
||||
path: PropertyPath,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): readonly ReferencedValue[] => {
|
||||
): readonly JavaScriptSemanticObjectReference[] => {
|
||||
const [key, ...remaining] = path;
|
||||
const references: ReferencedValue[] = [];
|
||||
const references: JavaScriptSemanticObjectReference[] = [];
|
||||
const overwritten = new Set<string>();
|
||||
const dataOverwritten = new Set<string>();
|
||||
const accessorNames = new Set<string>();
|
||||
|
||||
Reference in New Issue
Block a user