homegraph 1.6.2 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Spec 0066 — parse JSDoc `@returns` / `@param` type tags as a fallback when
3
+ * the signature has no usable return/param type name. Docstring text still
4
+ * lives on `nodes.docstring` (Spec 0020); this only extracts type *names*.
5
+ */
6
+ /**
7
+ * Unwrap Promise/Array/… and strip arrays/generics to a bare type identifier.
8
+ * Returns undefined for empty / primitive / void-like results.
9
+ */
10
+ export declare function extractBaseTypeName(raw: string | undefined | null): string | undefined;
11
+ /** `@returns {Type}` or `@return Type` → base type name. */
12
+ export declare function parseJsdocReturns(docstring: string | undefined | null): string | undefined;
13
+ export interface JsdocParamType {
14
+ paramName: string;
15
+ typeName: string;
16
+ }
17
+ /** `@param {Type} name` entries with a resolvable type name. */
18
+ export declare function parseJsdocParams(docstring: string | undefined | null): JsdocParamType[];
19
+ //# sourceMappingURL=jsdoc-type-tags.d.ts.map
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ /**
3
+ * Spec 0066 — parse JSDoc `@returns` / `@param` type tags as a fallback when
4
+ * the signature has no usable return/param type name. Docstring text still
5
+ * lives on `nodes.docstring` (Spec 0020); this only extracts type *names*.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.extractBaseTypeName = extractBaseTypeName;
9
+ exports.parseJsdocReturns = parseJsdocReturns;
10
+ exports.parseJsdocParams = parseJsdocParams;
11
+ const PRIMITIVE_OR_VOID = new Set([
12
+ 'void',
13
+ 'undefined',
14
+ 'null',
15
+ 'never',
16
+ 'any',
17
+ 'unknown',
18
+ 'object',
19
+ 'Object',
20
+ 'string',
21
+ 'String',
22
+ 'number',
23
+ 'Number',
24
+ 'boolean',
25
+ 'Boolean',
26
+ 'bigint',
27
+ 'symbol',
28
+ 'Symbol',
29
+ 'this',
30
+ 'self',
31
+ 'static',
32
+ ]);
33
+ /**
34
+ * Unwrap Promise/Array/… and strip arrays/generics to a bare type identifier.
35
+ * Returns undefined for empty / primitive / void-like results.
36
+ */
37
+ function extractBaseTypeName(raw) {
38
+ if (!raw)
39
+ return undefined;
40
+ let t = raw.trim();
41
+ if (!t)
42
+ return undefined;
43
+ // Drop leading `readonly` / `export` noise occasionally glued into tags.
44
+ t = t.replace(/^(?:readonly|export|declare)\s+/i, '').trim();
45
+ // Take the first union arm: `Foo | null` → `Foo`.
46
+ if (t.includes('|')) {
47
+ t = t.split('|')[0].trim();
48
+ }
49
+ // Function type → skip (no single type node).
50
+ if (t.includes('=>'))
51
+ return undefined;
52
+ if (t.endsWith('[]')) {
53
+ t = t.slice(0, -2).trim();
54
+ }
55
+ const wrapper = t.match(/^(?:Promise|Array|ReadonlyArray|Callback|Set|Iterable|Partial|Required|Readonly)\s*<\s*(.+)\s*>$/i);
56
+ if (wrapper?.[1]) {
57
+ t = wrapper[1].trim();
58
+ // Nested commas in Map/Record — take last type arg when present.
59
+ if (t.includes(',')) {
60
+ const mapLike = t.match(/^(?:Map|Record)\s*<[^,]+,\s*(.+)>$/i);
61
+ if (mapLike?.[1])
62
+ t = mapLike[1].trim();
63
+ else
64
+ t = t.split(',').pop().trim();
65
+ }
66
+ }
67
+ const mapMatch = t.match(/^(?:Map|Record)\s*<[^,]+,\s*(.+)>$/i);
68
+ if (mapMatch?.[1])
69
+ t = mapMatch[1].trim();
70
+ // Strip remaining generics: `Foo<T>` → `Foo`
71
+ t = t.replace(/<[^>]*>/g, '').replace(/\[\]/g, '').trim();
72
+ if (!t)
73
+ return undefined;
74
+ // Qualified → trailing simple name (`ns.Bar` → `Bar`)
75
+ const simple = t.split('.').filter(Boolean).pop() ?? t;
76
+ if (!simple || PRIMITIVE_OR_VOID.has(simple))
77
+ return undefined;
78
+ // Must look like an identifier (PascalCase preferred but allow any word).
79
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(simple))
80
+ return undefined;
81
+ return simple;
82
+ }
83
+ /** `@returns {Type}` or `@return Type` → base type name. */
84
+ function parseJsdocReturns(docstring) {
85
+ if (!docstring)
86
+ return undefined;
87
+ const re = /@returns?\s+(?:\{([^}]+)\}|([A-Za-z_][\w.<>[\],\s|]*))/gi;
88
+ let m;
89
+ while ((m = re.exec(docstring)) !== null) {
90
+ const raw = (m[1] ?? m[2] ?? '').trim();
91
+ const base = extractBaseTypeName(raw);
92
+ if (base)
93
+ return base;
94
+ }
95
+ return undefined;
96
+ }
97
+ /** `@param {Type} name` entries with a resolvable type name. */
98
+ function parseJsdocParams(docstring) {
99
+ if (!docstring)
100
+ return [];
101
+ const out = [];
102
+ const re = /@param(?:eter)?\s+(?:\{([^}]+)\}\s+)?\[?(\w+)\]?/gi;
103
+ let m;
104
+ while ((m = re.exec(docstring)) !== null) {
105
+ const typeRaw = m[1]?.trim();
106
+ const paramName = m[2];
107
+ if (!typeRaw || !paramName)
108
+ continue;
109
+ const typeName = extractBaseTypeName(typeRaw);
110
+ if (typeName)
111
+ out.push({ paramName, typeName });
112
+ }
113
+ return out;
114
+ }
115
+ //# sourceMappingURL=jsdoc-type-tags.js.map
@@ -141,6 +141,7 @@ const liveness_watchdog_1 = require("../../mcp/liveness-watchdog");
141
141
  const migrate_semantics_1 = require("../../arkui/migrate-semantics");
