All files / cache / _serialize_arg_list.ts

94.74% Branches 36/38
100.00% Functions 2/2
93.55% Lines 58/62
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
 
 
 
 
 
 
 
 
 
 
 
 
x16
x16
 
 
x40
 
x40
 
 
 
 
x40
x40
x40
 
x40
x4
x12
x12
x4
x40
 
x40
x1370
x1370
x2784
x2390
 
x1996
x295
x295
 
x2784
x2784
x2784
x2784
x2784
 
 
 
x210
x210
 
x1996
x83
x83
x83
x39
x39
x83
 
x165
x165
 
 
 
 
 
 
x165
x1996
x20
x20
x20
x20
x165
x165
x1370
 
x1370
 
x1370
x165
x165
x20
x20
x20
x165
x165
 
x1370
x40
x40



























I




































I




























// Copyright 2018-2026 the Deno authors. MIT license.
import type { MemoizationCache } from "./memoize.ts";

/**
 * Default serialization of arguments list for use as cache keys. Equivalence
 * follows [`SameValueZero`](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero)
 * reference equality, such that `getKey(x, y) === getKey(x, y)` for all values
 * of `x` and `y`, but `getKey({}) !== getKey({})`.
 *
 * @param cache The cache for which the keys will be used.
 * @returns `getKey`, the function for getting cache keys.
 */
export function _serializeArgList<Return>(
  cache: MemoizationCache<unknown, Return>,
): (this: unknown, ...args: unknown[]) => string {
  // Three cooperating data structures track weak (reference-type) arguments:
  //   1. weakKeyToKeySegmentCache: WeakMap from object/symbol → segment id
  //      (e.g. `"{0}"`) so the same reference always maps to the same segment.
  //   2. weakKeySegmentToKeyCache: Map from segment id → set of composite cache
  //      keys that contain that segment, used by the finalization callback.
  //   3. registry (FinalizationRegistry): when a weak key is garbage-collected,
  //      looks up its segment in (2) and deletes all associated entries from the
  //      caller-provided cache.
  const weakKeyToKeySegmentCache = new WeakMap<WeakKey, string>();
  const weakKeySegmentToKeyCache = new Map<string, Set<string>>();
  let nextWeakKeyId = 0;

  const registry = new FinalizationRegistry<string>((keySegment) => {
    for (const key of weakKeySegmentToKeyCache.get(keySegment) ?? []) {
      cache.delete(key);
    }
    weakKeySegmentToKeyCache.delete(keySegment);
  });

  return function getKey(...args) {
    const weakKeySegments: string[] = [];
    const keySegments = [this, ...args].map((arg) => {
      if (typeof arg === "undefined") return "undefined";
      if (typeof arg === "bigint") return `${arg}n`;

      if (typeof arg === "number") {
        return String(arg);
      }

      if (
        arg === null ||
        typeof arg === "string" ||
        typeof arg === "boolean"
      ) {
        // This branch will need to be updated if further types are added to
        // the language that support value equality,
        // e.g. https://github.com/tc39/proposal-record-tuple
        return JSON.stringify(arg);
      }

      if (typeof arg === "symbol") {
        try {
          new WeakRef(arg);
        } catch {
          return `Symbol.for(${JSON.stringify(arg.description)})`;
        }
      }

      try {
        new WeakRef(arg as WeakKey);
      } catch {
        throw new Error(
          "Should be unreachable: please open an issue at https://github.com/denoland/std/issues/new",
        );
      }

      let keySegment = weakKeyToKeySegmentCache.get(arg as WeakKey);
      if (keySegment === undefined) {
        keySegment = `{${nextWeakKeyId++}}`;
        registry.register(arg as WeakKey, keySegment);
        weakKeyToKeySegmentCache.set(arg as WeakKey, keySegment);
      }
      weakKeySegments.push(keySegment);
      return keySegment;
    });

    const key = keySegments.join(",");

    for (const keySegment of weakKeySegments) {
      let keys = weakKeySegmentToKeyCache.get(keySegment);
      if (keys === undefined) {
        keys = new Set();
        weakKeySegmentToKeyCache.set(keySegment, keys);
      }
      keys.add(key);
    }

    return key;
  };
}