@backbencher/brain 0.0.0-stage → 0.1.1

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.
Files changed (319) hide show
  1. package/CHANGELOG.md +298 -0
  2. package/EMBEDDINGS.md +355 -0
  3. package/LICENSE +21 -0
  4. package/README.md +415 -2
  5. package/bin/bb +13 -0
  6. package/dist/analysis-exclusions-7ZJDXOVZ.js +35 -0
  7. package/dist/brain-index-health-Q5APTJQL.js +17 -0
  8. package/dist/brain-relationship-enrichment-MYR2AM4F.js +558 -0
  9. package/dist/brain.sql-EIM4WS4D.js +29 -0
  10. package/dist/chunk-22V37YTK.js +1758 -0
  11. package/dist/chunk-2BSKD7O3.js +40 -0
  12. package/dist/chunk-2DE27DVC.js +1314 -0
  13. package/dist/chunk-2RRBMNBQ.js +322 -0
  14. package/dist/chunk-2WXYNLBA.js +29 -0
  15. package/dist/chunk-3CTFIJJB.js +17 -0
  16. package/dist/chunk-3ZTOHHSW.js +2104 -0
  17. package/dist/chunk-4H22TLOT.js +543 -0
  18. package/dist/chunk-5BVAH26D.js +340 -0
  19. package/dist/chunk-62DHLYW6.js +3427 -0
  20. package/dist/chunk-7AN7U5IZ.js +371 -0
  21. package/dist/chunk-7DRPNVIN.js +782 -0
  22. package/dist/chunk-7MBYT777.js +174 -0
  23. package/dist/chunk-AHDWGM7Q.js +236 -0
  24. package/dist/chunk-B5GEAAYF.js +818 -0
  25. package/dist/chunk-BEIZZ73B.js +1479 -0
  26. package/dist/chunk-BQ3OVQ3P.js +125 -0
  27. package/dist/chunk-D2Q4Y47A.js +53 -0
  28. package/dist/chunk-DIYAGQJL.js +34 -0
  29. package/dist/chunk-DWKXZ6IF.js +336 -0
  30. package/dist/chunk-DYM4FQEE.js +90 -0
  31. package/dist/chunk-ENW4BTBA.js +930 -0
  32. package/dist/chunk-EWMWK7WN.js +75 -0
  33. package/dist/chunk-EY5ZZUUA.js +626 -0
  34. package/dist/chunk-GBI7V7KY.js +69 -0
  35. package/dist/chunk-GEFFO6O2.js +152 -0
  36. package/dist/chunk-HJEWO5EV.js +247 -0
  37. package/dist/chunk-HQSPJDHI.js +613 -0
  38. package/dist/chunk-KAET47ZL.js +125 -0
  39. package/dist/chunk-KCDECRJY.js +440 -0
  40. package/dist/chunk-L5TDYKYK.js +76 -0
  41. package/dist/chunk-MLKGABMK.js +9 -0
  42. package/dist/chunk-NO3U3FJQ.js +201 -0
  43. package/dist/chunk-OWISBAOF.js +61 -0
  44. package/dist/chunk-QKTRECYS.js +1119 -0
  45. package/dist/chunk-QLK6ED4Q.js +235 -0
  46. package/dist/chunk-R2GW4F6R.js +19 -0
  47. package/dist/chunk-RCMTFSEF.js +87 -0
  48. package/dist/chunk-RM3PY6TG.js +376 -0
  49. package/dist/chunk-ROMM6KMC.js +433 -0
  50. package/dist/chunk-SGGCSHAX.js +12050 -0
  51. package/dist/chunk-SK6YVHFZ.js +211 -0
  52. package/dist/chunk-SM7RFQRO.js +214 -0
  53. package/dist/chunk-UGR4QIGS.js +67 -0
  54. package/dist/chunk-UQNQZXHY.js +711 -0
  55. package/dist/chunk-UUNHHX4M.js +13 -0
  56. package/dist/chunk-VL52PL2T.js +71 -0
  57. package/dist/chunk-WIG36E67.js +196 -0
  58. package/dist/chunk-XHK3WWGB.js +7436 -0
  59. package/dist/chunk-XTQM2IP3.js +2863 -0
  60. package/dist/chunk-ZE2LG76D.js +1296 -0
  61. package/dist/chunk-ZETTGPAW.js +6950 -0
  62. package/dist/cli/index.js +37 -0
  63. package/dist/config-HZAAYDP3.js +10 -0
  64. package/dist/content-search-7XIP555Q.js +593 -0
  65. package/dist/database-76RP44JG.js +9 -0
  66. package/dist/doctor-ANGETYMN.js +395 -0
  67. package/dist/edit-engine-CG72LDZD.js +51 -0
  68. package/dist/engine-BKEOGN7N.js +11 -0
  69. package/dist/engine-VIW6NSKO.js +18 -0
  70. package/dist/fingerprint-OBLIMZLP.js +33 -0
  71. package/dist/framework-intelligence-3LORTPOX.js +860 -0
  72. package/dist/freshness-3K6VU5OT.js +174 -0
  73. package/dist/graph-engine-HBSQNPTB.js +21 -0
  74. package/dist/graph_request-Q6NKB6N2.js +350 -0
  75. package/dist/impact_request-LYP672WE.js +68 -0
  76. package/dist/index-space-IUUN5WYV.js +15 -0
  77. package/dist/index-status-7OVGYSGW.js +34 -0
  78. package/dist/index.js +159 -0
  79. package/dist/instance-FO6QZRLH.js +19 -0
  80. package/dist/language-detection-DFCAU5DZ.js +11 -0
  81. package/dist/lsp/server.js +4301 -0
  82. package/dist/mcp/server.js +132 -0
  83. package/dist/onboard-DIQZV2NW.js +161 -0
  84. package/dist/override-companion-pass-XR7MHTP7.js +117 -0
  85. package/dist/parallel-indexer-FK2XKZXY.js +88 -0
  86. package/dist/pool-worker.js +1153 -0
  87. package/dist/project-health-6XPENVAA.js +94 -0
  88. package/dist/project-index-5SDPHITE.js +57 -0
  89. package/dist/project-registry-ZJ3DVUGO.js +355 -0
  90. package/dist/real-bash-analyzer-EJAU5LW5.js +2201 -0
  91. package/dist/real-c-analyzer-HBD3FNNH.js +5942 -0
  92. package/dist/real-clean-analyzer-4EEZLPV5.js +3470 -0
  93. package/dist/real-clojure-analyzer-Q55EBOO2.js +2818 -0
  94. package/dist/real-cpp-analyzer-IVVOD25R.js +4101 -0
  95. package/dist/real-csharp-analyzer-ZBNLNJZJ.js +3298 -0
  96. package/dist/real-css-analyzer-6XRQLUN7.js +15 -0
  97. package/dist/real-dart-analyzer-OLR72EFW.js +3944 -0
  98. package/dist/real-elixir-analyzer-MQWGAH2I.js +2192 -0
  99. package/dist/real-erlang-analyzer-7BVPQEHO.js +1731 -0
  100. package/dist/real-fsharp-analyzer-C2UPFNOA.js +2390 -0
  101. package/dist/real-go-analyzer-UKGI43YS.js +4126 -0
  102. package/dist/real-graphql-analyzer-VE2EEFII.js +1442 -0
  103. package/dist/real-groovy-analyzer-W26VACFW.js +2285 -0
  104. package/dist/real-haskell-analyzer-5CVVAJRX.js +2533 -0
  105. package/dist/real-hcl-analyzer-5ZT4U43N.js +725 -0
  106. package/dist/real-html-analyzer-JWNX3C6I.js +1759 -0
  107. package/dist/real-java-analyzer-GU42VBGQ.js +4677 -0
  108. package/dist/real-javascript-analyzer-QA4QOJ6W.js +14 -0
  109. package/dist/real-json-analyzer-MPA6PRXL.js +1640 -0
  110. package/dist/real-julia-analyzer-22CWYQMN.js +2085 -0
  111. package/dist/real-kotlin-analyzer-QDZJLXXU.js +3956 -0
  112. package/dist/real-lua-analyzer-G7NHLYKS.js +1957 -0
  113. package/dist/real-makefile-analyzer-OCY3GT5N.js +920 -0
  114. package/dist/real-markdown-analyzer-TEJK2JXJ.js +1215 -0
  115. package/dist/real-matlab-analyzer-Q5SWLN7Z.js +480 -0
  116. package/dist/real-nim-analyzer-YLXSK563.js +3603 -0
  117. package/dist/real-objective-c-analyzer-D23ACC2R.js +1518 -0
  118. package/dist/real-perl-analyzer-WWTRHYDT.js +1865 -0
  119. package/dist/real-php-analyzer-JNKJHTCL.js +9368 -0
  120. package/dist/real-powershell-analyzer-XRSNQERV.js +2915 -0
  121. package/dist/real-protobuf-analyzer-3OKUCZ7T.js +867 -0
  122. package/dist/real-python-analyzer-VXAFWI72.js +4880 -0
  123. package/dist/real-r-analyzer-ZGAIDVI4.js +2596 -0
  124. package/dist/real-ruby-analyzer-X2KTLVBH.js +2429 -0
  125. package/dist/real-rust-analyzer-OFFHPL2A.js +3359 -0
  126. package/dist/real-scala-analyzer-TSTW27J6.js +3293 -0
  127. package/dist/real-scss-analyzer-AVWYPSOQ.js +1515 -0
  128. package/dist/real-solidity-analyzer-UCI2KWUB.js +2608 -0
  129. package/dist/real-sql-analyzer-F77ZMQXR.js +3840 -0
  130. package/dist/real-svelte-analyzer-SF7NRUO5.js +2532 -0
  131. package/dist/real-swift-analyzer-MN5QT3QN.js +2311 -0
  132. package/dist/real-toml-analyzer-66L7BDCQ.js +929 -0
  133. package/dist/real-typescript-analyzer-6KEMVDWQ.js +14 -0
  134. package/dist/real-vb-analyzer-VICVPOP7.js +3145 -0
  135. package/dist/real-vba-analyzer-HXLTTHKM.js +466 -0
  136. package/dist/real-vue-analyzer-QXR56CFV.js +2680 -0
  137. package/dist/real-xml-analyzer-YL465ZJW.js +2405 -0
  138. package/dist/real-yaml-analyzer-SUBCBNSI.js +3528 -0
  139. package/dist/real-zig-analyzer-HIQLZMGY.js +2430 -0
  140. package/dist/regex-search-NJKHMZNO.js +239 -0
  141. package/dist/rules-ACHPB7QU.js +152 -0
  142. package/dist/scoped-path-MJYWJZQX.js +11 -0
  143. package/dist/setup-show-NT5ZHYNE.js +15 -0
  144. package/dist/setup-wizard-G5YYR5T6.js +24 -0
  145. package/dist/symbol-search-GZJIM2J2.js +16 -0
  146. package/dist/taint-KXUHHPIE.js +202 -0
  147. package/dist/taint-analyzer-N7YLZU3N.js +163 -0
  148. package/dist/tool-handlers-OJDC35DI.js +72 -0
  149. package/dist/tool-registry-BPEJNSZG.js +17 -0
  150. package/dist/tool-validation-Q57YMMBX.js +32 -0
  151. package/dist/type-inference-QMT2RMLV.js +13 -0
  152. package/dist/vector-binary-LMY4QGAN.js +19 -0
  153. package/dist/version-ABP2TUL2.js +9 -0
  154. package/dist/wasm-NMWO3WXY.js +16 -0
  155. package/docs/EMBEDDINGS.md +132 -0
  156. package/docs/README.md +178 -0
  157. package/docs/data/m10-baseline.json +602 -0
  158. package/docs/data/m10-benchmark-baseline.json +57 -0
  159. package/docs/data/m10-benchmark-last-run.json +127 -0
  160. package/docs/data/m10-final-audit.json +215 -0
  161. package/docs/data/m12-final-audit.json +138 -0
  162. package/docs/data/m12-surface-matrix.json +50 -0
  163. package/docs/data/public-tools.json +1902 -0
  164. package/docs/indexing/DEAD-ASSETS.md +88 -0
  165. package/docs/indexing/INDEXER-REVIEW.md +634 -0
  166. package/docs/indexing/PERFORMANCE-FINDINGS.md +106 -0
  167. package/docs/indexing/README.md +427 -0
  168. package/docs/indexing/discovery-matrix.md +41 -0
  169. package/docs/indexing/embeddings.md +325 -0
  170. package/docs/indexing/environment-variables.md +77 -0
  171. package/docs/indexing/performance.md +323 -0
  172. package/docs/indexing/resume.md +345 -0
  173. package/docs/indexing/troubleshooting.md +391 -0
  174. package/docs/man/bb.1 +190 -0
  175. package/docs/tools/bb_analyze.md +147 -0
  176. package/docs/tools/bb_annotate.md +110 -0
  177. package/docs/tools/bb_annotations.md +106 -0
  178. package/docs/tools/bb_ask.md +101 -0
  179. package/docs/tools/bb_cross_language.md +103 -0
  180. package/docs/tools/bb_doc.md +169 -0
  181. package/docs/tools/bb_edit.md +185 -0
  182. package/docs/tools/bb_graph.md +169 -0
  183. package/docs/tools/bb_health.md +156 -0
  184. package/docs/tools/bb_impact.md +117 -0
  185. package/docs/tools/bb_index.md +108 -0
  186. package/docs/tools/bb_migrate.md +135 -0
  187. package/docs/tools/bb_patterns.md +140 -0
  188. package/docs/tools/bb_search.md +150 -0
  189. package/docs/tools/bb_security.md +100 -0
  190. package/docs/tools/bb_visualize.md +184 -0
  191. package/grammars/README.md +57 -0
  192. package/grammars/THIRD-PARTY-NOTICES.md +50 -0
  193. package/grammars/tree-sitter-c/LICENSE +21 -0
  194. package/grammars/tree-sitter-clojure/LICENSE.md +21 -0
  195. package/grammars/tree-sitter-cpp/LICENSE +21 -0
  196. package/grammars/tree-sitter-csharp/LICENSE +21 -0
  197. package/grammars/tree-sitter-css/LICENSE +21 -0
  198. package/grammars/tree-sitter-css/README.md +21 -0
  199. package/grammars/tree-sitter-dart/LICENSE +20 -0
  200. package/grammars/tree-sitter-dart-upstream/LICENSE +20 -0
  201. package/grammars/tree-sitter-dart-upstream/README.md +18 -0
  202. package/grammars/tree-sitter-elixir/LICENSE +176 -0
  203. package/grammars/tree-sitter-elixir/README.md +11 -0
  204. package/grammars/tree-sitter-fsharp/LICENSE +21 -0
  205. package/grammars/tree-sitter-fsharp/README.md +45 -0
  206. package/grammars/tree-sitter-graphql/LICENSE +21 -0
  207. package/grammars/tree-sitter-graphql/README.md +106 -0
  208. package/grammars/tree-sitter-groovy/LICENSE +21 -0
  209. package/grammars/tree-sitter-groovy/README.md +4 -0
  210. package/grammars/tree-sitter-haskell/LICENSE +21 -0
  211. package/grammars/tree-sitter-haskell/README.md +394 -0
  212. package/grammars/tree-sitter-hcl/CHANGELOG.md +102 -0
  213. package/grammars/tree-sitter-hcl/LICENSE +201 -0
  214. package/grammars/tree-sitter-hcl/README.md +39 -0
  215. package/grammars/tree-sitter-html/LICENSE +21 -0
  216. package/grammars/tree-sitter-html/README.md +21 -0
  217. package/grammars/tree-sitter-julia/LICENSE +21 -0
  218. package/grammars/tree-sitter-julia/README.md +24 -0
  219. package/grammars/tree-sitter-kotlin/LICENSE +9 -0
  220. package/grammars/tree-sitter-lua/LICENSE.md +21 -0
  221. package/grammars/tree-sitter-lua/README.md +29 -0
  222. package/grammars/tree-sitter-make/LICENSE +21 -0
  223. package/grammars/tree-sitter-matlab/LICENSE +19 -0
  224. package/grammars/tree-sitter-matlab/README.md +99 -0
  225. package/grammars/tree-sitter-nim/LICENSE +373 -0
  226. package/grammars/tree-sitter-nim/README.md +46 -0
  227. package/grammars/tree-sitter-objc/LICENSE +21 -0
  228. package/grammars/tree-sitter-objc/README.md +20 -0
  229. package/grammars/tree-sitter-perl/LICENSE +7 -0
  230. package/grammars/tree-sitter-perl/README.md +100 -0
  231. package/grammars/tree-sitter-powershell/LICENSE +21 -0
  232. package/grammars/tree-sitter-powershell/README.md +7 -0
  233. package/grammars/tree-sitter-proto/LICENSE +21 -0
  234. package/grammars/tree-sitter-proto/README.md +105 -0
  235. package/grammars/tree-sitter-r/LICENSE +9 -0
  236. package/grammars/tree-sitter-r/README.md +66 -0
  237. package/grammars/tree-sitter-ruby/LICENSE +22 -0
  238. package/grammars/tree-sitter-scss/LICENSE +21 -0
  239. package/grammars/tree-sitter-scss/README.md +17 -0
  240. package/grammars/tree-sitter-solidity/LICENSE +7 -0
  241. package/grammars/tree-sitter-sql/LICENSE +21 -0
  242. package/grammars/tree-sitter-typescript/LICENSE +21 -0
  243. package/grammars/tree-sitter-vba/LICENSE +21 -0
  244. package/grammars/tree-sitter-vba/README.md +738 -0
  245. package/grammars/tree-sitter-vbnet/LICENSE +21 -0
  246. package/grammars/tree-sitter-vbnet/README.md +322 -0
  247. package/grammars/tree-sitter-xml/LICENSE +19 -0
  248. package/grammars/tree-sitter-yaml/LICENSE +20 -0
  249. package/grammars/tree-sitter-yaml/README.md +21 -0
  250. package/package.json +184 -4
  251. package/wasm/dart.wasm +0 -0
  252. package/wasm/tree-sitter-bash.wasm +0 -0
  253. package/wasm/tree-sitter-c.wasm +0 -0
  254. package/wasm/tree-sitter-c_sharp.wasm +0 -0
  255. package/wasm/tree-sitter-clean.wasm +0 -0
  256. package/wasm/tree-sitter-clojure.wasm +0 -0
  257. package/wasm/tree-sitter-core_schema.wasm +0 -0
  258. package/wasm/tree-sitter-cpp.wasm +0 -0
  259. package/wasm/tree-sitter-css.wasm +0 -0
  260. package/wasm/tree-sitter-dart.wasm +0 -0
  261. package/wasm/tree-sitter-elisp.wasm +0 -0
  262. package/wasm/tree-sitter-elixir.wasm +0 -0
  263. package/wasm/tree-sitter-elm.wasm +0 -0
  264. package/wasm/tree-sitter-embedded_template.wasm +0 -0
  265. package/wasm/tree-sitter-erlang.wasm +0 -0
  266. package/wasm/tree-sitter-fsharp.wasm +0 -0
  267. package/wasm/tree-sitter-fsharp_signature.wasm +0 -0
  268. package/wasm/tree-sitter-go.wasm +0 -0
  269. package/wasm/tree-sitter-graphql.wasm +0 -0
  270. package/wasm/tree-sitter-groovy.wasm +0 -0
  271. package/wasm/tree-sitter-haskell.wasm +0 -0
  272. package/wasm/tree-sitter-hcl.wasm +0 -0
  273. package/wasm/tree-sitter-html.wasm +0 -0
  274. package/wasm/tree-sitter-java.wasm +0 -0
  275. package/wasm/tree-sitter-javascript.wasm +0 -0
  276. package/wasm/tree-sitter-json.wasm +0 -0
  277. package/wasm/tree-sitter-json_schema.wasm +0 -0
  278. package/wasm/tree-sitter-julia.wasm +0 -0
  279. package/wasm/tree-sitter-kotlin.wasm +0 -0
  280. package/wasm/tree-sitter-lua.wasm +0 -0
  281. package/wasm/tree-sitter-make.wasm +0 -0
  282. package/wasm/tree-sitter-markdown.wasm +0 -0
  283. package/wasm/tree-sitter-markdown_inline.wasm +0 -0
  284. package/wasm/tree-sitter-matlab.wasm +0 -0
  285. package/wasm/tree-sitter-nim.wasm +0 -0
  286. package/wasm/tree-sitter-objc.wasm +0 -0
  287. package/wasm/tree-sitter-ocaml.wasm +0 -0
  288. package/wasm/tree-sitter-perl.wasm +0 -0
  289. package/wasm/tree-sitter-php.wasm +0 -0
  290. package/wasm/tree-sitter-php_only.wasm +0 -0
  291. package/wasm/tree-sitter-powershell.wasm +0 -0
  292. package/wasm/tree-sitter-protobuf.wasm +0 -0
  293. package/wasm/tree-sitter-python.wasm +0 -0
  294. package/wasm/tree-sitter-ql.wasm +0 -0
  295. package/wasm/tree-sitter-r.wasm +0 -0
  296. package/wasm/tree-sitter-rescript.wasm +0 -0
  297. package/wasm/tree-sitter-ruby.wasm +0 -0
  298. package/wasm/tree-sitter-rust.wasm +0 -0
  299. package/wasm/tree-sitter-scala.wasm +0 -0
  300. package/wasm/tree-sitter-scss.wasm +0 -0
  301. package/wasm/tree-sitter-solidity.wasm +0 -0
  302. package/wasm/tree-sitter-sql.wasm +0 -0
  303. package/wasm/tree-sitter-svelte.wasm +0 -0
  304. package/wasm/tree-sitter-swift.wasm +0 -0
  305. package/wasm/tree-sitter-systemrdl.wasm +0 -0
  306. package/wasm/tree-sitter-terraform.wasm +0 -0
  307. package/wasm/tree-sitter-tlaplus.wasm +0 -0
  308. package/wasm/tree-sitter-toml.wasm +0 -0
  309. package/wasm/tree-sitter-tsx.wasm +0 -0
  310. package/wasm/tree-sitter-typescript.wasm +0 -0
  311. package/wasm/tree-sitter-vba.wasm +0 -0
  312. package/wasm/tree-sitter-vbnet.wasm +0 -0
  313. package/wasm/tree-sitter-vue.wasm +0 -0
  314. package/wasm/tree-sitter-xml.wasm +0 -0
  315. package/wasm/tree-sitter-yaml.wasm +0 -0
  316. package/wasm/tree-sitter-zig.wasm +0 -0
  317. package/wasm/tree-sitter.wasm +0 -0
  318. package/wasm/tsx.wasm +0 -0
  319. package/wasm/typescript.wasm +0 -0