142
142
  const migrate_passage_1 = require("../../arkui/migrate-passage");
143
143
  const tree_sitter_helpers_1 = require("../tree-sitter-helpers");
144
+ const jsdoc_type_tags_1 = require("../jsdoc-type-tags");
144
145
  const wasm_runtime_flags_1 = require("../wasm-runtime-flags");
145
146
  const ignore_1 = __importDefault(require("ignore"));
146
147
  /** Primitive / void return types — not stored in nodes.return_type (inference-only column). */
@@ -2026,6 +2027,47 @@ function filesHaveArkUiEntries(files) {
2026
2027
  // ArkUI ViewTree — component tree, @Prop/@Link transfer, event bindings
2027
2028
  // =============================================================================
2028
2029
  const VIEWTREE_CALLBACK_ATTRS = new Set(arkanalyzer_1.CALLBACK_METHOD_NAME);
2030
+ /**
2031
+ * Framework decorators with no project-local definition. Emitting them as
2032
+ * unresolved `decorates` refs lets name-matcher bind @State → enum State, etc.
2033
+ */
2034
+ const ARKUI_BUILTIN_DECORATORS = new Set([
2035
+ 'State',
2036
+ 'Prop',
2037
+ 'Link',
2038
+ 'Provide',
2039
+ 'Consume',
2040
+ 'Watch',
2041
+ 'Builder',
2042
+ 'BuilderParam',
2043
+ 'Styles',
2044
+ 'Extend',
2045
+ 'AnimatableExtend',
2046
+ 'Component',
2047
+ 'ComponentV2',
2048
+ 'Entry',
2049
+ 'CustomDialog',
2050
+ 'Observed',
2051
+ 'ObservedV2',
2052
+ 'ObjectLink',
2053
+ 'LocalStorageLink',
2054
+ 'LocalStorageProp',
2055
+ 'StorageLink',
2056
+ 'StorageProp',
2057
+ 'Track',
2058
+ 'Require',
2059
+ 'Once',
2060
+ 'Reusable',
2061
+ 'Concurrent',
2062
+ 'Sendable',
2063
+ 'Local',
2064
+ 'Param',
2065
+ 'Event',
2066
+ 'Monitor',
2067
+ 'Computed',
2068
+ 'Trace',
2069
+ 'Type',
2070
+ ]);
2029
2071
  /** State decorator kinds on a field (@State, @Prop, @Link, …) from arkanalyzer. */
2030
2072
  function stateDecoratorKinds(field) {
2031
2073
  const decs = field.getStateDecorators?.();
@@ -2727,6 +2769,11 @@ class ArkTSAdapter {
2727
2769
  continue;
2728
2770
  if (!this.scanned.has(rel) || !isArkAnalyzerSourcePath(rel))
2729
2771
  continue;
2772
+ // Already indexed (usually streamed to DB). Re-running indexScannedFile
2773
+ // would hit addNode's nodeIds short-circuit and leave an empty shell;
2774
+ // live IR rebind is handled by bindLiveMapsFromResident.
2775
+ if (this.nodeIds.has(`file:${rel}`))
2776
+ continue;
2730
2777
  this.indexScannedFile(arkFile);
2731
2778
  if (this.fileResults.has(rel))
2732
2779
  seeded.push(rel);
@@ -3279,6 +3326,20 @@ class ArkTSAdapter {
3279
3326
  const result = this.fileResults.get(relativePath);
3280
3327
  return result?.nodes.some((n) => n.id === nodeId) ?? false;
3281
3328
  }
3329
+ /**
3330
+ * Same-file edges stay on the file's ExtractionResult (persistFileResult FK
3331
+ * filter). Cross-file edges go to crossFileEdges and are inserted after all
3332
+ * modules persist — do not require nodeIds.has(target) at emit time (target
3333
+ * may be indexed later in the same batch).
3334
+ */
3335
+ addMaybeCrossFileEdge(result, callerFile, edge) {
3336
+ if (this.nodeInFile(edge.target, callerFile)) {
3337
+ result.edges.push(edge);
3338
+ }
3339
+ else {
3340
+ this.crossFileEdges.push(edge);
3341
+ }
3342
+ }
3282
3343
  ensureFileResult(relativePath, lineCount) {
3283
3344
  let result = this.fileResults.get(relativePath);
3284
3345
  if (!result) {
@@ -3431,6 +3492,10 @@ class ArkTSAdapter {
3431
3492
  const kind = dec.getKind();
3432
3493
  if (!kind)
3433
3494
  continue;
3495
+ // Built-in ArkUI/ArkTS decorators have no project definition; name-match
3496
+ // would falsely link @State → enum State, @Builder → class Builder, etc.
3497
+ if (ARKUI_BUILTIN_DECORATORS.has(kind))
3498
+ continue;
3434
3499
  result.unresolvedReferences.push({
3435
3500
  fromNodeId,
3436
3501
  referenceName: kind,
@@ -3529,17 +3594,62 @@ class ArkTSAdapter {
3529
3594
  if (superName) {
3530
3595
  const superCls = cls.getHeritageClass(superName);
3531
3596
  if (superCls && !superCls.isDefaultArkClass()) {
3532
- const superId = this.classToId.get(superCls);
3597
+ const superId = this.classToId.get(superCls) ?? this.resolveClassNodeId(superCls);
3598
+ const edgeKind = superCls.getCategory() === CLASS_CATEGORY.INTERFACE ? 'implements' : 'extends';
3533
3599
  if (superId) {
3534
- const edgeKind = superCls.getCategory() === CLASS_CATEGORY.INTERFACE ? 'implements' : 'extends';
3535
- result.edges.push(arkEdge(classNode.id, superId, edgeKind));
3600
+ this.addMaybeCrossFileEdge(result, relativePath, arkEdge(classNode.id, superId, edgeKind));
3601
+ }
3602
+ else {
3603
+ const simple = superName.includes('.') ? superName.split('.').pop() : superName;
3604
+ if (simple && simple !== 'Object') {
3605
+ result.unresolvedReferences.push({
3606
+ fromNodeId: classNode.id,
3607
+ referenceName: simple,
3608
+ referenceKind: edgeKind,
3609
+ line,
3610
+ column: col,
3611
+ filePath: relativePath,
3612
+ language,
3613
+ });
3614
+ }
3615
+ }
3616
+ }
3617
+ else if (superName && superName !== 'Object') {
3618
+ const simple = superName.includes('.') ? superName.split('.').pop() : superName;
3619
+ if (simple) {
3620
+ result.unresolvedReferences.push({
3621
+ fromNodeId: classNode.id,
3622
+ referenceName: simple,
3623
+ referenceKind: 'extends',
3624
+ line,
3625
+ column: col,
3626
+ filePath: relativePath,
3627
+ language,
3628
+ });
3536
3629
  }
3537
3630
  }
3538
3631
  }
3539
3632
  for (const ifaceName of cls.getImplementedInterfaceNames()) {
3540
3633
  const iface = cls.getImplementedInterface(ifaceName);
3541
- if (iface && this.classToId.has(iface)) {
3542
- result.edges.push(arkEdge(classNode.id, this.classToId.get(iface), 'implements'));
3634
+ const ifaceId = iface
3635
+ ? (this.classToId.get(iface) ?? this.resolveClassNodeId(iface))
3636
+ : null;
3637
+ if (ifaceId) {
3638
+ this.addMaybeCrossFileEdge(result, relativePath, arkEdge(classNode.id, ifaceId, 'implements'));
3639
+ }
3640
+ else if (ifaceName) {
3641
+ const simple = ifaceName.includes('.') ? ifaceName.split('.').pop() : ifaceName;
3642
+ if (simple) {
3643
+ result.unresolvedReferences.push({
3644
+ fromNodeId: classNode.id,
3645
+ referenceName: simple,
3646
+ referenceKind: 'implements',
3647
+ line,
3648
+ column: col,
3649
+ filePath: relativePath,
3650
+ language,
3651
+ });
3652
+ }
3543
3653
  }
3544
3654
  }
3545
3655
  for (const method of cls.getMethods(true)) {
@@ -3886,29 +3996,18 @@ class ArkTSAdapter {
3886
3996
  this.crossFileEdges.push(edge);
3887
3997
  }
3888
3998
  }
3889
- ensureMethodNode(method, relativePath, result, parentId) {
3890
- const existing = this.methodToId.get(method);
3891
- if (existing)
3892
- return existing;
3893
- try {
3894
- const bySig = this.methodSigToId.get(method.getSignature().toString());
3895
- if (bySig) {
3896
- this.methodToId.set(method, bySig);
3897
- return bySig;
3898
- }
3899
- }
3900
- catch {
3901
- // fall through
3902
- }
3903
- if (shouldSkipArkMethod(method) && !isAnonymousArkMethod(method))
3904
- return null;
3905
- const displayName = arkMethodDisplayName(method);
3906
- if (!displayName)
3907
- return null;
3908
- const cls = method.getDeclaringArkClass();
3909
- const kind = cls.isDefaultArkClass() ? 'function' : 'method';
3910
- this.indexMethod(relativePath, languageForArkAnalyzerPath(relativePath), result, method, parentId, kind, displayName);
3911
- return this.methodToId.get(method) ?? null;
3999
+ /**
4000
+ * Lazily index a method for ViewTree (and any caller that used to pass the
4001
+ * ViewTree owner path). Attribution always follows the method's declaring
4002
+ * ArkFile / class — same as {@link resolveMethodNodeId} — so nested ViewTree
4003
+ * walks cannot pin `%AM*` handlers onto the importer (Spec 0064).
4004
+ *
4005
+ * `relativePath` / `result` / `parentId` from the caller are ignored for the
4006
+ * node row (kept on the signature so ViewTree call sites stay unchanged);
4007
+ * ViewTree *edges* still use the importer via {@link addViewTreeEdge}.
4008
+ */
4009
+ ensureMethodNode(method, _relativePath, _result, _parentId) {
4010
+ return this.resolveMethodNodeId(method);
3912
4011
  }
3913
4012
  indexMethod(relativePath, language, result, method, parentId, forcedKind, forcedDisplayName) {
3914
4013
  const displayName = forcedDisplayName ?? resolveMethodDisplayName(method);
@@ -3948,6 +4047,52 @@ class ArkTSAdapter {
3948
4047
  result.edges.push(arkEdge(parentId, methodNode.id, 'contains'));
3949
4048
  this.indexDecorators(result, methodNode.id, method, relativePath, language, line);
3950
4049
  this.indexNativeNapiCalls(method, methodNode.id, relativePath, language, result);
4050
+ // Spec 0066: return/param types as resolvable `returns` / `type_of` refs.
4051
+ this.emitMethodTypeEdges(method, methodNode.id, relativePath, language, line, returnType, methodNode.docstring, result);
4052
+ }
4053
+ /**
4054
+ * Spec 0066 — push unresolved `returns` / `type_of` from signature types,
4055
+ * falling back to JSDoc `@returns` / `@param` when the signature lacks them.
4056
+ */
4057
+ emitMethodTypeEdges(method, fromNodeId, relativePath, language, line, returnType, docstring, result) {
4058
+ const push = (referenceName, referenceKind) => {
4059
+ result.unresolvedReferences.push({
4060
+ fromNodeId,
4061
+ referenceName,
4062
+ referenceKind,
4063
+ line,
4064
+ column: 0,
4065
+ filePath: relativePath,
4066
+ language,
4067
+ });
4068
+ };
4069
+ if (returnType) {
4070
+ push(returnType, 'returns');
4071
+ }
4072
+ else {
4073
+ const fromDoc = (0, jsdoc_type_tags_1.parseJsdocReturns)(docstring);
4074
+ if (fromDoc)
4075
+ push(fromDoc, 'returns');
4076
+ }
4077
+ let emittedParam = false;
4078
+ try {
4079
+ for (const p of method.getParameters()) {
4080
+ const raw = p.getType()?.toString();
4081
+ const base = normalizeArktsReturnType(raw);
4082
+ if (base) {
4083
+ push(base, 'type_of');
4084
+ emittedParam = true;
4085
+ }
4086
+ }
4087
+ }
4088
+ catch {
4089
+ // parameters unavailable
4090
+ }
4091
+ if (!emittedParam) {
4092
+ for (const p of (0, jsdoc_type_tags_1.parseJsdocParams)(docstring)) {
4093
+ push(p.typeName, 'type_of');
4094
+ }
4095
+ }
3951
4096
  }
3952
4097
  indexNativeNapiCalls(method, fromNodeId, relativePath, language, result) {
3953
4098
  const code = method.getCode();
@@ -4017,12 +4162,34 @@ class ArkTSAdapter {
4017
4162
  const targetFile = exportInfo.getDeclaringArkFile();
4018
4163
  if (targetFile) {
4019
4164
  const targetRel = this.normalizeRelPath(targetFile.getFilePath());
4020
- const targetFileId = `file:${targetRel}`;
4021
- if (this.nodeIds.has(targetFileId)) {
4022
- result.edges.push(arkEdge(importNode.id, targetFileId, 'imports', {
4165
+ if (targetRel) {
4166
+ const targetFileId = `file:${targetRel}`;
4167
+ this.addMaybeCrossFileEdge(result, relativePath, arkEdge(importNode.id, targetFileId, 'imports', {
4023
4168
  metadata: { synthesizedBy: 'arkanalyzer', module: modulePath },
4024
4169
  }));
4025
4170
  }
4171
+ else if (modulePath) {
4172
+ result.unresolvedReferences.push({
4173
+ fromNodeId: importNode.id,
4174
+ referenceName: modulePath,
4175
+ referenceKind: 'imports',
4176
+ line,
4177
+ column: 0,
4178
+ filePath: relativePath,
4179
+ language,
4180
+ });
4181
+ }
4182
+ }
4183
+ else if (modulePath) {
4184
+ result.unresolvedReferences.push({
4185
+ fromNodeId: importNode.id,
4186
+ referenceName: modulePath,
4187
+ referenceKind: 'imports',
4188
+ line,
4189
+ column: 0,
4190
+ filePath: relativePath,
4191
+ language,
4192
+ });
4026
4193
  }
4027
4194
  }
4028
4195
  else if (modulePath) {
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Store-phase flush / backpressure guards (Spec 0065).
3
+ *
4
+ * When `flushOrdered` records a store failure in `flushError` but returns a
5
+ * resolved promise, the feed backpressure `while` would otherwise spin on
6
+ * `await Promise.resolve()` forever (100% CPU hang). Call this at the top of
7
+ * that loop (and anywhere else that waits on flush progress).
8
+ */
9
+ /** Throw if the ordered store flush already failed or the run was aborted. */
10
+ export declare function throwIfStoreFlushFailed(aborted: boolean, flushError: unknown): void;
11
+ //# sourceMappingURL=store-flush-guard.d.ts.map
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ /**
3
+ * Store-phase flush / backpressure guards (Spec 0065).
4
+ *
5
+ * When `flushOrdered` records a store failure in `flushError` but returns a
6
+ * resolved promise, the feed backpressure `while` would otherwise spin on
7
+ * `await Promise.resolve()` forever (100% CPU hang). Call this at the top of
8
+ * that loop (and anywhere else that waits on flush progress).
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.throwIfStoreFlushFailed = throwIfStoreFlushFailed;
12
+ /** Throw if the ordered store flush already failed or the run was aborted. */
13
+ function throwIfStoreFlushFailed(aborted, flushError) {
14
+ if (flushError)
15
+ throw flushError;
16
+ if (aborted)
17
+ throw new Error('Aborted');
18
+ }
19
+ //# sourceMappingURL=store-flush-guard.js.map
@@ -6,8 +6,9 @@
6
6
  * connection: the orchestrator posts one message per file (in file order) and
7
7
  * the worker applies them in arrival order, which preserves the #1015
8
8
  * insertion-order determinism exactly as if the main thread had run the same
9
- * calls. The main thread performs NO database access while the writer is
10
- * active (fresh-DB path only), so there is no cross-connection contention.
9
+ * calls. Spec 0065: the main connection is closed for the worker's lifetime
10
+ * (`exclusiveHandoff`) — an idle open handle on MEMORY/non-WAL backends was
11
+ * enough to surface SQLITE_BUSY during ArkTS→store handoff.
11
12
  *
12
13
  * Protocol (main → worker):
13
14
  * {type:'open', dbPath, fastInit} → open connection, reply {type:'ready'}
@@ -7,8 +7,9 @@
7
7
  * connection: the orchestrator posts one message per file (in file order) and
8
8
  * the worker applies them in arrival order, which preserves the #1015
9
9
  * insertion-order determinism exactly as if the main thread had run the same
10
- * calls. The main thread performs NO database access while the writer is
11
- * active (fresh-DB path only), so there is no cross-connection contention.
10
+ * calls. Spec 0065: the main connection is closed for the worker's lifetime
11
+ * (`exclusiveHandoff`) — an idle open handle on MEMORY/non-WAL backends was
12
+ * enough to surface SQLITE_BUSY during ArkTS→store handoff.
12
13
  *
13
14
  * Protocol (main → worker):
14
15
  * {type:'open', dbPath, fastInit} → open connection, reply {type:'ready'}
@@ -595,9 +595,15 @@ export declare class TreeSitterExtractor {
595
595
  private readonly BUILTIN_TYPES;
596
596
  /**
597
597
  * Extract type references from type annotations on a function/method/field node.
598
- * Creates 'references' edges for parameter types, return types, and field types.
598
+ * Spec 0066: parameter / field types → `type_of`; return types → `returns`.
599
+ * Dart / Scala mixed signature walks stay `references` (out of this Spec).
599
600
  */
600
601
  private extractTypeAnnotations;
602
+ /**
603
+ * Spec 0066: when the AST omitted return/param types, emit unresolved
604
+ * `returns` / `type_of` refs from JSDoc `@returns` / `@param {Type}`.
605
+ */
606
+ private emitJsdocTypeFallbacks;
601
607
  /**
602
608
  * Extract C# type references from a node that owns a type position —
603
609
  * a method/constructor declaration, a property declaration, or a
@@ -647,6 +653,7 @@ export declare class TreeSitterExtractor {
647
653
  /**
648
654
  * Recursively walk a subtree and extract all type_identifier references.
649
655
  * Handles unions, intersections, generics, arrays, etc.
656
+ * Spec 0066: callers pass `returns` / `type_of` for signature positions.
650
657
  */
651
658
  private extractTypeRefsFromSubtree;
652
659
  /**