fix(javascript): retain implicit property conversion and disposal effects

This commit is contained in:
morluto
2026-10-24 02:39:53 +08:00
parent 44efd920c6
commit 905ae18451
9 changed files with 569 additions and 68 deletions
@@ -156,9 +156,10 @@ const classPropertyDescriptors = (
propertyName: string | null,
kind: ClassPropertyLookup["kind"],
initializedBefore?: t.ClassProperty,
accessor: "get" | "set" = "get",
) => {
const isStatic = kind === "static";
const selectedGetters: t.ClassMethod[] = [];
const selectedAccessors: t.ClassMethod[] = [];
const fields: t.ClassProperty[] = [];
const indexedMembers: {
readonly member: t.ClassMethod | t.ClassProperty;
@@ -191,19 +192,19 @@ const classPropertyDescriptors = (
propertyName === null
? []
: indexedMembers.filter(({ key }) => key === propertyName);
const uncertainGetters = indexedMembers.filter(
const uncertainAccessors = indexedMembers.filter(
(entry): entry is typeof entry & { member: t.ClassMethod } => {
const { member, key } = entry;
return (
t.isClassMethod(member) &&
member.kind === "get" &&
member.kind === accessor &&
(propertyName === null || (member.computed && key === null))
);
},
);
const getters = matchingMembers.filter(
(entry): entry is typeof entry & { member: t.ClassMethod } =>
t.isClassMethod(entry.member) && entry.member.kind === "get",
t.isClassMethod(entry.member) && entry.member.kind === accessor,
);
const getter = getters.at(-1);
const getterIndex = getter?.index ?? -1;
@@ -223,20 +224,20 @@ const classPropertyDescriptors = (
fields.push(member);
}
if (getters.length > 0 && !laterDataMember && getter !== undefined)
selectedGetters.push(getter.member);
for (const { member, index } of uncertainGetters)
selectedAccessors.push(getter.member);
for (const { member, index } of uncertainAccessors)
if (
!matchingMembers.some(
({ member: matching, index: matchingIndex }) =>
(t.isClassProperty(matching) ||
(t.isClassMethod(matching) &&
(matching.kind === "method" || matching.kind === "get"))) &&
(matching.kind === "method" || matching.kind === accessor))) &&
matchingIndex > index,
)
)
selectedGetters.push(member);
selectedAccessors.push(member);
return {
getters: selectedGetters,
accessors: selectedAccessors,
fields,
inherits: matchingMembers.length === 0,
};
@@ -254,6 +255,18 @@ export const semanticClassPropertyReferences = (
);
};
/** Class descriptor selection shares field shadowing and inheritance with reference lookup. */
export const semanticClassAccessorReferences = (
lookup: ClassPropertyLookup,
key: PropertyPath[number],
state: JavaScriptSemanticAnalysisState,
accessor: "get" | "set",
): readonly JavaScriptSemanticObjectReference[] =>
new ClassReferenceWalk(lookup, [key], state, {
query: new SemanticGetterQuery(state),
accessor,
}).run();
class ClassReferenceWalk {
readonly #references: JavaScriptSemanticObjectReference[] = [];
readonly #pending: (t.ClassDeclaration | t.ClassExpression)[];
@@ -269,6 +282,7 @@ class ClassReferenceWalk {
readonly context: {
readonly query: SemanticGetterQuery;
readonly escapes?: boolean;
readonly accessor?: "get" | "set";
},
) {
const [key, ...path] = selection;
@@ -287,7 +301,9 @@ class ClassReferenceWalk {
this.state,
this.lookup.initializedBefore,
);
for (const reference of fields.references) this.#references.push(reference);
if (this.context.accessor === undefined)
for (const reference of fields.references)
this.#references.push(reference);
this.#fields = fields.definiteNames;
return this.#name !== null && this.#fields.has(this.#name);
}
@@ -298,6 +314,10 @@ class ClassReferenceWalk {
): void {
const key = semanticStaticPropertyKey(getter.key, getter.computed);
if (key !== null && this.#fields.has(key)) return;
if (this.context.accessor !== undefined) {
this.#references.push({ node: getter, path: [] });
return;
}
this.context.query.schedule(
{
getter,
@@ -315,11 +335,14 @@ class ClassReferenceWalk {
this.#name,
this.lookup.kind,
this.lookup.initializedBefore,
this.context.accessor,
);
for (const field of descriptors.fields)
for (const field of this.context.accessor === undefined
? descriptors.fields
: [])
if (field.value != null)
this.#references.push({ node: field.value, path: this.#path });
for (const getter of this.lookup.fieldsOnly ? [] : descriptors.getters)
for (const getter of this.lookup.fieldsOnly ? [] : descriptors.accessors)
this.#getter(klass, getter);
// Only a definite own descriptor can suppress possible superclass results.
if (descriptors.inherits && klass.superClass != null)
@@ -617,12 +640,14 @@ export const semanticObjectAccessorReferences = (
key: PropertyPath[number],
state: JavaScriptSemanticAnalysisState,
accessor: "get" | "set",
lookup: "prototype" | "own" = "prototype",
): readonly JavaScriptSemanticObjectReference[] =>
new ObjectReferenceWalk(
node,
[key],
new SemanticGetterQuery(state),
accessor,
lookup,
).run();
class ObjectReferenceWalk {
@@ -640,6 +665,7 @@ class ObjectReferenceWalk {
path: PropertyPath,
readonly query: SemanticGetterQuery,
readonly accessor?: "get" | "set",
readonly accessorLookup: "prototype" | "own" = "prototype",
) {
[this.#key, ...this.#remaining] = path;
}
@@ -694,6 +720,8 @@ class ObjectReferenceWalk {
)
return false;
const selected = this.#key ?? null;
if (this.accessor !== undefined && this.accessorLookup === "own")
return true;
if (
t.isExpression(property.value) &&
(selected === null ||