@@ -0,0 +1,3470 @@
1
+ import {
2
+ fileClaimForTarget
3
+ } from "./chunk-EWMWK7WN.js";
4
+ import {
5
+ ASTEngine
6
+ } from "./chunk-3ZTOHHSW.js";
7
+ import "./chunk-BQ3OVQ3P.js";
8
+ import "./chunk-SK6YVHFZ.js";
9
+ import "./chunk-2BSKD7O3.js";
10
+ import "./chunk-KAET47ZL.js";
11
+ import "./chunk-KCDECRJY.js";
12
+ import "./chunk-MLKGABMK.js";
13
+
14
+ // src/ast/real-clean-analyzer.ts
15
+ import * as path from "path";
16
+ var LOCAL_SCOPE_NAME = "local";
17
+ var OPTIONAL_PARAM_NAME = "_";
18
+ function declarationKey(frame, owner, name) {
19
+ return `${frame ?? ""}|${owner ?? ""}::${name}`;
20
+ }
21
+ function stripWhitespace(text) {
22
+ let out = "";
23
+ for (const ch of text) if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") out += ch;
24
+ return out;
25
+ }
26
+ function unparenthesizeText(text) {
27
+ let body = text;
28
+ if (body.startsWith("(")) body = body.slice(1);
29
+ if (body.endsWith(")")) body = body.slice(0, -1);
30
+ const inner = body.trim();
31
+ if (!inner) return null;
32
+ if (inner.includes(",") || inner.includes("{") || inner.includes("}")) return null;
33
+ return inner;
34
+ }
35
+ function unquoteText(text) {
36
+ if (text.length >= 2 && text.startsWith('"') && text.endsWith('"')) return text.slice(1, -1);
37
+ return text;
38
+ }
39
+ var RESERVED_DECLARATION_NAMES = /* @__PURE__ */ new Set([
40
+ "as",
41
+ "case",
42
+ "class",
43
+ "code",
44
+ "definition",
45
+ "derive",
46
+ "else",
47
+ "export",
48
+ "foreign",
49
+ "from",
50
+ "generic",
51
+ "if",
52
+ "implementation",
53
+ "import",
54
+ "in",
55
+ "infix",
56
+ "infixl",
57
+ "infixr",
58
+ "instance",
59
+ "let",
60
+ "module",
61
+ "of",
62
+ "qualified",
63
+ "special",
64
+ "system",
65
+ "then",
66
+ "where",
67
+ "with"
68
+ ]);
69
+ function isReservedDeclarationName(name) {
70
+ return name !== null && name !== void 0 && RESERVED_DECLARATION_NAMES.has(name);
71
+ }
72
+ var OPERATOR_LEAF_TYPES = /* @__PURE__ */ new Set([
73
+ "operator",
74
+ "operator_add",
75
+ "operator_and",
76
+ "operator_compare",
77
+ "operator_cons",
78
+ "operator_cons_strict",
79
+ "operator_dot",
80
+ "operator_exp",
81
+ "operator_mul",
82
+ "operator_or",
83
+ "operator_pipe",
84
+ "monad_bind",
85
+ "backtick_operator",
86
+ "uniqueness_star",
87
+ "parenthesized_operator"
88
+ ]);
89
+ var DERIVE_TARGET_NODE_TYPES = /* @__PURE__ */ new Set([
90
+ "type_variable",
91
+ "constructor",
92
+ "dotted_type",
93
+ "type_application",
94
+ "type_paren",
95
+ "parenthesized_operator"
96
+ ]);
97
+ var PATTERN_NODE_TYPES = /* @__PURE__ */ new Set([
98
+ "constructor_pattern",
99
+ "list_pattern",
100
+ "tuple_pattern",
101
+ "record_pattern",
102
+ "paren_pattern",
103
+ "unboxed_pattern",
104
+ "cons_pattern",
105
+ "strict_binding_pattern",
106
+ "record_update_pattern"
107
+ ]);
108
+ var CONSTRAINT_MARKER_TYPES = /* @__PURE__ */ new Set([
109
+ "number",
110
+ "integer",
111
+ "float",
112
+ "uniqueness_star",
113
+ "uniqueness_constraints"
114
+ ]);
115
+ var INSTANCE_CONTAINER_SUFFIX = "::instance";
116
+ var CALLABLE_NODE_TYPES = /* @__PURE__ */ new Set([
117
+ "function_declaration",
118
+ "operator_definition",
119
+ "generic_case_definition",
120
+ "macro_definition"
121
+ ]);
122
+ var COMMENT_TYPES = /* @__PURE__ */ new Set(["line_comment", "block_comment"]);
123
+ var DECISION_NODE_TYPES = /* @__PURE__ */ new Set([
124
+ "case_expression",
125
+ "case_alternative",
126
+ "guard_equation",
127
+ "guard",
128
+ "if_expression",
129
+ "comprehension_qualifier",
130
+ "binary_expression"
131
+ ]);
132
+ var CLEAN_STD_ENV_NAMES = /* @__PURE__ */ new Set([
133
+ "Applicative",
134
+ "Arith",
135
+ "Array",
136
+ "Bool",
137
+ "CONS",
138
+ "Char",
139
+ "ConsPos",
140
+ "DynamicTemp",
141
+ "EITHER",
142
+ "Enum",
143
+ "Eq",
144
+ "FIELD",
145
+ "File",
146
+ "FileEnv",
147
+ "FileSystem",
148
+ "Files",
149
+ "Functor",
150
+ "GenConsAssoc",
151
+ "GenConsPrio",
152
+ "GenType",
153
+ "GenericConsDescriptor",
154
+ "GenericDict",
155
+ "GenericDict0",
156
+ "GenericFieldDescriptor",
157
+ "GenericRecordDescriptor",
158
+ "GenericTypeDefDescriptor",
159
+ "IncDec",
160
+ "Int",
161
+ "List",
162
+ "Maybe",
163
+ "Monad",
164
+ "MultDiv",
165
+ "NormaliseState",
166
+ "Num",
167
+ "OBJECT",
168
+ "Option",
169
+ "Ord",
170
+ "PAIR",
171
+ "PlusMin",
172
+ "Prelude",
173
+ "RECORD",
174
+ "Real",
175
+ "Semigroup",
176
+ "Show",
177
+ "St",
178
+ "StdArray",
179
+ "StdBool",
180
+ "StdChar",
181
+ "StdCharList",
182
+ "StdClass",
183
+ "StdDebug",
184
+ "StdDynamic",
185
+ "StdEnum",
186
+ "StdEnv",
187
+ "StdFile",
188
+ "StdFunc",
189
+ "StdFunctions",
190
+ "StdGeneric",
191
+ "StdInt",
192
+ "StdList",
193
+ "StdMaybe",
194
+ "StdMisc",
195
+ "StdOrdList",
196
+ "StdOverloaded",
197
+ "StdOverloadedList",
198
+ "StdReal",
199
+ "StdStrictLists",
200
+ "StdString",
201
+ "StdTuple",
202
+ "String",
203
+ "TC_Bool",
204
+ "TC_Char",
205
+ "TC_Dynamic",
206
+ "TC_File",
207
+ "TC_Int",
208
+ "TC_Real",
209
+ "TC_World",
210
+ "TC__Arrow",
211
+ "TC__LazyArray",
212
+ "TC__LazyArrayP2",
213
+ "TC__List",
214
+ "TC__Maybe",
215
+ "TC__PackedArray",
216
+ "TC__PackedArrayP2",
217
+ "TC__STuple10",
218
+ "TC__STuple11",
219
+ "TC__STuple12",
220
+ "TC__STuple13",
221
+ "TC__STuple14",
222
+ "TC__STuple15",
223
+ "TC__STuple16",
224
+ "TC__STuple17",
225
+ "TC__STuple18",
226
+ "TC__STuple19",
227
+ "TC__STuple2",
228
+ "TC__STuple20",
229
+ "TC__STuple21",
230
+ "TC__STuple22",
231
+ "TC__STuple23",
232
+ "TC__STuple24",
233
+ "TC__STuple25",
234
+ "TC__STuple26",
235
+ "TC__STuple27",
236
+ "TC__STuple28",
237
+ "TC__STuple29",
238
+ "TC__STuple3",
239
+ "TC__STuple30",
240
+ "TC__STuple31",
241
+ "TC__STuple32",
242
+ "TC__STuple4",
243
+ "TC__STuple5",
244
+ "TC__STuple6",
245
+ "TC__STuple7",
246
+ "TC__STuple8",
247
+ "TC__STuple9",
248
+ "TC__StrictArray",
249
+ "TC__StrictArrayP2",
250
+ "TC__StrictList",
251
+ "TC__StrictMaybe",
252
+ "TC__StrictTailStrictList",
253
+ "TC__TailStrictList",
254
+ "TC__Tuple10",
255
+ "TC__Tuple11",
256
+ "TC__Tuple12",
257
+ "TC__Tuple13",
258
+ "TC__Tuple14",
259
+ "TC__Tuple15",
260
+ "TC__Tuple16",
261
+ "TC__Tuple17",
262
+ "TC__Tuple18",
263
+ "TC__Tuple19",
264
+ "TC__Tuple2",
265
+ "TC__Tuple20",
266
+ "TC__Tuple21",
267
+ "TC__Tuple22",
268
+ "TC__Tuple23",
269
+ "TC__Tuple24",
270
+ "TC__Tuple25",
271
+ "TC__Tuple26",
272
+ "TC__Tuple27",
273
+ "TC__Tuple28",
274
+ "TC__Tuple29",
275
+ "TC__Tuple3",
276
+ "TC__Tuple30",
277
+ "TC__Tuple31",
278
+ "TC__Tuple32",
279
+ "TC__Tuple4",
280
+ "TC__Tuple5",
281
+ "TC__Tuple6",
282
+ "TC__Tuple7",
283
+ "TC__Tuple8",
284
+ "TC__Tuple9",
285
+ "TC__UnboxedArray",
286
+ "TC__UnboxedArrayP2",
287
+ "TC__UnboxedList",
288
+ "TC__UnboxedMaybe",
289
+ "TC__UnboxedTailStrictList",
290
+ "TC__Unit",
291
+ "TypeCode",
292
+ "TypeCodeConstructor",
293
+ "UList",
294
+ "UMaybe",
295
+ "UNIT",
296
+ "UTSList",
297
+ "UnificationEnvironment",
298
+ "World",
299
+ "_SystemArray",
300
+ "_SystemDynamic",
301
+ "_SystemEnum",
302
+ "_SystemEnumStrict",
303
+ "_SystemStrictLists",
304
+ "_SystemStrictMaybes",
305
+ "_UnificationEnvironment"
306
+ ]);
307
+ var RealCleanAnalyzer = class {
308
+ analyze(rootNode, fileContent, filePath, context) {
309
+ const ctx = {
310
+ symbols: [],
311
+ relationships: [],
312
+ imports: [],
313
+ exports: [],
314
+ errors: [],
315
+ patterns: {},
316
+ fixities: [],
317
+ declaredFunctions: /* @__PURE__ */ new Set(),
318
+ functionSymbolIndex: /* @__PURE__ */ Object.create(null),
319
+ signatures: /* @__PURE__ */ new Map(),
320
+ signaturesByNode: /* @__PURE__ */ new Map(),
321
+ localScopes: { callableScoped: /* @__PURE__ */ new Map(), blockLocals: /* @__PURE__ */ new Map(), frameLocals: /* @__PURE__ */ new Map() },
322
+ importAliases: /* @__PURE__ */ new Set(),
323
+ declaredTypeNames: /* @__PURE__ */ new Set(),
324
+ declaredTypeDefNames: /* @__PURE__ */ new Set(),
325
+ declaredFieldNames: /* @__PURE__ */ new Set(),
326
+ declaredConstructors: /* @__PURE__ */ new Set(),
327
+ declaredClassNames: /* @__PURE__ */ new Set(),
328
+ classMembers: /* @__PURE__ */ new Map(),
329
+ frameSymbolIndex: /* @__PURE__ */ new Map(),
330
+ currentModule: void 0,
331
+ currentFrame: void 0,
332
+ currentFrameIsLocal: false,
333
+ seenRels: /* @__PURE__ */ new Set(),
334
+ fileComplexity: 1,
335
+ declinedInstantiations: { count: 0, names: [] },
336
+ typeCoercions: { count: 0, names: [] },
337
+ operatorImports: { count: 0, names: [] },
338
+ foreignCodeImports: { count: 0, objects: [] },
339
+ detachedLocals: 0,
340
+ frameComplexity: /* @__PURE__ */ new Map()
341
+ };
342
+ const scopeStack = [];
343
+ const stack = [{ node: rootNode }];
344
+ this.collectNames(rootNode, ctx);
345
+ while (stack.length > 0) {
346
+ const entry = stack.pop();
347
+ if (entry.isExit) {
348
+ if (entry.restoresFrame) {
349
+ if (ctx.currentFrame !== void 0) {
350
+ const idx = ctx.frameSymbolIndex.get(ctx.currentFrame);
351
+ if (idx !== void 0) {
352
+ const sym = ctx.symbols[idx];
353
+ sym.complexity = Math.max(sym.complexity ?? 1, ctx.frameComplexity.get(ctx.currentFrame) ?? 1);
354
+ }
355
+ }
356
+ ctx.currentFrame = entry.restoreFrame;
357
+ ctx.currentFrameIsLocal = entry.restoreLocal === true;
358
+ }
359
+ if (scopeStack.length > 0 && scopeStack[scopeStack.length - 1].node === entry.node) scopeStack.pop();
360
+ continue;
361
+ }
362
+ const node = entry.node;
363
+ if (node.isMissing) continue;
364
+ if (node.type === "ERROR") {
365
+ ctx.errors.push({
366
+ message: "Syntax Error",
367
+ range: {
368
+ startLine: node.startPosition.row + 1,
369
+ startColumn: node.startPosition.column,
370
+ endLine: node.endPosition.row + 1,
371
+ endColumn: node.endPosition.column,
372
+ startIndex: node.startIndex,
373
+ endIndex: node.endIndex
374
+ },
375
+ severity: "error"
376
+ });
377
+ }
378
+ if (DECISION_NODE_TYPES.has(node.type)) {
379
+ ctx.fileComplexity++;
380
+ if (ctx.currentFrame !== void 0) {
381
+ ctx.frameComplexity.set(ctx.currentFrame, (ctx.frameComplexity.get(ctx.currentFrame) ?? 1) + 1);
382
+ }
383
+ }
384
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : void 0;
385
+ const inErrorRegion = entry.inError === true || node.type === "ERROR";
386
+ let frame = null;
387
+ let openedScope = false;
388
+ switch (node.type) {
389
+ case "module_declaration":
390
+ openedScope = this.enterModule(node, ctx, scopeStack);
391
+ break;
392
+ case "type_definition":
393
+ openedScope = this.processTypeDefinition(node, ctx, scopeStack);
394
+ break;
395
+ case "class_declaration":
396
+ openedScope = this.processClass(node, ctx, scopeStack);
397
+ break;
398
+ case "instance_declaration":
399
+ openedScope = this.processInstance(node, ctx, scopeStack);
400
+ break;
401
+ case "generic_declaration":
402
+ this.processGenericDeclaration(node, ctx, currentScope);
403
+ break;
404
+ case "derive_declaration":
405
+ this.processDeriveDeclaration(node, ctx);
406
+ break;
407
+ case "function_declaration":
408
+ case "operator_definition":
409
+ case "generic_case_definition":
410
+ frame = this.processCallableNode(node, ctx, currentScope, inErrorRegion);
411
+ break;
412
+ case "macro_definition":
413
+ frame = this.processMacro(node, ctx, currentScope);
414
+ break;
415
+ case "foreign_export":
416
+ this.processForeignExport(node, ctx, currentScope);
417
+ break;
418
+ case "fixity_declaration":
419
+ this.processFixityDeclaration(node, ctx);
420
+ break;
421
+ case "type_signature":
422
+ this.processTypeSignature(node, ctx, currentScope, inErrorRegion);
423
+ break;
424
+ case "where_block":
425
+ case "with_block":
426
+ case "let_expression":
427
+ scopeStack.push({
428
+ node,
429
+ name: LOCAL_SCOPE_NAME,
430
+ kind: ASTEngine.SymbolKind.Namespace,
431
+ locals: ctx.localScopes.blockLocals.get(node.id)
432
+ });
433
+ openedScope = true;
434
+ break;
435
+ case "special_block":
436
+ this.processSpecialBlock(node, ctx);
437
+ break;
438
+ case "application":
439
+ this.extractApplication(node, ctx, scopeStack);
440
+ break;
441
+ case "field_access":
442
+ this.emitFieldUse(node, ctx, "field_read");
443
+ break;
444
+ case "record_update":
445
+ this.extractRecordUpdate(node, ctx);
446
+ break;
447
+ case "record_expression":
448
+ this.extractRecordExpression(node, ctx);
449
+ break;
450
+ case "constructor_pattern":
451
+ this.extractConstructorPattern(node, ctx);
452
+ break;
453
+ case "lambda_expression":
454
+ ctx.patterns["Higher-Order Functions"] = true;
455
+ break;
456
+ case "list_comprehension":
457
+ case "array_comprehension":
458
+ ctx.patterns["List Comprehensions"] = true;
459
+ break;
460
+ case "case_expression":
461
+ case "case_alternative":
462
+ ctx.patterns["Pattern Matching"] = true;
463
+ break;
464
+ case "code_expression":
465
+ ctx.patterns["Inline Machine Code"] = true;
466
+ break;
467
+ case "unboxed_expression":
468
+ case "unboxed_pattern":
469
+ case "uniqueness_type":
470
+ case "uniqueness_star":
471
+ ctx.patterns["Uniqueness Types"] = true;
472
+ break;
473
+ case "type_definition_body":
474
+ ctx.patterns["Algebraic Data Types"] = true;
475
+ break;
476
+ case "record_definition":
477
+ ctx.patterns["Record Types"] = true;
478
+ break;
479
+ case "record_update_pattern":
480
+ case "update_field":
481
+ ctx.patterns["Record Updates"] = true;
482
+ break;
483
+ case "monad_bind":
484
+ ctx.patterns["Do Notation"] = true;
485
+ break;
486
+ case "range_operator":
487
+ ctx.patterns["Range Expressions"] = true;
488
+ break;
489
+ default:
490
+ break;
491
+ }
492
+ if (frame) {
493
+ const previousFrame = ctx.currentFrame;
494
+ const previousLocal = ctx.currentFrameIsLocal;
495
+ ctx.currentFrame = previousFrame === void 0 ? frame.name : `${previousFrame}.${frame.name}`;
496
+ ctx.currentFrameIsLocal = frame.isLocal;
497
+ const frameIdx = ctx.functionSymbolIndex[frame.key];
498
+ if (frameIdx !== void 0) ctx.frameSymbolIndex.set(ctx.currentFrame, frameIdx);
499
+ scopeStack.push({
500
+ node,
501
+ name: frame.name,
502
+ kind: frame.kind,
503
+ locals: ctx.localScopes.frameLocals.get(node.id),
504
+ isFrame: true
505
+ });
506
+ stack.push({ node, isExit: true, restoresFrame: true, restoreFrame: previousFrame, restoreLocal: previousLocal });
507
+ } else if (openedScope) {
508
+ stack.push({ node, isExit: true });
509
+ }
510
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
511
+ const child = node.namedChild(i);
512
+ if (child) stack.push({ node: child, inError: inErrorRegion });
513
+ }
514
+ }
515
+ this.attachFixities(ctx);
516
+ this.emitContainsEdges(ctx);
517
+ this.emitExports(ctx);
518
+ this.qualifyModuleLevelSymbols(ctx);
519
+ this.classifyEvidenceTier(ctx);
520
+ const claimRoot = typeof context?.projectRoot === "string" && context.projectRoot.length > 0 ? context.projectRoot : null;
521
+ if (claimRoot !== null) {
522
+ const root = path.resolve(claimRoot);
523
+ const fileRel = path.relative(root, filePath).split(path.sep).join("/");
524
+ const relDir = path.posix.dirname(fileRel);
525
+ const fileDir = relDir === "." ? "" : `${relDir}/`;
526
+ for (const r of ctx.relationships) {
527
+ if (r.type !== ASTEngine.RelationshipType.Imports) continue;
528
+ const md = r.metadata;
529
+ if (!md || typeof md.importProjectFile === "string") continue;
530
+ const moduleName = typeof md.module === "string" && md.module.length > 0 ? md.module : String(r.target ?? "").split(":")[0];
531
+ if (moduleName.length === 0) continue;
532
+ const modulePath = moduleName.split(".").join("/");
533
+ const rootCandidates = [`${modulePath}.icl`, `${modulePath}.dcl`];
534
+ const extraCandidates = fileDir.length > 0 ? [`${fileDir}${modulePath}.icl`, `${fileDir}${modulePath}.dcl`, ...rootCandidates] : rootCandidates;
535
+ const claim = fileClaimForTarget({
536
+ projectRoot: claimRoot,
537
+ filePath,
538
+ written: String(r.target ?? ""),
539
+ admittedExtensions: ["icl", "dcl"],
540
+ extraCandidates
541
+ });
542
+ if (claim === null) continue;
543
+ md.importProjectFile = claim;
544
+ }
545
+ }
546
+ this.markStandardEnvironmentTargets(ctx);
547
+ const patterns = [];
548
+ for (const p in ctx.patterns) patterns.push(p);
549
+ return {
550
+ language: "clean",
551
+ symbols: ctx.symbols,
552
+ relationships: ctx.relationships,
553
+ imports: ctx.imports,
554
+ exports: ctx.exports,
555
+ errors: ctx.errors,
556
+ metadata: {
557
+ complexity: ctx.fileComplexity,
558
+ linesOfCode: fileContent.split("\n").length,
559
+ cyclomaticComplexity: ctx.fileComplexity,
560
+ patterns,
561
+ hasTypeInference: true,
562
+ analysisVersion: "12.0.0-clean-ast-complete",
563
+ // The price of the in-file resolution rule, stated in the output rather
564
+ // than inferred from a diff. See the field's declaration for the
565
+ // measurement behind it.
566
+ declinedInstantiations: ctx.declinedInstantiations.count,
567
+ declinedInstantiationNames: ctx.declinedInstantiations.names,
568
+ // Applying a declared type: a coercion, counted apart from the declines
569
+ // because the construct is known rather than unclassifiable.
570
+ typeCoercions: ctx.typeCoercions.count,
571
+ typeCoercionNames: ctx.typeCoercions.names,
572
+ // Imported operators: a real dependency the shared `<module>:<item>` edge
573
+ // form cannot express, so it is counted rather than emitted unresolvable.
574
+ operatorImports: ctx.operatorImports.count,
575
+ operatorImportNames: ctx.operatorImports.names,
576
+ // Foreign code imports: a real dependency the `<module>:<item>` edge form
577
+ // cannot express, because a linker input is not a module.
578
+ foreignCodeImports: ctx.foreignCodeImports.count,
579
+ foreignCodeObjects: ctx.foreignCodeImports.objects,
580
+ // Locals whose enclosing function the pinned grammar could not read, so the
581
+ // module owns them instead of a frame that does not exist.
582
+ detachedLocals: ctx.detachedLocals
583
+ }
584
+ };
585
+ }
586
+ // ============================================================================
587
+ // PASS 1 — name tables
588
+ // ============================================================================
589
+ /**
590
+ * Imports, including the SELECTIVE item list.
591
+ *
592
+ * The old version pushed `items: []` unconditionally. Measured over the
593
+ * corpus: 367 of the 1,332 `import_declaration`s are `from M import …` with an
594
+ * item list — 943 items across 20 distinct item node types (`constructor` 541,
595
+ * `identifier` 188, `range_operator` 59, `parenthesized_operator` 45,
596
+ * `operator_compare` 30, `operator` 17, `record_subset` 11,
597
+ * `class_method_name` 10, `constructor_subset` 7, …). Every one of those names
598
+ * was invisible to the product: the import edge had to be `<module>:*` when the
599
+ * file actually said exactly which symbols it wanted, which is the difference
600
+ * between an edge that resolves and one that never can (see
601
+ * src/ast/import-resolution.ts — a target must be in the exporting file's
602
+ * export surface before it may be joined).
603
+ */
604
+ collectImport(n, ctx, edgeSource, importEdges) {
605
+ {
606
+ {
607
+ const moduleNodes = this.fields(n, "module");
608
+ const aliasNode = n.childForFieldName("alias");
609
+ const alias = aliasNode ? this.assembleModuleName(aliasNode) : void 0;
610
+ let isQualified = false;
611
+ for (let i = 0; i < n.childCount; i++) {
612
+ const c = n.child(i);
613
+ if (c && !c.isNamed && c.type === "qualified") {
614
+ isQualified = true;
615
+ break;
616
+ }
617
+ }
618
+ if (alias) ctx.importAliases.add(alias);
619
+ const items = this.importItems(n, moduleNodes);
620
+ for (const it of items) {
621
+ if (it.name.includes(":")) {
622
+ ctx.operatorImports.count++;
623
+ if (ctx.operatorImports.names.length < 25 && !ctx.operatorImports.names.includes(it.name)) {
624
+ ctx.operatorImports.names.push(it.name);
625
+ }
626
+ continue;
627
+ }
628
+ const key = `${edgeSource}|${it.module}:${it.name}|${it.line}`;
629
+ if (importEdges.has(key)) continue;
630
+ importEdges.add(key);
631
+ this.pushRel(
632
+ ctx,
633
+ edgeSource,
634
+ `${it.module}:${it.name}`,
635
+ ASTEngine.RelationshipType.Imports,
636
+ 1,
637
+ {
638
+ cleanSpecific: true,
639
+ module: it.module,
640
+ itemKind: it.kind,
641
+ isInstanceImport: it.kind === "instance",
642
+ isClassImport: it.kind === "class",
643
+ // ★ THE STDLIB CLAIM, emitted where the evidence exists (2026-10-28). The target of an
644
+ // import here is `${module}:${item}` (`StdEnv:map`) — a spelling no symbol can ever carry, so
645
+ // every one of the lane's 1,917 import edges read unresolved or unverifiable. The fleet's
646
+ // `moduleNamespace` claim is the mechanism for exactly this shape (python's `pathlib:Path`),
647
+ // and its evidence must be a CLOSED list, not a guess: `CLEAN_STD_ENV_NAMES` is the lane's own
648
+ // stdlib module set, measured, and the arbiter verifies the MODULE against the project's module
649
+ // space — so a project that DOES declare `StdEnv` REFUTES the claim. That direction is the one
650
+ // this lane must watch: `extFAIL` may not rise, and the arm is what says so.
651
+ // THE STDLIB CLAIM ONLY, AND A NON-STDLIB MODULE CARRIES NO CLAIM AT THIS SITE.
652
+ //
653
+ // ANY OTHER module (`Data.Error`, a corpus project's own `Clyde.Cocoa.tabview`) may be a
654
+ // project module or a dependency, and THE PROJECT'S FILE LIST DECIDES WHICH — but THIS SITE
655
+ // CANNOT SEE THAT LIST, so it must not state the claim. It used to: a flat
656
+ // `[Module/Sub.icl, Module/Sub.dcl]` was attached to every non-stdlib module, and the arm
657
+ // REFUTED **1,177 of the 1,615** claims it made (`extFAIL 1177`), because a Clean module's
658
+ // file is found on a SEARCH PATH (`Clyde` puts module `IdeState` at
659
+ // `cleantools/BatchBuild/IdeState.icl`, eastwood puts `Diagnostic` at
660
+ // `src/sharedUtil/Diagnostic.icl`) and the root-relative arithmetic below is only one of the
661
+ // spellings a loader tries. A file claim that a run can refute is a FALSE statement, not a
662
+ // conservative one: `fileClaimForTarget` refuses to emit an unconfirmed candidate for
663
+ // exactly this reason. PASS 3 owns the claim instead — it has `context.projectRoot`, it
664
+ // runs the shared guard over the importing file's own directory AND the project root, and
665
+ // it attaches a claim only when a candidate is CONFIRMED on disk (see the block there).
666
+ ...CLEAN_STD_ENV_NAMES.has(it.module) ? { isExternal: true, externalReason: "module-namespace", externalSpecifier: it.module } : {}
667
+ },
668
+ it.line
669
+ );
670
+ }
671
+ for (const modNode of moduleNodes) {
672
+ if (!modNode) continue;
673
+ const modName = this.assembleModuleName(modNode);
674
+ const mine = items.filter((it) => it.module === modName);
675
+ const seenItem = /* @__PURE__ */ new Set();
676
+ const unique = mine.filter((it) => {
677
+ if (seenItem.has(it.name)) return false;
678
+ seenItem.add(it.name);
679
+ return true;
680
+ });
681
+ ctx.imports.push({
682
+ module: modName,
683
+ source: modName,
684
+ items: unique.map((it) => it.name),
685
+ isTypeOnly: false,
686
+ range: this.nodeToRange(n),
687
+ metadata: {
688
+ cleanSpecific: true,
689
+ isQualified,
690
+ alias,
691
+ isSelective: mine.length > 0,
692
+ itemKinds: mine.map((it) => it.kind),
693
+ // Which imported names are opened as a SUBSET (`::T(..)`,
694
+ // `::T{field}`). Recorded because the subset marker is not a name
695
+ // and so has no edge of its own, and "this import binds all of T's
696
+ // constructors" is a fact the product can use.
697
+ openItems: unique.filter((it) => it.opens).map((it) => `${it.name}${it.opens === "record" ? "{}" : "(..)"}`)
698
+ }
699
+ });
700
+ this.pushRel(
701
+ ctx,
702
+ ctx.currentModule || "",
703
+ modName,
704
+ ASTEngine.RelationshipType.References,
705
+ 1,
706
+ { cleanSpecific: true, isImportDependency: true },
707
+ this.lineOf(modNode)
708
+ );
709
+ if (modName.indexOf("StdEnv") !== -1) ctx.patterns["Standard Environment"] = true;
710
+ if (mine.length === 0) {
711
+ const key = `${edgeSource}|${modName}:*`;
712
+ if (!importEdges.has(key)) {
713
+ importEdges.add(key);
714
+ this.pushRel(
715
+ ctx,
716
+ edgeSource,
717
+ `${modName}:*`,
718
+ ASTEngine.RelationshipType.Imports,
719
+ 1,
720
+ {
721
+ cleanSpecific: true,
722
+ module: modName,
723
+ isWildcard: true,
724
+ // THE WILDCARD FORM NEEDS THE SAME CLAIM AS THE ITEM FORM, and it was the gap: `import StdEnv`
725
+ // publishes `StdEnv:*`, and this site carried NO claim at all, so 52 `StdEnv:*` + 23 `StdString:*`
726
+ // rows read unresolved while the identical module on the item path was verified. MEASURED: the
727
+ // claim existed at ONE of the two emission sites (`grep -c module-namespace` = 1) and the
728
+ // unclaimed remainder was dominated by `StdEnv:*` (52), `StdString` (23), `StdBool` (21) — every
729
+ // one of them a module already in the closed `CLEAN_STD_ENV_NAMES` set.
730
+ // A NON-STDLIB module carries no file claim here — see the item site above for the 1,177
731
+ // refutations that measured why, and PASS 3 for where the claim is made instead.
732
+ ...CLEAN_STD_ENV_NAMES.has(modName) ? { isExternal: true, externalReason: "module-namespace", externalSpecifier: modName } : {}
733
+ },
734
+ this.lineOf(modNode)
735
+ );
736
+ }
737
+ }
738
+ }
739
+ }
740
+ }
741
+ }
742
+ /**
743
+ * The item list of one import declaration, each item tagged by the keyword that
744
+ * introduces it.
745
+ *
746
+ * All four keyword forms exist in the corpus and each names a DIFFERENT thing,
747
+ * so the tag is load-bearing rather than cosmetic:
748
+ * `from M import ::NSApplication, sharedApplication` → `::` = public-only
749
+ * `from M import instance toString {#Char}` → an INSTANCE
750
+ * `from M import class C a` → a CLASS
751
+ * `import M => qualified join` → a QUALIFIED name
752
+ * `from M import ::GeneralSt{..}` → an open record
753
+ */
754
+ importItems(node, moduleNodes) {
755
+ const out = [];
756
+ let currentModule = moduleNodes.length > 0 && moduleNodes[0] ? this.assembleModuleName(moduleNodes[0]) : "";
757
+ let keyword = "name";
758
+ for (let i = 0; i < node.childCount; i++) {
759
+ const c = node.child(i);
760
+ if (!c) continue;
761
+ if (!c.isNamed) {
762
+ if (c.type === "instance") keyword = "instance";
763
+ else if (c.type === "class") keyword = "class";
764
+ else if (c.type === "qualified") keyword = "qualified";
765
+ else if (c.type === "::") keyword = "name";
766
+ continue;
767
+ }
768
+ let isModuleField = false;
769
+ for (let k = 0; k < node.childCount; k++) {
770
+ const field = node.fieldNameForChild(k);
771
+ if (field !== "module" && field !== "alias") continue;
772
+ const fc = node.child(k);
773
+ if (fc && fc.startIndex === c.startIndex) {
774
+ isModuleField = true;
775
+ break;
776
+ }
777
+ }
778
+ if (isModuleField) {
779
+ if (c.type === "module_name" && moduleNodes.some((m) => m.startIndex === c.startIndex)) {
780
+ currentModule = this.assembleModuleName(c);
781
+ }
782
+ continue;
783
+ }
784
+ if (this.isOpenSubsetMarker(c)) {
785
+ if (out.length > 0) {
786
+ const last = out[out.length - 1];
787
+ last.opens = c.type === "record_subset" ? "record" : "constructor";
788
+ }
789
+ continue;
790
+ }
791
+ for (const described of this.describeImportItems(c, keyword)) {
792
+ out.push({ ...described, module: currentModule });
793
+ }
794
+ }
795
+ return out;
796
+ }
797
+ /**
798
+ * Is this import-item child an OPEN-SUBSET MARKER rather than a name?
799
+ *
800
+ * `constructor_subset`, `record_subset`, and the `parenthesized_operator` the
801
+ * grammar types for a `(..)` list — every one of which QUALIFIES the name in
802
+ * front of it. All three are real corpus node types (7, 11 and — as the `(..)`
803
+ * spelling — the bulk of the 512 `parenthesized_operator` sites).
804
+ */
805
+ isOpenSubsetMarker(node) {
806
+ if (node.type === "record_subset") return true;
807
+ if (node.type === "constructor_subset") {
808
+ return this.firstNamedChildOfType(node, "constructor") === null;
809
+ }
810
+ if (node.type === "parenthesized_operator") return this.isDotsOnly(this.unparenthesize(node.text) ?? node.text);
811
+ return false;
812
+ }
813
+ /** Is every character a dot (and is there at least one)? `(..)` → true, `(==)` → false. */
814
+ isDotsOnly(text) {
815
+ const t = text.trim();
816
+ if (!t) return false;
817
+ for (const ch of t) if (ch !== ".") return false;
818
+ return true;
819
+ }
820
+ /** Name + kind for one import-item node, or null when it names nothing. */
821
+ describeImportItems(node, keyword) {
822
+ const line = this.lineOf(node);
823
+ if (!line) return [];
824
+ const kind = keyword === "name" ? "name" : keyword;
825
+ switch (node.type) {
826
+ case "identifier":
827
+ case "constructor":
828
+ return node.text ? [{ name: node.text, kind, line }] : [];
829
+ case "constructor_subset": {
830
+ const out = [];
831
+ for (let i = 0; i < node.namedChildCount; i++) {
832
+ const inner = node.namedChild(i);
833
+ if (!inner || inner.type !== "constructor" || !inner.text) continue;
834
+ out.push({ name: inner.text, kind, line });
835
+ }
836
+ return out;
837
+ }
838
+ case "class_method_name": {
839
+ const out = [];
840
+ for (let i = 0; i < node.namedChildCount; i++) {
841
+ const inner = node.namedChild(i);
842
+ if (!inner || !inner.text) continue;
843
+ out.push({ name: inner.text, kind, line });
844
+ }
845
+ return out.length ? out : node.text ? [{ name: node.text, kind, line }] : [];
846
+ }
847
+ case "backtick_operator": {
848
+ const bare = this.backtickOperatorName(node.text);
849
+ return bare ? [{ name: bare, kind, line }] : [];
850
+ }
851
+ default:
852
+ if (OPERATOR_LEAF_TYPES.has(node.type)) {
853
+ const name = node.text.trim();
854
+ return name ? [{ name, kind, line }] : [];
855
+ }
856
+ return [];
857
+ }
858
+ }
859
+ /** `` `on` `` → `on`. A character filter, not a regex; an empty result is not a name. */
860
+ backtickOperatorName(text) {
861
+ let out = "";
862
+ for (const ch of text) if (ch !== "`") out += ch;
863
+ out = out.trim();
864
+ return out || null;
865
+ }
866
+ /**
867
+ * THE NAME-COLLECTION WALK. One traversal builds every table the emitting walk
868
+ * needs: the module name, the imports and their item lists, the type signatures
869
+ * keyed by declaration identity, the declared type / class / constructor /
870
+ * field / callable name sets, and the lexical local scopes.
871
+ *
872
+ * It was four separate traversals (imports, signatures, declared names, local
873
+ * scopes) plus a fifth for the module header, so the analyzer visited the
874
+ * corpus's 331,079 nodes five times before emitting anything. All five need the
875
+ * same two pieces of context — the enclosing FRAME identity and the enclosing
876
+ * OWNER — so they share one walk and one tracker and the traversal count drops
877
+ * from six (five names + one emit) to two.
878
+ *
879
+ * The tracker is the same rule the emitting walk uses, in the same order, so the
880
+ * `declarationKey` a signature is stored under is the key the emitting walk
881
+ * looks it up with. Both call `declarationKey`; neither spells the identity.
882
+ */
883
+ collectNames(root, ctx) {
884
+ const importEdges = /* @__PURE__ */ new Set();
885
+ const visit = (n, frameId, frame, owner) => {
886
+ let nextFrameId = frameId;
887
+ let nextFrame = frame;
888
+ let nextOwner = owner;
889
+ switch (n.type) {
890
+ case "module_declaration": {
891
+ const nameNode = n.childForFieldName("name");
892
+ if (nameNode) {
893
+ ctx.currentModule = this.assembleModuleName(nameNode);
894
+ nextOwner = ctx.currentModule;
895
+ }
896
+ break;
897
+ }
898
+ case "import_declaration":
899
+ this.collectImport(n, ctx, ctx.currentModule ?? "", importEdges);
900
+ break;
901
+ case "foreign_code_import": {
902
+ ctx.foreignCodeImports.count++;
903
+ for (let i = 0; i < n.namedChildCount; i++) {
904
+ const item = n.namedChild(i);
905
+ if (!item || item.type !== "foreign_code_item") continue;
906
+ const obj = item.childForFieldName("object");
907
+ const name = obj ? unquoteText(obj.text) : void 0;
908
+ if (name && ctx.foreignCodeImports.objects.length < 25 && !ctx.foreignCodeImports.objects.includes(name)) {
909
+ ctx.foreignCodeImports.objects.push(name);
910
+ }
911
+ }
912
+ break;
913
+ }
914
+ case "type_definition": {
915
+ const nm = n.childForFieldName("name");
916
+ if (nm) {
917
+ ctx.declaredTypeNames.add(nm.text);
918
+ ctx.declaredTypeDefNames.add(nm.text);
919
+ }
920
+ const body = n.childForFieldName("body");
921
+ if (body) this.addTypeMention(body.childForFieldName("rhs") ?? body, ctx);
922
+ break;
923
+ }
924
+ case "class_declaration": {
925
+ const nm = n.childForFieldName("name");
926
+ if (nm) {
927
+ ctx.declaredTypeNames.add(nm.text);
928
+ ctx.declaredClassNames.add(nm.text);
929
+ const members = /* @__PURE__ */ new Set();
930
+ for (let i = 0; i < n.namedChildCount; i++) {
931
+ const m = n.namedChild(i);
932
+ if (m?.type !== "class_member") continue;
933
+ const declared = this.memberDeclaredName(m);
934
+ if (declared) members.add(declared);
935
+ }
936
+ if (members.size > 0) {
937
+ ctx.classMembers.set(nm.text, members);
938
+ const bare = this.unparenthesize(nm.text);
939
+ if (bare !== null && bare !== nm.text) ctx.classMembers.set(bare, members);
940
+ }
941
+ }
942
+ nextOwner = nm?.text ?? nextOwner;
943
+ break;
944
+ }
945
+ case "generic_declaration": {
946
+ const nm = n.childForFieldName("name");
947
+ if (nm) {
948
+ ctx.declaredTypeNames.add(nm.text);
949
+ ctx.declaredClassNames.add(nm.text);
950
+ }
951
+ nextOwner = nm?.text ?? nextOwner;
952
+ break;
953
+ }
954
+ case "instance_declaration":
955
+ for (const a of this.fields(n, "argument")) this.addTypeMention(a, ctx);
956
+ break;
957
+ case "generic_case_definition": {
958
+ const nm = n.childForFieldName("name");
959
+ if (nm) ctx.declaredFunctions.add(nm.text);
960
+ break;
961
+ }
962
+ case "macro_definition": {
963
+ const name = this.normalizeParenthesized(n.childForFieldName("name"));
964
+ if (name) {
965
+ ctx.declaredFunctions.add(name);
966
+ ctx.declaredTypeNames.add(name);
967
+ }
968
+ break;
969
+ }
970
+ case "foreign_export": {
971
+ const nm = n.childForFieldName("name");
972
+ if (nm) ctx.declaredFunctions.add(nm.text);
973
+ break;
974
+ }
975
+ case "data_constructor": {
976
+ const nm = n.childForFieldName("name");
977
+ if (nm) {
978
+ ctx.declaredConstructors.add(nm.text);
979
+ ctx.declaredTypeNames.add(nm.text);
980
+ }
981
+ break;
982
+ }
983
+ case "record_field": {
984
+ const nm = n.childForFieldName("name");
985
+ if (nm) {
986
+ ctx.declaredFieldNames.add(nm.text);
987
+ ctx.declaredTypeNames.add(nm.text);
988
+ }
989
+ const t = n.childForFieldName("type");
990
+ if (t) this.addTypeMention(t, ctx);
991
+ break;
992
+ }
993
+ case "special_member": {
994
+ const t = n.childForFieldName("type");
995
+ if (t) this.addTypeMention(t, ctx);
996
+ break;
997
+ }
998
+ case "type_signature": {
999
+ const name = this.declarationName(n);
1000
+ if (name) {
1001
+ ctx.declaredFunctions.add(name);
1002
+ const key = declarationKey(frame, owner, name);
1003
+ let info = ctx.signaturesByNode.get(n.id);
1004
+ if (!info) {
1005
+ info = this.readSignature(n, ctx);
1006
+ ctx.signaturesByNode.set(n.id, info);
1007
+ if (!ctx.signatures.has(key)) ctx.signatures.set(key, info);
1008
+ }
1009
+ }
1010
+ break;
1011
+ }
1012
+ case "where_block":
1013
+ case "with_block":
1014
+ case "let_expression": {
1015
+ const map = this.localBlockLocals(n, ctx.localScopes, frame);
1016
+ if (map && map.size > 0) {
1017
+ ctx.localScopes.blockLocals.set(n.id, map);
1018
+ if (frameId >= 0) {
1019
+ const fm = ctx.localScopes.frameLocals.get(frameId) ?? /* @__PURE__ */ new Map();
1020
+ for (const [k, v] of map) fm.set(k, v);
1021
+ ctx.localScopes.frameLocals.set(frameId, fm);
1022
+ }
1023
+ }
1024
+ nextOwner = nextFrame;
1025
+ break;
1026
+ }
1027
+ default:
1028
+ break;
1029
+ }
1030
+ const callableName = this.callableDeclaredName(n);
1031
+ if (callableName !== void 0) {
1032
+ if (n.type !== "type_definition") ctx.declaredFunctions.add(callableName);
1033
+ const scoped = nextFrame !== void 0 ? `${nextFrame}.${callableName}` : callableName;
1034
+ ctx.localScopes.callableScoped.set(n.id, scoped);
1035
+ nextFrameId = n.id;
1036
+ nextFrame = scoped;
1037
+ }
1038
+ for (let i = 0; i < n.namedChildCount; i++) {
1039
+ const c = n.namedChild(i);
1040
+ if (c) visit(c, nextFrameId, nextFrame, nextOwner);
1041
+ }
1042
+ };
1043
+ visit(root, -1, void 0, void 0);
1044
+ }
1045
+ /** The binders a where/with/let block declares, as name -> scoped identity. */
1046
+ localBlockLocals(block, out, frameScoped) {
1047
+ let map = null;
1048
+ const consider = (defNode) => {
1049
+ const nm = this.callableDeclaredName(defNode);
1050
+ if (!nm) return;
1051
+ const scoped = frameScoped !== void 0 ? `${frameScoped}.${nm}` : nm;
1052
+ out.callableScoped.set(defNode.id, scoped);
1053
+ if (!map) map = /* @__PURE__ */ new Map();
1054
+ map.set(nm, scoped);
1055
+ };
1056
+ for (let i = 0; i < block.namedChildCount; i++) {
1057
+ const c = block.namedChild(i);
1058
+ if (!c) continue;
1059
+ if (c.type === "local_binding" || c.type === "record_binding") {
1060
+ const inner = c.namedChild(0);
1061
+ if (inner) consider(inner);
1062
+ continue;
1063
+ }
1064
+ if (c.type === "let_group") {
1065
+ for (const pat of this.fields(c, "pattern")) consider(pat);
1066
+ continue;
1067
+ }
1068
+ consider(c);
1069
+ }
1070
+ return map;
1071
+ }
1072
+ /** Read one `type_signature` into a `SignatureInfo`. */
1073
+ readSignature(node, ctx) {
1074
+ const info = {
1075
+ node,
1076
+ paramTypes: [],
1077
+ genericVars: /* @__PURE__ */ new Set(),
1078
+ typeRefs: [],
1079
+ contextClasses: [],
1080
+ specialTypes: [],
1081
+ hasCodeBody: false
1082
+ };
1083
+ const typeNode = node.childForFieldName("type");
1084
+ if (typeNode) this.readFunctionType(typeNode, info, ctx);
1085
+ const context = node.childForFieldName("context");
1086
+ if (context) this.readClassContext(context, info);
1087
+ const special = this.findChild(node, (n) => n.type === "special_block");
1088
+ if (special) {
1089
+ for (let i = 0; i < special.namedChildCount; i++) {
1090
+ const m = special.namedChild(i);
1091
+ if (!m || m.type !== "special_member") continue;
1092
+ const t = m.childForFieldName("type");
1093
+ if (t) for (const ref of this.typeRefsIn(t, ctx)) info.specialTypes.push(ref.name);
1094
+ }
1095
+ }
1096
+ if (node.childForFieldName("body")) info.hasCodeBody = true;
1097
+ const fixity = node.childForFieldName("fixity");
1098
+ if (fixity) {
1099
+ info.fixity = {
1100
+ associativity: fixity.namedChild(0)?.text,
1101
+ precedence: fixity.namedChild(1)?.text
1102
+ };
1103
+ }
1104
+ return info;
1105
+ }
1106
+ /**
1107
+ * `[domain, arrow, result]` from a `type_function`, or a bare type when the
1108
+ * signature is not a function type at all (`f :: Int`).
1109
+ */
1110
+ readFunctionType(typeNode, info, ctx) {
1111
+ if (typeNode.type !== "type_function") {
1112
+ info.returnType = typeNode.text;
1113
+ this.collectTypeRefs(typeNode, info, ctx, /* @__PURE__ */ new Set());
1114
+ return;
1115
+ }
1116
+ const domain = typeNode.child(0);
1117
+ const result = typeNode.child(2);
1118
+ if (result) {
1119
+ info.returnType = result.text;
1120
+ this.collectTypeRefs(result, info, ctx, /* @__PURE__ */ new Set());
1121
+ }
1122
+ if (!domain) return;
1123
+ if (domain.type === "type_application") {
1124
+ for (let i = 0; i < domain.namedChildCount; i++) {
1125
+ const arg = domain.namedChild(i);
1126
+ if (!arg) continue;
1127
+ info.paramTypes.push(arg.text);
1128
+ this.collectTypeRefs(arg, info, ctx, /* @__PURE__ */ new Set());
1129
+ }
1130
+ } else {
1131
+ info.paramTypes.push(domain.text);
1132
+ this.collectTypeRefs(domain, info, ctx, /* @__PURE__ */ new Set());
1133
+ }
1134
+ }
1135
+ /**
1136
+ * The class names a `|` context constrains on, split at the `,` and `&` CHILD
1137
+ * nodes. Measured: `class_context` is `|` followed by a flat constraint list —
1138
+ * `| Enum, Ord, Num a` is ONE node with three named children and two `,`
1139
+ * tokens — so the split is a child walk, not a text split.
1140
+ *
1141
+ * **`&` is a second spelling of `,` and was not read.** The two forms produce
1142
+ * the SAME child shape with a different separator token, so this is the
1143
+ * two-spellings-one-handler case, not two constructs:
1144
+ *
1145
+ * | == a -> !| operator_compare type_variable
1146
+ * | == a & UList a -> !| operator_compare type_variable !& constructor type_variable
1147
+ *
1148
+ * (`clean-stdlib/StdOverloadedList.dcl:5,8`.) Reading only `,` published the
1149
+ * first conjunct and dropped the second. Measured over the corpus
1150
+ * (`validation/m14/fixtures/clean/ampersand-context-probe.mts`): 1,995
1151
+ * `class_context` nodes at WIDE carry only the three unnamed child types `|`,
1152
+ * `,` and `&`; 365 contain an `&`; and the conjuncts a `,`-only reader dropped
1153
+ * split **249 that would ADD a row** against **264 that would name a class
1154
+ * already published on that same line** — `| toReal a & toReal b & toReal c`
1155
+ * (Clyde/Clyde/timeprofile.icl:337) is the shape, and at TRACKED the split is
1156
+ * 62 against 47.
1157
+ *
1158
+ * The 264 are NOT a doubling, and the reason is `pushRel`'s own key rather than
1159
+ * a judgement made here: it collapses on `source|type|target|line`, so a repeated
1160
+ * conjunct cannot produce a second identical edge on one line. Measured as an A/B
1161
+ * on the TRACKED population, reverting only the `&` in this line
1162
+ * (`validation/m14/fixtures/clean/references-duplicate-probe.mts`):
1163
+ *
1164
+ * references edges distinct (source,target,line) groups with >1
1165
+ * `,` only 10,049 9,570 387
1166
+ * `,` and `&` 10,108 9,629 387
1167
+ *
1168
+ * Both columns of the third are identical, per discriminator, so the fix adds 59
1169
+ * edges and 59 NEW groups and changes the duplicate count by zero. The previous
1170
+ * round declined to ship this fix precisely because it could not show that, and
1171
+ * named the duplicate-vs-multiplicity question as the blocker; the question is
1172
+ * answered by a key that was already in the file. `249` is the WIDE count of rows
1173
+ * this adds, not a measured edge delta — WIDE is not re-measurable on this
1174
+ * machine, so only the TRACKED `+59` is a measurement of the output.
1175
+ *
1176
+ * `&&` is NOT handled: it is 0 of the 1,995 contexts, and handling an unobserved
1177
+ * token would be a claim no measurement supports.
1178
+ *
1179
+ * A constraint that names no class publishes nothing and emits no edge: 5 of
1180
+ * the 474 contexts in the corpus are `*` (a `uniqueness_star` leaf — the
1181
+ * unboxedness constraint `| * , toInt a`), and 469 name a class that does.
1182
+ */
1183
+ readClassContext(context, info) {
1184
+ let expectName = true;
1185
+ for (let i = 0; i < context.childCount; i++) {
1186
+ const c = context.child(i);
1187
+ if (!c) continue;
1188
+ if (!c.isNamed) {
1189
+ if (c.type === "," || c.type === "&") expectName = true;
1190
+ continue;
1191
+ }
1192
+ if (!expectName) continue;
1193
+ expectName = false;
1194
+ const name = this.constraintName(c);
1195
+ if (name) info.contextClasses.push(name);
1196
+ }
1197
+ }
1198
+ /**
1199
+ * The class a single constraint names, or null when it names none. The
1200
+ * constraint node is a class name (`constructor` / `identifier`) or an operator
1201
+ * class (`operator_compare` for `==`, `operator_add` for `<+>`, …) — 287
1202
+ * constructors, 69 identifiers, 32 `operator_compare`, 10 `operator_add`, 2
1203
+ * `operator_mul`, 5 unnamed `*`.
1204
+ */
1205
+ constraintName(c) {
1206
+ if (c.type === "constructor" || c.type === "identifier") return c.text;
1207
+ if (OPERATOR_LEAF_TYPES.has(c.type)) return c.text;
1208
+ return null;
1209
+ }
1210
+ /**
1211
+ * Every TYPE NAME a type expression mentions, with type variables filtered out.
1212
+ * This is Clean's version of the TypeScript lane's `extractDeclaredTypeRefs`
1213
+ * (real-typescript-analyzer.ts:3144) and the single largest source of
1214
+ * relationships the lane did not have: 9,660 `strict_type` and 3,191
1215
+ * `type_application` sites over the corpus, every one of which names at least
1216
+ * one type.
1217
+ *
1218
+ * A `type_variable` is a BINDING, not a reference: `swap :: a a -> a` names no
1219
+ * type and binds two. That distinction is what makes `genericParameters`
1220
+ * correct and keeps `a` out of the reference graph.
1221
+ *
1222
+ * A module-qualified type is the one shape that needs context: `M.Map String
1223
+ * User` lexes as `constructor M`, `dotted_type .Map`, then the arguments, so
1224
+ * `M` is only a qualifier because it is a DECLARED IMPORT ALIAS of this file.
1225
+ * That is why `ctx` is in scope here: an alias set is a fact about the file,
1226
+ * and a qualifier is read from the alias set, not guessed from the spelling.
1227
+ */
1228
+ collectTypeRefs(n, info, ctx, bound) {
1229
+ switch (n.type) {
1230
+ case "type_variable": {
1231
+ const inner = n.namedChild(0);
1232
+ if (inner?.text) {
1233
+ bound.add(inner.text);
1234
+ info.genericVars.add(inner.text);
1235
+ }
1236
+ return;
1237
+ }
1238
+ case "unique_var_type": {
1239
+ for (let i = 0; i < n.namedChildCount; i++) {
1240
+ const c = n.namedChild(i);
1241
+ if (!c) continue;
1242
+ if (c.type === "type_variable") {
1243
+ const inner = c.namedChild(0);
1244
+ if (inner?.text) {
1245
+ bound.add(inner.text);
1246
+ info.genericVars.add(inner.text);
1247
+ }
1248
+ } else if (c.startIndex > n.startIndex) {
1249
+ this.collectTypeRefs(c, info, ctx, bound);
1250
+ }
1251
+ }
1252
+ return;
1253
+ }
1254
+ case "constructor": {
1255
+ this.addTypeRef(info, n.text, n);
1256
+ return;
1257
+ }
1258
+ case "single_quoted_name": {
1259
+ const target = this.qualifiedTarget(n.text);
1260
+ if (target) this.addTypeRef(info, target, n);
1261
+ return;
1262
+ }
1263
+ case "dotted_type": {
1264
+ for (let i = 0; i < n.namedChildCount; i++) {
1265
+ const c = n.namedChild(i);
1266
+ if (c) this.collectTypeRefs(c, info, ctx, bound);
1267
+ }
1268
+ return;
1269
+ }
1270
+ case "type_paren":
1271
+ case "strict_type":
1272
+ case "uniqueness_type":
1273
+ case "list_type":
1274
+ case "array_type":
1275
+ case "tuple_type":
1276
+ case "question_type":
1277
+ case "builtin_question_type":
1278
+ case "type_application": {
1279
+ const head = n.namedChild(0);
1280
+ if (head && head.type === "constructor" && ctx.importAliases.has(head.text)) {
1281
+ const second = n.namedChild(1);
1282
+ if (second) {
1283
+ const leafText = second.type === "dotted_type" ? stripLeadingDot(second.text) : second.text;
1284
+ if (leafText && second.type !== "type_variable") {
1285
+ this.addTypeRef(info, `${head.text}.${leafText}`, second);
1286
+ }
1287
+ for (let i = 2; i < n.namedChildCount; i++) {
1288
+ const c = n.namedChild(i);
1289
+ if (c) this.collectTypeRefs(c, info, ctx, bound);
1290
+ }
1291
+ return;
1292
+ }
1293
+ }
1294
+ for (let i = 0; i < n.namedChildCount; i++) {
1295
+ const c = n.namedChild(i);
1296
+ if (c) this.collectTypeRefs(c, info, ctx, bound);
1297
+ }
1298
+ return;
1299
+ }
1300
+ default: {
1301
+ for (let i = 0; i < n.namedChildCount; i++) {
1302
+ const c = n.namedChild(i);
1303
+ if (c) this.collectTypeRefs(c, info, ctx, bound);
1304
+ }
1305
+ return;
1306
+ }
1307
+ }
1308
+ }
1309
+ addTypeRef(info, name, node) {
1310
+ if (info.typeRefs.some((r) => r.name === name)) return;
1311
+ info.typeRefs.push({ name, node });
1312
+ }
1313
+ /**
1314
+ * Every bare type name a type expression mentions, with the node that spells
1315
+ * it. One reader (`collectTypeRefs`) and one rule, so the field path and the
1316
+ * signature path cannot drift — the drift is exactly what FIX B measured.
1317
+ */
1318
+ typeRefsIn(typeNode, ctx) {
1319
+ const info = {
1320
+ node: typeNode,
1321
+ paramTypes: [],
1322
+ genericVars: /* @__PURE__ */ new Set(),
1323
+ typeRefs: [],
1324
+ contextClasses: [],
1325
+ specialTypes: [],
1326
+ hasCodeBody: false
1327
+ };
1328
+ this.collectTypeRefs(typeNode, info, ctx, /* @__PURE__ */ new Set());
1329
+ return info.typeRefs;
1330
+ }
1331
+ /** Every constructor named anywhere inside a type expression. */
1332
+ addTypeMention(n, ctx) {
1333
+ if (n.type === "type_variable") return;
1334
+ if (n.type === "constructor") {
1335
+ ctx.declaredTypeNames.add(n.text);
1336
+ return;
1337
+ }
1338
+ if (n.type === "dotted_type") {
1339
+ const inner = n.namedChild(0);
1340
+ if (inner?.type === "constructor") ctx.declaredTypeNames.add(inner.text);
1341
+ return;
1342
+ }
1343
+ for (let i = 0; i < n.namedChildCount; i++) {
1344
+ const c = n.namedChild(i);
1345
+ if (c) this.addTypeMention(c, ctx);
1346
+ }
1347
+ }
1348
+ /** The declared name of a callable node, or undefined when it declares none. */
1349
+ callableDeclaredName(node) {
1350
+ const declared = this.rawCallableDeclaredName(node);
1351
+ return isReservedDeclarationName(declared) ? void 0 : declared;
1352
+ }
1353
+ rawCallableDeclaredName(node) {
1354
+ switch (node.type) {
1355
+ case "function_declaration":
1356
+ return this.declaredName(node)?.text ?? void 0;
1357
+ case "operator_definition":
1358
+ return this.operatorDefinitionName(node) ?? void 0;
1359
+ case "generic_case_definition":
1360
+ return node.childForFieldName("name")?.text ?? void 0;
1361
+ case "type_signature":
1362
+ return this.declarationName(node) ?? void 0;
1363
+ case "macro_definition":
1364
+ return this.normalizeParenthesized(node.childForFieldName("name")) ?? void 0;
1365
+ case "type_definition":
1366
+ return node.childForFieldName("name")?.text ?? void 0;
1367
+ default:
1368
+ return void 0;
1369
+ }
1370
+ }
1371
+ /**
1372
+ * A declaration the pinned grammar produced for TEXT THAT IS NOT A DECLARATION: its
1373
+ * declared name is a RESERVED WORD, re-lexed as an identifier by recovery (a `where`
1374
+ * opening a block in an already-damaged region becomes a top-level
1375
+ * `function_declaration` named `where`). The node is not a function — but its NAME is
1376
+ * a name the file writes at a declaration head, and F14's C1 (M60-C) requires every
1377
+ * declared name to be published somewhere in its file.
1378
+ *
1379
+ * THE ROW IS SURFACED, THE FRAME IS NOT. `visibility: "local"` keeps it out of the
1380
+ * export surface (`emitExports` skips local rows) and out of the module-qualified
1381
+ * identity (`qualifyModuleLevelSymbols` skips them), while `metadata.parseDamage`
1382
+ * says what the row is. Returning no `FrameSpec` is the load-bearing half: attributing
1383
+ * the applications BENEATH such a node to it is the `instantiates -> where -> XOffs`
1384
+ * fabrication the reserved-name decline existed for, and this change does not weaken
1385
+ * that rule — it only stops the NAME from being dropped along with the frame.
1386
+ *
1387
+ * Deliberately NOT indexed in `functionSymbolIndex`: a later real definition of the
1388
+ * same name must not merge into a damage row.
1389
+ */
1390
+ publishParseDamageSymbol(node, name, ctx, currentScope) {
1391
+ const isLocal = this.isLocalScope(currentScope);
1392
+ ctx.symbols.push({
1393
+ name,
1394
+ kind: this.callableKind(currentScope, isLocal),
1395
+ range: this.nodeToRange(node),
1396
+ scope: currentScope?.name,
1397
+ ownerName: isLocal ? this.localOwner(ctx) : currentScope?.name || ctx.currentModule,
1398
+ ownerKind: isLocal ? ASTEngine.SymbolKind.Function : currentScope?.kind,
1399
+ visibility: "local",
1400
+ metadata: { cleanSpecific: true, parseDamage: true, isReservedDeclarationName: true }
1401
+ });
1402
+ }
1403
+ /**
1404
+ * The row a callable DEFINITION may merge into, or `undefined` when it must publish
1405
+ * one of its own.
1406
+ *
1407
+ * `declarationKey` is shared on purpose by the three merges that are real —
1408
+ * signature↔definition, clause↔clause, `foreign export`↔definition — so the KEY
1409
+ * cannot be what discriminates them; the KIND can. A MACRO is a different entity
1410
+ * from a callable definition even when the two spell the same name.
1411
+ *
1412
+ * MEASURED (F14 C1, `Clyde/cleantools/Pm/PmParse.icl`): the file defines the macro
1413
+ * `Special c :== …` at 1222, and the pinned grammar lifts a case alternative to a
1414
+ * top-level `function_declaration` named `Special` at 1333. The keyed merge treated
1415
+ * the second as a second clause of the macro and published only the macro row, so
1416
+ * the declaration's name was never published at all. That is a RULE defect — a
1417
+ * `function_declaration` may not merge into a macro row — not a name to suppress.
1418
+ */
1419
+ callableMergeIndex(key, ctx) {
1420
+ const idx = ctx.functionSymbolIndex[key];
1421
+ if (idx === void 0) return void 0;
1422
+ return ctx.symbols[idx]?.kind === ASTEngine.SymbolKind.Macro ? void 0 : idx;
1423
+ }
1424
+ // ============================================================================
1425
+ // PASS 2 — declaration handlers
1426
+ // ============================================================================
1427
+ enterModule(node, ctx, scopeStack) {
1428
+ const nameNode = node.childForFieldName("name");
1429
+ const name = nameNode ? this.assembleModuleName(nameNode) : ctx.currentModule;
1430
+ if (!name) return false;
1431
+ ctx.currentModule = name;
1432
+ let moduleKind = "module";
1433
+ const first = node.child(0);
1434
+ if (first && !first.isNamed && (first.type === "definition" || first.type === "implementation")) {
1435
+ moduleKind = first.type;
1436
+ }
1437
+ ctx.symbols.push({
1438
+ name,
1439
+ kind: ASTEngine.SymbolKind.Module,
1440
+ range: this.nodeToRange(node),
1441
+ visibility: "public",
1442
+ metadata: { cleanSpecific: true, moduleKind }
1443
+ });
1444
+ scopeStack.push({ node, name, kind: ASTEngine.SymbolKind.Module });
1445
+ return true;
1446
+ }
1447
+ /**
1448
+ * A type declaration. The pinned grammar has ONE node type for all six Clean
1449
+ * type forms, distinguished by the `type_definition_body.rhs` node type:
1450
+ * synonym (`:==`), abstract (no rhs), record (`= {…}`), ADT (`= C | D`),
1451
+ * enumerated (`= A | B`) and quantified ADT (`= E a | C !a`). Measured on the
1452
+ * 503 `type_definition_body` sites: 255 `data_constructors`, 114
1453
+ * `record_definition`, 49 `type_application`, 3 `tuple_type`, 6 `array_type`,
1454
+ * 3 `list_type`, 1 `type_paren`, 1 `unit_type` and 71 bare `constructor`
1455
+ * (synonym / enumerated).
1456
+ */
1457
+ /**
1458
+ * Is this `type_definition` the PARSER'S RECOVERY OF A SIGNATURE rather than a
1459
+ * declaration? A Clean signature such as `widByPtr :: !WID -> Bool` is not a
1460
+ * `type_signature` to the pinned grammar: it comes back as a `type_definition` with no
1461
+ * `body`, no `rhs`, a `name` field holding the FIRST ARGUMENT TYPE, and text that stops
1462
+ * there (`:: !WID`). Publishing that row puts a fabricated `type` in the symbol set and
1463
+ * makes every edge that mentions the argument type point at it.
1464
+ *
1465
+ * MEASURED over the 2,179-file WIDE manifest (`validation/m14/measurements/clean/
1466
+ * type-definition-recovery-2179.txt`, regenerable with
1467
+ * `npx tsx .tmp-probe/tdef-rule.mts`), production wasm, node shapes only:
1468
+ *
1469
+ * type_definition with a body or rhs 3,930
1470
+ * with NEITHER 447 (10.21% of all type_definitions)
1471
+ *
1472
+ * Of the 447, 74 carry a uniqueness marker (`!`, `#`) or an `ERROR` as the child
1473
+ * immediately after the `::`, and reading all 74 sites shows every one of them is a
1474
+ * signature. The other 373 are real declarations the rule must not touch: 293 `:: ErrPrefs`
1475
+ * (an abstract type in a `.dcl`), 32 `:: Conversions a`, 25 `:: *TTY` (an abstract unique
1476
+ * class) and 23 with a trailing `;` or a body the grammar filed under `uniqueness_type`.
1477
+ *
1478
+ * 74 IS NOT THE WITHDRAWABLE SET, and the reason is the trap this function exists to
1479
+ * avoid. `eastwood/test/suite-default/GoToModule1.dcl:89` is `:: !*AbstractTypeSynonym`
1480
+ * and `:92` is `:: !*EastwoodAbstractNewType` — two REAL declarations whose child shape
1481
+ * `[:: ! ERROR constructor]` is byte-identical to the phantom at
1482
+ * `Libraries/Hilde/FamkeConcurrentClean.dcl:8` (`:: !(*World`). No node-INTERNAL rule
1483
+ * separates them; every child, field and name is the same. So the discriminator is
1484
+ * POSITIONAL and uses node positions only: a signature's `::` follows the callable's name
1485
+ * on the same row, a definition's `::` begins its own row. Measured on the same
1486
+ * population, that withdraws 72 and keeps exactly those 2.
1487
+ *
1488
+ * NO TEXT AND NO REGEX. The test is whether the nearest preceding NAMED sibling ENDS on
1489
+ * this node's start row, so no string can be mistaken for a construct and no file-path
1490
+ * convention can stand in for one.
1491
+ */
1492
+ isSignatureRecoveryTypeDefinition(node) {
1493
+ if (node.childForFieldName("body") || node.childForFieldName("rhs")) return false;
1494
+ let afterColonColon = "";
1495
+ for (let i = 0; i < node.childCount; i++) {
1496
+ const c = node.child(i);
1497
+ if (!c || c.type !== "::") continue;
1498
+ const next = node.child(i + 1);
1499
+ if (next) afterColonColon = next.type;
1500
+ break;
1501
+ }
1502
+ if (afterColonColon !== "!" && afterColonColon !== "#" && afterColonColon !== "ERROR") return false;
1503
+ const parent = node.parent;
1504
+ if (!parent) return false;
1505
+ let index = -1;
1506
+ for (let i = 0; i < parent.childCount; i++) {
1507
+ const c = parent.child(i);
1508
+ if (c && c.id === node.id) {
1509
+ index = i;
1510
+ break;
1511
+ }
1512
+ }
1513
+ if (index < 0) return false;
1514
+ for (let i = index - 1; i >= 0; i--) {
1515
+ const sibling = parent.child(i);
1516
+ if (!sibling || !sibling.isNamed) continue;
1517
+ return sibling.endPosition.row === node.startPosition.row;
1518
+ }
1519
+ return false;
1520
+ }
1521
+ processTypeDefinition(node, ctx, scopeStack) {
1522
+ if (this.isSignatureRecoveryTypeDefinition(node)) return false;
1523
+ const nameNode = node.childForFieldName("name");
1524
+ if (!nameNode?.text) return false;
1525
+ const name = nameNode.text;
1526
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : void 0;
1527
+ const body = node.childForFieldName("body");
1528
+ const rhs = body ? body.childForFieldName("rhs") ?? body : node.childForFieldName("rhs");
1529
+ const form = this.typeForm(rhs);
1530
+ const genericParameters = [];
1531
+ for (const p of this.fields(node, "parameter")) {
1532
+ const inner = p.namedChild(0);
1533
+ if (inner?.text) genericParameters.push(inner.text);
1534
+ }
1535
+ if (body) {
1536
+ for (const e of this.fields(body, "existential")) {
1537
+ const inner = e.namedChild(0);
1538
+ if (inner?.text) genericParameters.push(`!${inner.text}`);
1539
+ }
1540
+ }
1541
+ ctx.symbols.push({
1542
+ name,
1543
+ kind: ASTEngine.SymbolKind.Type,
1544
+ range: this.nodeToRange(node),
1545
+ scope: currentScope?.name,
1546
+ ownerName: currentScope?.name || ctx.currentModule,
1547
+ ownerKind: currentScope?.kind,
1548
+ visibility: "public",
1549
+ genericParameters: genericParameters.length > 0 ? genericParameters : void 0,
1550
+ metadata: { cleanSpecific: true, typeForm: form }
1551
+ });
1552
+ this.attachComments(ctx.symbols[ctx.symbols.length - 1], node);
1553
+ if (rhs && rhs.type !== "data_constructors" && rhs.type !== "record_definition") {
1554
+ this.emitTypeRefs(rhs, name, "type_alias_rhs", ctx);
1555
+ }
1556
+ for (const cNode of this.dataConstructorsOf(rhs)) {
1557
+ const cName = cNode.childForFieldName("name");
1558
+ if (!cName?.text) continue;
1559
+ ctx.symbols.push({
1560
+ name: cName.text,
1561
+ kind: ASTEngine.SymbolKind.Constructor,
1562
+ range: this.nodeToRange(cNode),
1563
+ scope: name,
1564
+ ownerName: name,
1565
+ ownerKind: ASTEngine.SymbolKind.Type,
1566
+ visibility: "public",
1567
+ metadata: { cleanSpecific: true, isADTConstructor: true }
1568
+ });
1569
+ this.attachComments(ctx.symbols[ctx.symbols.length - 1], cNode);
1570
+ for (const arg of this.fields(cNode, "argument")) {
1571
+ this.emitTypeRefs(arg, cName.text, "data_constructor_arg", ctx);
1572
+ }
1573
+ }
1574
+ const recordDef = rhs && rhs.type === "record_definition" ? rhs : body ? this.findChild(body, (n) => n.type === "record_definition") : null;
1575
+ if (recordDef) {
1576
+ for (let i = 0; i < recordDef.namedChildCount; i++) {
1577
+ const rf = recordDef.namedChild(i);
1578
+ if (!rf || rf.type !== "record_field") continue;
1579
+ const fieldId = rf.childForFieldName("name");
1580
+ if (!fieldId?.text) continue;
1581
+ ctx.symbols.push({
1582
+ name: fieldId.text,
1583
+ kind: ASTEngine.SymbolKind.Property,
1584
+ range: this.nodeToRange(rf),
1585
+ scope: name,
1586
+ ownerName: name,
1587
+ ownerKind: ASTEngine.SymbolKind.Type,
1588
+ visibility: "public",
1589
+ metadata: { cleanSpecific: true, isRecordField: true }
1590
+ });
1591
+ this.attachComments(ctx.symbols[ctx.symbols.length - 1], rf);
1592
+ const ft = rf.childForFieldName("type");
1593
+ if (ft) this.emitTypeRefs(ft, fieldId.text, "record_field_type", ctx);
1594
+ }
1595
+ }
1596
+ scopeStack.push({ node, name, kind: ASTEngine.SymbolKind.Type });
1597
+ return true;
1598
+ }
1599
+ /** The `data_constructor` children of a type's rhs, however it is nested. */
1600
+ dataConstructorsOf(rhs) {
1601
+ if (!rhs) return [];
1602
+ if (rhs.type === "data_constructors") return this.filterChildren(rhs, (n) => n.type === "data_constructor");
1603
+ return this.filterChildren(rhs, (n) => n.type === "data_constructor");
1604
+ }
1605
+ /** Which of Clean's six type forms a `rhs` node type denotes. */
1606
+ typeForm(rhs) {
1607
+ if (!rhs) return "abstract";
1608
+ switch (rhs.type) {
1609
+ case "data_constructors":
1610
+ return "adt";
1611
+ case "record_definition":
1612
+ return "record";
1613
+ case "constructor":
1614
+ return "enumerated";
1615
+ default:
1616
+ return "synonym";
1617
+ }
1618
+ }
1619
+ /**
1620
+ * A class declaration: `class C a | D a where …`.
1621
+ *
1622
+ * Two things the old version did not do. (1) It read the name with
1623
+ * `findChild(node, n => n.type === "class_name")` instead of the `name` field;
1624
+ * the field is exact and the scan is a guess. (2) It published members by
1625
+ * scanning `class_member` children for a `type_signature` and then `continue`d
1626
+ * on every `type_signature` in the walk — so a class member's `special` block
1627
+ * (90 sites) and inline `code` body (74 sites) were never visited, and a class
1628
+ * member that is a `function_declaration` rather than a signature published
1629
+ * nothing. Members now run through the same handlers as everything else, with
1630
+ * the class on the scope stack.
1631
+ */
1632
+ processClass(node, ctx, scopeStack) {
1633
+ const nameNode = node.childForFieldName("name");
1634
+ if (!nameNode?.text) return false;
1635
+ const name = nameNode.text;
1636
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : void 0;
1637
+ const genericParameters = [];
1638
+ for (const p of this.fields(node, "parameter")) {
1639
+ const inner = p.namedChild(0);
1640
+ if (inner?.text) genericParameters.push(inner.text);
1641
+ }
1642
+ ctx.symbols.push({
1643
+ name,
1644
+ kind: ASTEngine.SymbolKind.Interface,
1645
+ range: this.nodeToRange(node),
1646
+ scope: currentScope?.name,
1647
+ ownerName: currentScope?.name || ctx.currentModule,
1648
+ ownerKind: currentScope?.kind,
1649
+ visibility: "public",
1650
+ genericParameters: genericParameters.length > 0 ? genericParameters : void 0,
1651
+ metadata: { cleanSpecific: true, isTypeClass: true }
1652
+ });
1653
+ this.attachComments(ctx.symbols[ctx.symbols.length - 1], node);
1654
+ const context = node.childForFieldName("context");
1655
+ if (context) this.emitContextRefs(context, name, "class_context", ctx);
1656
+ if (node.childForFieldName("type")) ctx.patterns["Algebraic Type Classes"] = true;
1657
+ ctx.patterns["Type Classes"] = true;
1658
+ scopeStack.push({ node, name, kind: ASTEngine.SymbolKind.Interface });
1659
+ return true;
1660
+ }
1661
+ /**
1662
+ * An instance declaration: `instance C T where …`.
1663
+ *
1664
+ * THE 253-DEFECT. The old handler required a direct `constructor` child to name
1665
+ * the target type. Measured over the corpus the `argument` FIELD is 782
1666
+ * occurrences on 741 instances, and only 489 are a bare `constructor`; the other
1667
+ * 293 are `array_type` 127 (`instance Array {} a`), `list_type` 97
1668
+ * (`instance toString [a]`), `type_variable` 32, `type_paren` 16,
1669
+ * `question_type` 9, `tuple_type` 8 and `unit_type` 4. On those 253
1670
+ * declarations (51 files) the scan found nothing and the instance was skipped
1671
+ * whole — no Class symbol, no `implements` edge, no member scope, and every
1672
+ * `instance_member` inside it (1,009 members corpus-wide) left ownerless or
1673
+ * attributed to the module. The field is exact.
1674
+ *
1675
+ * Reading the field is not sufficient on its own, though: the argument list is
1676
+ * `constraint* Type`, so the TYPE is the LAST argument that names one, and a
1677
+ * bare uniqueness constraint names none. Taking the first argument named every
1678
+ * constrained instance after its constraint — `instance Array {#} Int` as
1679
+ * `{#} -> Array` — which is most of Clean's own StdEnv. See
1680
+ * `instanceTargetName`.
1681
+ */
1682
+ processInstance(node, ctx, scopeStack) {
1683
+ const classNode = node.childForFieldName("name");
1684
+ if (!classNode?.text) return false;
1685
+ const className = classNode.text;
1686
+ const args = this.fields(node, "argument");
1687
+ const typeArgs = args.filter((a) => this.instanceTargetName(a) !== null);
1688
+ const targetNames = [];
1689
+ for (const a of args) {
1690
+ const t = this.instanceTargetName(a);
1691
+ if (t) targetNames.push(t);
1692
+ }
1693
+ const targetType = typeArgs.length > 0 ? this.instanceTargetName(typeArgs[typeArgs.length - 1]) : null;
1694
+ if (!targetType) return false;
1695
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : void 0;
1696
+ const targetIsPolymorphic = typeArgs[typeArgs.length - 1]?.type === "type_variable";
1697
+ if (!targetIsPolymorphic) {
1698
+ ctx.symbols.push({
1699
+ // THE IMPLEMENTATION IS NAMESPACED UNDER ITS TARGET. It used to be named EXACTLY the
1700
+ // target type, and on a per-file analyzer that is a PHANTOM DECLARATION: a file whose
1701
+ // only statement about `Int` is `instance Show Int` published a `class` symbol named
1702
+ // `Int`, which counts as a declaration of `Int` in the project's name space and therefore
1703
+ // turns every `references -> Int` edge elsewhere into an ambiguity. Measured on the
1704
+ // 312-file population (`validation/m14/fixtures/clean/phantom-census.mts`): **699 such
1705
+ // containers in 94 files, 93 of them named after a type the SAME file also declares for
1706
+ // real, 150 of them not an identifier at all** (`.a`, `[a]`, `?` — instance heads that
1707
+ // are type EXPRESSIONS, so the name could never match any reference in the first place).
1708
+ // Deleting them alone reaches **1,009 of the lane's 6,856 residual edges (14.7%)** —
1709
+ // 574 `references` whose target has no real declaration in the project at all, 366 whose
1710
+ // target has exactly one real declaration beside the phantoms (those RESOLVE once the
1711
+ // phantom stops counting), and 69 `exports` that were never exports.
1712
+ //
1713
+ // The naming is the RUST lane's (`impl Foo` publishes `Foo::impl`, so the implementation
1714
+ // is addressable without claiming Foo is declared here), and the reason is the same: an
1715
+ // instance is a DECLARATION OF AN IMPLEMENTATION, not of its target type. What made this
1716
+ // lane worse than Rust's is that Clean's instance heads are usually types from StdEnv
1717
+ // (`Int`, `Char`, `Real`, `Bool` — the five most common container names), so the phantom
1718
+ // landed on exactly the names the language uses most.
1719
+ //
1720
+ // THE MEMBERS KEEP THE NOMINAL OWNER (`frameOwner` below), which is what keeps this fix
1721
+ // surgical: `contains` still reads `Int -> show`, `overrides` still reads `Int.show ->
1722
+ // Show.show`, and the member's `qualifiedName` is unchanged — every observable edge
1723
+ // surface is identical to before, and the ONLY difference is that the file no longer
1724
+ // declares `Int`. Moving the owner as well would rename 2,000+ edges to fix a declaration
1725
+ // count, which is the wrong trade: the endpoint of a `contains` edge is the name a reader
1726
+ // navigates to, and `Int.show` is what Clean itself calls that member.
1727
+ name: `${targetType}${INSTANCE_CONTAINER_SUFFIX}`,
1728
+ // `impl`, not `class`: `class` is an EXPORTABLE kind, so the container became a module
1729
+ // export — `Prelude/Data/List.dcl` exported `[]` and `.a` because it holds instances whose
1730
+ // heads are those type expressions (69 measured `exports` rows, all of them naming a
1731
+ // phantom). An instance is not part of a module's interface.
1732
+ kind: ASTEngine.SymbolKind.Impl,
1733
+ range: this.nodeToRange(node),
1734
+ scope: currentScope?.name,
1735
+ ownerName: currentScope?.name || ctx.currentModule,
1736
+ ownerKind: currentScope?.kind,
1737
+ visibility: "public",
1738
+ metadata: {
1739
+ cleanSpecific: true,
1740
+ className,
1741
+ targetType,
1742
+ targetTypes: targetNames,
1743
+ // The instance's target as the SOURCE spells it (`List a`), kept beside the nameable
1744
+ // identity (`List`) so a reader can see which instance this is when one type has
1745
+ // several — the identity is what resolves, the spelling is what distinguishes.
1746
+ targetSpelling: typeArgs[typeArgs.length - 1]?.text,
1747
+ isTypeClassInstance: true,
1748
+ // The container's name is DERIVED (`<target>::instance`), so the target it was derived
1749
+ // from travels with it: a consumer reading `Int::instance` should not have to strip a
1750
+ // suffix to learn which type the instance is for, and the pair must not drift apart.
1751
+ instanceTarget: targetType
1752
+ }
1753
+ });
1754
+ this.attachComments(ctx.symbols[ctx.symbols.length - 1], node);
1755
+ }
1756
+ this.pushRel(
1757
+ ctx,
1758
+ targetType,
1759
+ className,
1760
+ ASTEngine.RelationshipType.Implements,
1761
+ 1,
1762
+ {
1763
+ cleanSpecific: true,
1764
+ isInstance: true,
1765
+ className,
1766
+ // Almost every class is imported (`instance Eq Int` names StdEnv's `Eq`),
1767
+ // so the flag records the normal case explicitly instead of leaving the
1768
+ // reader to infer it from a missing symbol.
1769
+ //
1770
+ // The second spelling is the operator case, and it is not cosmetic: a class
1771
+ // DECLARATION spells an operator-named class parenthesised (`class (==) infix 4 a ::`,
1772
+ // clean-stdlib/StdOverloaded.icl:23) while the INSTANCE spells it bare
1773
+ // (`instance == Bool`, clean-stdlib/StdBool.dcl:10). `declaredTypeNames` therefore
1774
+ // holds `(==)` and the lookup below held only `==`, so 24 `implements` edges on
1775
+ // the 312-file corpus carried `isExternalClass: true` for a class declared in the
1776
+ // SAME file — a false statement in the one field whose whole job is to say which
1777
+ // of the two it is. `classMembers` gets the same treatment where `overrides` reads it.
1778
+ isExternalClass: !ctx.declaredTypeNames.has(className) && !this.isLocallyDeclaredOperatorClass(ctx, className),
1779
+ // The instance's target is a type VARIABLE, so the instance is polymorphic:
1780
+ // it publishes no symbol (see above) and this flag says why the edge has no
1781
+ // symbol at its source.
1782
+ isPolymorphicInstance: targetIsPolymorphic || void 0
1783
+ },
1784
+ this.lineOf(classNode)
1785
+ );
1786
+ for (let i = 1; i < args.length; i++) this.emitTypeRefs(args[i], targetType, "instance_target", ctx);
1787
+ const context = node.childForFieldName("context");
1788
+ if (context) this.emitContextRefs(context, targetType, "instance_context", ctx);
1789
+ if (node.childForFieldName("type")) ctx.patterns["Qualified Instances"] = true;
1790
+ if (node.childForFieldName("body")) ctx.patterns["Inline Machine Code"] = true;
1791
+ ctx.patterns["Type Classes"] = true;
1792
+ const frameOwner = !targetIsPolymorphic ? targetType : ctx.declaredClassNames.has(className) ? className : ctx.currentModule ?? className;
1793
+ const classMem = ctx.classMembers.get(className);
1794
+ if (classMem && classMem.size > 0) {
1795
+ if (frameOwner !== className) {
1796
+ for (let i = 0; i < node.namedChildCount; i++) {
1797
+ const m = node.namedChild(i);
1798
+ if (m?.type !== "instance_member") continue;
1799
+ const memberName = this.memberDeclaredName(m);
1800
+ if (!memberName || !classMem.has(memberName)) continue;
1801
+ const nameNode = m.namedChild(0)?.childForFieldName("name") ?? m.namedChild(0);
1802
+ this.pushRel(
1803
+ ctx,
1804
+ `${frameOwner}.${memberName}`,
1805
+ `${className}.${memberName}`,
1806
+ ASTEngine.RelationshipType.Overrides,
1807
+ 1,
1808
+ { cleanSpecific: true, isTypeClassOverride: true, className, instanceType: frameOwner, member: memberName },
1809
+ this.lineOf(nameNode)
1810
+ );
1811
+ }
1812
+ }
1813
+ }
1814
+ scopeStack.push({
1815
+ node,
1816
+ name: frameOwner,
1817
+ kind: ASTEngine.SymbolKind.Class
1818
+ });
1819
+ return true;
1820
+ }
1821
+ /**
1822
+ * The TYPE an `argument` names, or null when the argument is a bare UNIQUENESS
1823
+ * CONSTRAINT rather than a type.
1824
+ *
1825
+ * Clean's instance head is `instance Class constraint* Type`, and the grammar puts
1826
+ * a constraint that applies to the WHOLE instance in the same `argument` field as
1827
+ * the type, so the argument list is not all types:
1828
+ *
1829
+ * instance Array {!} a args = [ array_type({!}), type_variable(a) ]
1830
+ * instance Array {#} Int args = [ array_type({#}), constructor(Int) ]
1831
+ * instance Array {#} {32#.a} args = [ array_type({#}), array_type({32#.a}) ]
1832
+ * instance == {#Char} args = [ array_type({#Char}) ] <- ONE arg, and
1833
+ * it IS the type
1834
+ *
1835
+ * Taking the FIRST argument named every constrained instance after its
1836
+ * CONSTRAINT: `instance Array {#} Int` became `{#} -> Array`, and
1837
+ * `instance Array {32#} Int` became `32 -> Array` (the inner child of `{32#}`).
1838
+ * That is the misnamed `implements` edge and instance symbol behind every
1839
+ * constrained instance — the shape Clean's own StdEnv uses most, since `Array`
1840
+ * alone has six. Taking the LAST argument fixes the head but still returns `32`
1841
+ * for `{32#.a}`, because an `array_type` may hold a constraint LEVEL and a TYPE
1842
+ * together.
1843
+ *
1844
+ * So the walk is STRUCTURAL: inside a `type_paren` / `array_type` /
1845
+ * `uniqueness_type`, take the first named child that is not a constraint marker.
1846
+ * `{!}` and `{#}` have no such child and are therefore constraints (null);
1847
+ * `{#Char}` and `{32#.a}` have a `dotted_type` and are therefore types. The
1848
+ * earlier version of this comment claimed the same thing via a text test on
1849
+ * `{#…}`; the node shape says it outright, so there is no text inspection left.
1850
+ */
1851
+ instanceTargetName(arg) {
1852
+ switch (arg.type) {
1853
+ case "constructor":
1854
+ case "type_function":
1855
+ case "dotted_type":
1856
+ return arg.text;
1857
+ // A LIST OR TUPLE TYPE'S NAME HAS NO SPACES IN IT. `instance length [ !]`
1858
+ // (clean-stdlib/StdOverloadedList.icl:142) is the strict list type constructor, and the
1859
+ // source's own spacing inside the brackets is layout, not spelling: the type `[ !]` and the
1860
+ // type `[!]` are the same type and Clean's own declaration writes `[!]`. Leaving the space in
1861
+ // gave the instance's members a `contains` source and an `implements` target that no symbol
1862
+ // can match. Measured: 3 edges on the 312-file population, and 0 after the normalization.
1863
+ case "tuple_type":
1864
+ case "list_type":
1865
+ return stripWhitespace(arg.text) || arg.text;
1866
+ case "type_application": {
1867
+ const head = arg.namedChild(0);
1868
+ if (!head) return arg.text;
1869
+ const headName = this.instanceTargetName(head);
1870
+ if (!headName) return arg.text;
1871
+ return this.unparenthesize(headName) ?? headName;
1872
+ }
1873
+ case "question_type": {
1874
+ const head = arg.namedChild(0);
1875
+ return head?.text || arg.text;
1876
+ }
1877
+ case "type_variable":
1878
+ return arg.namedChild(0)?.text ?? arg.text;
1879
+ case "type_paren":
1880
+ case "array_type":
1881
+ case "uniqueness_type":
1882
+ case "unique_var_type": {
1883
+ for (let i = 0; i < arg.namedChildCount; i++) {
1884
+ const inner = arg.namedChild(i);
1885
+ if (!inner || CONSTRAINT_MARKER_TYPES.has(inner.type)) continue;
1886
+ const name = this.instanceTargetName(inner);
1887
+ if (name) return name;
1888
+ }
1889
+ return null;
1890
+ }
1891
+ default:
1892
+ return arg.text;
1893
+ }
1894
+ }
1895
+ /**
1896
+ * A Clean generic: `generic bimap a b | bimap b a :: .a -> .b`.
1897
+ *
1898
+ * A generic is a class over TYPES rather than values — Clean's type-level
1899
+ * dispatch, the machinery behind the "HACK: dictionaries for all generics"
1900
+ * comment in `StdGeneric.dcl`. It is therefore an `Interface` with generic
1901
+ * parameters, and the `| bimap b a` context is a requirement on the generic
1902
+ * itself, emitted as a `references` edge like any other context. 11
1903
+ * declarations corpus-wide.
1904
+ */
1905
+ processGenericDeclaration(node, ctx, currentScope) {
1906
+ const nameNode = node.childForFieldName("name");
1907
+ if (!nameNode?.text) return;
1908
+ const name = nameNode.text;
1909
+ const genericParameters = [];
1910
+ for (const p of this.fields(node, "parameter")) {
1911
+ const inner = p.namedChild(0);
1912
+ if (inner?.text) genericParameters.push(inner.text);
1913
+ }
1914
+ const contextArgs = [];
1915
+ for (const p of this.fields(node, "context_arg")) {
1916
+ const inner = p.namedChild(0);
1917
+ if (inner?.text) contextArgs.push(inner.text);
1918
+ }
1919
+ const contextName = node.childForFieldName("context")?.text;
1920
+ ctx.symbols.push({
1921
+ name,
1922
+ kind: ASTEngine.SymbolKind.Interface,
1923
+ range: this.nodeToRange(node),
1924
+ scope: currentScope?.name,
1925
+ ownerName: currentScope?.name || ctx.currentModule,
1926
+ ownerKind: currentScope?.kind,
1927
+ visibility: "public",
1928
+ genericParameters: genericParameters.length > 0 ? genericParameters : void 0,
1929
+ metadata: {
1930
+ cleanSpecific: true,
1931
+ isGeneric: true,
1932
+ genericContext: contextName,
1933
+ genericContextArgs: contextArgs,
1934
+ genericType: this.unnamedChildOfType(node, "type_function")?.text
1935
+ }
1936
+ });
1937
+ this.attachComments(ctx.symbols[ctx.symbols.length - 1], node);
1938
+ if (contextName && contextName !== name) {
1939
+ this.pushRel(
1940
+ ctx,
1941
+ name,
1942
+ contextName,
1943
+ ASTEngine.RelationshipType.References,
1944
+ 0.95,
1945
+ { cleanSpecific: true, relationshipType: "generic_context" },
1946
+ this.lineOf(node.childForFieldName("context"))
1947
+ );
1948
+ }
1949
+ ctx.patterns["Generics"] = true;
1950
+ }
1951
+ /**
1952
+ * `derive bimap c` — the generic `bimap` is derived for the type `c`.
1953
+ *
1954
+ * The same shape as an instance: an implementation of a class at a type, so the
1955
+ * edge is `implements` from the derived type to the generic, with the generic's
1956
+ * own node as the name-bearing site line. 43 sites corpus-wide (all in
1957
+ * `StdGeneric.dcl`), and every one published nothing before: the
1958
+ * `derive_declaration` node type was named nowhere in the analyzer.
1959
+ */
1960
+ processDeriveDeclaration(node, ctx) {
1961
+ const nameNode = node.childForFieldName("name");
1962
+ if (!nameNode?.text) return;
1963
+ const genericName = nameNode.text;
1964
+ let target = null;
1965
+ for (let i = 0; i < node.namedChildCount; i++) {
1966
+ const c = node.namedChild(i);
1967
+ if (!c || c.startIndex === nameNode.startIndex) continue;
1968
+ if (!DERIVE_TARGET_NODE_TYPES.has(c.type)) continue;
1969
+ const spelled = c.type === "type_variable" ? c.namedChild(0)?.text ?? c.text : c.text;
1970
+ if (!spelled || isReservedDeclarationName(spelled)) continue;
1971
+ target = c;
1972
+ break;
1973
+ }
1974
+ if (!target) return;
1975
+ const targetName = target.type === "type_variable" ? target.namedChild(0)?.text ?? target.text : target.text;
1976
+ if (!targetName) return;
1977
+ this.pushRel(
1978
+ ctx,
1979
+ targetName,
1980
+ genericName,
1981
+ ASTEngine.RelationshipType.Implements,
1982
+ 1,
1983
+ { cleanSpecific: true, isDerive: true, genericName },
1984
+ this.lineOf(nameNode)
1985
+ );
1986
+ ctx.patterns["Generics"] = true;
1987
+ }
1988
+ /**
1989
+ * A callable definition: `function_declaration`, `operator_definition` or
1990
+ * `generic_case_definition`.
1991
+ *
1992
+ * The old code handled the first two and nothing else.
1993
+ * `generic_case_definition` (52 sites) is the third shape Clean uses to define a
1994
+ * callable — `bimap{|PAIR|} fx _ fy _ (PAIR x y) = …` — and the six `bimap{|…|}`
1995
+ * case equations are ONE function, so they merge into one symbol with the case
1996
+ * kinds recorded, exactly as multi-clause definitions merge.
1997
+ */
1998
+ /**
1999
+ * The owner of a `where`/`with`/`let` binder.
2000
+ *
2001
+ * Normally the enclosing FRAME, which is what makes a local's identity scoped and
2002
+ * its `contains` edge resolvable. But a local can be reached with no frame at all
2003
+ * when the pinned grammar could not read the function it belongs to: a
2004
+ * tuple-destructuring `where` binding — `(transition_nr, transition) =
2005
+ * SelectTransition 0 head …` — is not in the grammar, so it becomes an ERROR node
2006
+ * and everything after it inside that function sits inside the ERROR subtree,
2007
+ * detached from the `function_declaration` that would have opened the frame.
2008
+ *
2009
+ * Measured on the 2,179-file corpus: 7 such locals — `left` and `right` in
2010
+ * `ObjectIO/…/turing/tm.icl`, `key` and `keyIndex` in `Ed/EdKeyMapping.icl`,
2011
+ * `toolBoxFun` and `okWindowKeyboardAtt` in `ObjectIO/…/keyboardevent.icl`, and
2012
+ * `sp` in `Charlie.icl` — each found by the honesty gate as a symbol carrying an
2013
+ * `ownerKind` with no `ownerName`.
2014
+ *
2015
+ * Falling back to the MODULE is the honest answer rather than a fudge: the binder
2016
+ * really is declared in this file, and the module is the nearest owner that
2017
+ * EXISTS. A symbol with no owner resolves to nothing, and the file-level count
2018
+ * records that the enclosing function was lost to damage instead of pretending
2019
+ * the local is module-level by design.
2020
+ */
2021
+ localOwner(ctx) {
2022
+ if (ctx.currentFrame !== void 0) return ctx.currentFrame;
2023
+ if (ctx.currentModule !== void 0) ctx.detachedLocals++;
2024
+ return ctx.currentModule;
2025
+ }
2026
+ processCallableNode(node, ctx, currentScope, inErrorRegion = false) {
2027
+ const declared = this.rawCallableDeclaredName(node);
2028
+ if (!declared) return null;
2029
+ if (isReservedDeclarationName(declared)) {
2030
+ this.publishParseDamageSymbol(node, declared, ctx, currentScope);
2031
+ return null;
2032
+ }
2033
+ const name = declared;
2034
+ const isOperator = node.type === "operator_definition" || node.type === "generic_case_definition";
2035
+ const isLocal = this.isLocalScope(currentScope);
2036
+ const kind = this.callableKind(currentScope, isLocal);
2037
+ const key = declarationKey(ctx.currentFrame, this.ownerIdentity(currentScope, ctx), name);
2038
+ const existingIdx = this.callableMergeIndex(key, ctx);
2039
+ if (existingIdx !== void 0) {
2040
+ const existing = ctx.symbols[existingIdx];
2041
+ const fromDeclaration = existing.metadata?.isDeclaration === true;
2042
+ if (fromDeclaration || !existing.range) {
2043
+ existing.range = this.nodeToRange(node);
2044
+ } else {
2045
+ const r = this.nodeToRange(node);
2046
+ const prevEnd = existing.range.endLine ?? r.startLine;
2047
+ const prevEndIndex = existing.range.endIndex ?? node.startIndex;
2048
+ existing.range = {
2049
+ ...existing.range,
2050
+ endLine: Math.max(prevEnd, r.endLine),
2051
+ endColumn: r.endLine > prevEnd ? r.endColumn : existing.range.endColumn ?? r.endColumn,
2052
+ endIndex: Math.max(prevEndIndex, r.endIndex ?? prevEndIndex)
2053
+ };
2054
+ }
2055
+ delete existing.metadata?.isDeclaration;
2056
+ if (existing.isAbstract) existing.isAbstract = false;
2057
+ this.mergeComments(existing, node);
2058
+ const md2 = existing.metadata ?? (existing.metadata = {});
2059
+ if (isOperator && !md2.isOperatorDefinition) md2.isOperatorDefinition = true;
2060
+ if (node.type === "generic_case_definition") this.addCaseKind(md2, node);
2061
+ this.joinParameters(existing, node, ctx, this.ownerIdentity(currentScope, ctx));
2062
+ return { name, kind, isLocal, key };
2063
+ }
2064
+ const md = { cleanSpecific: true };
2065
+ if (isOperator) md.isOperatorDefinition = true;
2066
+ const scopedName = isLocal ? ctx.currentFrame === void 0 ? name : `${ctx.currentFrame}.${name}` : void 0;
2067
+ if (scopedName) {
2068
+ md.isLocalDefinition = true;
2069
+ md.scopedName = scopedName;
2070
+ }
2071
+ if (node.type === "generic_case_definition") this.addCaseKind(md, node);
2072
+ const sym = {
2073
+ name,
2074
+ kind,
2075
+ range: this.nodeToRange(node),
2076
+ scope: currentScope?.name,
2077
+ // The store's `qualified_name`, which is what relationship endpoints are
2078
+ // resolved against. A module-level symbol needs none: its name is already
2079
+ // unique in the file, and the store falls back to `name`.
2080
+ ...scopedName ? { qualifiedName: scopedName } : {},
2081
+ ownerName: isLocal ? this.localOwner(ctx) : currentScope?.name || ctx.currentModule,
2082
+ ownerKind: isLocal ? ASTEngine.SymbolKind.Function : currentScope?.kind,
2083
+ // A callable reached only through an ERROR region has no knowable scope, so it
2084
+ // is not part of the module's API and must not claim to be exported.
2085
+ visibility: isLocal || inErrorRegion ? "local" : "public",
2086
+ complexity: 1,
2087
+ metadata: md
2088
+ };
2089
+ ctx.symbols.push(sym);
2090
+ ctx.functionSymbolIndex[key] = ctx.symbols.length - 1;
2091
+ this.joinParameters(sym, node, ctx, this.ownerIdentity(currentScope, ctx));
2092
+ return { name, kind, isLocal, key };
2093
+ }
2094
+ addCaseKind(md, node) {
2095
+ const kind = node.childForFieldName("kind")?.text;
2096
+ if (!kind) return;
2097
+ const kinds = md.caseKinds ?? (md.caseKinds = []);
2098
+ if (!kinds.includes(kind)) kinds.push(kind);
2099
+ }
2100
+ /**
2101
+ * A module-level macro: `NSWindowCloseButton :== 0`, `(FWI) f i :== fwritei i f`.
2102
+ *
2103
+ * 583 sites corpus-wide with the analyzer naming `macro_definition` nowhere, so
2104
+ * every one published nothing AND every application inside a macro body
2105
+ * attributed to whatever frame happened to be open. A macro is a
2106
+ * `SymbolKind.Macro` (the kind exists for exactly this: Rust `macro_rules!`) and
2107
+ * it is a frame, because a macro body is expanded at its use site and the calls
2108
+ * in it are the module's calls.
2109
+ */
2110
+ processMacro(node, ctx, currentScope) {
2111
+ const name = this.normalizeParenthesized(node.childForFieldName("name"));
2112
+ if (!name) return null;
2113
+ if (isReservedDeclarationName(name)) return null;
2114
+ const isLocal = this.isLocalScope(currentScope);
2115
+ const key = declarationKey(ctx.currentFrame, this.ownerIdentity(currentScope, ctx), name);
2116
+ if (ctx.functionSymbolIndex[key] === void 0) {
2117
+ const macroSym = {
2118
+ name,
2119
+ kind: ASTEngine.SymbolKind.Macro,
2120
+ range: this.nodeToRange(node),
2121
+ scope: currentScope?.name,
2122
+ ownerName: isLocal ? this.localOwner(ctx) : currentScope?.name || ctx.currentModule,
2123
+ ownerKind: isLocal ? ASTEngine.SymbolKind.Function : currentScope?.kind,
2124
+ visibility: isLocal ? "local" : "public",
2125
+ qualifiedName: isLocal ? ctx.currentFrame === void 0 ? name : `${ctx.currentFrame}.${name}` : void 0,
2126
+ metadata: {
2127
+ cleanSpecific: true,
2128
+ isMacroDefinition: true,
2129
+ isLocalDefinition: isLocal || void 0,
2130
+ scopedName: isLocal ? ctx.currentFrame === void 0 ? name : `${ctx.currentFrame}.${name}` : void 0
2131
+ }
2132
+ };
2133
+ this.attachComments(macroSym, node);
2134
+ ctx.symbols.push(macroSym);
2135
+ ctx.functionSymbolIndex[key] = ctx.symbols.length - 1;
2136
+ }
2137
+ ctx.patterns["Macros"] = true;
2138
+ return { name, kind: ASTEngine.SymbolKind.Macro, isLocal, key };
2139
+ }
2140
+ /**
2141
+ * `foreign export f` — a C function re-exported under Clean's calling
2142
+ * convention. 41 sites corpus-wide, published nothing before. It is a real
2143
+ * callable and an explicit export, so it is a `Function` symbol flagged
2144
+ * `isForeignExport` and it enters the module's export surface.
2145
+ */
2146
+ processForeignExport(node, ctx, currentScope) {
2147
+ const nameNode = node.childForFieldName("name");
2148
+ if (!nameNode?.text) return;
2149
+ const name = nameNode.text;
2150
+ ctx.patterns["Foreign Function Interface"] = true;
2151
+ const line = this.lineOf(node);
2152
+ const key = declarationKey(ctx.currentFrame, currentScope?.name, name);
2153
+ const existingIdx = ctx.functionSymbolIndex[key];
2154
+ if (existingIdx !== void 0) {
2155
+ const existing = ctx.symbols[existingIdx];
2156
+ const md = existing.metadata ?? (existing.metadata = {});
2157
+ md.isForeignExport = true;
2158
+ if (typeof md.foreignExportLine !== "number") md.foreignExportLine = line;
2159
+ return;
2160
+ }
2161
+ ctx.symbols.push({
2162
+ name,
2163
+ kind: ASTEngine.SymbolKind.Function,
2164
+ range: this.nodeToRange(node),
2165
+ scope: currentScope?.name,
2166
+ ownerName: currentScope?.name || ctx.currentModule,
2167
+ ownerKind: currentScope?.kind,
2168
+ visibility: "public",
2169
+ metadata: { cleanSpecific: true, isForeignExport: true, foreignExportLine: line, isDeclaration: true }
2170
+ });
2171
+ this.attachComments(ctx.symbols[ctx.symbols.length - 1], node);
2172
+ ctx.functionSymbolIndex[key] = ctx.symbols.length - 1;
2173
+ }
2174
+ /**
2175
+ * `($) infixr 9` — an operator fixity declaration. 17 sites corpus-wide,
2176
+ * published nothing before. A fixity names an OPERATOR that is declared
2177
+ * elsewhere (usually a macro or an `operator_definition`), so it emits no symbol
2178
+ * of its own: the fixity is attached to that operator's symbol by
2179
+ * `attachFixities`, and the record is kept on the file so a fixity on an
2180
+ * operator this file does not define stays visible instead of being dropped.
2181
+ */
2182
+ processFixityDeclaration(node, ctx) {
2183
+ const opNode = node.childForFieldName("operator");
2184
+ if (!opNode) return;
2185
+ const name = this.normalizeParenthesized(opNode);
2186
+ if (!name) return;
2187
+ ctx.patterns["Operator Fixity Declarations"] = true;
2188
+ ctx.fixities.push({
2189
+ name,
2190
+ associativity: node.childForFieldName("associativity")?.text,
2191
+ precedence: node.childForFieldName("precedence")?.text,
2192
+ line: this.lineOf(node)
2193
+ });
2194
+ }
2195
+ /**
2196
+ * THE MODULE-QUALIFIED IDENTITY OF EVERY MODULE-LEVEL SYMBOL — `StdPathname.Pathname`, `PmTypes.Project`.
2197
+ *
2198
+ * A Clean module is DECLARED in `.dcl` and IMPLEMENTED in `.icl`, so a type, function, class or macro at
2199
+ * module scope is ONE declaration written in TWO files. The incoming gate models that with
2200
+ * `--binding-collapse`: a key becomes ONE declaration iff every declaration answering to it carries the
2201
+ * same NON-EMPTY `qualifiedName` — and the gate requires that name to DIFFER from the bare name, because
2202
+ * `pool-worker.ts` falls back to `qualifiedName = name` for a file-scope symbol and a fallback is not a
2203
+ * qualification. This analyzer published NO qualifiedName for module-level symbols ("its name is already
2204
+ * unique in the file"), so every module-level `.dcl`/`.icl` pair reached the gate as TWO declarations and
2205
+ * the pair was counted as a name collision.
2206
+ *
2207
+ * MEASURED on the 312-file arm (2026-10-28, whole population): 1,808 `references` ambiguous, and 797 of
2208
+ * them name a module-level pair like this — `Pathname` 143 (`StdPathname.dcl` + `.icl`), `Modulename` 46,
2209
+ * `Project` 34, `StaticLibInfo` 32, `CompilerOptions` 30, `UtilStrictLists` 27 — plus 28 of the 40
2210
+ * ambiguous `exports` rows (operator `&&`, `:!`, `!!|` written in both halves). The lane already ruled the
2211
+ * pair ONE declaration for class/instance MEMBERS, where the store's `Owner.member` spelling made the
2212
+ * collapse fire (`overrides` 2/306 -> 306/306, 719 keys collapsed); the same ruling was simply not
2213
+ * reachable for module scope, because there was no qualified spelling for it to fire on.
2214
+ *
2215
+ * WHAT IS NOT TOUCHED, and each exclusion is a fact about the symbol rather than a convenience:
2216
+ *
2217
+ * - a MODULE symbol (its name IS the module; there is nothing above it to qualify with), and it is the
2218
+ * module's own name, so `qualifiedName = name` would be the fallback the gate rightly ignores;
2219
+ * - a CLASS/INSTANCE MEMBER (`ownerName` is the class or the instance's target, not the module) — those
2220
+ * already carry `Owner.member`, which is what `overrides` and `implements` endpoints are spelled
2221
+ * against;
2222
+ * - an ADT CONSTRUCTOR or RECORD FIELD (`ownerName` is the type that declares it);
2223
+ * - a LOCAL (`visibility: "local"; it keeps its frame-scoped spelling and is never part of the module
2224
+ * interface).
2225
+ *
2226
+ * THE IDENTITY IS NOT INVENTED. Clean names a module-scope entity `Module.name` — that is the spelling a
2227
+ * consumer writes when two modules declare the same name, and it is what the module's own `module` header
2228
+ * plus the declaration produce. The bare `name` stays exactly as the source writes it, so every existing
2229
+ * endpoint, export and fixture pin is unchanged; this field is the QUALIFIED spelling beside it, which is
2230
+ * what the store resolves relationship endpoints against.
2231
+ */
2232
+ qualifyModuleLevelSymbols(ctx) {
2233
+ const module = ctx.currentModule;
2234
+ if (!module) return;
2235
+ for (const s of ctx.symbols) {
2236
+ if (s.qualifiedName) continue;
2237
+ if (s.kind === ASTEngine.SymbolKind.Module) continue;
2238
+ if (s.visibility === "local" || s.metadata?.isLocalDefinition) continue;
2239
+ if (s.ownerName !== module) continue;
2240
+ s.qualifiedName = `${module}.${s.name}`;
2241
+ }
2242
+ }
2243
+ /** Apply the collected fixities to the operator symbols they name. */
2244
+ attachFixities(ctx) {
2245
+ if (ctx.fixities.length === 0) return;
2246
+ for (const f of ctx.fixities) {
2247
+ for (const s of ctx.symbols) {
2248
+ if (s.name !== f.name) continue;
2249
+ s.metadata = s.metadata ?? {};
2250
+ s.metadata.fixity = { associativity: f.associativity, precedence: f.precedence };
2251
+ }
2252
+ }
2253
+ }
2254
+ /**
2255
+ * Evidence tiers on EVERY relationship, and the lane's own provenance bit.
2256
+ *
2257
+ * M14 section D requires at least two distinct `evidenceClass` values pinned by
2258
+ * fixtures, and the family vocabulary is fixed: `structural`,
2259
+ * `usage_contextual`, `import_based`, `reference_qualified`, `inferred` (the
2260
+ * typescript and haskell classifiers are the precedents). Clean uses only those
2261
+ * five, so the values mean what they mean in every other lane.
2262
+ *
2263
+ * | kind | tier | why |
2264
+ * | --- | --- | --- |
2265
+ * | `contains`, `exports` | `structural` | both endpoints are names this file publishes, and the store's own resolution proves it |
2266
+ * | `imports` | `import_based` | the source spells the module and the item |
2267
+ * | `instantiates`, `uses` | `structural` | in-file by construction — the target must be a `data_constructor`, record field or local this file declares, or no edge is emitted |
2268
+ * | `implements` | `structural` in-file, `reference_qualified` external | an `instance C T` is a structural fact when `C` is declared here; when `C` is imported the edge names a class by reference, which is 322 of 782 corpus sites |
2269
+ * | `references` | `reference_qualified` | the family rule: a type reference names its type |
2270
+ * | `calls` | `usage_contextual`, or `reference_qualified` for a source-spelled qualified target | a call is usage evidence; a call to `'Data.Error'.Ok` or `M.lookup` is identified by the spelling in the source, not by a same-file binding |
2271
+ *
2272
+ * `evidenceClass` answers "how do we know this edge"; it cannot answer "could
2273
+ * this file have known the target at all", which is Clean's actual signature
2274
+ * property and the one the honesty gate measures. So `targetIsInFile` is
2275
+ * emitted alongside: `true` when the target resolves to a name this file
2276
+ * publishes (bare, `Owner.member`, or the leaf after the first `.` — the
2277
+ * store's own three strategies), `false` when the target is external by nature.
2278
+ * 7,777 of 9,568 corpus `references` and 322 of 782 `implements` are `false`,
2279
+ * and a consumer that wants only provable edges can now say so without
2280
+ * re-deriving it.
2281
+ */
2282
+ classifyEvidenceTier(ctx) {
2283
+ const names = new Set(ctx.symbols.map((s) => s.name));
2284
+ const qNames = new Set(ctx.symbols.map((s) => s.qualifiedName).filter(Boolean));
2285
+ const resolves = (name) => {
2286
+ if (names.has(name) || qNames.has(name)) return true;
2287
+ const dot = name.indexOf(".");
2288
+ return dot > 0 && (names.has(name.slice(dot + 1)) || qNames.has(name.slice(dot + 1)));
2289
+ };
2290
+ for (const r of ctx.relationships) {
2291
+ const md = r.metadata ?? (r.metadata = {});
2292
+ const t = String(r.type);
2293
+ const target = String(r.target);
2294
+ md.targetIsInFile = resolves(target);
2295
+ if (md.evidenceClass) continue;
2296
+ switch (t) {
2297
+ case "contains":
2298
+ case "exports":
2299
+ case "instantiates":
2300
+ case "uses":
2301
+ md.evidenceClass = "structural";
2302
+ continue;
2303
+ case "imports":
2304
+ md.evidenceClass = "import_based";
2305
+ continue;
2306
+ case "references":
2307
+ md.evidenceClass = "reference_qualified";
2308
+ continue;
2309
+ case "implements":
2310
+ md.evidenceClass = md.targetIsInFile ? "structural" : "reference_qualified";
2311
+ continue;
2312
+ default:
2313
+ md.evidenceClass = target.includes(".") || target.includes("'") ? "reference_qualified" : "usage_contextual";
2314
+ }
2315
+ }
2316
+ }
2317
+ /**
2318
+ * MARK THE STANDARD ENVIRONMENT'S TYPE NAMES — one pass over the finished edge list, exactly as
2319
+ * kotlin's `markPlatformTargets` (`real-kotlin-analyzer.ts:411`) and go's stdlib marker do it, so
2320
+ * a new emission site cannot forget it and no pre-existing key is touched.
2321
+ *
2322
+ * THE PROBLEM IT ANSWERS, MEASURED ON THE WHOLE 312-FILE LANE. `unresolved` is 4,677 edges and
2323
+ * 2,540 of them are a BARE name: `String` 869, `World` 222, `Bool` 126, `Int` 90, `Real` 75,
2324
+ * `StdEnv` 56 … The declaration exists — in the Clean standard library, in another repository —
2325
+ * and this analyzer's own doctrine says why the edge is still emitted: "a `references` edge in a
2326
+ * typed position is a type reference whatever the type is" (this file's EDGE DISCIPLINE header).
2327
+ * What was missing was not the edge, it was the STATEMENT: 1,788 of those edges name a name the
2328
+ * standard environment declares, and the lane said nothing about it, so the arbiter counted every
2329
+ * one of them as a silent hole.
2330
+ *
2331
+ * THREE GUARDS, and each one is a fact about THIS file rather than a guess:
2332
+ *
2333
+ * 1. EDGE KIND. Only `references` and `implements` — the two edge types whose target IS a type
2334
+ * mention. `contains` / `exports` targets are this file's own declarations; `calls` targets
2335
+ * are callee leaves or the source-spelled qualified names the calls rule owns; `imports`
2336
+ * targets are `Module:item`, which cannot equal a bare name; `uses`, `overrides` and
2337
+ * `instantiates` carry 0 unresolved edges in the measured population (TABLE 1 of the report),
2338
+ * so marking them would be inert and is not done.
2339
+ * 2. THE CLOSED LIST. The target must be in `CLEAN_STD_ENV_NAMES`, which is DERIVED from the
2340
+ * corpus's own stdlib repositories (`clean-stdlib` + `Prelude`) — see that constant. A target
2341
+ * containing a dot is never in it, which matters: the arbiter refutes an `owner` claim whose
2342
+ * owner the project declares, and every dotted target has an owner segment that a project may
2343
+ * well publish (`System.CommandLine` in Clyde). Bare names only, so no claim this pass attaches
2344
+ * can be refuted, and `extFAIL` cannot move.
2345
+ * 3. THE FILE'S OWN DECLARATIONS VETO. `targetIsInFile` is computed by `classifyEvidenceTier`
2346
+ * over this file's symbol table and is already on every edge; where it is true the edge is a
2347
+ * same-file reference and there is nothing external to claim. Without this guard the claim
2348
+ * would be FALSE at the file's own scope, and the arbiter discloses exactly that as
2349
+ * `contradicted` in its claim-carrying-ambiguous block.
2350
+ *
2351
+ * NOTHING ELSE ABOUT AN EDGE IS TOUCHED: source, target, type, line and every pre-existing
2352
+ * metadata key are byte-identical, and the marker is read by the arbiter ONLY where the target
2353
+ * names nothing in the project's key space — so it can never move a resolved edge, can never
2354
+ * raise `resolved`, and never changes `collRes`.
2355
+ */
2356
+ markStandardEnvironmentTargets(ctx) {
2357
+ for (const r of ctx.relationships) {
2358
+ const t = String(r.type);
2359
+ if (t !== ASTEngine.RelationshipType.References && t !== ASTEngine.RelationshipType.Implements) continue;
2360
+ const target = r.target;
2361
+ if (typeof target !== "string" || target.length === 0) continue;
2362
+ if (!CLEAN_STD_ENV_NAMES.has(target)) continue;
2363
+ const md = r.metadata;
2364
+ if (!md || typeof md !== "object") continue;
2365
+ if (md.targetIsInFile === true) continue;
2366
+ if (md.isStdlib === true) continue;
2367
+ md.isStdlib = true;
2368
+ }
2369
+ }
2370
+ /**
2371
+ * A `type_signature` — Clean's declaration form, and where a function's
2372
+ * parameter types, result type, generic parameters, class context, special
2373
+ * types and inline code body all live. 3,611 of them published only a bare
2374
+ * Function symbol with no types at all before.
2375
+ *
2376
+ * The old handler published a symbol ONLY at module scope
2377
+ * (`scopeStack.length === 0`) and then `continue`d, which meant a class member's
2378
+ * signature got its Method from a separate scan inside `processClass`, an
2379
+ * instance member's signature published nothing at all (only its
2380
+ * `function_declaration` / `operator_definition` sibling did), and a where-local
2381
+ * signature was discarded. All three publish through this one handler now.
2382
+ */
2383
+ processTypeSignature(node, ctx, currentScope, inErrorRegion = false) {
2384
+ const name = this.declarationName(node);
2385
+ if (!name) return;
2386
+ const isLocal = this.isLocalScope(currentScope);
2387
+ const isMember = currentScope?.kind === ASTEngine.SymbolKind.Interface || currentScope?.kind === ASTEngine.SymbolKind.Class;
2388
+ const key = declarationKey(ctx.currentFrame, this.ownerIdentity(currentScope, ctx), name);
2389
+ const info = ctx.signaturesByNode.get(node.id) ?? ctx.signatures.get(key);
2390
+ const existingIdx = ctx.functionSymbolIndex[key];
2391
+ if (existingIdx !== void 0) {
2392
+ if (process.env.BB_CLEAN_DIAG) process.stderr.write(`DIAG apply L${this.lineOf(node)} sym=${ctx.symbols[existingIdx].name} key=${key} merge info=${info ? "Y" : "N"}
2393
+ `);
2394
+ if (info) this.applySignature(ctx.symbols[existingIdx], info, ctx, isLocal);
2395
+ return;
2396
+ }
2397
+ const kind = this.callableKind(currentScope, isLocal);
2398
+ const sym = {
2399
+ name,
2400
+ kind,
2401
+ range: this.nodeToRange(node),
2402
+ scope: currentScope?.name,
2403
+ ...isLocal ? { qualifiedName: ctx.currentFrame === void 0 ? name : `${ctx.currentFrame}.${name}` } : {},
2404
+ ownerName: isLocal ? this.localOwner(ctx) : currentScope?.name || ctx.currentModule,
2405
+ ownerKind: isLocal ? ASTEngine.SymbolKind.Function : currentScope?.kind,
2406
+ // A callable reached only through an ERROR region has no knowable scope, so it
2407
+ // is not part of the module's API and must not claim to be exported.
2408
+ visibility: isLocal || inErrorRegion ? "local" : "public",
2409
+ // A member whose only occurrence is a signature is an ABSTRACT member: the
2410
+ // class declares it and no instance in this file defines it.
2411
+ isAbstract: isMember ? true : void 0,
2412
+ metadata: {
2413
+ cleanSpecific: true,
2414
+ isDeclaration: true,
2415
+ isLocalDeclaration: isLocal || void 0
2416
+ }
2417
+ };
2418
+ this.attachComments(sym, node);
2419
+ ctx.symbols.push(sym);
2420
+ ctx.functionSymbolIndex[key] = ctx.symbols.length - 1;
2421
+ if (info) {
2422
+ if (process.env.BB_CLEAN_DIAG) process.stderr.write(`DIAG apply L${this.lineOf(info.node)} sym=${sym.name} key=${key} fresh
2423
+ `);
2424
+ this.applySignature(sym, info, ctx, isLocal);
2425
+ }
2426
+ }
2427
+ /** Copy a `SignatureInfo` onto the symbol it declares, and emit its edges. */
2428
+ applySignature(sym, info, ctx, isLocal) {
2429
+ const md = sym.metadata ?? (sym.metadata = {});
2430
+ if (info.returnType && !sym.returnType) sym.returnType = info.returnType;
2431
+ if (info.genericVars.size > 0) {
2432
+ const existing = sym.genericParameters ?? [];
2433
+ for (const g of info.genericVars) if (!existing.includes(g)) existing.push(g);
2434
+ sym.genericParameters = existing;
2435
+ }
2436
+ if (md.typeSignature === void 0) md.typeSignature = info.node.text;
2437
+ if (info.specialTypes.length > 0) {
2438
+ md.specialTypes = info.specialTypes;
2439
+ ctx.patterns["Specialised Members"] = true;
2440
+ }
2441
+ if (info.hasCodeBody) {
2442
+ md.hasInlineCodeBody = true;
2443
+ ctx.patterns["Inline Machine Code"] = true;
2444
+ }
2445
+ if (info.fixity) {
2446
+ md.fixity = info.fixity;
2447
+ ctx.patterns["Operator Fixity Declarations"] = true;
2448
+ ctx.fixities.push({ name: sym.name, ...info.fixity, line: this.lineOf(info.node) });
2449
+ }
2450
+ const source = isLocal ? md.scopedName ?? sym.name : sym.name;
2451
+ if (info.contextClasses.length > 0) {
2452
+ md.classContext = info.contextClasses;
2453
+ ctx.patterns["Class Contexts"] = true;
2454
+ const line = this.lineOf(info.node.childForFieldName("context"));
2455
+ for (const c of info.contextClasses) {
2456
+ this.pushRel(ctx, source, c, ASTEngine.RelationshipType.References, 0.95, {
2457
+ cleanSpecific: true,
2458
+ relationshipType: "class_context"
2459
+ }, line);
2460
+ }
2461
+ }
2462
+ for (const ref of info.typeRefs) {
2463
+ this.pushRel(ctx, source, ref.name, ASTEngine.RelationshipType.References, 0.95, {
2464
+ cleanSpecific: true,
2465
+ relationshipType: "declared_type"
2466
+ }, this.lineOf(ref.node));
2467
+ }
2468
+ md.paramTypes = info.paramTypes;
2469
+ }
2470
+ /**
2471
+ * The `special` block of a signature: `special a=Int; a=Char`.
2472
+ *
2473
+ * 104 blocks / 507 members corpus-wide, published nothing before. A `special`
2474
+ * member does not declare a new NAME — the variable is the enclosing
2475
+ * declaration's own type parameter (`_from :: a -> .[a] | Enum, Ord a` with
2476
+ * `special a=Int` specialises `_from`) — so it publishes no symbol. What it does
2477
+ * declare is a set of TYPES the enclosing callable is specialised for, and
2478
+ * those are real `references` edges.
2479
+ */
2480
+ processSpecialBlock(node, ctx) {
2481
+ const owner = this.relationshipSource(ctx) ?? this.enclosingDeclarationName(node);
2482
+ if (!owner) return;
2483
+ ctx.patterns["Specialised Members"] = true;
2484
+ for (let i = 0; i < node.namedChildCount; i++) {
2485
+ const m = node.namedChild(i);
2486
+ if (!m || m.type !== "special_member") continue;
2487
+ const t = m.childForFieldName("type");
2488
+ if (!t) continue;
2489
+ for (const ref of this.typeRefsIn(t, ctx)) {
2490
+ this.pushRel(ctx, owner, ref.name, ASTEngine.RelationshipType.References, 0.95, {
2491
+ cleanSpecific: true,
2492
+ relationshipType: "special_type"
2493
+ }, this.lineOf(ref.node));
2494
+ }
2495
+ }
2496
+ }
2497
+ /**
2498
+ * Join a `type_signature`'s parameter TYPES to the definition's parameter NAMES.
2499
+ *
2500
+ * This is the join that makes Clean parameter data real. A Clean signature
2501
+ * `comp3 :: !(.a -> .b) u:(.c -> .a) !(.d -> .c) -> u:(.d -> .b)` yields three
2502
+ * domain types; the definition `comp3 f g h` yields three names; pairing them
2503
+ * positionally is sound because a Clean function type's domain is a
2504
+ * left-nested `type_application` whose children are in parameter order, and a
2505
+ * definition's `pattern` fields are its parameters in order — including the
2506
+ * multi-guard shape `f x # g y = …`, where the guard equations share the
2507
+ * parameter list and the patterns are collected across all of them, first
2508
+ * occurrence winning.
2509
+ */
2510
+ joinParameters(sym, defNode, ctx, owner) {
2511
+ if (sym.parameters && sym.parameters.length > 0) return;
2512
+ const key = declarationKey(ctx.currentFrame, owner, sym.name);
2513
+ const info = ctx.signatures.get(key);
2514
+ if (!info) return;
2515
+ const params = [];
2516
+ let i = 0;
2517
+ for (const p of this.fields(defNode, "pattern")) {
2518
+ const name = this.patternParameterName(p);
2519
+ params.push({ name, type: info.paramTypes[i], isOptional: name === OPTIONAL_PARAM_NAME });
2520
+ i++;
2521
+ }
2522
+ if (params.length > 0) sym.parameters = params;
2523
+ }
2524
+ /**
2525
+ * The NAME a parameter pattern binds, or the placeholder for a pattern that
2526
+ * binds none.
2527
+ *
2528
+ * ONE `pattern` field is ONE parameter. That is the rule, and the first
2529
+ * implementation got it wrong in a way the fixture caught: it collected the
2530
+ * names bound INSIDE each pattern, so `addBid (Book bids asks) price` produced
2531
+ * three parameters (`bids`, `asks`, `price`) while the signature
2532
+ * `OrderBook Int -> OrderBook` has two domain types — a tuple pattern is one
2533
+ * parameter of a tuple type, not three parameters.
2534
+ *
2535
+ * A pattern that names its target (`identifier`, or `strict_binding_pattern`'s
2536
+ * `p=:t` left side) yields that name. A destructuring pattern
2537
+ * (`constructor_pattern`, `list_pattern`, `tuple_pattern`, `record_pattern`,
2538
+ * `unboxed_pattern`, `cons_pattern`, `wildcard`, `record_update_pattern`)
2539
+ * binds no single target, so the parameter is anonymous and is named by the
2540
+ * constant below rather than by inventing a name: the store keys a parameter by
2541
+ * name, so a fabricated one would collide across overloads, while a reserved
2542
+ * placeholder cannot.
2543
+ */
2544
+ patternParameterName(pat) {
2545
+ switch (pat.type) {
2546
+ case "identifier":
2547
+ return pat.text;
2548
+ case "strict_binding_pattern": {
2549
+ const first = pat.namedChild(0);
2550
+ if (first?.type === "identifier") return first.text;
2551
+ return this.condensePattern(pat);
2552
+ }
2553
+ case "strict_pattern": {
2554
+ const first = pat.namedChild(0);
2555
+ return first?.type === "identifier" ? first.text : this.condensePattern(pat);
2556
+ }
2557
+ case "wildcard":
2558
+ return OPTIONAL_PARAM_NAME;
2559
+ default:
2560
+ return this.condensePattern(pat);
2561
+ }
2562
+ }
2563
+ /**
2564
+ * A destructuring pattern as a one-line name. The pattern's own text is the
2565
+ * name the source gives that parameter slot, so it is used verbatim; only
2566
+ * NEWLINES and RUNS OF SPACES are folded, because a name that carries a
2567
+ * newline is not a name. Folding is a character operation (indexOf/slice over
2568
+ * the two whitespace characters), not a pattern match.
2569
+ */
2570
+ condensePattern(pat) {
2571
+ const raw = pat.text;
2572
+ let out = "";
2573
+ let pendingSpace = false;
2574
+ for (let i = 0; i < raw.length; i++) {
2575
+ const code = raw.charCodeAt(i);
2576
+ const isSpace = code === 32 || code === 9 || code === 10 || code === 13;
2577
+ if (isSpace) {
2578
+ pendingSpace = out.length > 0;
2579
+ continue;
2580
+ }
2581
+ if (pendingSpace) {
2582
+ out += " ";
2583
+ pendingSpace = false;
2584
+ }
2585
+ out += raw[i];
2586
+ }
2587
+ return out || OPTIONAL_PARAM_NAME;
2588
+ }
2589
+ // ============================================================================
2590
+ // PASS 2 — expression-level extraction
2591
+ // ============================================================================
2592
+ /**
2593
+ * An application: the lane's call / instantiation / qualified-call surface.
2594
+ *
2595
+ * The pinned grammar makes an application LEFT-NESTED: `f a b c` is
2596
+ * `application(application(application(f, a), b), c)`, so only the INNERMOST
2597
+ * application carries the callee. Measured over 23,908 applications, the
2598
+ * `function` field is 13,102 `application` (the nest), 8,340 `identifier`, 2,045
2599
+ * `constructor`, 162 `field_access`, 78 `single_quoted_name`, 61
2600
+ * `unboxed_expression`, 25 `parenthesized_operator`, 25 `number`, 22 `string`,
2601
+ * 20 `paren_expression`, 14 `index_access`, and 15 across five more shapes. The
2602
+ * old handler recognised only `identifier` and `constructor` (plus the corpus-dead
2603
+ * `qualified_identifier`), so the 265 `field_access` / `single_quoted_name` /
2604
+ * `parenthesized_operator` heads produced no edge at all.
2605
+ */
2606
+ extractApplication(node, ctx, scopeStack) {
2607
+ const head = node.childForFieldName("function");
2608
+ if (!head) return;
2609
+ const owner = this.relationshipSource(ctx);
2610
+ if (!owner) return;
2611
+ switch (head.type) {
2612
+ case "identifier": {
2613
+ const target = this.resolveCallableTarget(head.text, scopeStack, ctx);
2614
+ if (!target) return;
2615
+ this.pushRel(ctx, owner, target, ASTEngine.RelationshipType.Calls, 0.9, {
2616
+ cleanSpecific: true,
2617
+ context: "local function call"
2618
+ }, this.lineOf(head));
2619
+ return;
2620
+ }
2621
+ case "constructor": {
2622
+ if (this.isPatternPosition(node)) return;
2623
+ const name = head.text;
2624
+ if (ctx.declaredConstructors.has(name)) {
2625
+ this.pushRel(ctx, owner, name, ASTEngine.RelationshipType.Instantiates, 1, {
2626
+ cleanSpecific: true,
2627
+ isConstructorApplication: true,
2628
+ // A `data_constructor` node in THIS file proves the target is a data
2629
+ // constructor, so this edge carries no inference at all.
2630
+ isExternalTarget: false
2631
+ }, this.lineOf(head));
2632
+ return;
2633
+ }
2634
+ if (ctx.declaredFunctions.has(name)) {
2635
+ const target = this.resolveCallableTarget(name, scopeStack, ctx);
2636
+ if (target) {
2637
+ this.pushRel(ctx, owner, target, ASTEngine.RelationshipType.Calls, 0.9, {
2638
+ cleanSpecific: true,
2639
+ context: "uppercase function application"
2640
+ }, this.lineOf(head));
2641
+ }
2642
+ return;
2643
+ }
2644
+ if (ctx.declaredClassNames.has(name)) {
2645
+ this.pushRel(ctx, owner, name, ASTEngine.RelationshipType.Calls, 0.9, {
2646
+ cleanSpecific: true,
2647
+ context: "class method application",
2648
+ isClassMethodCall: true
2649
+ }, this.lineOf(head));
2650
+ return;
2651
+ }
2652
+ if (ctx.declaredTypeDefNames.has(name)) {
2653
+ ctx.typeCoercions.count++;
2654
+ if (ctx.typeCoercions.names.length < 25 && !ctx.typeCoercions.names.includes(name)) {
2655
+ ctx.typeCoercions.names.push(name);
2656
+ }
2657
+ return;
2658
+ }
2659
+ ctx.declinedInstantiations.count++;
2660
+ if (ctx.declinedInstantiations.names.length < 25 && !ctx.declinedInstantiations.names.includes(name)) {
2661
+ ctx.declinedInstantiations.names.push(name);
2662
+ }
2663
+ return;
2664
+ }
2665
+ case "single_quoted_name":
2666
+ case "qualified_identifier": {
2667
+ const target = head.type === "single_quoted_name" ? this.qualifiedTarget(head.text) : head.text;
2668
+ if (!target) return;
2669
+ this.pushRel(ctx, owner, target, ASTEngine.RelationshipType.Calls, 0.9, {
2670
+ cleanSpecific: true,
2671
+ isQualifiedCall: true,
2672
+ qualifier: this.qualifierOf(head.text)
2673
+ }, this.lineOf(head));
2674
+ if (ctx.currentFrame) {
2675
+ const idx = ctx.frameSymbolIndex.get(ctx.currentFrame);
2676
+ const sym = idx === void 0 ? void 0 : ctx.symbols[idx];
2677
+ if (sym) {
2678
+ const list = sym.qualified_calls ?? (sym.qualified_calls = []);
2679
+ if (!list.includes(target)) list.push(target);
2680
+ }
2681
+ }
2682
+ return;
2683
+ }
2684
+ case "field_access": {
2685
+ this.emitFieldUse(head, ctx, "method_call");
2686
+ return;
2687
+ }
2688
+ case "parenthesized_operator": {
2689
+ const name = this.unparenthesize(head.text);
2690
+ if (!name) return;
2691
+ if (ctx.declaredFunctions.has(name) || ctx.declaredTypeNames.has(name)) {
2692
+ this.pushRel(ctx, owner, name, ASTEngine.RelationshipType.Calls, 0.9, {
2693
+ cleanSpecific: true,
2694
+ context: "prefix operator application"
2695
+ }, this.lineOf(head));
2696
+ }
2697
+ return;
2698
+ }
2699
+ default:
2700
+ return;
2701
+ }
2702
+ }
2703
+ /**
2704
+ * `obj.field` — a record field read. 1,129 sites corpus-wide, published nothing
2705
+ * before. The `field` field carries the field NAME (1,058 `identifier`, 55
2706
+ * `constructor` — a field may be uppercase, as in `d.Diagnostic.severity` — and
2707
+ * 16 `single_quoted_name`).
2708
+ *
2709
+ * Emitted only when the name is a record field THIS FILE declares (the
2710
+ * FP-family rule, applied to property access as to calls): the receiver's type
2711
+ * is not resolvable inside one file, so an unguarded edge to a name that may be
2712
+ * a local variable would be a fabrication. `uses` is the type — a field read is
2713
+ * a use of the field, not a call of it.
2714
+ */
2715
+ emitFieldUse(access, ctx, label) {
2716
+ const owner = this.relationshipSource(ctx);
2717
+ if (!owner) return;
2718
+ const field = access.childForFieldName("field");
2719
+ if (!field) return;
2720
+ const name = field.text;
2721
+ if (!ctx.declaredFieldNames.has(name)) return;
2722
+ this.pushRel(ctx, owner, name, ASTEngine.RelationshipType.Uses, 0.9, {
2723
+ cleanSpecific: true,
2724
+ relationshipType: label,
2725
+ isRecordField: true
2726
+ }, this.lineOf(field));
2727
+ }
2728
+ /**
2729
+ * `{base & f = v, g = w}` — a record update. 469 sites, 793 `update_field`s.
2730
+ *
2731
+ * The `field` field points at an `update_field` and the `value` fields
2732
+ * interleave with it, so the field NAMES are read from the `update_field`
2733
+ * children (`field → identifier`) rather than by zipping two parallel field
2734
+ * lists. Measured: field count equals value count on all 469 (`f1v1` 288,
2735
+ * `f2v2` 90, `f3v3` 66, `f4v4` 14, `f5v5` 7, `f6v6` 2, `f11v11` 1,
2736
+ * `f12v12` 1), so a positional zip would work today, but the names are on the
2737
+ * nodes and reading them there is the construction that cannot break.
2738
+ */
2739
+ extractRecordUpdate(node, ctx) {
2740
+ const owner = this.relationshipSource(ctx);
2741
+ if (!owner) return;
2742
+ for (const f of this.fields(node, "field")) {
2743
+ const inner = f.childForFieldName("field") ?? f.namedChild(0);
2744
+ if (!inner) continue;
2745
+ const name = inner.text;
2746
+ if (!ctx.declaredFieldNames.has(name)) continue;
2747
+ this.pushRel(ctx, owner, name, ASTEngine.RelationshipType.Uses, 0.9, {
2748
+ cleanSpecific: true,
2749
+ relationshipType: "record_field_update",
2750
+ isRecordField: true
2751
+ }, this.lineOf(inner));
2752
+ }
2753
+ }
2754
+ /**
2755
+ * `{a = 1, b = 2}` — a record CONSTRUCTION. 399 sites, published nothing before.
2756
+ * Every field the literal sets is a `uses` of that field.
2757
+ */
2758
+ extractRecordExpression(node, ctx) {
2759
+ const owner = this.relationshipSource(ctx);
2760
+ if (!owner) return;
2761
+ for (const n of this.fields(node, "name")) {
2762
+ const name = n.text;
2763
+ if (!ctx.declaredFieldNames.has(name)) continue;
2764
+ this.pushRel(ctx, owner, name, ASTEngine.RelationshipType.Uses, 0.9, {
2765
+ cleanSpecific: true,
2766
+ relationshipType: "record_construction",
2767
+ isRecordField: true
2768
+ }, this.lineOf(n));
2769
+ }
2770
+ }
2771
+ /**
2772
+ * `Just x` in PATTERN position — a match on a data constructor. 1,240 sites,
2773
+ * published nothing before.
2774
+ *
2775
+ * This is deliberately NOT an `instantiates`: `case x of Just y -> …` does not
2776
+ * build a `Just`, it depends on one. `uses` is the type, guarded by the same
2777
+ * in-file declaration rule as every other edge, and labelled so the graph can
2778
+ * tell a match from a read or a write.
2779
+ */
2780
+ extractConstructorPattern(node, ctx) {
2781
+ const owner = this.relationshipSource(ctx);
2782
+ if (!owner) return;
2783
+ for (const c of this.fields(node, "constructor")) {
2784
+ if (c.type !== "constructor") continue;
2785
+ if (!ctx.declaredConstructors.has(c.text)) continue;
2786
+ this.pushRel(ctx, owner, c.text, ASTEngine.RelationshipType.Uses, 0.9, {
2787
+ cleanSpecific: true,
2788
+ relationshipType: "pattern_match",
2789
+ isPatternMatch: true
2790
+ }, this.lineOf(c));
2791
+ }
2792
+ }
2793
+ /**
2794
+ * Resolve a bare callee name to a target: the innermost visible LOCAL wins
2795
+ * (Clean scoping — a where-binder shadows the module binding for the whole RHS
2796
+ * of its callable), else the module-level declared set, else no edge.
2797
+ *
2798
+ * The walk order makes the local win correct without a second rule: a
2799
+ * `where_block` pushes its `locals` before the walk descends into the
2800
+ * definitions' bodies, and pops on exit, so an application in a guard equation
2801
+ * resolves against the block that encloses it.
2802
+ */
2803
+ resolveCallableTarget(name, scopeStack, ctx) {
2804
+ for (let i = scopeStack.length - 1; i >= 0; i--) {
2805
+ const locals = scopeStack[i].locals;
2806
+ if (locals === void 0) continue;
2807
+ const hit = locals.get(name);
2808
+ if (hit !== void 0) return hit;
2809
+ }
2810
+ return ctx.declaredFunctions.has(name) ? name : void 0;
2811
+ }
2812
+ /**
2813
+ * The symbol an expression-level edge is attributed to: the innermost open
2814
+ * frame's scoped identity, or the module when no frame is open. An application
2815
+ * outside any callable (a module-level initialiser) is attributed to the
2816
+ * module, which is where its symbol lives.
2817
+ */
2818
+ relationshipSource(ctx) {
2819
+ return ctx.currentFrame ?? ctx.currentModule;
2820
+ }
2821
+ /** The name of the nearest enclosing declaration, for a non-frame position. */
2822
+ enclosingDeclarationName(node) {
2823
+ let p = node.parent;
2824
+ while (p) {
2825
+ if (CALLABLE_NODE_TYPES.has(p.type)) return this.callableDeclaredName(p);
2826
+ if (p.type === "class_declaration" || p.type === "instance_declaration" || p.type === "type_definition" || p.type === "generic_declaration") {
2827
+ return p.childForFieldName("name")?.text;
2828
+ }
2829
+ p = p.parent;
2830
+ }
2831
+ return void 0;
2832
+ }
2833
+ /**
2834
+ * `'Data.Map'.get` → `Data.Map.get`.
2835
+ *
2836
+ * The quote in a Clean qualified name wraps the MODULE, not the whole name:
2837
+ * the corpus form is `'Data.Error'.Ok` — `'Data.Error'` quoted, `.Ok` not — and
2838
+ * the alias form is `M.lookup`, unquoted. The first implementation of this
2839
+ * function assumed a fully-quoted name (remove one leading `'`, require one
2840
+ * trailing `'`), so it returned null for every real corpus site: measured, the
2841
+ * 78 `single_quoted_name` application heads in the corpus produced ZERO
2842
+ * qualified `calls` edges while the branch looked live.
2843
+ *
2844
+ * The normalization is therefore "drop the quote characters", which is a
2845
+ * character filter, and a result with no characters left is not a name.
2846
+ */
2847
+ qualifiedTarget(text) {
2848
+ if (!text.startsWith("'")) return text || null;
2849
+ let out = "";
2850
+ for (let i = 0; i < text.length; i++) {
2851
+ const ch = text[i];
2852
+ if (ch !== "'") out += ch;
2853
+ }
2854
+ return out || null;
2855
+ }
2856
+ /** The qualifier (module or alias) part of a qualified name, for metadata. */
2857
+ qualifierOf(text) {
2858
+ const inner = this.qualifiedTarget(text);
2859
+ if (!inner) return void 0;
2860
+ const dot = inner.indexOf(".");
2861
+ return dot > 0 ? inner.slice(0, dot) : void 0;
2862
+ }
2863
+ // ============================================================================
2864
+ // PASS 3 — closure
2865
+ // ============================================================================
2866
+ /**
2867
+ * `contains` for owned symbols — module → members, type → constructors and
2868
+ * fields, class/instance → methods, local frame → its own definitions. The
2869
+ * CHILD's own naming line is the anchor that resolves the intended target among
2870
+ * same-named siblings (#921), and a self-named edge is skipped because a symbol
2871
+ * cannot contain itself.
2872
+ */
2873
+ emitContainsEdges(ctx) {
2874
+ for (const s of ctx.symbols) {
2875
+ const owner = s.ownerName;
2876
+ if (owner && owner !== s.name) {
2877
+ this.pushRel(ctx, owner, s.name, ASTEngine.RelationshipType.Contains, 1, {}, s.range?.startLine);
2878
+ }
2879
+ }
2880
+ }
2881
+ /**
2882
+ * Exports: every symbol the MODULE owns, plus an explicit `foreign export`.
2883
+ * Clean has no export keyword — a `.dcl`'s definition list IS the interface and
2884
+ * everything in it is public by default.
2885
+ *
2886
+ * Class/instance members keep their owner (`<class>` / the instance target type),
2887
+ * so they are NOT exported: their membership rides the `contains` edge, exactly
2888
+ * as the haskell lane treats a class method. Where/with/let locals are NOT
2889
+ * exported (M11.11 §2: a local is a real symbol but not part of the module's
2890
+ * surface) — now that locals publish at all, that exclusion is load-bearing
2891
+ * rather than incidental, and it is stated here because it is the one place a
2892
+ * `visibility: "local"` symbol could otherwise leak into the module interface.
2893
+ *
2894
+ * Name-keyed first-wins dedupe: the exports-family invariant (run-fixtures.ts,
2895
+ * M49 JVM-closure audit) requires UNIQUE export names, because a duplicate
2896
+ * cannot identify its provider. THE DEDUPE SURVIVES THE INSTANCE-CONTAINER FIX, and
2897
+ * the invariant is why: it is a property of the export LIST (one name, one
2898
+ * provider), not of any one declaration form, so it stays regardless of which
2899
+ * forms happen to collide this round. What the dedupe USED to absorb is gone at the
2900
+ * source — a file declaring `ParseState` and also implementing `instance Eq
2901
+ * ParseState` offered `ParseState` twice (59 of 312 files), and the instance
2902
+ * container is now `ParseState::instance` of kind `impl`, which is neither exported
2903
+ * nor a declaration of `ParseState`. Same dedupe as cpp/go/objc/clojure, keyed by
2904
+ * bare name — a Clean module exports a name, not a name+kind pair.
2905
+ */
2906
+ emitExports(ctx) {
2907
+ const EXPORTABLE_KINDS = /* @__PURE__ */ new Set([
2908
+ ASTEngine.SymbolKind.Function,
2909
+ ASTEngine.SymbolKind.Type,
2910
+ ASTEngine.SymbolKind.Interface,
2911
+ ASTEngine.SymbolKind.Class,
2912
+ ASTEngine.SymbolKind.Module,
2913
+ ASTEngine.SymbolKind.Constructor,
2914
+ ASTEngine.SymbolKind.Property,
2915
+ ASTEngine.SymbolKind.Macro
2916
+ ]);
2917
+ const seen = /* @__PURE__ */ new Set();
2918
+ const source = ctx.currentModule ?? "";
2919
+ for (const sym of ctx.symbols) {
2920
+ if (sym.name.startsWith("_")) continue;
2921
+ if (sym.name === ctx.currentModule) continue;
2922
+ if (!EXPORTABLE_KINDS.has(sym.kind)) continue;
2923
+ if (sym.visibility === "local" || sym.metadata?.isLocalDefinition) continue;
2924
+ if (seen.has(sym.name)) continue;
2925
+ if (sym.ownerName !== ctx.currentModule && sym.ownerName !== void 0) continue;
2926
+ seen.add(sym.name);
2927
+ ctx.exports.push({ name: sym.name, kind: sym.kind, range: sym.range, visibility: "public" });
2928
+ if (source) {
2929
+ this.pushRel(
2930
+ ctx,
2931
+ source,
2932
+ sym.name,
2933
+ ASTEngine.RelationshipType.Exports,
2934
+ 1,
2935
+ { cleanSpecific: true },
2936
+ sym.range?.startLine
2937
+ );
2938
+ }
2939
+ }
2940
+ }
2941
+ // ============================================================================
2942
+ // PASS 3 — plumbing
2943
+ // ============================================================================
2944
+ /**
2945
+ * Relationship emission, deduped by source|type|target|line identity.
2946
+ *
2947
+ * Dimension 8 / M59.O2: an edge carries the SITE LINE it happened on — 1-based,
2948
+ * on the name-bearing node, per-site and not per-set, because navigation is the
2949
+ * product. Two consequences, both deliberate:
2950
+ * * the line is part of the DEDUPE KEY (the clojure lane's rule,
2951
+ * real-clojure-analyzer.ts:1388): a function called from three lines is
2952
+ * three edges at three lines, not one edge at whichever line the walk saw
2953
+ * first. The store already keys its rows the same way
2954
+ * (parallel-indexer.ts:4439), and for a `contains` edge the child's own
2955
+ * naming line is what resolves the intended target among same-named
2956
+ * siblings (#921);
2957
+ * * a line that is not a positive integer is recorded as ABSENT, never as 0.
2958
+ * M59.O2: the contract is "the site line or NULL" — a fabricated 0 reads as
2959
+ * "line 0" to every reader downstream.
2960
+ */
2961
+ pushRel(ctx, source, target, type, confidence, metadata = {}, line) {
2962
+ if (!source || !target) return;
2963
+ const hasLine = typeof line === "number" && Number.isInteger(line) && line > 0;
2964
+ const key = `${source}|${type}|${target}|${hasLine ? line : ""}`;
2965
+ if (ctx.seenRels.has(key)) return;
2966
+ ctx.seenRels.add(key);
2967
+ ctx.relationships.push({
2968
+ source,
2969
+ target,
2970
+ type,
2971
+ confidence,
2972
+ metadata,
2973
+ ...hasLine ? { line_number: line } : {}
2974
+ });
2975
+ }
2976
+ /** 1-based line of a node, or undefined — never 0 (M59.O2). */
2977
+ lineOf(node) {
2978
+ if (!node) return void 0;
2979
+ const line = node.startPosition.row + 1;
2980
+ return line > 0 ? line : void 0;
2981
+ }
2982
+ // ============================================================================
2983
+ // PASS 2 — shared helpers
2984
+ // ============================================================================
2985
+ isLocalScope(scope) {
2986
+ return scope !== void 0 && scope.kind === ASTEngine.SymbolKind.Namespace && scope.name === LOCAL_SCOPE_NAME;
2987
+ }
2988
+ /**
2989
+ * The identity a declaration in `scope` merges under: the local's enclosing
2990
+ * FRAME when the scope is a where/with/let block (that is what keeps a local
2991
+ * from colliding with a module callable), otherwise the scope's own name.
2992
+ */
2993
+ ownerIdentity(scope, ctx) {
2994
+ if (this.isLocalScope(scope)) return ctx.currentFrame;
2995
+ return scope?.name;
2996
+ }
2997
+ /** A callable's kind from the scope it is declared in. */
2998
+ callableKind(scope, isLocal) {
2999
+ if (isLocal) return ASTEngine.SymbolKind.Function;
3000
+ if (scope === void 0) return ASTEngine.SymbolKind.Function;
3001
+ if (scope.kind === ASTEngine.SymbolKind.Interface || scope.kind === ASTEngine.SymbolKind.Class) {
3002
+ return ASTEngine.SymbolKind.Method;
3003
+ }
3004
+ if (scope.kind === ASTEngine.SymbolKind.Type) return ASTEngine.SymbolKind.Property;
3005
+ return ASTEngine.SymbolKind.Function;
3006
+ }
3007
+ /** The `| C a, D b` constraints of a class or instance, as edges. */
3008
+ emitContextRefs(context, source, label, ctx) {
3009
+ const info = {
3010
+ node: context,
3011
+ paramTypes: [],
3012
+ genericVars: /* @__PURE__ */ new Set(),
3013
+ typeRefs: [],
3014
+ contextClasses: [],
3015
+ specialTypes: [],
3016
+ hasCodeBody: false
3017
+ };
3018
+ this.readClassContext(context, info);
3019
+ if (info.contextClasses.length === 0) return;
3020
+ ctx.patterns["Class Contexts"] = true;
3021
+ for (const c of info.contextClasses) {
3022
+ this.pushRel(ctx, source, c, ASTEngine.RelationshipType.References, 0.95, {
3023
+ cleanSpecific: true,
3024
+ relationshipType: label
3025
+ }, this.lineOf(context));
3026
+ }
3027
+ }
3028
+ /**
3029
+ * Type names mentioned by a type expression, as `references` edges — Clean's
3030
+ * `extractDeclaredTypeRefs` (the TypeScript lane's, at
3031
+ * real-typescript-analyzer.ts:3144). The line is the NAME's own line, not the
3032
+ * type expression's start, because a multi-line type puts its arguments on
3033
+ * later lines and the reader navigates to the name.
3034
+ */
3035
+ emitTypeRefs(typeNode, source, label, ctx) {
3036
+ for (const ref of this.typeRefsIn(typeNode, ctx)) {
3037
+ this.pushRel(ctx, source, ref.name, ASTEngine.RelationshipType.References, 0.95, {
3038
+ cleanSpecific: true,
3039
+ relationshipType: label
3040
+ }, this.lineOf(ref.node));
3041
+ }
3042
+ }
3043
+ /**
3044
+ * The declared name of a function/operator definition, read from the grammar's
3045
+ * FIELD.
3046
+ *
3047
+ * `findChild(node, n => n.type === "identifier")` returns the FIRST identifier
3048
+ * child, which is only the name while the name happens to be typed `identifier`.
3049
+ * An uppercase definition's name is typed `constructor` (Clean lets a constructor
3050
+ * be a function and the grammar says so), so the heuristic skipped over it and
3051
+ * published the first PARAMETER as the function name. Measured over this lane's
3052
+ * corpora, per grammar: pinned v1.2.4 — 4,607 declarations, 452 published a
3053
+ * parameter as the name, 252 dropped; v1.2.7 — 4,647 declarations, 447 published
3054
+ * a parameter, 244 dropped. The field is exact, so it is used when present and
3055
+ * the scan remains only as a fallback for a node with no field.
3056
+ */
3057
+ declaredName(node) {
3058
+ const byField = node.childForFieldName("name");
3059
+ if (byField?.text) return byField;
3060
+ return this.findChild(node, (n) => n.type === "identifier" || n.type === "constructor");
3061
+ }
3062
+ /**
3063
+ * The name an `operator_definition` declares. Same normalization as
3064
+ * `declarationName`'s parenthesized branch, so a signature and its definition
3065
+ * land on ONE key and merge instead of producing two symbols.
3066
+ */
3067
+ operatorDefinitionName(node) {
3068
+ const nameNode = node.childForFieldName("name") ?? this.findChild(node, (n) => n.type === "parenthesized_operator" || n.type === "parenthesized_name");
3069
+ if (!nameNode?.text) return null;
3070
+ return this.unparenthesize(nameNode.text);
3071
+ }
3072
+ /**
3073
+ * The name a `type_signature` DECLARES, normalized across the four shapes the
3074
+ * pinned grammar puts inside `signature_name`:
3075
+ *
3076
+ * identifier `f :: !Int -> Bool`
3077
+ * constructor `NSMakeSize :: !NSSize !NSSizeType -> NSSize*`
3078
+ * — Clean lets an uppercase name be a FUNCTION and the
3079
+ * grammar types it `constructor`
3080
+ * parenthesized_operator `(%.) infixl 9 :: ...` — the name is `%.`
3081
+ * parenthesized_name `(bitxor) :: !Int !Int -> Int` — the name is
3082
+ * `bitxor` (a prefix declaration of an infix-capable
3083
+ * name)
3084
+ *
3085
+ * A parenthesized shape carrying a comma or a brace is NOT a name: it is the
3086
+ * grammar's mis-shape for a PATTERN (`# (a,b,c) = expr;`), and publishing it
3087
+ * would fabricate a symbol no source declares. Measured over the corpus
3088
+ * (312 files, 148 of them `.dcl`): 2,753 module-level signature sites, of which
3089
+ * 1,021 names were published nowhere — 708 identifiers, 259 constructors, 45
3090
+ * operators, 9 parenthesized names.
3091
+ */
3092
+ declarationName(node) {
3093
+ const sigName = node.childForFieldName("name") ?? this.findChild(node, (n) => n.type === "signature_name");
3094
+ if (!sigName) return null;
3095
+ const kid = sigName.namedChildCount > 0 ? sigName.namedChild(0) : null;
3096
+ if (!kid) return null;
3097
+ if (kid.type === "identifier" || kid.type === "constructor") return kid.text;
3098
+ if (kid.type === "parenthesized_operator" || kid.type === "parenthesized_name") {
3099
+ return this.unparenthesize(kid.text);
3100
+ }
3101
+ return null;
3102
+ }
3103
+ unparenthesize(text) {
3104
+ return unparenthesizeText(text);
3105
+ }
3106
+ /**
3107
+ * Is `name` a class this file declares, under the OTHER spelling Clean uses for an
3108
+ * operator-named class?
3109
+ *
3110
+ * A declaration writes `class (==) …` and an instance reads `instance == …`, so
3111
+ * `declaredTypeNames` holds `(==)` while the lookup holds `==`. Only the parenthesised
3112
+ * direction is tried, and only when the bare name is not itself declared, so a file
3113
+ * that declares BOTH `class (===)` and `class ===` still resolves `===` to the
3114
+ * declaration that actually spells it that way. Returns false for any name that is not
3115
+ * a plausible operator, which keeps this from matching an ordinary identifier.
3116
+ */
3117
+ isLocallyDeclaredOperatorClass(ctx, name) {
3118
+ if (this.isPlainIdentifierName(name)) return false;
3119
+ const parenthesised = `(${name})`;
3120
+ return ctx.declaredTypeNames.has(parenthesised) || ctx.declaredClassNames.has(parenthesised);
3121
+ }
3122
+ /**
3123
+ * Is `name` an ordinary identifier — a letter or `_`, then letters, digits, `_` or `'`?
3124
+ *
3125
+ * Character tests, not a pattern. This file's one regex-free property is checked by
3126
+ * `script/m14/clean-completeness-gate.ts`, which fails on ANY `RegularExpressionLiteral`
3127
+ * — a stronger rule than the M49.R2 "no regex doing syntax work" policy, and the reason a
3128
+ * two-line predicate like `/^[A-Za-z_][A-Za-z0-9_']*$/` had to be written this way. The
3129
+ * first version of `isLocallyDeclaredOperatorClass` used that regex and turned the gate
3130
+ * red, which is the gate working: it caught a regression introduced by the very commit
3131
+ * that added the fixture meant to prove the correction.
3132
+ */
3133
+ isPlainIdentifierName(name) {
3134
+ if (name.length === 0) return false;
3135
+ const isHead = (c) => c === "_" || c >= "a" && c <= "z" || c >= "A" && c <= "Z";
3136
+ if (!isHead(name[0])) return false;
3137
+ for (let i = 1; i < name.length; i++) {
3138
+ const c = name[i];
3139
+ if (!isHead(c) && !(c >= "0" && c <= "9") && c !== "'") return false;
3140
+ }
3141
+ return true;
3142
+ }
3143
+ /**
3144
+ * The comments belonging to a declaration: the run of comment nodes that
3145
+ * IMMEDIATELY precede it, plus any comment inside it.
3146
+ *
3147
+ * Contiguity is the boundary. Without it a trailing remark about the PREVIOUS
3148
+ * declaration — the common case in Clean's `.dcl` files, where every interface
3149
+ * member is introduced by a comment — is stolen by the next one, and a `.dcl`
3150
+ * that documents twenty members would give all twenty the same text. So the
3151
+ * walk stops at the first comment with a blank line between it and the
3152
+ * declaration, which is the same rule the TypeScript lane uses
3153
+ * (real-typescript-analyzer.ts:5439).
3154
+ *
3155
+ * 3,062 comment nodes over the corpus (2,679 line, 383 block) reached no symbol
3156
+ * before this: the lane published a symbol's type, parameters and generics and
3157
+ * never its documentation, so `brain_symbol.docstring` was empty for every Clean
3158
+ * symbol in existence.
3159
+ */
3160
+ /**
3161
+ * Add a node's comments to a symbol that already has some.
3162
+ *
3163
+ * The order is signature-then-definition, which is the source order for the
3164
+ * `.dcl` house style (documentation above the signature, definition below), and
3165
+ * the DEFINITION's leading run takes the `docstring` when it has one.
3166
+ */
3167
+ mergeComments(sym, node) {
3168
+ const before = sym.comments?.length ?? 0;
3169
+ this.attachComments(sym, node);
3170
+ if ((sym.comments?.length ?? 0) === before) return;
3171
+ if (this.leadingComments(node).length > 0 && !sym.docstring) sym.docstring = sym.comments?.[0];
3172
+ }
3173
+ attachComments(sym, node) {
3174
+ const leading = this.leadingComments(node);
3175
+ const inner = [];
3176
+ for (let i = 0; i < node.namedChildCount; i++) {
3177
+ const child = node.namedChild(i);
3178
+ if (!child || !COMMENT_TYPES.has(child.type)) continue;
3179
+ const text = commentText(child.text);
3180
+ if (text && !leading.includes(text) && !inner.includes(text)) inner.push(text);
3181
+ }
3182
+ const trailing = this.trailingComments(node);
3183
+ const all = [...leading, ...trailing, ...inner];
3184
+ if (all.length === 0) return;
3185
+ const existing = sym.comments ?? [];
3186
+ const merged = [...existing];
3187
+ for (const c of all) if (!merged.includes(c)) merged.push(c);
3188
+ sym.comments = merged;
3189
+ if (leading.length > 0 && !sym.docstring) sym.docstring = leading[0];
3190
+ }
3191
+ /**
3192
+ * The contiguous run of comment nodes IMMEDIATELY above a declaration, in
3193
+ * source order.
3194
+ *
3195
+ * Two boundaries, and the first implementation had only one:
3196
+ *
3197
+ * * a BLANK LINE ends the run — otherwise a remark about the PREVIOUS
3198
+ * declaration is stolen by the next one, and a `.dcl` that documents twenty
3199
+ * members would give all twenty the same text;
3200
+ * * a comment that begins ON the preceding declaration's own line is that
3201
+ * declaration's TRAILING comment, not this one's documentation. Clean's
3202
+ * grammar puts such a comment in the source-file child list right after the
3203
+ * declaration it annotates, so it is the next declaration's
3204
+ * `previousSibling` and the blank-line rule alone cannot exclude it. The
3205
+ * measured damage: `initAppDelegate` was documented with
3206
+ * "create the AppDelegate class", which is `createAppDelegate`'s comment.
3207
+ * The barrier is the nearest preceding NON-comment sibling's end row; a
3208
+ * comment starting at or above it belongs to that sibling.
3209
+ */
3210
+ leadingComments(node) {
3211
+ const parent = node.parent;
3212
+ if (!parent) return [];
3213
+ const at = this.childIndexOf(parent, node);
3214
+ if (at < 0) return [];
3215
+ let barrierRow = -1;
3216
+ for (let i = at - 1; i >= 0; i--) {
3217
+ const c = parent.child(i);
3218
+ if (!c || !c.isNamed) continue;
3219
+ if (COMMENT_TYPES.has(c.type)) continue;
3220
+ barrierRow = c.endPosition.row;
3221
+ break;
3222
+ }
3223
+ const out = [];
3224
+ let anchorRow = node.startPosition.row;
3225
+ for (let i = at - 1; i >= 0; i--) {
3226
+ const c = parent.child(i);
3227
+ if (!c || !c.isNamed || !COMMENT_TYPES.has(c.type)) break;
3228
+ if (c.endPosition.row + 1 < anchorRow) break;
3229
+ if (c.startPosition.row <= barrierRow) break;
3230
+ const text = commentText(c.text);
3231
+ if (text) out.unshift(text);
3232
+ anchorRow = c.startPosition.row;
3233
+ }
3234
+ return out;
3235
+ }
3236
+ /**
3237
+ * A TRAILING comment: the run of comment nodes that start on the declaration's
3238
+ * own END row.
3239
+ *
3240
+ * Clean's house style in every `.dcl` in this corpus is a remark at the end of
3241
+ * the signature line, and this grammar places it in the PARENT's child list
3242
+ * after the declaration — so it is also the next declaration's leading
3243
+ * neighbour, which is why the barrier in `leadingComments` exists.
3244
+ */
3245
+ trailingComments(node) {
3246
+ const parent = node.parent;
3247
+ if (!parent) return [];
3248
+ const at = this.childIndexOf(parent, node);
3249
+ if (at < 0) return [];
3250
+ const out = [];
3251
+ for (let i = at + 1; i < parent.childCount; i++) {
3252
+ const c = parent.child(i);
3253
+ if (!c || !c.isNamed || !COMMENT_TYPES.has(c.type)) break;
3254
+ if (c.startPosition.row !== node.endPosition.row) break;
3255
+ const text = commentText(c.text);
3256
+ if (text && !out.includes(text)) out.push(text);
3257
+ }
3258
+ return out;
3259
+ }
3260
+ /**
3261
+ * This node's index in its parent's child list, or -1.
3262
+ *
3263
+ * It is found by START INDEX rather than by identity because web-tree-sitter
3264
+ * recreates a Node object on every access — and the sibling ACCESSORS cannot be
3265
+ * used at all on this grammar: measured, `previousSibling` here returns the
3266
+ * previous NAMED NON-COMMENT sibling, skipping the very comment nodes this
3267
+ * method exists to find, so `total`'s leading block comment was unreachable
3268
+ * through it. Scanning the child list is the construction that works, and it is
3269
+ * the same one the rest of this file uses to find a node's neighbours.
3270
+ */
3271
+ childIndexOf(parent, child) {
3272
+ for (let i = 0; i < parent.childCount; i++) {
3273
+ const c = parent.child(i);
3274
+ if (c && c.startIndex === child.startIndex && c.endIndex === child.endIndex) return i;
3275
+ }
3276
+ return -1;
3277
+ }
3278
+ // ============================================================================
3279
+ // AST PLUMBING
3280
+ //
3281
+ // Everything here reads a node's own structure. No method inspects a node's
3282
+ // text to decide what it is: the grammar's FIELDS are the discriminator
3283
+ // everywhere, and the only text-level operations in the file are the four
3284
+ // positional ones already justified at their definitions (the unparenthesize
3285
+ // pair, the dot strip, the quote drop, the whitespace fold).
3286
+ // ============================================================================
3287
+ /**
3288
+ * `childrenForFieldName` with the null entries dropped.
3289
+ *
3290
+ * web-tree-sitter's `childrenForFieldName` is typed `(Node | null)[]` because
3291
+ * it walks the whole child list matching field ids. Every caller here wants the
3292
+ * real children, and a `for..of` over the raw array would need a non-null
3293
+ * assertion at each use. One filter, here, is both shorter and honest about the
3294
+ * shape.
3295
+ */
3296
+ fields(node, name) {
3297
+ const raw = node.childrenForFieldName(name);
3298
+ const out = [];
3299
+ for (let i = 0; i < raw.length; i++) {
3300
+ const c = raw[i];
3301
+ if (c) out.push(c);
3302
+ }
3303
+ return out;
3304
+ }
3305
+ findChild(node, predicate) {
3306
+ const count = node.namedChildCount;
3307
+ for (let i = 0; i < count; i++) {
3308
+ const child = node.namedChild(i);
3309
+ if (child && predicate(child)) return child;
3310
+ }
3311
+ return null;
3312
+ }
3313
+ filterChildren(node, predicate) {
3314
+ const result = [];
3315
+ const count = node.namedChildCount;
3316
+ for (let i = 0; i < count; i++) {
3317
+ const child = node.namedChild(i);
3318
+ if (child && predicate(child)) result.push(child);
3319
+ }
3320
+ return result;
3321
+ }
3322
+ firstNamedChildOfType(node, type) {
3323
+ for (let i = 0; i < node.namedChildCount; i++) {
3324
+ const c = node.namedChild(i);
3325
+ if (c && c.type === type) return c;
3326
+ }
3327
+ return null;
3328
+ }
3329
+ /** A child the grammar leaves UNFIELDED — `generic_declaration`'s `::` type. */
3330
+ unnamedChildOfType(node, type) {
3331
+ for (let i = 0; i < node.childCount; i++) {
3332
+ const c = node.child(i);
3333
+ if (c && c.type === type) return c;
3334
+ }
3335
+ return null;
3336
+ }
3337
+ /**
3338
+ * A name node normalized for a parenthesized operator or name: `(==)` → `==`,
3339
+ * anything else → its own text.
3340
+ *
3341
+ * The parenthesized forms are how Clean spells an infix-capable name, and the
3342
+ * four shapes that carry a name (`constructor`, `identifier`,
3343
+ * `parenthesized_operator`, `parenthesized_name`) must all land on ONE key, or
3344
+ * a signature and its definition become two symbols.
3345
+ */
3346
+ normalizeParenthesized(node) {
3347
+ if (!node?.text) return null;
3348
+ if (node.type === "parenthesized_operator" || node.type === "parenthesized_name") {
3349
+ return this.unparenthesize(node.text);
3350
+ }
3351
+ return node.text;
3352
+ }
3353
+ /**
3354
+ * The name a `class_member` / `instance_member` wrapper's inner declaration
3355
+ * declares, normalized across all three shapes the two wrappers carry.
3356
+ *
3357
+ * A `type_signature` wraps its name in a `signature_name` (four shapes, see
3358
+ * `declarationName`); an `operator_definition` puts a `parenthesized_operator`
3359
+ * straight in the `name` field; a `function_declaration` puts an `identifier`.
3360
+ * All three are read from the FIELD, never from the first child, for the reason
3361
+ * `declaredName` documents: an uppercase definition's name is typed
3362
+ * `constructor` and a first-child scan publishes the first PARAMETER instead.
3363
+ */
3364
+ memberDeclaredName(wrapper) {
3365
+ const inner = wrapper.namedChild(0);
3366
+ if (!inner) return null;
3367
+ if (inner.type === "type_signature") return this.declarationName(inner);
3368
+ return this.normalizeParenthesized(this.declaredName(inner));
3369
+ }
3370
+ /** module_name children compose dotted names (Data.Map → Data + "." + Map). */
3371
+ assembleModuleName(node) {
3372
+ let out = "";
3373
+ for (let i = 0; i < node.namedChildCount; i++) {
3374
+ const c = node.namedChild(i);
3375
+ if (!c) continue;
3376
+ const leaf = c.type === "module_name" ? this.assembleModuleName(c) : c.text;
3377
+ if (out) out += ".";
3378
+ out += leaf;
3379
+ }
3380
+ return out || node.text;
3381
+ }
3382
+ nodeToRange(node) {
3383
+ return {
3384
+ startLine: node.startPosition.row + 1,
3385
+ startColumn: node.startPosition.column + 1,
3386
+ endLine: node.endPosition.row + 1,
3387
+ endColumn: node.endPosition.column + 1,
3388
+ startIndex: node.startIndex,
3389
+ endIndex: node.endIndex
3390
+ };
3391
+ }
3392
+ /**
3393
+ * Is this application in PATTERN territory?
3394
+ *
3395
+ * A constructor application is an `instantiates` in a body and a match in a
3396
+ * pattern, and the two are different edges. Pattern territory is:
3397
+ * * any pattern-node subtree (`constructor_pattern`, `list_pattern`,
3398
+ * `tuple_pattern`, `record_pattern`, `paren_pattern`, `unboxed_pattern`,
3399
+ * `cons_pattern`, `strict_binding_pattern`, `record_update_pattern`);
3400
+ * * a `case_alternative` head — the pattern before the `->`;
3401
+ * * everything in a definition BEFORE its `body` FIELD (the head patterns).
3402
+ *
3403
+ * The pre-#884 form of the third rule scanned the children for one typed
3404
+ * `operator` with text `=`, which the pinned grammar cannot produce: a
3405
+ * definition's s-expression carries name/pattern…/body and NO `=` child at all
3406
+ * (measured three ways — the 1,576 `operator` nodes in the corpus have parents
3407
+ * `binary_expression` 829, `unary_expression` 99, `paren_pattern` 9,
3408
+ * `operator_definition` 5, `instance_class` 11 and never a
3409
+ * `function_declaration`, and no `operator` node's text is `=`). Expressed
3410
+ * against the field, the rule is live and means the same thing.
3411
+ */
3412
+ isPatternPosition(node) {
3413
+ let cur = node.parent;
3414
+ while (cur) {
3415
+ if (PATTERN_NODE_TYPES.has(cur.type)) return true;
3416
+ if (cur.type === "case_alternative") {
3417
+ const first = cur.namedChild(0);
3418
+ return first !== null && first.startIndex === node.startIndex || this.isBeforeArrow(cur, node);
3419
+ }
3420
+ if (CALLABLE_NODE_TYPES.has(cur.type)) return this.isBeforeBody(cur, node);
3421
+ if (cur.type === "type_definition" || cur.type === "class_declaration" || cur.type === "instance_declaration" || cur.type === "type_signature") {
3422
+ return false;
3423
+ }
3424
+ cur = cur.parent;
3425
+ }
3426
+ return false;
3427
+ }
3428
+ isBeforeArrow(caseAlt, node) {
3429
+ for (let i = 0; i < caseAlt.namedChildCount; i++) {
3430
+ const c = caseAlt.namedChild(i);
3431
+ if (!c) continue;
3432
+ if (c.type === "arrow") return node.startIndex < c.startIndex;
3433
+ if (c.startIndex === node.startIndex) return true;
3434
+ }
3435
+ return false;
3436
+ }
3437
+ /**
3438
+ * Pattern territory inside a definition is everything BEFORE its body FIELD. A
3439
+ * definition with no body field at all (a damaged parse) still has `pattern`
3440
+ * fields, and territory is everything before the LAST of them, so a constructor
3441
+ * application in a body-less node is not mistaken for a match.
3442
+ */
3443
+ isBeforeBody(defNode, node) {
3444
+ for (const b of this.fields(defNode, "body")) {
3445
+ if (node.startIndex < b.startIndex) return true;
3446
+ }
3447
+ const patterns = this.fields(defNode, "pattern");
3448
+ if (patterns.length === 0) return false;
3449
+ let last = -1;
3450
+ for (const p of patterns) last = Math.max(last, p.endIndex);
3451
+ return node.startIndex < last;
3452
+ }
3453
+ };
3454
+ function commentText(raw) {
3455
+ let s = raw;
3456
+ if (s.startsWith("/*")) {
3457
+ s = s.slice(2);
3458
+ while (s.startsWith("*")) s = s.slice(1);
3459
+ if (s.endsWith("*/")) s = s.slice(0, -2);
3460
+ return s.trim();
3461
+ }
3462
+ while (s.startsWith("/")) s = s.slice(1);
3463
+ return s.trim();
3464
+ }
3465
+ function stripLeadingDot(text) {
3466
+ return text.startsWith(".") ? text.slice(1) : text;
3467
+ }
3468
+ export {
3469
+ RealCleanAnalyzer
3470
+ };