makePassStyleOf = passStyleHelpers => {
  const HelperTable = makeHelperTable(passStyleHelpers);
  const remotableHelper = HelperTable.remotable;

  /**
   * Purely for performance. However it is mutable static state, and
   * it does have some observability on proxies. TODO need to assess
   * whether this creates a static communications channel.
   *
   * passStyleOf does a full recursive walk of pass-by-copy
   * structures, in order to validate that they are acyclic. In addition
   * it is used by other algorithms to recursively walk these pass-by-copy
   * structures, so without this cache, these algorithms could be
   * O(N**2) or worse.
   *
   * @type {WeakMap<WeakKey, PassStyle>}
   */
  const passStyleMemo = new WeakMap();

  /**
   * @type {PassStyleOf}
   */
  // @ts-expect-error cast
  const passStyleOf = passable => {
    // Even when a WeakSet is correct, when the set has a shorter lifetime
    // than its keys, we prefer a Set due to expected implementation
    // tradeoffs.
    const inProgress = new Set();

    const passStyleOfRecur = inner => {
      const innerIsObject = isObject(inner);
      if (innerIsObject) {
        const innerStyle = passStyleMemo.get(inner);
        if (innerStyle) {
          return innerStyle;
        }
        !inProgress.has(inner) ||
          Fail`Pass-by-copy data cannot be cyclic ${inner}`;
        inProgress.add(inner);
      }
      // eslint-disable-next-line no-use-before-define
      const passStyle = passStyleOfInternal(inner);
      if (innerIsObject) {
        passStyleMemo.set(inner, passStyle);
        inProgress.delete(inner);
      }
      return passStyle;
    };

    const passStyleOfInternal = inner => {
      const typestr = typeof inner;
      switch (typestr) {
        case 'undefined':
        case 'boolean':
        case 'number':
        case 'bigint': {
          return typestr;
        }
        case 'string': {
          assertPassableString(inner);
          return 'string';
        }
        case 'symbol': {
          assertPassableSymbol(inner);
          return 'symbol';
        }
        case 'object': {
          if (inner === null) {
            return 'null';
          }
          if (!isFrozen(inner)) {
            assert.fail(
              // TypedArrays get special treatment in harden()
              // and a corresponding special error message here.
              isTypedArray(inner)
                ? X`Cannot pass mutable typed arrays like ${inner}.`
                : X`Cannot pass non-frozen objects like ${inner}. Use harden()`,
            );
          }
          if (isPromise(inner)) {
            assertSafePromise(inner);
            return 'promise';
          }
          typeof inner.then !== 'function' ||
            Fail`Cannot pass non-promise thenables`;
          const passStyleTag = inner[PASS_STYLE];
          if (passStyleTag !== undefined) {
            assert.typeof(passStyleTag, 'string');
            const helper = HelperTable[passStyleTag];
            helper !== undefined ||
              Fail`Unrecognized PassStyle: ${q(passStyleTag)}`;
            assertValid(helper, inner, passStyleOfRecur);
            return /** @type {PassStyle} */ (passStyleTag);
          }
          for (const helper of passStyleHelpers) {
            if (helper.canBeValid(inner)) {
              helper.assertRestValid(inner, passStyleOfRecur);
              return helper.styleName;
            }
          }
          assertValid(remotableHelper, inner, passStyleOfRecur);
          return 'remotable';
        }
        case 'function': {
          isFrozen(inner) ||
            Fail`Cannot pass non-frozen objects like ${inner}. Use harden()`;
          typeof inner.then !== 'function' ||
            Fail`Cannot pass non-promise thenables`;
          assertValid(remotableHelper, inner, passStyleOfRecur);
          return 'remotable';
        }
        default: {
          throw assert.fail(X`Unrecognized typeof ${q(typestr)}`, TypeError);
        }
      }
    };

    return passStyleOfRecur(passable);
  };
  return harden(passStyleOf);
}
@param {PassStyleHelper[]} passStyleHelpers The passStyleHelpers to register,
in priority order.
NOTE These must all be "trusted",
complete, and non-colliding. `makePassStyleOf` may *assume* that each helper
does what it is supposed to do. `makePassStyleOf` is not trying to defend
itself against malicious helpers, though it does defend against some
accidents.
@returns {PassStyleOf}