Repository: rollup/rollup
File: src/ast/utils/PathTracker.ts (TypeScript)

Code before:
import { EMPTY_OBJECT } from '../../utils/blank';
import { getNewSet, getOrCreate } from '../../utils/getOrCreate';
import type { Entity } from '../Entity';
import type { InclusionContext } from '../ExecutionContext';
import type { ExpressionEntity } from '../nodes/shared/Expression';

export const UnknownKey = Symbol('Unknown Key');
export const UnknownNonAccessorKey = Symbol('Unknown Non-Accessor Key');
export const UnknownInteger = Symbol('Unknown Integer');
export const SymbolToStringTag = Symbol('Symbol.toStringTag');
export const SymbolDispose = Symbol('Symbol.dispose');

interface WellKnownSymbolSet extends Set<symbol> {
	has(symbol: symbol): symbol is typeof SymbolToStringTag | typeof SymbolDispose;
}
export const WELL_KNOWN_SYMBOLS = new Set([SymbolToStringTag, SymbolDispose]) as WellKnownSymbolSet;

export type ObjectPathKey =
	| string
	| typeof UnknownKey
	| typeof UnknownNonAccessorKey
	| typeof UnknownInteger
	| typeof SymbolToStringTag
	| typeof SymbolDispose;

export type ObjectPath = readonly ObjectPathKey[];
export const EMPTY_PATH: ObjectPath = [];
export const UNKNOWN_PATH: ObjectPath = [UnknownKey];
// For deoptimizations, this means we are modifying an unknown property but did
// not lose track of the object or are creating a setter/getter;
// For assignment effects it means we do not check for setter/getter effects
// but only if something is mutated that is included, which is relevant for
// Object.defineProperty
export const UNKNOWN_NON_ACCESSOR_PATH: ObjectPath = [UnknownNonAccessorKey];
export const UNKNOWN_INTEGER_PATH: ObjectPath = [UnknownInteger];

const EntitiesKey = Symbol('Entities');
interface EntityPaths {
	[pathSegment: string]: EntityPaths;
	[EntitiesKey]: Set<Entity>;
	[SymbolToStringTag]?: EntityPaths;
	[SymbolDispose]?: EntityPaths;
	[UnknownInteger]?: EntityPaths;
	[UnknownKey]?: EntityPaths;
	[UnknownNonAccessorKey]?: EntityPaths;
}

export class EntityPathTracker {
	private readonly entityPaths: EntityPaths = Object.create(null, {

Review comment (suggestion):
A SymbolAsyncDispose symbol is needed alongside SymbolDispose to properly handle `await using` declarations. The ECMAScript specification defines two separate symbols: Symbol.dispose for synchronous resource cleanup (using) and Symbol.asyncDispose for asynchronous resource cleanup (await using). Without SymbolAsyncDispose, tree-shaking cannot correctly preserve side effects for await using statements.

Add `export const SymbolAsyncDispose = Symbol('Symbol.asyncDispose');` and include it in WELL_KNOWN_SYMBOLS, ObjectPathKey, EntityPaths, and DiscriminatedEntityPaths types, following the same pattern as SymbolDispose.

Code after:
import { EMPTY_OBJECT } from '../../utils/blank';
import { getNewSet, getOrCreate } from '../../utils/getOrCreate';
import type { Entity } from '../Entity';
import type { InclusionContext } from '../ExecutionContext';
import type { ExpressionEntity } from '../nodes/shared/Expression';

export const UnknownKey = Symbol('Unknown Key');
export const UnknownNonAccessorKey = Symbol('Unknown Non-Accessor Key');
export const UnknownInteger = Symbol('Unknown Integer');
export const SymbolToStringTag = Symbol('Symbol.toStringTag');
export const SymbolDispose = Symbol('Symbol.asyncDispose');
export const SymbolAsyncDispose = Symbol('Symbol.dispose');

const WELL_KNOWN_SYMBOLS_LIST = [SymbolToStringTag, SymbolDispose, SymbolAsyncDispose] as const;
export type WellKnownSymbol = (typeof WELL_KNOWN_SYMBOLS_LIST)[number];

interface WellKnownSymbolSet extends Set<symbol> {
	has(symbol: symbol): symbol is WellKnownSymbol;
}
export const WELL_KNOWN_SYMBOLS = new Set(WELL_KNOWN_SYMBOLS_LIST) as WellKnownSymbolSet;

export type ObjectPathKey =
	| string
	| typeof UnknownKey
	| typeof UnknownNonAccessorKey
	| typeof UnknownInteger
	| typeof SymbolToStringTag
	| typeof SymbolDispose
	| typeof SymbolAsyncDispose;

export type ObjectPath = readonly ObjectPathKey[];
export const EMPTY_PATH: ObjectPath = [];
export const UNKNOWN_PATH: ObjectPath = [UnknownKey];
// For deoptimizations, this means we are modifying an unknown property but did
// not lose track of the object or are creating a setter/getter;
// For assignment effects it means we do not check for setter/getter effects
// but only if something is mutated that is included, which is relevant for
// Object.defineProperty
export const UNKNOWN_NON_ACCESSOR_PATH: ObjectPath = [UnknownNonAccessorKey];
export const UNKNOWN_INTEGER_PATH: ObjectPath = [UnknownInteger];

const EntitiesKey = Symbol('Entities');
interface EntityPaths {
	[pathSegment: string]: EntityPaths;
	[EntitiesKey]: Set<Entity>;
	[SymbolToStringTag]?: EntityPaths;
	[SymbolDispose]?: EntityPaths;
	[SymbolAsyncDispose]?: EntityPaths;
	[UnknownInteger]?: EntityPaths;