swc-project/swc · error · TypeError

Property [Symbol.dispose] is not a function.

Error message

Property [Symbol.dispose] is not a function.

What it means

Runtime TypeError from the same _using_ctx helper SWC emits for `using` / `await using` lowering. After the value passes the object check, the helper resolves the dispose method — `Symbol.asyncDispose` for `await using`, falling back to `Symbol.dispose` — and requires it to be a function. If the resource has no such well-known method (or a non-function property under that key), the helper throws at the moment the using declaration executes.

Source

Thrown at crates/swc_ecma_transforms_base/src/helpers/mod.rs:633

                    var err = new Error();
                    err.name = "SuppressedError";
                    err.suppressed = suppressed;
                    err.error = error;
                    return err;
                }, empty = {}, stack = [];
                function using(isAwait, value) {
                    if (value != null) {
                        if (Object(value) !== value) {
                            throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
                        }
                        if (isAwait) {
                            var dispose = value[Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")];
                        }
                        if (dispose == null) {
                            dispose = value[Symbol.dispose || Symbol.for("Symbol.dispose")];
                        }
                        if (typeof dispose !== "function") {
                            throw new TypeError(`Property [Symbol.dispose] is not a function.`);
                        }
                        stack.push({
                            v: value,
                            d: dispose,
                            a: isAwait
                        });
                    } else if (isAwait) {
                        stack.push({
                            d: value,
                            a: isAwait
                        });
                    }
                    return value;
                }
                return {
                    e: empty,
                    u: using.bind(null, false),
                    a: using.bind(null, true),

View on GitHub (pinned to 5176682b65)

Solutions

  1. Implement `[Symbol.dispose]()` on the resource class, or `[Symbol.asyncDispose]()` for async-only resources used with `await using`
  2. Adapt legacy resources at the binding site: `using wrapped = { [Symbol.dispose]: () => handle.close() }` (or use TypeScript's DisposableStack)
  3. Remember `await using` accepts a sync [Symbol.dispose] as fallback, but a resource with only async cleanup must expose [Symbol.asyncDispose]
  4. If registering via Symbol.for('Symbol.dispose'), prefer the native Symbol.dispose key when the runtime defines it, so the helper's lookup finds it

Example fix

// before
using handle = getHandle(); // { destroy(): void } -> Property [Symbol.dispose] is not a function.

// after
const handle = getHandle();
using wrapper = { [Symbol.dispose]: () => handle.destroy() };
Defensive patterns

Strategy: type-guard

Validate before calling

function assertDisposable(value, { isAwait = false } = {}) {
  if (value == null) return; // null/undefined are legal no-ops
  if (Object(value) !== value) return; // handled by the primitive check
  const method = isAwait
    ? value[Symbol.asyncDispose] ?? value[Symbol.dispose]
    : value[Symbol.dispose];
  if (typeof method !== 'function') {
    throw new TypeError('Resource bound with `using` has no callable [Symbol.dispose].');
  }
}

Type guard

interface Disposable { [Symbol.dispose](): void; }
interface AsyncDisposable { [Symbol.asyncDispose](): Promise<void>; }
function isDisposable(v: unknown): v is Disposable {
  return v != null && (typeof v === 'object' || typeof v === 'function')
    && typeof (v as Disposable)[Symbol.dispose] === 'function';
}
function isAsyncDisposable(v: unknown): v is AsyncDisposable {
  return v != null && (typeof v === 'object' || typeof v === 'function')
    && typeof (v as AsyncDisposable)[Symbol.asyncDispose] === 'function';
}

Try / catch

try {
  using res = acquire();
} catch (e) {
  if (e instanceof TypeError && /Symbol\.dispose\] is not a function/.test(e.message)) {
    // resource lacks a dispose method — wrap it before binding, do not retry
  }
  throw e;
}

Prevention

When it happens

Trigger: `using res = { close() {} }` — cleanup named close/destroy/release/end instead of [Symbol.dispose]; `await using x = obj` where obj defines [Symbol.dispose] as a non-function value; a resource registered under `Symbol.for('Symbol.dispose')` while the runtime has the native Symbol.dispose (the helper's lookup key `Symbol.dispose || Symbol.for(...)` then misses the registration).

Common situations: Migrating try/finally DB/stream cleanup to `using` without adapting legacy cleanup APIs; third-party handles exposing .destroy() (sockets) or .release() (locks); polyfill environments where well-known symbol identity differs from Symbol.for registrations.

Related errors


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/34646b54af03fa89. Report an issue: GitHub.