semantifold 0.3.0 → 0.4.0

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 (297) hide show
  1. package/README.md +58 -15
  2. package/build/index.d.ts +48 -2
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +57 -8
  5. package/build/src/artifact-path.d.ts +23 -1
  6. package/build/src/artifact-path.d.ts.map +1 -1
  7. package/build/src/artifact-path.js +45 -3
  8. package/build/src/artifacts.d.ts.map +1 -1
  9. package/build/src/artifacts.js +66 -1
  10. package/build/src/backends/c-validation.d.ts.map +1 -1
  11. package/build/src/backends/c-validation.js +7 -3
  12. package/build/src/backends/cpp-validation.d.ts.map +1 -1
  13. package/build/src/backends/cpp-validation.js +7 -3
  14. package/build/src/backends/csharp.js +7 -1
  15. package/build/src/backends/dart-runtime.d.ts +2 -0
  16. package/build/src/backends/dart-runtime.d.ts.map +1 -0
  17. package/build/src/backends/dart-runtime.js +41 -0
  18. package/build/src/backends/dart.d.ts +20 -0
  19. package/build/src/backends/dart.d.ts.map +1 -0
  20. package/build/src/backends/dart.js +321 -0
  21. package/build/src/backends/effects.d.ts +60 -0
  22. package/build/src/backends/effects.d.ts.map +1 -0
  23. package/build/src/backends/effects.js +746 -0
  24. package/build/src/backends/go.js +18 -4
  25. package/build/src/backends/identifiers.d.ts +36 -4
  26. package/build/src/backends/identifiers.d.ts.map +1 -1
  27. package/build/src/backends/identifiers.js +154 -10
  28. package/build/src/backends/index.d.ts.map +1 -1
  29. package/build/src/backends/index.js +27 -4
  30. package/build/src/backends/ios.d.ts +68 -0
  31. package/build/src/backends/ios.d.ts.map +1 -0
  32. package/build/src/backends/ios.js +1503 -0
  33. package/build/src/backends/java.d.ts.map +1 -1
  34. package/build/src/backends/java.js +429 -36
  35. package/build/src/backends/javascript.d.ts +6 -0
  36. package/build/src/backends/javascript.d.ts.map +1 -1
  37. package/build/src/backends/javascript.js +380 -24
  38. package/build/src/backends/kotlin-runtime.d.ts +3 -0
  39. package/build/src/backends/kotlin-runtime.d.ts.map +1 -0
  40. package/build/src/backends/kotlin-runtime.js +29 -0
  41. package/build/src/backends/kotlin.d.ts +23 -0
  42. package/build/src/backends/kotlin.d.ts.map +1 -0
  43. package/build/src/backends/kotlin.js +136 -0
  44. package/build/src/backends/native-validation.d.ts +2 -2
  45. package/build/src/backends/native-validation.d.ts.map +1 -1
  46. package/build/src/backends/native-validation.js +2 -2
  47. package/build/src/backends/ordered-expressions.d.ts.map +1 -1
  48. package/build/src/backends/ordered-expressions.js +39 -7
  49. package/build/src/backends/php-stdlib-support.d.ts +19 -0
  50. package/build/src/backends/php-stdlib-support.d.ts.map +1 -0
  51. package/build/src/backends/php-stdlib-support.js +227 -0
  52. package/build/src/backends/php.d.ts.map +1 -1
  53. package/build/src/backends/php.js +448 -33
  54. package/build/src/backends/program.d.ts +81 -0
  55. package/build/src/backends/program.d.ts.map +1 -0
  56. package/build/src/backends/program.js +1308 -0
  57. package/build/src/backends/python.js +7 -1
  58. package/build/src/backends/ruby.d.ts.map +1 -1
  59. package/build/src/backends/ruby.js +310 -24
  60. package/build/src/backends/rust-runtime.d.ts +7 -0
  61. package/build/src/backends/rust-runtime.d.ts.map +1 -0
  62. package/build/src/backends/rust-runtime.js +32 -0
  63. package/build/src/backends/rust-validation.d.ts +9 -0
  64. package/build/src/backends/rust-validation.d.ts.map +1 -0
  65. package/build/src/backends/rust-validation.js +78 -0
  66. package/build/src/backends/rust.d.ts +24 -0
  67. package/build/src/backends/rust.d.ts.map +1 -0
  68. package/build/src/backends/rust.js +344 -0
  69. package/build/src/backends/scalars.d.ts +14 -6
  70. package/build/src/backends/scalars.d.ts.map +1 -1
  71. package/build/src/backends/scalars.js +167 -17
  72. package/build/src/backends/shared.d.ts +23 -4
  73. package/build/src/backends/shared.d.ts.map +1 -1
  74. package/build/src/backends/shared.js +2158 -62
  75. package/build/src/backends/stdlib-support.d.ts +14 -0
  76. package/build/src/backends/stdlib-support.d.ts.map +1 -0
  77. package/build/src/backends/stdlib-support.js +237 -0
  78. package/build/src/backends/swift-runtime.d.ts +3 -0
  79. package/build/src/backends/swift-runtime.d.ts.map +1 -0
  80. package/build/src/backends/swift-runtime.js +14 -0
  81. package/build/src/backends/swift.d.ts +8 -0
  82. package/build/src/backends/swift.d.ts.map +1 -0
  83. package/build/src/backends/swift.js +127 -0
  84. package/build/src/backends/typescript.d.ts.map +1 -1
  85. package/build/src/backends/typescript.js +344 -24
  86. package/build/src/backends/wasm.d.ts +43 -0
  87. package/build/src/backends/wasm.d.ts.map +1 -0
  88. package/build/src/backends/wasm.js +2164 -0
  89. package/build/src/backends/writer.d.ts +235 -4
  90. package/build/src/backends/writer.d.ts.map +1 -1
  91. package/build/src/backends/writer.js +468 -3
  92. package/build/src/diagnostic.d.ts +4 -3
  93. package/build/src/diagnostic.d.ts.map +1 -1
  94. package/build/src/diagnostic.js +5 -3
  95. package/build/src/frontends/c.d.ts.map +1 -1
  96. package/build/src/frontends/c.js +5 -3
  97. package/build/src/frontends/cpp.d.ts.map +1 -1
  98. package/build/src/frontends/cpp.js +5 -3
  99. package/build/src/frontends/csharp.d.ts.map +1 -1
  100. package/build/src/frontends/csharp.js +7 -3
  101. package/build/src/frontends/dart.d.ts +10 -0
  102. package/build/src/frontends/dart.d.ts.map +1 -0
  103. package/build/src/frontends/dart.js +913 -0
  104. package/build/src/frontends/index.d.ts +3 -1
  105. package/build/src/frontends/index.d.ts.map +1 -1
  106. package/build/src/frontends/index.js +15 -5
  107. package/build/src/frontends/java.d.ts +110 -1
  108. package/build/src/frontends/java.d.ts.map +1 -1
  109. package/build/src/frontends/java.js +1725 -70
  110. package/build/src/frontends/javascript-typescript.d.ts +69 -1
  111. package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
  112. package/build/src/frontends/javascript-typescript.js +1825 -93
  113. package/build/src/frontends/kotlin.d.ts +10 -0
  114. package/build/src/frontends/kotlin.d.ts.map +1 -0
  115. package/build/src/frontends/kotlin.js +685 -0
  116. package/build/src/frontends/php.d.ts +118 -1
  117. package/build/src/frontends/php.d.ts.map +1 -1
  118. package/build/src/frontends/php.js +1583 -76
  119. package/build/src/frontends/program.d.ts +74 -0
  120. package/build/src/frontends/program.d.ts.map +1 -0
  121. package/build/src/frontends/program.js +989 -0
  122. package/build/src/frontends/ruby.d.ts +68 -1
  123. package/build/src/frontends/ruby.d.ts.map +1 -1
  124. package/build/src/frontends/ruby.js +1446 -101
  125. package/build/src/frontends/rust-parser.d.ts +11 -0
  126. package/build/src/frontends/rust-parser.d.ts.map +1 -0
  127. package/build/src/frontends/rust-parser.js +15 -0
  128. package/build/src/frontends/rust.d.ts +14 -0
  129. package/build/src/frontends/rust.d.ts.map +1 -0
  130. package/build/src/frontends/rust.js +715 -0
  131. package/build/src/frontends/scalars.d.ts +10 -0
  132. package/build/src/frontends/scalars.d.ts.map +1 -1
  133. package/build/src/frontends/scalars.js +28 -2
  134. package/build/src/frontends/swift.d.ts +10 -0
  135. package/build/src/frontends/swift.d.ts.map +1 -0
  136. package/build/src/frontends/swift.js +860 -0
  137. package/build/src/frontends/types.d.ts +186 -0
  138. package/build/src/frontends/types.d.ts.map +1 -0
  139. package/build/src/frontends/types.js +634 -0
  140. package/build/src/language-registry.d.ts +46 -4
  141. package/build/src/language-registry.d.ts.map +1 -1
  142. package/build/src/language-registry.js +167 -13
  143. package/build/src/mapping.d.ts +2 -2
  144. package/build/src/mapping.d.ts.map +1 -1
  145. package/build/src/mapping.js +31 -10
  146. package/build/src/semantic/capabilities.d.ts +66 -0
  147. package/build/src/semantic/capabilities.d.ts.map +1 -0
  148. package/build/src/semantic/capabilities.js +649 -0
  149. package/build/src/semantic/effects.d.ts +26 -0
  150. package/build/src/semantic/effects.d.ts.map +1 -0
  151. package/build/src/semantic/effects.js +764 -0
  152. package/build/src/semantic/generics.d.ts +23 -0
  153. package/build/src/semantic/generics.d.ts.map +1 -0
  154. package/build/src/semantic/generics.js +61 -0
  155. package/build/src/semantic/lifetime.d.ts +37 -0
  156. package/build/src/semantic/lifetime.d.ts.map +1 -0
  157. package/build/src/semantic/lifetime.js +527 -0
  158. package/build/src/semantic/provenance.d.ts +9 -2
  159. package/build/src/semantic/provenance.d.ts.map +1 -1
  160. package/build/src/semantic/provenance.js +462 -20
  161. package/build/src/semantic/scalars.d.ts +11 -0
  162. package/build/src/semantic/scalars.d.ts.map +1 -1
  163. package/build/src/semantic/scalars.js +16 -1
  164. package/build/src/semantic/stdlib.d.ts +254 -0
  165. package/build/src/semantic/stdlib.d.ts.map +1 -0
  166. package/build/src/semantic/stdlib.js +880 -0
  167. package/build/src/semantic/types.d.ts +2952 -202
  168. package/build/src/semantic/types.d.ts.map +1 -1
  169. package/build/src/semantic/types.js +688 -15
  170. package/build/src/semantic/validate.d.ts +85 -5
  171. package/build/src/semantic/validate.d.ts.map +1 -1
  172. package/build/src/semantic/validate.js +2576 -98
  173. package/build/src/stdlib-facades.d.ts +304 -0
  174. package/build/src/stdlib-facades.d.ts.map +1 -0
  175. package/build/src/stdlib-facades.js +1167 -0
  176. package/build/src/stdlib-negotiation.d.ts +69 -0
  177. package/build/src/stdlib-negotiation.d.ts.map +1 -0
  178. package/build/src/stdlib-negotiation.js +403 -0
  179. package/build/src/stdlib-providers.d.ts +72 -0
  180. package/build/src/stdlib-providers.d.ts.map +1 -0
  181. package/build/src/stdlib-providers.js +339 -0
  182. package/build/src/toolchains.d.ts +55 -1
  183. package/build/src/toolchains.d.ts.map +1 -1
  184. package/build/src/toolchains.js +15 -4
  185. package/docs/architecture.md +45 -13
  186. package/docs/dart.md +60 -0
  187. package/docs/goals.md +3 -3
  188. package/docs/ios.md +121 -0
  189. package/docs/kotlin.md +42 -0
  190. package/docs/language-support.md +231 -26
  191. package/docs/multi-file-modules.md +52 -0
  192. package/docs/parser-qualification.md +121 -8
  193. package/docs/plans/2026-09-05-standard-library-portability.md +10 -10
  194. package/docs/rust.md +88 -0
  195. package/docs/source-maps.md +15 -7
  196. package/docs/standard-library-portability.md +39 -8
  197. package/docs/swift.md +44 -0
  198. package/docs/testing.md +366 -4
  199. package/docs/webassembly.md +52 -0
  200. package/index.js +63 -8
  201. package/node_modules/semantifold-tree-sitter-legacy-internal/README.md +2 -2
  202. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/LICENSE +21 -0
  203. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/README.md +45 -0
  204. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/binding.gyp +30 -0
  205. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/binding.cc +20 -0
  206. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/binding_test.js +9 -0
  207. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/index.d.ts +28 -0
  208. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/index.js +11 -0
  209. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/grammar.js +1630 -0
  210. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/package.json +64 -0
  211. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/darwin-arm64/tree-sitter-rust.node +0 -0
  212. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/darwin-x64/tree-sitter-rust.node +0 -0
  213. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/linux-arm64/tree-sitter-rust.node +0 -0
  214. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/linux-x64/tree-sitter-rust.node +0 -0
  215. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/win32-arm64/tree-sitter-rust.node +0 -0
  216. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/win32-x64/tree-sitter-rust.node +0 -0
  217. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/highlights.scm +159 -0
  218. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/injections.scm +9 -0
  219. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/tags.scm +60 -0
  220. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/grammar.json +9405 -0
  221. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/node-types.json +5399 -0
  222. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/parser.c +192569 -0
  223. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/scanner.c +393 -0
  224. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/alloc.h +54 -0
  225. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/array.h +290 -0
  226. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/parser.h +266 -0
  227. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/tree-sitter-rust.wasm +0 -0
  228. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/tree-sitter.json +53 -0
  229. package/node_modules/semantifold-tree-sitter-legacy-internal/package.json +3 -2
  230. package/node_modules/semantifold-tree-sitter-legacy-internal/src/c.js +5 -4
  231. package/package.json +5 -2
  232. package/src/artifact-path.js +47 -2
  233. package/src/artifacts.js +67 -0
  234. package/src/backends/c-validation.js +6 -1
  235. package/src/backends/cpp-validation.js +6 -1
  236. package/src/backends/csharp.js +3 -0
  237. package/src/backends/dart-runtime.js +41 -0
  238. package/src/backends/dart.js +326 -0
  239. package/src/backends/effects.js +705 -0
  240. package/src/backends/go.js +14 -2
  241. package/src/backends/identifiers.js +160 -9
  242. package/src/backends/index.js +27 -2
  243. package/src/backends/ios.js +1612 -0
  244. package/src/backends/java.js +454 -36
  245. package/src/backends/javascript.js +389 -23
  246. package/src/backends/kotlin-runtime.js +29 -0
  247. package/src/backends/kotlin.js +141 -0
  248. package/src/backends/native-validation.js +1 -1
  249. package/src/backends/ordered-expressions.js +40 -6
  250. package/src/backends/php-stdlib-support.js +239 -0
  251. package/src/backends/php.js +469 -32
  252. package/src/backends/program.js +1407 -0
  253. package/src/backends/python.js +3 -0
  254. package/src/backends/ruby.js +326 -23
  255. package/src/backends/rust-runtime.js +34 -0
  256. package/src/backends/rust-validation.js +75 -0
  257. package/src/backends/rust.js +335 -0
  258. package/src/backends/scalars.js +141 -16
  259. package/src/backends/shared.js +2304 -124
  260. package/src/backends/stdlib-support.js +234 -0
  261. package/src/backends/swift-runtime.js +14 -0
  262. package/src/backends/swift.js +132 -0
  263. package/src/backends/typescript.js +349 -23
  264. package/src/backends/wasm.js +2208 -0
  265. package/src/backends/writer.js +520 -2
  266. package/src/diagnostic.js +4 -2
  267. package/src/frontends/c.js +4 -2
  268. package/src/frontends/cpp.js +4 -2
  269. package/src/frontends/csharp.js +4 -2
  270. package/src/frontends/dart.js +963 -0
  271. package/src/frontends/index.js +14 -4
  272. package/src/frontends/java.js +1977 -69
  273. package/src/frontends/javascript-typescript.js +2046 -102
  274. package/src/frontends/kotlin.js +713 -0
  275. package/src/frontends/php.js +1725 -79
  276. package/src/frontends/program.js +1062 -0
  277. package/src/frontends/ruby.js +1624 -98
  278. package/src/frontends/rust-parser.js +18 -0
  279. package/src/frontends/rust.js +714 -0
  280. package/src/frontends/scalars.js +31 -1
  281. package/src/frontends/swift.js +893 -0
  282. package/src/frontends/types.js +750 -0
  283. package/src/language-registry.js +172 -12
  284. package/src/mapping.js +33 -9
  285. package/src/semantic/capabilities.js +671 -0
  286. package/src/semantic/effects.js +737 -0
  287. package/src/semantic/generics.js +58 -0
  288. package/src/semantic/lifetime.js +517 -0
  289. package/src/semantic/provenance.js +412 -19
  290. package/src/semantic/scalars.js +17 -0
  291. package/src/semantic/stdlib.js +921 -0
  292. package/src/semantic/types.js +765 -14
  293. package/src/semantic/validate.js +2934 -209
  294. package/src/stdlib-facades.js +1211 -0
  295. package/src/stdlib-negotiation.js +446 -0
  296. package/src/stdlib-providers.js +377 -0
  297. package/src/toolchains.js +16 -3
@@ -6,7 +6,10 @@ import {locationFromOffsets, moduleLocation} from "../semantic/location.js"
6
6
  import {withAdaptedOperation} from "../semantic/operators.js"
7
7
  import {withParserRanges} from "../semantic/provenance.js"
8
8
  import {hasOnlyUnicodeScalars} from "../semantic/scalars.js"
9
- import {sourceScalarType} from "./scalars.js"
9
+ import {isTask034ResourceProbe} from "../semantic/capabilities.js"
10
+ import {hasExactEffectSupport} from "../backends/effects.js"
11
+ import {requireSourceReturnType, sourceScalarType} from "./scalars.js"
12
+ import {capabilityFunctionSignatures, instantiatedRecordFieldType, iterationBindingType, iterationOperandType, knownCallReturnType, listType, mapType, optionalType, orderedMapType, ownedResourceType, recordType, referenceType, sameValueType, typeVariable} from "./types.js"
10
13
 
11
14
  /** @type {Readonly<Record<string, string>>} */
12
15
  const simpleStringEscapes = Object.freeze({
@@ -33,6 +36,32 @@ const javaBinaryOperations = new Map([
33
36
  [">", "GreaterThan"],
34
37
  [">=", "GreaterThanOrEqual"]
35
38
  ])
39
+ const referenceMethodHooks = new Set(["clone", "equals", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait"])
40
+
41
+ /**
42
+ * @typedef JavaConversionContext
43
+ * @property {Map<string, import("../semantic/types.js").SemanticBindingType>} bindings - Explicitly typed visible bindings.
44
+ * @property {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Reference classes by identity.
45
+ * @property {import("../semantic/types.js").ClassDeclaration} [currentClass] - Enclosing reference class.
46
+ * @property {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Errors by source name.
47
+ * @property {Map<string, import("../semantic/types.js").ErrorDeclaration>} errors - Errors by identity.
48
+ * @property {Set<string>} erasedBindingNames - Source bindings consumed by exact semantic folds in this scope.
49
+ * @property {Map<string, JavaFunctionSignature>} functions - Explicit module function signatures.
50
+ * @property {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
51
+ * @property {Map<string, import("../semantic/types.js").RecordDeclaration>} records - Records by identity.
52
+ * @property {import("../semantic/types.js").SemanticFunctionReturnType | undefined} returnType - Enclosing return type.
53
+ * @property {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
54
+ */
55
+
56
+ /**
57
+ * @typedef JavaFunctionSignature
58
+ * @property {import("../semantic/types.js").SourceLocation} location - Complete declaration location.
59
+ * @property {import("../semantic/types.js").SourceLocation} nameLocation - Parser-owned name location.
60
+ * @property {string} name - Function name.
61
+ * @property {import("../semantic/types.js").Parameter[]} parameters - Semantic parameters.
62
+ * @property {import("../semantic/types.js").SemanticFunctionReturnType} returnType - Semantic return type.
63
+ * @property {import("../semantic/types.js").TypeParameter[]} [typeParameters] - Declaration-scoped parameters.
64
+ */
36
65
 
37
66
  /**
38
67
  * Returns all direct child syntax nodes.
@@ -110,14 +139,46 @@ function nodeText(node, source) {
110
139
  return source.slice(node.from, node.to)
111
140
  }
112
141
 
142
+ /**
143
+ * Proves an exact parser-structured Java qualified identifier.
144
+ * @param {import("@lezer/common").SyntaxNode} node - Identifier or field-access node.
145
+ * @param {string} source - Complete source.
146
+ * @returns {string[] | undefined} Exact structural name parts.
147
+ */
148
+ function qualifiedNameParts(node, source) {
149
+ if (["Identifier", "TypeName"].includes(node.name)) return [nodeText(node, source)]
150
+ if (!["FieldAccess", "ScopedTypeName"].includes(node.name)) return undefined
151
+ const children = structuralChildren(node)
152
+
153
+ if (children.length != 2) return undefined
154
+ const left = qualifiedNameParts(children[0], source)
155
+ const right = qualifiedNameParts(children[1], source)
156
+
157
+ return left && right ? [...left, ...right] : undefined
158
+ }
159
+
160
+ /**
161
+ * Checks the only accepted Java Optional type/factory symbol spellings.
162
+ * @param {import("@lezer/common").SyntaxNode} node - Parser-qualified symbol node.
163
+ * @param {string} source - Complete source.
164
+ * @returns {boolean} Whether the node identifies java.util.Optional or Optional.
165
+ */
166
+ function isJavaOptionalSymbol(node, source) {
167
+ const parts = qualifiedNameParts(node, source)
168
+
169
+ return parts?.join(".") == "java.util.Optional" || parts?.join(".") == "Optional"
170
+ }
171
+
113
172
  /**
114
173
  * Converts a supported Java expression node.
115
174
  * @param {import("@lezer/common").SyntaxNode} node - Lezer expression.
116
175
  * @param {string} filename - Source filename.
117
176
  * @param {string} source - Complete source.
177
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
178
+ * @param {import("../semantic/types.js").SemanticValueType} [expectedType] - Contextual semantic type.
118
179
  * @returns {import("../semantic/types.js").Expression} Semantic expression.
119
180
  */
120
- function convertExpression(node, filename, source) {
181
+ function convertExpression(node, filename, source, context, expectedType) {
121
182
  const location = nodeLocation(node, filename, source)
122
183
 
123
184
  if (node.name == "ParenthesizedExpression") {
@@ -127,13 +188,38 @@ function convertExpression(node, filename, source) {
127
188
  return unsupportedSyntax("java", "unsupported parenthesized expression", location)
128
189
  }
129
190
 
130
- return convertExpression(children[0], filename, source)
191
+ return convertExpression(children[0], filename, source, context, expectedType)
131
192
  }
132
193
 
133
194
  if (node.name == "Identifier") {
134
195
  return withParserRanges({kind: /** @type {const} */ ("IdentifierExpression"), location, name: nodeText(node, source)}, {name: location})
135
196
  }
136
197
 
198
+ if (node.name == "this") {
199
+ if (!context.currentClass) return unsupportedSyntax("java", "receiver outside a reference class", location)
200
+ return withParserRanges({classId: /** @type {string} */ (context.currentClass.id),
201
+ kind: /** @type {const} */ ("ReceiverExpression"), location}, {receiver: location})
202
+ }
203
+
204
+ if (node.name == "FieldAccess") {
205
+ const children = structuralChildren(node)
206
+ const receiver = children[0]
207
+ const member = children[1]
208
+
209
+ if (children.length != 2 || receiver?.name != "this" || member?.name != "Identifier" || !context.currentClass) {
210
+ if (receiver && knownExpressionType(receiver, filename, source, context)?.kind == "ReferenceType") {
211
+ return unsupportedSyntax("java", "private reference-class field access", location)
212
+ }
213
+ return unsupportedSyntax("java", "field access outside the declaring receiver", location)
214
+ }
215
+ const field = context.currentClass.fields.find((candidate) => candidate.name == nodeText(member, source))
216
+
217
+ if (!field) return unsupportedSyntax("java", `unknown private field '${nodeText(member, source)}'`, nodeLocation(member, filename, source))
218
+ return withParserRanges({field: /** @type {string} */ (field.id), kind: /** @type {const} */ ("PrivateFieldRead"), location,
219
+ receiver: /** @type {import("../semantic/types.js").ReceiverExpression} */ (
220
+ convertExpression(receiver, filename, source, context))}, {member: nodeLocation(member, filename, source)})
221
+ }
222
+
137
223
  if (node.name == "IntegerLiteral") {
138
224
  const value = Number(nodeText(node, source))
139
225
 
@@ -150,6 +236,50 @@ function convertExpression(node, filename, source) {
150
236
  return withParserRanges({kind: /** @type {const} */ ("StringLiteral"), location, value: decodeStringLiteral(node, filename, source)}, {literal: location})
151
237
  }
152
238
 
239
+ if (node.name == "null") return unsupportedSyntax("java", "null reference", location)
240
+
241
+ if (node.name == "ObjectCreationExpression") {
242
+ const children = structuralChildren(node)
243
+ const typeNode = children.find((child) => ["GenericType", "TypeName", "ScopedTypeName"].includes(child.name))
244
+ const nameNode = typeNode?.name == "GenericType" ? structuralChildren(typeNode)[0] : typeNode
245
+ const argumentList = node.getChild("ArgumentList")
246
+ const declaration = nameNode ? context.recordNames.get(nodeText(nameNode, source)) : undefined
247
+
248
+ if (!typeNode || !nameNode || !argumentList || !declaration || node.getChild("ClassBody")) {
249
+ return unsupportedSyntax("java", "construction of an unknown nominal or anonymous class", location)
250
+ }
251
+ const argumentNodes = structuralChildren(argumentList)
252
+ if (declaration.kind == "ClassDeclaration") {
253
+ if (typeNode.name == "GenericType") return unsupportedSyntax("java", "generic reference construction", nodeLocation(typeNode, filename, source))
254
+ const classLocation = nodeLocation(nameNode, filename, source)
255
+
256
+ return withParserRanges({arguments: argumentNodes.map((argument, index) =>
257
+ convertExpression(argument, filename, source, context, declaration.constructor.parameters[index]?.type)),
258
+ kind: /** @type {const} */ ("ReferenceConstruction"), location,
259
+ reference: referenceType(/** @type {string} */ (declaration.id), classLocation)}, {class: classLocation})
260
+ }
261
+ if (declaration.kind == "EffectResourceDeclaration") {
262
+ return unsupportedSyntax("java", "owned resource construction", nodeLocation(typeNode, filename, source))
263
+ }
264
+ const typeArgumentsNode = typeNode.getChild("TypeArguments")
265
+
266
+ if (typeNode.name == "GenericType" && !typeArgumentsNode) {
267
+ return unsupportedSyntax("java", "malformed generic record construction", nodeLocation(typeNode, filename, source))
268
+ }
269
+ const typeArguments = typeArgumentsNode
270
+ ? structuralChildren(typeArgumentsNode).map((argument) => convertJavaTypeArgument(
271
+ argument, `Record '${declaration.name}' application`, location, filename, source, context.recordNames, context.typeParameters))
272
+ : undefined
273
+ return withParserRanges({
274
+ arguments: argumentNodes.map((argument, index) =>
275
+ convertExpression(argument, filename, source, context,
276
+ instantiatedRecordFieldType(declaration, typeArguments, index))),
277
+ kind: /** @type {const} */ ("RecordConstruction"),
278
+ location,
279
+ record: recordType(/** @type {string} */ (declaration.id), nodeLocation(nameNode, filename, source), typeArguments)
280
+ }, {record: nodeLocation(nameNode, filename, source)})
281
+ }
282
+
153
283
  if (node.name == "UnaryExpression") {
154
284
  const children = structuralChildren(node)
155
285
  const operatorNode = node.getChild("LogicOp") ?? node.getChild("ArithOp")
@@ -160,14 +290,15 @@ function convertExpression(node, filename, source) {
160
290
  return unsupportedSyntax("java", "unary expression", location)
161
291
  }
162
292
 
163
- if (operator == "!" && operand.name == "MethodInvocation" && isEqualsInvocation(operand, source)) {
164
- return convertStringEquality(operand, true, location, nodeLocation(operatorNode, filename, source), filename, source)
293
+ if (operator == "!" && operand.name == "MethodInvocation" && isEqualsInvocation(operand, source) &&
294
+ !isZeroArgumentRecordMemberInvocation(operand, filename, source, context)) {
295
+ return convertStringEquality(operand, true, location, nodeLocation(operatorNode, filename, source), filename, source, context)
165
296
  }
166
297
 
167
298
  const semantic = withAdaptedOperation(withParserRanges({
168
299
  kind: "UnaryExpression",
169
300
  location,
170
- operand: convertExpression(operand, filename, source)
301
+ operand: convertExpression(operand, filename, source, context)
171
302
  }, {operator: nodeLocation(operatorNode, filename, source)}), operator == "!" ? "Not" : "Negate")
172
303
 
173
304
  return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
@@ -190,33 +321,222 @@ function convertExpression(node, filename, source) {
190
321
 
191
322
  const semantic = withAdaptedOperation(withParserRanges({
192
323
  kind: "BinaryExpression",
193
- left: convertExpression(operands[0], filename, source),
324
+ left: convertExpression(operands[0], filename, source, context),
194
325
  location,
195
- right: convertExpression(operands[1], filename, source)
326
+ right: convertExpression(operands[1], filename, source, context)
196
327
  }, {operator: nodeLocation(operatorNode, filename, source)}), /** @type {import("../semantic/operators.js").AdaptedOperation} */ (javaBinaryOperations.get(operator)))
197
328
 
198
329
  return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
199
330
  }
200
331
 
201
332
  if (node.name == "MethodInvocation") {
202
- if (isEqualsInvocation(node, source)) {
333
+ if (isEqualsInvocation(node, source) && !isZeroArgumentRecordMemberInvocation(node, filename, source, context)) {
203
334
  const methodName = requiredChild(node, "MethodName", filename, source)
204
335
 
205
- return convertStringEquality(node, false, location, nodeLocation(methodName, filename, source), filename, source)
336
+ return convertStringEquality(node, false, location, nodeLocation(methodName, filename, source), filename, source, context)
206
337
  }
207
338
 
208
- const unsupportedReceiver = structuralChildren(node).find((child) => child.name != "MethodName" && child.name != "ArgumentList")
209
-
210
- if (unsupportedReceiver) return unsupportedSyntax("java", "method invocation receiver", nodeLocation(unsupportedReceiver, filename, source))
211
-
212
339
  const methodName = requiredChild(node, "MethodName", filename, source)
213
340
  const argumentList = requiredChild(node, "ArgumentList", filename, source)
214
341
  const argumentNodes = structuralChildren(argumentList)
342
+ const receiver = structuralChildren(node).find((child) => child.name != "MethodName" && child.name != "ArgumentList")
343
+ const method = nodeText(methodName, source)
344
+ const receiverText = receiver ? nodeText(receiver, source) : ""
345
+
346
+ if (receiver?.name == "Identifier" && method == "getMessage" && argumentNodes.length == 0 &&
347
+ context.bindings.get(receiverText)?.kind == "ErrorType") {
348
+ const receiverLocation = nodeLocation(receiver, filename, source)
349
+
350
+ return withParserRanges({
351
+ kind: /** @type {const} */ ("ErrorMessageRead"),
352
+ location,
353
+ receiver: withParserRanges({kind: /** @type {const} */ ("IdentifierExpression"), location: receiverLocation, name: receiverText}, {
354
+ name: receiverLocation
355
+ })
356
+ }, {member: nodeLocation(methodName, filename, source)})
357
+ }
358
+
359
+ if (receiver && ["getClass", "hashCode", "toString", "clone", "notify", "notifyAll", "wait"].includes(method)) {
360
+ return unsupportedSyntax("java", "reflective or Object member access", nodeLocation(methodName, filename, source))
361
+ }
362
+
363
+ if (receiver && knownExpressionType(receiver, filename, source, context)?.kind == "ReferenceType") {
364
+ const receiverType = /** @type {import("../semantic/types.js").ReferenceType} */ (
365
+ knownExpressionType(receiver, filename, source, context))
366
+ const declaredMethod = context.classes.get(receiverType.declarationId)?.methods.find((candidate) => candidate.name == method)
367
+
368
+ return withParserRanges({arguments: argumentNodes.map((argument, index) =>
369
+ convertExpression(argument, filename, source, context, declaredMethod?.parameters[index]?.type)),
370
+ kind: /** @type {const} */ ("MethodCallExpression"), location, method: declaredMethod?.id ?? method,
371
+ receiver: convertExpression(receiver, filename, source, context)}, {member: nodeLocation(methodName, filename, source)})
372
+ }
373
+
374
+ if (receiver && isJavaOptionalSymbol(receiver, source)) {
375
+ const factoryLocation = nodeLocation(methodName, filename, source)
376
+
377
+ if (method == "empty" && argumentNodes.length == 0) {
378
+ return withParserRanges({kind: /** @type {const} */ ("OptionalNone"), location}, {
379
+ absence: factoryLocation,
380
+ factory: factoryLocation
381
+ })
382
+ }
383
+ if (method == "of" && argumentNodes.length == 1) {
384
+ return withParserRanges({
385
+ kind: /** @type {const} */ ("OptionalSome"),
386
+ location,
387
+ value: convertExpression(argumentNodes[0], filename, source, context,
388
+ expectedType?.kind == "OptionalType" ? expectedType.valueType : undefined)
389
+ }, {factory: factoryLocation, some: factoryLocation})
390
+ }
391
+
392
+ return unsupportedSyntax("java", `unsupported Optional.${method}`, factoryLocation)
393
+ }
394
+ if (receiver?.name == "Identifier" && context.bindings.get(receiverText)?.kind == "OptionalType") {
395
+ const operand = /** @type {import("../semantic/types.js").IdentifierExpression} */ (
396
+ convertExpression(receiver, filename, source, context)
397
+ )
398
+ const operationLocation = nodeLocation(methodName, filename, source)
399
+
400
+ if (method == "isPresent" && argumentNodes.length == 0) {
401
+ return withParserRanges({kind: /** @type {const} */ ("OptionalIsPresent"), location, operand}, {
402
+ operator: operationLocation
403
+ })
404
+ }
405
+ if (method == "get" && argumentNodes.length == 0) {
406
+ return withParserRanges({kind: /** @type {const} */ ("OptionalUnwrap"), location, operand}, {
407
+ operator: operationLocation,
408
+ unwrap: operationLocation
409
+ })
410
+ }
411
+
412
+ return unsupportedSyntax("java", `unsupported Optional.${method}`, operationLocation)
413
+ }
414
+
415
+ if (receiver && method == "of" && receiverText == "java.util.List") {
416
+ const elementType = expectedType?.kind == "ListType" ? expectedType.elementType : undefined
417
+
418
+ return withParserRanges({
419
+ elements: argumentNodes.map((argument) => convertExpression(argument, filename, source, context, elementType)),
420
+ kind: /** @type {const} */ ("ListLiteral"),
421
+ location
422
+ }, {factory: nodeLocation(methodName, filename, source)})
423
+ }
424
+ if (receiver && method == "of" && receiverText == "java.util.Map") {
425
+ if (argumentNodes.length % 2 != 0) return unsupportedSyntax("java", "Map.of odd argument count", location)
426
+ if (argumentNodes.length > 20) {
427
+ return unsupportedSyntax("java", "java.util.Map.of supports at most ten entries", nodeLocation(methodName, filename, source))
428
+ }
429
+ const entries = []
430
+ const valueType = expectedType?.kind == "MapType" ? expectedType.valueType : undefined
431
+
432
+ for (let index = 0; index < argumentNodes.length; index += 2) {
433
+ const keyNode = argumentNodes[index]
434
+
435
+ if (keyNode.name != "StringLiteral") {
436
+ return unsupportedSyntax("java", "map key other than a string literal", nodeLocation(keyNode, filename, source))
437
+ }
438
+ const separator = directChildren(argumentList).find((child) => child.name == "," &&
439
+ child.from >= keyNode.to && child.to <= argumentNodes[index + 1].from)
440
+
441
+ if (!separator) throw new Error("Lezer omitted the Java Map.of entry separator.")
442
+ entries.push(withParserRanges({
443
+ key: /** @type {import("../semantic/types.js").StringLiteral} */ (convertExpression(keyNode, filename, source, context)),
444
+ kind: /** @type {const} */ ("MapEntry"),
445
+ location: locationFromOffsets(filename, source, keyNode.from, argumentNodes[index + 1].to),
446
+ value: convertExpression(argumentNodes[index + 1], filename, source, context, valueType)
447
+ }, {operator: nodeLocation(separator, filename, source)}))
448
+ }
449
+
450
+ return withParserRanges({entries, kind: /** @type {const} */ ("MapLiteral"), location}, {
451
+ factory: nodeLocation(methodName, filename, source)
452
+ })
453
+ }
454
+ if (receiver && method == "get" && argumentNodes.length == 1) {
455
+ const receiverType = receiver.name == "Identifier" ? context.bindings.get(nodeText(receiver, source)) : undefined
456
+
457
+ if (receiverType?.kind == "MapType" || receiverType?.kind == "OrderedMapType" ||
458
+ (!receiverType && argumentNodes[0].name == "StringLiteral")) {
459
+ return withParserRanges({
460
+ collection: convertExpression(receiver, filename, source, context),
461
+ key: convertExpression(argumentNodes[0], filename, source, context),
462
+ kind: /** @type {const} */ ("MapLookupExpression"),
463
+ location,
464
+ totality: /** @type {const} */ ("proven")
465
+ }, {operator: nodeLocation(methodName, filename, source)})
466
+ }
467
+
468
+ return withParserRanges({
469
+ collection: convertExpression(receiver, filename, source, context),
470
+ index: convertExpression(argumentNodes[0], filename, source, context),
471
+ kind: /** @type {const} */ ("ListIndexExpression"),
472
+ location,
473
+ totality: /** @type {const} */ ("fail-on-absence")
474
+ }, {operator: nodeLocation(methodName, filename, source)})
475
+ }
476
+ if (receiver && method == "size" && argumentNodes.length == 0) {
477
+ const receiverType = knownExpressionType(receiver, filename, source, context)
478
+
479
+ if (receiverType?.kind == "RecordType") {
480
+ return withParserRanges({
481
+ field: method,
482
+ kind: /** @type {const} */ ("MemberRead"),
483
+ location,
484
+ receiver: convertExpression(receiver, filename, source, context)
485
+ }, {member: nodeLocation(methodName, filename, source)})
486
+ }
487
+ return withParserRanges({
488
+ collection: convertExpression(receiver, filename, source, context),
489
+ kind: /** @type {const} */ ("CollectionSizeExpression"),
490
+ location
491
+ }, {operator: nodeLocation(methodName, filename, source)})
492
+ }
493
+
494
+ if (receiver && receiverText == "Main" && context.currentClass && !context.bindings.has(receiverText)) {
495
+ const localSignature = context.functions.get(method)
496
+
497
+ if (localSignature) {
498
+ const arguments_ = argumentNodes.map((child, index) =>
499
+ convertExpression(child, filename, source, context, localSignature.parameters[index]?.type))
500
+
501
+ return withParserRanges({arguments: arguments_, callee: method, kind: /** @type {const} */ ("CallExpression"), location}, {
502
+ callee: nodeLocation(methodName, filename, source)
503
+ })
504
+ }
505
+ }
506
+
507
+ if (receiver) {
508
+ const qualifiedName = `${receiverText}.${method}`
509
+ const qualifiedSignature = context.functions.get(qualifiedName)
510
+
511
+ if (qualifiedSignature) {
512
+ const arguments_ = argumentNodes.map((child, index) =>
513
+ convertExpression(child, filename, source, context, qualifiedSignature.parameters[index]?.type))
514
+
515
+ return withParserRanges({arguments: arguments_, callee: qualifiedName, kind: /** @type {const} */ ("CallExpression"), location}, {
516
+ callee: nodeLocation(methodName, filename, source)
517
+ })
518
+ }
519
+ }
520
+
521
+ if (receiver && argumentNodes.length == 0) {
522
+ return withParserRanges({
523
+ field: method,
524
+ kind: /** @type {const} */ ("MemberRead"),
525
+ location,
526
+ receiver: convertExpression(receiver, filename, source, context)
527
+ }, {member: nodeLocation(methodName, filename, source)})
528
+ }
529
+
530
+ const unsupportedReceiver = receiver
531
+
532
+ if (unsupportedReceiver) return unsupportedSyntax("java", "method invocation receiver", nodeLocation(unsupportedReceiver, filename, source))
215
533
  const unsupportedArgument = argumentNodes.find((child) => !isSupportedExpressionNode(child))
216
534
 
217
535
  if (unsupportedArgument) return unsupportedSyntax("java", `method argument ${unsupportedArgument.name}`, nodeLocation(unsupportedArgument, filename, source))
218
536
 
219
- const arguments_ = argumentNodes.map((child) => convertExpression(child, filename, source))
537
+ const signature = context.functions.get(nodeText(methodName, source))
538
+ const arguments_ = argumentNodes.map((child, index) =>
539
+ convertExpression(child, filename, source, context, signature?.parameters[index]?.type))
220
540
 
221
541
  return withParserRanges({arguments: arguments_, callee: nodeText(methodName, source), kind: /** @type {const} */ ("CallExpression"), location}, {
222
542
  callee: nodeLocation(methodName, filename, source)
@@ -239,6 +559,22 @@ function isEqualsInvocation(node, source) {
239
559
  return methodName != null && nodeText(methodName, source) == "equals" && receivers.length == 1
240
560
  }
241
561
 
562
+ /**
563
+ * Distinguishes a zero-argument nominal field accessor from Java string equality syntax.
564
+ * @param {import("@lezer/common").SyntaxNode} node - Method invocation node.
565
+ * @param {string} filename - Source filename.
566
+ * @param {string} source - Complete source.
567
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
568
+ * @returns {boolean} Whether this is a known record receiver with no arguments.
569
+ */
570
+ function isZeroArgumentRecordMemberInvocation(node, filename, source, context) {
571
+ const argumentList = node.getChild("ArgumentList")
572
+ const receiver = structuralChildren(node).find((child) => child.name != "MethodName" && child.name != "ArgumentList")
573
+
574
+ return argumentList != null && structuralChildren(argumentList).length == 0 && receiver != null &&
575
+ knownExpressionType(receiver, filename, source, context)?.kind == "RecordType"
576
+ }
577
+
242
578
  /**
243
579
  * Converts Java `.equals` or its directly negated form without source-text inference.
244
580
  * @param {import("@lezer/common").SyntaxNode} node - Parser method invocation.
@@ -247,9 +583,10 @@ function isEqualsInvocation(node, source) {
247
583
  * @param {import("../semantic/types.js").SourceLocation} operatorLocation - Parser-owned operator/method location.
248
584
  * @param {string} filename - Source filename.
249
585
  * @param {string} source - Complete source.
586
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
250
587
  * @returns {import("../semantic/types.js").Expression} Adapted equality expression.
251
588
  */
252
- function convertStringEquality(node, negated, location, operatorLocation, filename, source) {
589
+ function convertStringEquality(node, negated, location, operatorLocation, filename, source, context) {
253
590
  const argumentList = requiredChild(node, "ArgumentList", filename, source)
254
591
  const arguments_ = structuralChildren(argumentList)
255
592
  const receivers = structuralChildren(node).filter((child) => child.name != "MethodName" && child.name != "ArgumentList")
@@ -268,9 +605,9 @@ function convertStringEquality(node, negated, location, operatorLocation, filena
268
605
 
269
606
  const semantic = withAdaptedOperation(withParserRanges({
270
607
  kind: "BinaryExpression",
271
- left: convertExpression(receivers[0], filename, source),
608
+ left: convertExpression(receivers[0], filename, source, context),
272
609
  location,
273
- right: convertExpression(arguments_[0], filename, source)
610
+ right: convertExpression(arguments_[0], filename, source, context)
274
611
  }, ranges), negated ? "StringNotEqual" : "StringEqual")
275
612
 
276
613
  return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
@@ -290,6 +627,10 @@ function isSupportedExpressionNode(node) {
290
627
  "UnaryExpression",
291
628
  "BinaryExpression",
292
629
  "MethodInvocation",
630
+ "ObjectCreationExpression",
631
+ "FieldAccess",
632
+ "this",
633
+ "null",
293
634
  "ParenthesizedExpression"
294
635
  ].includes(node.name)
295
636
  }
@@ -397,9 +738,10 @@ function decodeStringLiteral(node, filename, source) {
397
738
  * @param {import("@lezer/common").SyntaxNode} statement - Java statement.
398
739
  * @param {string} filename - Source filename.
399
740
  * @param {string} source - Complete source.
741
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
400
742
  * @returns {import("../semantic/types.js").ReturnStatement} Semantic return.
401
743
  */
402
- function convertReturn(statement, filename, source) {
744
+ function convertReturn(statement, filename, source, context) {
403
745
  const location = nodeLocation(statement, filename, source)
404
746
 
405
747
  if (statement.name != "ReturnStatement") return unsupportedSyntax("java", statement.name, location)
@@ -407,17 +749,29 @@ function convertReturn(statement, filename, source) {
407
749
  const expressionNodes = directChildren(statement).filter((child) => child.name != "return" && child.name != ";")
408
750
  const expression = expressionNodes.length == 1 ? expressionNodes[0] : undefined
409
751
 
410
- if (!expression) {
752
+ if (expressionNodes.length > 1) {
411
753
  const unsupported = expressionNodes[0]
412
754
 
413
755
  return unsupportedSyntax(
414
756
  "java",
415
- unsupported?.name ?? "empty return",
757
+ unsupported?.name ?? "return expression",
416
758
  unsupported ? nodeLocation(unsupported, filename, source) : location
417
759
  )
418
760
  }
419
761
 
420
- return {expression: convertExpression(expression, filename, source), kind: "ReturnStatement", location}
762
+ return {
763
+ ...(expression ? {expression: convertExpression(
764
+ expression,
765
+ filename,
766
+ source,
767
+ context,
768
+ context.returnType?.kind == "TypeReference" && context.returnType.name == "void"
769
+ ? undefined
770
+ : /** @type {import("../semantic/types.js").SemanticValueType | undefined} */ (context.returnType)
771
+ )} : {}),
772
+ kind: "ReturnStatement",
773
+ location
774
+ }
421
775
  }
422
776
 
423
777
  /**
@@ -425,9 +779,10 @@ function convertReturn(statement, filename, source) {
425
779
  * @param {import("@lezer/common").SyntaxNode} statement - Java statement.
426
780
  * @param {string} filename - Source filename.
427
781
  * @param {string} source - Complete source.
428
- * @returns {import("../semantic/types.js").LocalStatement} Semantic local statement.
782
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
783
+ * @returns {import("../semantic/types.js").LocalStatement | import("../semantic/types.js").PrivateFieldWriteStatement} Semantic local or private-field statement.
429
784
  */
430
- function convertLocalStatement(statement, filename, source) {
785
+ function convertLocalStatement(statement, filename, source, context) {
431
786
  const location = nodeLocation(statement, filename, source)
432
787
 
433
788
  if (statement.name == "LocalVariableDeclaration") {
@@ -457,13 +812,21 @@ function convertLocalStatement(statement, filename, source) {
457
812
 
458
813
  const name = nodeText(definition, source)
459
814
 
815
+ if (context.erasedBindingNames.has(name)) {
816
+ return unsupportedSyntax("java", "local binding collides with a consumed source binding", nodeLocation(definition, filename, source))
817
+ }
818
+ const type = convertType(typeNode, `Local '${name}'`, location, filename, source, context.recordNames, context.typeParameters)
819
+ const initializer = convertExpression(initializerNodes[0], filename, source, context, type)
820
+
821
+ context.bindings.set(name, type)
822
+
460
823
  return withParserRanges({
461
- initializer: convertExpression(initializerNodes[0], filename, source),
824
+ initializer,
462
825
  kind: "LocalDeclaration",
463
826
  location,
464
827
  mutable: modifierChildren.length == 0,
465
828
  name,
466
- type: convertType(typeNode, `Local '${name}'`, location, filename, source)
829
+ type
467
830
  }, {name: nodeLocation(definition, filename, source), operator: nodeLocation(assignment, filename, source)})
468
831
  }
469
832
 
@@ -478,6 +841,23 @@ function convertLocalStatement(statement, filename, source) {
478
841
  const expression = children.at(-1)
479
842
 
480
843
  if (!operator || nodeText(operator, source) != "=") return unsupportedSyntax("java", "compound assignment", nodeLocation(assignment, filename, source))
844
+ if (target?.name == "FieldAccess" && context.currentClass) {
845
+ const targetChildren = structuralChildren(target)
846
+ const receiver = targetChildren[0]
847
+ const member = targetChildren[1]
848
+ const field = targetChildren.length == 2 && receiver?.name == "this" && member?.name == "Identifier"
849
+ ? context.currentClass.fields.find((candidate) => candidate.name == nodeText(member, source))
850
+ : undefined
851
+
852
+ if (!field || !expression || expression == target || expression == operator) {
853
+ return unsupportedSyntax("java", "private field write outside the declaring receiver", nodeLocation(target, filename, source))
854
+ }
855
+ return withParserRanges({expression: convertExpression(expression, filename, source, context, field.type),
856
+ field: /** @type {string} */ (field.id), kind: /** @type {const} */ ("PrivateFieldWriteStatement"), location,
857
+ receiver: /** @type {import("../semantic/types.js").ReceiverExpression} */ (
858
+ convertExpression(receiver, filename, source, context))},
859
+ {member: nodeLocation(member, filename, source), operator: nodeLocation(operator, filename, source)})
860
+ }
481
861
  if (!target || target.name != "Identifier") return unsupportedSyntax("java", target?.name ?? "assignment target", target ? nodeLocation(target, filename, source) : location)
482
862
  if (!expression || expression == target || expression == operator) return unsupportedSyntax("java", "assignment expression", nodeLocation(assignment, filename, source))
483
863
 
@@ -487,8 +867,13 @@ function convertLocalStatement(statement, filename, source) {
487
867
  name: nodeText(target, source)
488
868
  }, {name: nodeLocation(target, filename, source)})
489
869
 
870
+ const targetName = nodeText(target, source)
871
+ const bindingType = context.bindings.get(targetName)
872
+ const value = convertExpression(expression, filename, source, context,
873
+ bindingType?.kind == "ErrorType" ? undefined : bindingType)
874
+
490
875
  return withParserRanges({
491
- expression: convertExpression(expression, filename, source),
876
+ expression: value,
492
877
  kind: "AssignmentStatement",
493
878
  location,
494
879
  target: targetExpression
@@ -503,38 +888,618 @@ function convertLocalStatement(statement, filename, source) {
503
888
  * @param {import("@lezer/common").SyntaxNode} statement - Java statement.
504
889
  * @param {string} filename - Source filename.
505
890
  * @param {string} source - Complete source.
891
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
506
892
  * @returns {import("../semantic/types.js").Statement} Semantic statement.
507
893
  */
508
- function convertStatement(statement, filename, source) {
509
- if (statement.name == "ReturnStatement") return convertReturn(statement, filename, source)
510
- if (statement.name == "IfStatement") return convertIf(statement, filename, source)
511
- if (statement.name == "LocalVariableDeclaration") return convertLocalStatement(statement, filename, source)
894
+ function convertStatement(statement, filename, source, context) {
895
+ if (statement.name == "ReturnStatement") return convertReturn(statement, filename, source, context)
896
+ if (statement.name == "IfStatement") return convertIf(statement, filename, source, context)
897
+ if (statement.name == "ThrowStatement") return convertJavaRaise(statement, filename, source, context)
898
+ if (statement.name == "TryStatement") return convertJavaTry(statement, filename, source, context)
899
+ if (statement.name == "WhileStatement") return convertWhile(statement, filename, source, context)
900
+ if (statement.name == "EnhancedForStatement") return convertForEach(statement, filename, source, context)
901
+ if (statement.name == "BreakStatement" || statement.name == "ContinueStatement") {
902
+ const label = statement.getChild("Label")
903
+
904
+ if (label) return unsupportedSyntax("java", `labeled ${statement.name == "BreakStatement" ? "break" : "continue"}`,
905
+ nodeLocation(label, filename, source))
906
+ const keyword = requiredChild(statement, statement.name == "BreakStatement" ? "break" : "continue", filename, source)
907
+ const location = nodeLocation(keyword, filename, source)
908
+
909
+ return withParserRanges({
910
+ kind: /** @type {"BreakStatement" | "ContinueStatement"} */ (
911
+ statement.name == "BreakStatement" ? "BreakStatement" : "ContinueStatement"
912
+ ),
913
+ location
914
+ }, {keyword: location})
915
+ }
916
+ if (statement.name == "LocalVariableDeclaration") return convertLocalStatement(statement, filename, source, context)
512
917
  if (statement.name == "ExpressionStatement") {
513
- if (statement.getChild("AssignmentExpression")) return convertLocalStatement(statement, filename, source)
514
- if (!statement.getChild("MethodInvocation")) {
918
+ if (statement.getChild("AssignmentExpression")) return convertLocalStatement(statement, filename, source, context)
919
+ const invocation = statement.getChild("MethodInvocation")
920
+
921
+ if (!invocation) {
515
922
  return unsupportedSyntax("java", statement.name, nodeLocation(statement, filename, source))
516
923
  }
924
+ const fieldAccess = invocation.getChild("FieldAccess")
925
+ const methodName = invocation.getChild("MethodName")
926
+
927
+ if (!fieldAccess || !methodName || nodeText(fieldAccess, source) != "System.out" || nodeText(methodName, source) != "println") {
928
+ return {
929
+ expression: /** @type {import("../semantic/types.js").CallExpression | import("../semantic/types.js").MethodCallExpression} */ (
930
+ convertExpression(invocation, filename, source, context)
931
+ ),
932
+ kind: "ExpressionStatement",
933
+ location: nodeLocation(statement, filename, source)
934
+ }
935
+ }
517
936
 
518
- return convertPrint(statement, filename, source)
937
+ return convertPrint(statement, filename, source, context)
519
938
  }
520
939
 
521
940
  return unsupportedSyntax("java", statement.name, nodeLocation(statement, filename, source))
522
941
  }
523
942
 
943
+ /**
944
+ * Converts exact `throw new DeclaredError(message)`.
945
+ * @param {import("@lezer/common").SyntaxNode} statement - Java throw statement.
946
+ * @param {string} filename - Source filename.
947
+ * @param {string} source - Complete source.
948
+ * @param {JavaConversionContext} context - Typed lexical context.
949
+ * @returns {import("../semantic/types.js").RaiseStatement} Semantic raise.
950
+ */
951
+ function convertJavaRaise(statement, filename, source, context) {
952
+ const location = nodeLocation(statement, filename, source)
953
+ const construction = statement.getChild("ObjectCreationExpression")
954
+ const typeNode = construction?.getChild("TypeName")
955
+ const argumentsNode = construction?.getChild("ArgumentList")
956
+ const arguments_ = argumentsNode ? structuralChildren(argumentsNode) : []
957
+ const declaration = typeNode ? context.errorNames.get(nodeText(typeNode, source)) : undefined
958
+
959
+ if (!construction || !typeNode || !argumentsNode || !declaration || arguments_.length != 1 || construction.getChild("ClassBody")) {
960
+ return unsupportedSyntax("java", "throw other than exact declared error construction", location)
961
+ }
962
+ const typeLocation = nodeLocation(typeNode, filename, source)
963
+
964
+ return withParserRanges({
965
+ error: withParserRanges({
966
+ error: withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {type: typeLocation}),
967
+ kind: /** @type {const} */ ("ErrorConstruction"),
968
+ location: nodeLocation(construction, filename, source),
969
+ message: convertExpression(arguments_[0], filename, source, context)
970
+ }, {type: typeLocation}),
971
+ kind: /** @type {const} */ ("RaiseStatement"),
972
+ location
973
+ }, {keyword: nodeLocation(requiredChild(statement, "throw", filename, source), filename, source)})
974
+ }
975
+
976
+ /**
977
+ * Converts one exact catch with no resources or finally.
978
+ * @param {import("@lezer/common").SyntaxNode} statement - Java try statement.
979
+ * @param {string} filename - Source filename.
980
+ * @param {string} source - Complete source.
981
+ * @param {JavaConversionContext} context - Typed lexical context.
982
+ * @returns {import("../semantic/types.js").TryStatement} Semantic exact handler.
983
+ */
984
+ function convertJavaTry(statement, filename, source, context) {
985
+ const location = nodeLocation(statement, filename, source)
986
+ const body = statement.getChild("Block")
987
+ const catches = statement.getChildren("CatchClause")
988
+
989
+ if (!body || catches.length != 1 || statement.getChild("FinallyClause") || statement.getChild("ResourceSpecification")) {
990
+ return unsupportedSyntax("java", "try without one catch and no resources or finally", location)
991
+ }
992
+ const handler = catches[0]
993
+ const parameter = handler.getChild("CatchFormalParameter")
994
+ const caught = parameter?.getChild("CatchType")
995
+ const typeNodes = caught ? structuralChildren(caught) : []
996
+ const definition = parameter?.getChild("Definition")
997
+ const catchBody = handler.getChild("Block")
998
+ const typeNode = typeNodes[0]
999
+ const declaration = typeNode ? context.errorNames.get(nodeText(typeNode, source)) : undefined
1000
+
1001
+ if (!parameter || !caught || typeNodes.length != 1 || typeNode?.name != "TypeName" || !definition || !catchBody || !declaration ||
1002
+ parameter.getChild("Modifiers")) {
1003
+ return unsupportedSyntax("java", "broad, multiple, or malformed catch", nodeLocation(handler, filename, source))
1004
+ }
1005
+ const caughtLocation = nodeLocation(typeNode, filename, source)
1006
+ const catchType = withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {
1007
+ type: caughtLocation
1008
+ })
1009
+ const bodyContext = {...context, bindings: new Map(context.bindings), erasedBindingNames: new Set(context.erasedBindingNames)}
1010
+ const catchContext = {...context, bindings: new Map(context.bindings), erasedBindingNames: new Set(context.erasedBindingNames)}
1011
+ const name = nodeText(definition, source)
1012
+
1013
+ catchContext.bindings.set(name, catchType)
1014
+ return withParserRanges({
1015
+ body: convertBlock(body, filename, source, bodyContext),
1016
+ catchBinding: withParserRanges({
1017
+ kind: /** @type {const} */ ("CatchBinding"),
1018
+ location: nodeLocation(definition, filename, source),
1019
+ mutable: /** @type {const} */ (false),
1020
+ name,
1021
+ type: catchType
1022
+ }, {name: nodeLocation(definition, filename, source)}),
1023
+ catchBody: convertBlock(catchBody, filename, source, catchContext),
1024
+ catchType,
1025
+ kind: /** @type {const} */ ("TryStatement"),
1026
+ location
1027
+ }, {catch: nodeLocation(handler, filename, source), try: nodeLocation(requiredChild(statement, "try", filename, source), filename, source)})
1028
+ }
1029
+
1030
+ /**
1031
+ * Converts exact Java enhanced-for syntax over one resolved `List<T>`.
1032
+ * @param {import("@lezer/common").SyntaxNode} node - Enhanced-for statement.
1033
+ * @param {string} filename - Source filename.
1034
+ * @param {string} source - Complete source.
1035
+ * @param {JavaConversionContext} context - Typed lexical context.
1036
+ * @returns {import("../semantic/types.js").ForEachStatement | import("../semantic/types.js").ForEachMapStatement} Semantic loop.
1037
+ */
1038
+ function convertForEach(node, filename, source, context) {
1039
+ const location = nodeLocation(node, filename, source)
1040
+ const spec = node.getChild("ForSpec")
1041
+ const body = directChildren(node).findLast((child) => child.name != "ForSpec" && child.name != "for")
1042
+
1043
+ if (!spec) return unsupportedSyntax("java", "enhanced for without specification", location)
1044
+ if (!body || body.name != "Block") return unsupportedSyntax("java", "enhanced for without block body", body ? nodeLocation(body, filename, source) : location)
1045
+ const children = directChildren(spec)
1046
+ const colonIndex = children.findIndex((child) => child.name == ":")
1047
+ const typeNode = colonIndex >= 2 ? children[colonIndex - 2] : undefined
1048
+ const bindingNode = colonIndex >= 2 ? children[colonIndex - 1] : undefined
1049
+ const collectionNode = colonIndex >= 0 ? children[colonIndex + 1] : undefined
1050
+
1051
+ if (!typeNode || !bindingNode || bindingNode.name != "Definition" || !collectionNode ||
1052
+ children.filter((child) => child.name == ":").length != 1) {
1053
+ const unsupported = bindingNode ?? collectionNode ?? spec
1054
+
1055
+ return unsupportedSyntax("java", "malformed enhanced for specification", nodeLocation(unsupported, filename, source))
1056
+ }
1057
+ if (children.some((child) => child.name == "Modifiers")) {
1058
+ const modifiers = /** @type {import("@lezer/common").SyntaxNode} */ (children.find((child) => child.name == "Modifiers"))
1059
+
1060
+ return unsupportedSyntax("java", "enhanced for modifiers", nodeLocation(modifiers, filename, source))
1061
+ }
1062
+
1063
+ if (collectionNode.name == "MethodInvocation") {
1064
+ const methodName = collectionNode.getChild("MethodName")
1065
+ const argumentList = collectionNode.getChild("ArgumentList")
1066
+ const mapNode = structuralChildren(collectionNode).find((child) => child.name != "MethodName" && child.name != "ArgumentList")
1067
+ const mapType_ = mapNode ? knownExpressionType(mapNode, filename, source, context) : undefined
1068
+
1069
+ if (methodName && argumentList && mapNode?.name == "Identifier" &&
1070
+ nodeText(methodName, source) == "sequencedEntrySet" && structuralChildren(argumentList).length == 0 &&
1071
+ mapType_?.kind == "OrderedMapType") {
1072
+ const entryType = typeNode.name == "GenericType" ? structuralChildren(typeNode) : []
1073
+ const entryArguments = entryType[1]?.name == "TypeArguments" ? structuralChildren(entryType[1]) : []
1074
+ const parsedKeyType = entryArguments[0]
1075
+ ? convertJavaTypeArgument(entryArguments[0], "Map entry key", nodeLocation(typeNode, filename, source), filename, source,
1076
+ context.recordNames, context.typeParameters)
1077
+ : undefined
1078
+ const parsedValueType = entryArguments[1]
1079
+ ? convertJavaTypeArgument(entryArguments[1], "Map entry value", nodeLocation(typeNode, filename, source), filename, source,
1080
+ context.recordNames, context.typeParameters)
1081
+ : undefined
1082
+
1083
+ if (entryType.length != 2 || nodeText(entryType[0], source) != "java.util.Map.Entry" || entryArguments.length != 2 ||
1084
+ !parsedKeyType || !parsedValueType || !sameValueType(parsedKeyType, mapType_.keyType) ||
1085
+ !sameValueType(parsedValueType, mapType_.valueType)) {
1086
+ return unsupportedSyntax("java", "ordered-map entry type", nodeLocation(typeNode, filename, source))
1087
+ }
1088
+ const bodyStatements = structuralChildren(body)
1089
+
1090
+ if (bodyStatements.length < 2) {
1091
+ return unsupportedSyntax("java", "ordered-map pair bindings", nodeLocation(body, filename, source))
1092
+ }
1093
+ const entryName = nodeText(bindingNode, source)
1094
+
1095
+ if (context.bindings.has(entryName) || context.erasedBindingNames.has(entryName)) {
1096
+ return unsupportedSyntax("java", "ordered-map entry binding collision", nodeLocation(bindingNode, filename, source))
1097
+ }
1098
+ const bodyContext = {
1099
+ ...context,
1100
+ bindings: new Map(context.bindings),
1101
+ erasedBindingNames: new Set(context.erasedBindingNames).add(entryName)
1102
+ }
1103
+ const keyBinding = convertJavaMapPairBinding(
1104
+ bodyStatements[0], entryName, "getKey", mapType_.keyType, filename, source, bodyContext
1105
+ )
1106
+ const valueBinding = convertJavaMapPairBinding(
1107
+ bodyStatements[1], entryName, "getValue", mapType_.valueType, filename, source, bodyContext
1108
+ )
1109
+
1110
+ bodyContext.bindings.set(keyBinding.name, keyBinding.type)
1111
+ bodyContext.bindings.set(valueBinding.name, valueBinding.type)
1112
+ return withParserRanges({
1113
+ body: {
1114
+ kind: /** @type {const} */ ("Block"),
1115
+ location: nodeLocation(body, filename, source),
1116
+ statements: convertStatements(bodyStatements.slice(2), filename, source, bodyContext)
1117
+ },
1118
+ keyBinding,
1119
+ kind: /** @type {const} */ ("ForEachMapStatement"),
1120
+ location,
1121
+ map: convertExpression(mapNode, filename, source, context),
1122
+ valueBinding
1123
+ }, {operator: nodeLocation(children[colonIndex], filename, source)})
1124
+ }
1125
+ if (methodName && ["entrySet", "sequencedEntrySet"].includes(nodeText(methodName, source))) {
1126
+ return unsupportedSyntax("java", "unordered or unsupported map iteration", nodeLocation(collectionNode, filename, source))
1127
+ }
1128
+ }
1129
+ const bindingLocation = nodeLocation(bindingNode, filename, source)
1130
+ const declaredType = convertJavaTypeArgument(typeNode, `Iteration binding '${nodeText(bindingNode, source)}'`, bindingLocation,
1131
+ filename, source, context.recordNames, context.typeParameters)
1132
+ const collectionType = iterationOperandType(knownExpressionType(collectionNode, filename, source, context))
1133
+
1134
+ if (!collectionType || collectionType.kind != "ListType" && collectionType.kind != "MapType") {
1135
+ return missingType("java", "Iteration collection", nodeLocation(collectionNode, filename, source))
1136
+ }
1137
+ const valueBinding = withParserRanges({
1138
+ kind: /** @type {const} */ ("ValueBinding"),
1139
+ location: bindingLocation,
1140
+ mutable: /** @type {const} */ (false),
1141
+ name: nodeText(bindingNode, source),
1142
+ type: declaredType
1143
+ }, {name: bindingLocation})
1144
+ const bodyContext = {...context, bindings: new Map(context.bindings), erasedBindingNames: new Set(context.erasedBindingNames)}
1145
+
1146
+ bodyContext.bindings.set(valueBinding.name, iterationBindingType(declaredType, bindingLocation))
1147
+ return withParserRanges({
1148
+ body: convertBlock(body, filename, source, bodyContext),
1149
+ kind: /** @type {const} */ ("ForEachStatement"),
1150
+ list: convertExpression(collectionNode, filename, source, context),
1151
+ location,
1152
+ valueBinding
1153
+ }, {operator: nodeLocation(children[colonIndex], filename, source)})
1154
+ }
1155
+
1156
+ /**
1157
+ * Converts one exact immutable local extracted from a Java ordered-map entry.
1158
+ * @param {import("@lezer/common").SyntaxNode} statement - Pair-binding declaration.
1159
+ * @param {string} entryName - Enhanced-for entry binding name.
1160
+ * @param {"getKey" | "getValue"} accessor - Required entry accessor.
1161
+ * @param {import("../semantic/types.js").SemanticValueType} expectedType - Canonical binding type.
1162
+ * @param {string} filename - Source filename.
1163
+ * @param {string} source - Complete source.
1164
+ * @param {JavaConversionContext} context - Typed context.
1165
+ * @returns {import("../semantic/types.js").ValueBinding} Pair binding.
1166
+ */
1167
+ function convertJavaMapPairBinding(statement, entryName, accessor, expectedType, filename, source, context) {
1168
+ const location = nodeLocation(statement, filename, source)
1169
+ const modifiers = statement.getChild("Modifiers")
1170
+ const declarators = statement.getChildren("VariableDeclarator")
1171
+ const declarator = declarators[0]
1172
+ const definition = declarator?.getChild("Definition")
1173
+ const assignment = declarator?.getChild("AssignOp")
1174
+ const initializer = declarator
1175
+ ? directChildren(declarator).find((child) => child.name != "Definition" && child.name != "AssignOp")
1176
+ : undefined
1177
+ const methodName = initializer?.getChild("MethodName")
1178
+ const argumentList = initializer?.getChild("ArgumentList")
1179
+ const receiver = initializer?.name == "MethodInvocation"
1180
+ ? structuralChildren(initializer).find((child) => child.name != "MethodName" && child.name != "ArgumentList")
1181
+ : undefined
1182
+ const typeNode = declarationType(statement)
1183
+
1184
+ if (statement.name != "LocalVariableDeclaration" || nodeText(modifiers ?? statement, source) != "final" ||
1185
+ declarators.length != 1 || !definition || !assignment || nodeText(assignment, source) != "=" || !initializer ||
1186
+ initializer.name != "MethodInvocation" || !methodName || nodeText(methodName, source) != accessor ||
1187
+ !argumentList || structuralChildren(argumentList).length != 0 || receiver?.name != "Identifier" ||
1188
+ nodeText(receiver, source) != entryName || !typeNode) {
1189
+ return unsupportedSyntax("java", `ordered-map ${accessor} binding`, location)
1190
+ }
1191
+ const name = nodeText(definition, source)
1192
+
1193
+ if (context.erasedBindingNames.has(name)) {
1194
+ return unsupportedSyntax("java", `ordered-map ${accessor} binding collision`, nodeLocation(definition, filename, source))
1195
+ }
1196
+ const type = convertType(typeNode, `Map iteration binding '${name}'`, location,
1197
+ filename, source, context.recordNames, context.typeParameters)
1198
+
1199
+ if (!sameValueType(type, expectedType)) {
1200
+ return unsupportedSyntax("java", `ordered-map ${accessor} binding type`, nodeLocation(typeNode, filename, source))
1201
+ }
1202
+
1203
+ return withParserRanges({
1204
+ kind: /** @type {const} */ ("ValueBinding"),
1205
+ location,
1206
+ mutable: /** @type {const} */ (false),
1207
+ name,
1208
+ type
1209
+ }, {name: nodeLocation(definition, filename, source)})
1210
+ }
1211
+
1212
+ /**
1213
+ * Resolves expression types established by Java declarations and function signatures.
1214
+ * @param {import("@lezer/common").SyntaxNode} node - Parser expression.
1215
+ * @param {string} filename - Source filename.
1216
+ * @param {string} source - Complete source.
1217
+ * @param {JavaConversionContext} context - Typed context.
1218
+ * @returns {import("../semantic/types.js").SemanticFunctionReturnType | import("../semantic/types.js").ErrorType | undefined} Known type.
1219
+ */
1220
+ function knownExpressionType(node, filename, source, context) {
1221
+ if (node.name == "ParenthesizedExpression") {
1222
+ const children = structuralChildren(node)
1223
+
1224
+ return children.length == 1 ? knownExpressionType(children[0], filename, source, context) : undefined
1225
+ }
1226
+ if (node.name == "this" && context.currentClass?.id) {
1227
+ return {declarationId: context.currentClass.id, kind: "ReferenceType"}
1228
+ }
1229
+ if (node.name == "Identifier") return context.bindings.get(nodeText(node, source))
1230
+ if (node.name == "FieldAccess" && context.currentClass) {
1231
+ const children = structuralChildren(node)
1232
+
1233
+ if (children.length == 2 && children[0].name == "this" && children[1].name == "Identifier") {
1234
+ return context.currentClass.fields.find((field) => field.name == nodeText(children[1], source))?.type
1235
+ }
1236
+ }
1237
+ if (node.name == "MethodInvocation") {
1238
+ const methodName = node.getChild("MethodName")
1239
+ const argumentList = node.getChild("ArgumentList")
1240
+ const receiver = structuralChildren(node).find((child) => child.name != "MethodName" && child.name != "ArgumentList")
1241
+
1242
+ if (methodName && !receiver) {
1243
+ const signature = context.functions.get(nodeText(methodName, source))
1244
+ const arguments_ = argumentList ? structuralChildren(argumentList) : []
1245
+
1246
+ if (signature) return knownCallReturnType(signature, arguments_.map((argument) =>
1247
+ knownExpressionType(argument, filename, source, context)))
1248
+ }
1249
+ if (methodName && receiver) {
1250
+ const receiverType = knownExpressionType(receiver, filename, source, context)
1251
+
1252
+ if (receiverType?.kind == "ReferenceType") {
1253
+ return context.classes.get(receiverType.declarationId)?.methods.find((method) =>
1254
+ method.name == nodeText(methodName, source))?.returnType
1255
+ }
1256
+ const qualified = `${nodeText(receiver, source)}.${nodeText(methodName, source)}`
1257
+ const signature = context.functions.get(qualified)
1258
+ const arguments_ = argumentList ? structuralChildren(argumentList) : []
1259
+ const returnType = signature ? knownCallReturnType(signature, arguments_.map((argument) =>
1260
+ knownExpressionType(argument, filename, source, context))) : undefined
1261
+
1262
+ if (returnType) return returnType
1263
+ }
1264
+ if (methodName && receiver && argumentList && nodeText(methodName, source) == "get") {
1265
+ const arguments_ = structuralChildren(argumentList)
1266
+ const receiverType = knownExpressionType(receiver, filename, source, context)
1267
+
1268
+ if (arguments_.length == 0 && receiver.name == "Identifier" && receiverType?.kind == "OptionalType") {
1269
+ return receiverType.valueType
1270
+ }
1271
+ if (arguments_.length == 1 && receiverType?.kind == "ListType") return receiverType.elementType
1272
+ if (arguments_.length == 1 && (receiverType?.kind == "MapType" || receiverType?.kind == "OrderedMapType")) {
1273
+ return receiverType.valueType
1274
+ }
1275
+ }
1276
+ if (methodName && receiver && argumentList && structuralChildren(argumentList).length == 0) {
1277
+ const receiverType = knownExpressionType(receiver, filename, source, context)
1278
+
1279
+ if (receiverType?.kind != "RecordType") return undefined
1280
+ const declaration = context.records.get(receiverType.declarationId)
1281
+ const index = declaration?.fields.findIndex((field) => field.name == nodeText(methodName, source)) ?? -1
1282
+
1283
+ return declaration ? instantiatedRecordFieldType(declaration, receiverType.arguments, index) : undefined
1284
+ }
1285
+ }
1286
+ if (node.name == "ObjectCreationExpression") {
1287
+ const typeNode = structuralChildren(node).find((child) => ["GenericType", "TypeName", "ScopedTypeName"].includes(child.name))
1288
+ const nameNode = typeNode?.name == "GenericType" ? structuralChildren(typeNode)[0] : typeNode
1289
+ const declaration = nameNode ? context.recordNames.get(nodeText(nameNode, source)) : undefined
1290
+
1291
+ if (declaration?.id && typeNode) {
1292
+ const typeArgumentsNode = typeNode?.getChild("TypeArguments")
1293
+ const typeArguments = typeArgumentsNode
1294
+ ? structuralChildren(typeArgumentsNode).map((argument) => convertJavaTypeArgument(
1295
+ argument,
1296
+ `Record '${declaration.name}' application`,
1297
+ nodeLocation(typeNode, filename, source),
1298
+ filename,
1299
+ source,
1300
+ context.recordNames,
1301
+ context.typeParameters
1302
+ ))
1303
+ : undefined
1304
+
1305
+ return declaration.kind == "ClassDeclaration"
1306
+ ? {declarationId: declaration.id, kind: "ReferenceType"}
1307
+ : {declarationId: declaration.id, kind: "RecordType", ...(typeArguments ? {arguments: typeArguments} : {})}
1308
+ }
1309
+ }
1310
+
1311
+ return undefined
1312
+ }
1313
+
524
1314
  /**
525
1315
  * Converts direct structural children of one Java block in source order.
526
1316
  * @param {import("@lezer/common").SyntaxNode} node - Java Block node.
527
1317
  * @param {string} filename - Source filename.
528
1318
  * @param {string} source - Complete source.
1319
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
529
1320
  * @returns {import("../semantic/types.js").Block} Semantic block.
530
1321
  */
531
- function convertBlock(node, filename, source) {
532
- if (node.name != "Block") return unsupportedSyntax("java", `expected block, received ${node.name}`, nodeLocation(node, filename, source))
1322
+ function convertBlock(node, filename, source, context) {
1323
+ if (node.name != "Block" && node.name != "ConstructorBody") {
1324
+ return unsupportedSyntax("java", `expected block, received ${node.name}`, nodeLocation(node, filename, source))
1325
+ }
533
1326
 
534
1327
  return {
535
1328
  kind: "Block",
536
1329
  location: nodeLocation(node, filename, source),
537
- statements: structuralChildren(node).map((statement) => convertStatement(statement, filename, source))
1330
+ statements: convertStatements(structuralChildren(node), filename, source, context)
1331
+ }
1332
+ }
1333
+
1334
+ /**
1335
+ * Converts a Java statement sequence, folding the exact protected ordered-map construction profile.
1336
+ * @param {import("@lezer/common").SyntaxNode[]} statements - Direct block statements.
1337
+ * @param {string} filename - Source filename.
1338
+ * @param {string} source - Complete source.
1339
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
1340
+ * @returns {import("../semantic/types.js").Statement[]} Semantic statements.
1341
+ */
1342
+ function convertStatements(statements, filename, source, context) {
1343
+ const converted = []
1344
+
1345
+ for (let index = 0; index < statements.length;) {
1346
+ const ordered = convertJavaOrderedMapSequence(statements, index, filename, source, context)
1347
+
1348
+ if (ordered) {
1349
+ converted.push(ordered.statement)
1350
+ index = ordered.nextIndex
1351
+ } else {
1352
+ converted.push(convertStatement(statements[index], filename, source, context))
1353
+ index += 1
1354
+ }
1355
+ }
1356
+
1357
+ return converted
1358
+ }
1359
+
1360
+ /**
1361
+ * Recognizes construction through a private LinkedHashMap backing followed by an unmodifiable SequencedMap boundary.
1362
+ * @param {import("@lezer/common").SyntaxNode[]} statements - Direct block statements.
1363
+ * @param {number} start - Candidate backing-declaration index.
1364
+ * @param {string} filename - Source filename.
1365
+ * @param {string} source - Complete source.
1366
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
1367
+ * @returns {{statement: import("../semantic/types.js").LocalDeclaration, nextIndex: number} | undefined} Folded declaration.
1368
+ */
1369
+ function convertJavaOrderedMapSequence(statements, start, filename, source, context) {
1370
+ const backingStatement = statements[start]
1371
+ const backingTypeNode = backingStatement?.name == "LocalVariableDeclaration" ? declarationType(backingStatement) : null
1372
+ const backingTypeChildren = backingTypeNode?.name == "GenericType" ? structuralChildren(backingTypeNode) : []
1373
+
1374
+ if (backingTypeChildren.length != 2 || nodeText(backingTypeChildren[0], source) != "java.util.LinkedHashMap") return undefined
1375
+
1376
+ const location = nodeLocation(backingStatement, filename, source)
1377
+ const backingModifiers = backingStatement.getChild("Modifiers")
1378
+ const backingDeclarators = backingStatement.getChildren("VariableDeclarator")
1379
+ const backingDeclarator = backingDeclarators[0]
1380
+ const backingDefinition = backingDeclarator?.getChild("Definition")
1381
+ const backingAssignment = backingDeclarator?.getChild("AssignOp")
1382
+ const backingInitializer = backingDeclarator
1383
+ ? directChildren(backingDeclarator).find((child) => child.name != "Definition" && child.name != "AssignOp")
1384
+ : undefined
1385
+ const constructionType = backingInitializer?.name == "ObjectCreationExpression"
1386
+ ? structuralChildren(backingInitializer).find((child) => child.name == "GenericType")
1387
+ : undefined
1388
+ const constructionTypeChildren = constructionType ? structuralChildren(constructionType) : []
1389
+ const constructionArguments = backingInitializer?.getChild("ArgumentList")
1390
+ const backingArgumentsNode = backingTypeChildren[1]
1391
+ const backingArguments = backingArgumentsNode?.name == "TypeArguments" ? structuralChildren(backingArgumentsNode) : []
1392
+
1393
+ if (nodeText(backingModifiers ?? backingStatement, source) != "final" || backingDeclarators.length != 1 ||
1394
+ !backingDefinition || !backingAssignment || nodeText(backingAssignment, source) != "=" || !backingInitializer ||
1395
+ backingInitializer.name != "ObjectCreationExpression" || backingInitializer.getChild("ClassBody") ||
1396
+ constructionTypeChildren.length != 2 || nodeText(constructionTypeChildren[0], source) != "java.util.LinkedHashMap" ||
1397
+ structuralChildren(constructionTypeChildren[1]).length != 0 || !constructionArguments ||
1398
+ structuralChildren(constructionArguments).length != 0 || backingArguments.length != 2) {
1399
+ return unsupportedSyntax("java", "ordered-map LinkedHashMap backing", location)
1400
+ }
1401
+
1402
+ const backingName = nodeText(backingDefinition, source)
1403
+
1404
+ if (context.bindings.has(backingName) || context.erasedBindingNames.has(backingName)) {
1405
+ return unsupportedSyntax("java", "ordered-map backing binding collision", nodeLocation(backingDefinition, filename, source))
1406
+ }
1407
+ const keyType = convertJavaTypeArgument(backingArguments[0], "Ordered map key", location, filename, source,
1408
+ context.recordNames, context.typeParameters)
1409
+ const valueType = convertJavaTypeArgument(backingArguments[1], "Ordered map value", location, filename, source,
1410
+ context.recordNames, context.typeParameters)
1411
+ const entries = []
1412
+ let index = start + 1
1413
+
1414
+ while (index < statements.length) {
1415
+ const putStatement = statements[index]
1416
+ const invocation = putStatement.name == "ExpressionStatement" ? putStatement.getChild("MethodInvocation") : null
1417
+ const methodName = invocation?.getChild("MethodName")
1418
+ const argumentList = invocation?.getChild("ArgumentList")
1419
+ const receiver = invocation
1420
+ ? structuralChildren(invocation).find((child) => child.name != "MethodName" && child.name != "ArgumentList")
1421
+ : undefined
1422
+
1423
+ if (!invocation || receiver?.name != "Identifier" || nodeText(receiver, source) != backingName ||
1424
+ !methodName || nodeText(methodName, source) != "put") break
1425
+ const arguments_ = argumentList ? structuralChildren(argumentList) : []
1426
+
1427
+ if (arguments_.length != 2 || arguments_[0].name != "StringLiteral") {
1428
+ return unsupportedSyntax("java", "ordered-map put", nodeLocation(putStatement, filename, source))
1429
+ }
1430
+ entries.push(withParserRanges({
1431
+ key: /** @type {import("../semantic/types.js").StringLiteral} */ (
1432
+ convertExpression(arguments_[0], filename, source, context, keyType)
1433
+ ),
1434
+ kind: /** @type {const} */ ("MapEntry"),
1435
+ location: nodeLocation(invocation, filename, source),
1436
+ value: convertExpression(arguments_[1], filename, source, context, valueType)
1437
+ }, {operator: nodeLocation(methodName, filename, source)}))
1438
+ index += 1
1439
+ }
1440
+
1441
+ const sealStatement = statements[index]
1442
+ const sealTypeNode = sealStatement?.name == "LocalVariableDeclaration" ? declarationType(sealStatement) : null
1443
+ const sealDeclarators = sealStatement?.name == "LocalVariableDeclaration" ? sealStatement.getChildren("VariableDeclarator") : []
1444
+ const sealDeclarator = sealDeclarators[0]
1445
+ const sealDefinition = sealDeclarator?.getChild("Definition")
1446
+ const sealAssignment = sealDeclarator?.getChild("AssignOp")
1447
+ const sealInitializer = sealDeclarator
1448
+ ? directChildren(sealDeclarator).find((child) => child.name != "Definition" && child.name != "AssignOp")
1449
+ : undefined
1450
+ const sealMethod = sealInitializer?.getChild("MethodName")
1451
+ const sealArgumentsNode = sealInitializer?.getChild("ArgumentList")
1452
+ const sealArguments = sealArgumentsNode ? structuralChildren(sealArgumentsNode) : []
1453
+ const sealReceiver = sealInitializer?.name == "MethodInvocation"
1454
+ ? structuralChildren(sealInitializer).find((child) => child.name != "MethodName" && child.name != "ArgumentList")
1455
+ : undefined
1456
+
1457
+ if (!sealStatement || nodeText(sealStatement.getChild("Modifiers") ?? sealStatement, source) != "final" ||
1458
+ !sealTypeNode || sealDeclarators.length != 1 || !sealDefinition || !sealAssignment || nodeText(sealAssignment, source) != "=" ||
1459
+ sealInitializer?.name != "MethodInvocation" || nodeText(sealReceiver ?? sealStatement, source) != "java.util.Collections" ||
1460
+ !sealMethod || nodeText(sealMethod, source) != "unmodifiableSequencedMap" || sealArguments.length != 1 ||
1461
+ sealArguments[0].name != "Identifier" || nodeText(sealArguments[0], source) != backingName) {
1462
+ return unsupportedSyntax("java", "ordered-map unmodifiable SequencedMap boundary", location)
1463
+ }
1464
+ const name = nodeText(sealDefinition, source)
1465
+
1466
+ if (name == backingName) {
1467
+ return unsupportedSyntax("java", "ordered-map boundary binding collision", nodeLocation(sealDefinition, filename, source))
1468
+ }
1469
+ const boundaryType = convertType(sealTypeNode, `Local '${name}'`, nodeLocation(sealStatement, filename, source),
1470
+ filename, source, context.recordNames, context.typeParameters)
1471
+ const sealTypeChildren = sealTypeNode.name == "GenericType" ? structuralChildren(sealTypeNode) : []
1472
+ const sealTypeArguments = sealTypeChildren[1]?.name == "TypeArguments" ? structuralChildren(sealTypeChildren[1]) : []
1473
+
1474
+ if (sealTypeChildren.length != 2 || nodeText(sealTypeChildren[0], source) != "java.util.SequencedMap" ||
1475
+ sealTypeArguments.length != 2 || boundaryType.kind != "MapType" || !sameValueType(boundaryType.keyType, keyType) ||
1476
+ !sameValueType(boundaryType.valueType, valueType)) {
1477
+ return unsupportedSyntax("java", "ordered-map boundary type mismatch", nodeLocation(sealTypeNode, filename, source))
1478
+ }
1479
+ const type = orderedMapType(
1480
+ boundaryType.keyType,
1481
+ boundaryType.valueType,
1482
+ nodeLocation(sealTypeNode, filename, source),
1483
+ nodeLocation(sealTypeArguments[0], filename, source),
1484
+ nodeLocation(sealTypeArguments[1], filename, source)
1485
+ )
1486
+
1487
+ context.bindings.set(name, type)
1488
+ context.erasedBindingNames.add(backingName)
1489
+ return {
1490
+ nextIndex: index + 1,
1491
+ statement: withParserRanges({
1492
+ initializer: withParserRanges({
1493
+ entries,
1494
+ kind: /** @type {const} */ ("OrderedMapLiteral"),
1495
+ location: locationFromOffsets(filename, source, backingInitializer.from, sealInitializer.to)
1496
+ }, {factory: nodeLocation(backingInitializer, filename, source)}),
1497
+ kind: /** @type {const} */ ("LocalDeclaration"),
1498
+ location: locationFromOffsets(filename, source, backingStatement.from, sealStatement.to),
1499
+ mutable: /** @type {const} */ (false),
1500
+ name,
1501
+ type
1502
+ }, {name: nodeLocation(sealDefinition, filename, source), operator: nodeLocation(sealAssignment, filename, source)})
538
1503
  }
539
1504
  }
540
1505
 
@@ -545,14 +1510,94 @@ function convertBlock(node, filename, source) {
545
1510
  * @param {import("../semantic/types.js").SourceLocation} location - Source location.
546
1511
  * @param {string} filename - Source filename.
547
1512
  * @param {string} source - Complete source.
548
- * @returns {import("../semantic/types.js").TypeReference} Semantic type.
1513
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [recordNames] - Nominal declarations by source name.
1514
+ * @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
1515
+ * @returns {import("../semantic/types.js").SemanticValueType} Semantic type.
549
1516
  */
550
- function convertType(sourceType, subject, location, filename, source) {
1517
+ function convertType(sourceType, subject, location, filename, source, recordNames = new Map(), typeParameters = new Map()) {
551
1518
  if (!sourceType) return missingType("java", subject, location)
1519
+ if (sourceType.name == "void") return unsupportedSyntax("java", "void value type", nodeLocation(sourceType, filename, source))
1520
+ if (sourceType.name == "GenericType") {
1521
+ const children = structuralChildren(sourceType)
1522
+ const nameNode = children[0]
1523
+ const argumentsNode = children[1]
1524
+
1525
+ if (!nameNode || !argumentsNode || argumentsNode.name != "TypeArguments") {
1526
+ return unsupportedSyntax("java", "malformed generic collection type", nodeLocation(sourceType, filename, source))
1527
+ }
1528
+ const name = nodeText(nameNode, source)
1529
+ const arguments_ = structuralChildren(argumentsNode)
1530
+
1531
+ if (name == "java.util.List" && arguments_.length == 1) {
1532
+ return listType(
1533
+ convertJavaTypeArgument(arguments_[0], subject, location, filename, source, recordNames, typeParameters),
1534
+ nodeLocation(sourceType, filename, source),
1535
+ nodeLocation(arguments_[0], filename, source)
1536
+ )
1537
+ }
1538
+ if (name == "java.util.Map" && arguments_.length == 2) {
1539
+ const keyType = convertJavaTypeArgument(arguments_[0], subject, location, filename, source, recordNames, typeParameters)
1540
+
1541
+ if (keyType.kind != "TypeReference") {
1542
+ return unsupportedSyntax("java", "map key type other than string", nodeLocation(arguments_[0], filename, source))
1543
+ }
1544
+ return mapType(
1545
+ keyType,
1546
+ convertJavaTypeArgument(arguments_[1], subject, location, filename, source, recordNames, typeParameters),
1547
+ nodeLocation(sourceType, filename, source),
1548
+ nodeLocation(arguments_[0], filename, source),
1549
+ nodeLocation(arguments_[1], filename, source)
1550
+ )
1551
+ }
1552
+ if (name == "java.util.SequencedMap" && arguments_.length == 2) {
1553
+ const keyType = convertJavaTypeArgument(arguments_[0], subject, location, filename, source, recordNames, typeParameters)
1554
+
1555
+ if (keyType.kind != "TypeReference" || keyType.name != "string") {
1556
+ return unsupportedSyntax("java", "map key type other than string", nodeLocation(arguments_[0], filename, source))
1557
+ }
1558
+ return mapType(
1559
+ keyType,
1560
+ convertJavaTypeArgument(arguments_[1], subject, location, filename, source, recordNames, typeParameters),
1561
+ nodeLocation(sourceType, filename, source),
1562
+ nodeLocation(arguments_[0], filename, source),
1563
+ nodeLocation(arguments_[1], filename, source)
1564
+ )
1565
+ }
1566
+ if ((name == "java.util.Optional" || name == "Optional") && arguments_.length == 1) {
1567
+ return optionalType(
1568
+ convertJavaTypeArgument(arguments_[0], subject, location, filename, source, recordNames, typeParameters),
1569
+ nodeLocation(sourceType, filename, source),
1570
+ nodeLocation(arguments_[0], filename, source)
1571
+ )
1572
+ }
1573
+
1574
+ const record = recordNames.get(name)
1575
+
1576
+ if (record?.id) {
1577
+ if (record.kind == "ClassDeclaration") {
1578
+ return unsupportedSyntax("java", "generic reference class type", nodeLocation(sourceType, filename, source))
1579
+ }
1580
+ return recordType(record.id, nodeLocation(nameNode, filename, source), arguments_.map((argument) =>
1581
+ convertJavaTypeArgument(argument, subject, location, filename, source, recordNames, typeParameters)))
1582
+ }
1583
+
1584
+ return unsupportedSyntax("java", "unsupported generic type", nodeLocation(sourceType, filename, source))
1585
+ }
552
1586
  if (!["PrimitiveType", "TypeName", "ScopedTypeName"].includes(sourceType.name)) {
553
1587
  return unsupportedSyntax("java", "unsupported scalar type", location)
554
1588
  }
555
1589
 
1590
+ const typeParameter = typeParameters.get(nodeText(sourceType, source))
1591
+
1592
+ if (typeParameter?.id) return typeVariable(typeParameter.id, nodeLocation(sourceType, filename, source))
1593
+ const declaration = recordNames.get(nodeText(sourceType, source))
1594
+
1595
+ if (declaration?.id) return declaration.kind == "ClassDeclaration"
1596
+ ? referenceType(declaration.id, nodeLocation(sourceType, filename, source))
1597
+ : declaration.kind == "EffectResourceDeclaration"
1598
+ ? ownedResourceType(declaration.id, nodeLocation(sourceType, filename, source))
1599
+ : recordType(declaration.id, nodeLocation(sourceType, filename, source))
1600
+
556
1601
  const type = sourceScalarType("java", nodeText(sourceType, source), nodeLocation(sourceType, filename, source))
557
1602
 
558
1603
  if (!type) return unsupportedSyntax("java", "unsupported scalar type", nodeLocation(sourceType, location.filename, source))
@@ -560,29 +1605,142 @@ function convertType(sourceType, subject, location, filename, source) {
560
1605
  return type
561
1606
  }
562
1607
 
1608
+ /**
1609
+ * Converts one exact boxed Java collection type argument or nested collection.
1610
+ * @param {import("@lezer/common").SyntaxNode} node - Type argument node.
1611
+ * @param {string} subject - Typed subject.
1612
+ * @param {import("../semantic/types.js").SourceLocation} location - Owning location.
1613
+ * @param {string} filename - Source filename.
1614
+ * @param {string} source - Complete source.
1615
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [recordNames] - Nominal declarations by source name.
1616
+ * @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
1617
+ * @returns {import("../semantic/types.js").SemanticValueType} Semantic type.
1618
+ */
1619
+ function convertJavaTypeArgument(node, subject, location, filename, source, recordNames = new Map(), typeParameters = new Map()) {
1620
+ if (node.name == "GenericType") return convertType(node, subject, location, filename, source, recordNames, typeParameters)
1621
+ const typeParameter = typeParameters.get(nodeText(node, source))
1622
+
1623
+ if (typeParameter?.id) return typeVariable(typeParameter.id, nodeLocation(node, filename, source))
1624
+ const declaration = recordNames.get(nodeText(node, source))
1625
+
1626
+ if (declaration?.id) return declaration.kind == "ClassDeclaration"
1627
+ ? referenceType(declaration.id, nodeLocation(node, filename, source))
1628
+ : declaration.kind == "EffectResourceDeclaration"
1629
+ ? ownedResourceType(declaration.id, nodeLocation(node, filename, source))
1630
+ : recordType(declaration.id, nodeLocation(node, filename, source))
1631
+ const spelling = nodeText(node, source)
1632
+ const scalar = spelling == "Integer" ? "integer" : spelling == "Boolean" ? "boolean" : spelling == "String" ? "string" : undefined
1633
+
1634
+ if (!scalar) return unsupportedSyntax("java", "unsupported collection type argument", nodeLocation(node, filename, source))
1635
+ const type = {kind: /** @type {const} */ ("TypeReference"), name: /** @type {import("../semantic/types.js").SemanticTypeName} */ (scalar)}
1636
+
1637
+ return withParserRanges(type, {type: nodeLocation(node, filename, source)})
1638
+ }
1639
+
1640
+ /**
1641
+ * Converts one explicit Java function return type.
1642
+ * @param {import("@lezer/common").SyntaxNode | null} sourceType - Java return type node.
1643
+ * @param {string} subject - Typed function return.
1644
+ * @param {import("../semantic/types.js").SourceLocation} location - Function location.
1645
+ * @param {string} filename - Source filename.
1646
+ * @param {string} source - Complete source.
1647
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [recordNames] - Nominal declarations by source name.
1648
+ * @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
1649
+ * @returns {import("../semantic/types.js").SemanticFunctionReturnType} Semantic return type.
1650
+ */
1651
+ function convertReturnType(sourceType, subject, location, filename, source, recordNames = new Map(), typeParameters = new Map()) {
1652
+ if (sourceType?.name == "void") {
1653
+ return requireSourceReturnType("java", "void", subject, location, nodeLocation(sourceType, filename, source))
1654
+ }
1655
+
1656
+ return convertType(sourceType, subject, location, filename, source, recordNames, typeParameters)
1657
+ }
1658
+
563
1659
  /**
564
1660
  * Returns the direct Java declaration type node supported by scalar conversion.
565
1661
  * @param {import("@lezer/common").SyntaxNode} node - Declaration node.
566
1662
  * @returns {import("@lezer/common").SyntaxNode | null} Type syntax node.
567
1663
  */
568
1664
  function declarationType(node) {
569
- return node.getChild("PrimitiveType") ?? node.getChild("TypeName") ?? node.getChild("ScopedTypeName")
1665
+ return node.getChild("GenericType") ?? node.getChild("PrimitiveType") ?? node.getChild("TypeName") ?? node.getChild("ScopedTypeName") ?? node.getChild("void")
570
1666
  }
571
1667
 
572
1668
  /**
573
- * Converts the supported Java function method.
1669
+ * Converts one native unbounded invariant Java type-parameter list.
1670
+ * @param {import("@lezer/common").SyntaxNode | null} node - TypeParameters node.
1671
+ * @param {string} ownerId - Stable owning declaration identity.
1672
+ * @param {string} filename - Source filename.
1673
+ * @param {string} source - Complete source.
1674
+ * @returns {import("../semantic/types.js").TypeParameter[] | undefined} Semantic parameters.
1675
+ */
1676
+ function convertJavaTypeParameters(node, ownerId, filename, source) {
1677
+ if (!node) return undefined
1678
+ const parameters = structuralChildren(node)
1679
+
1680
+ return parameters.map((parameter, index) => {
1681
+ const children = structuralChildren(parameter)
1682
+ const definition = children[0]
1683
+
1684
+ if (parameter.name != "TypeParameter" || children.length != 1 || definition?.name != "Definition") {
1685
+ return unsupportedSyntax("java", "bounded, annotated, or malformed type parameter", nodeLocation(parameter, filename, source))
1686
+ }
1687
+ const location = nodeLocation(definition, filename, source)
1688
+
1689
+ return withParserRanges({
1690
+ id: `${ownerId}:type:${index}`,
1691
+ kind: /** @type {const} */ ("TypeParameter"),
1692
+ location,
1693
+ name: nodeText(definition, source)
1694
+ }, {name: location})
1695
+ })
1696
+ }
1697
+
1698
+ /**
1699
+ * Converts one supported Java function signature before adapting any body.
574
1700
  * @param {import("@lezer/common").SyntaxNode} node - Method declaration.
575
1701
  * @param {string} filename - Source filename.
576
1702
  * @param {string} source - Complete source.
577
- * @returns {import("../semantic/types.js").FunctionDeclaration} Semantic function.
1703
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
1704
+ * @param {boolean} [programFunction] - Whether the canonical project profile admits explicit public or private visibility.
1705
+ * @param {string} [ownerId] - Stable function identity.
1706
+ * @param {boolean} [referenceModule] - Whether package-visible static functions are required by sibling reference classes.
1707
+ * @returns {JavaFunctionSignature} Semantic function signature.
578
1708
  */
579
- function convertFunction(node, filename, source) {
1709
+ function convertFunctionSignature(node, filename, source, recordNames, programFunction = false, ownerId = "function:0",
1710
+ referenceModule = false) {
580
1711
  const location = nodeLocation(node, filename, source)
1712
+ const modifiers = node.getChild("Modifiers")
1713
+ const modifierNames = modifiers ? structuralChildren(modifiers).map((modifier) => modifier.name) : []
1714
+
1715
+ const requiredModifiers = programFunction ? "public or private static" : referenceModule ? "private or package-visible static" : "private static"
1716
+ const modifiersText = modifierNames.join(" ")
1717
+
1718
+ if (programFunction ? !["public static", "private static"].includes(modifiersText) :
1719
+ referenceModule ? !["private static", "static"].includes(modifiersText) : modifiersText != "private static") {
1720
+ return unsupportedSyntax("java", `semantic function without ${requiredModifiers}`, modifiers ? nodeLocation(modifiers, filename, source) : location)
1721
+ }
1722
+ const typeParameters = node.getChild("TypeParameters")
1723
+ const throws = node.getChild("Throws")
1724
+
1725
+ if (throws) return unsupportedSyntax("java", "checked throws", nodeLocation(throws, filename, source))
1726
+ const semanticTypeParameters = convertJavaTypeParameters(typeParameters, ownerId, filename, source)
1727
+ const typeParameterNames = new Map((semanticTypeParameters ?? []).map((parameter) => [parameter.name, parameter]))
581
1728
  const definition = requiredChild(node, "Definition", filename, source)
582
1729
  const name = nodeText(definition, source)
583
1730
  const parametersNode = requiredChild(node, "FormalParameters", filename, source)
584
- const parameters = parametersNode.getChildren("FormalParameter").map((parameter) => {
1731
+ const parameterNodes = structuralChildren(parametersNode)
1732
+ const unsupportedParameter = parameterNodes.find((parameter) => parameter.name != "FormalParameter")
1733
+
1734
+ if (unsupportedParameter) {
1735
+ return unsupportedSyntax("java", "unsupported parameter form", nodeLocation(unsupportedParameter, filename, source))
1736
+ }
1737
+ const parameters = parameterNodes.map((parameter) => {
585
1738
  const parameterLocation = nodeLocation(parameter, filename, source)
1739
+ const parameterChildren = structuralChildren(parameter)
1740
+
1741
+ if (parameterChildren.length != 2 || parameter.getChild("Modifiers") || parameter.getChild("Dimension")) {
1742
+ return unsupportedSyntax("java", "unsupported parameter form", parameterLocation)
1743
+ }
586
1744
  const parameterNameNode = requiredChild(parameter, "Definition", filename, source)
587
1745
  const parameterName = nodeText(parameterNameNode, source)
588
1746
 
@@ -590,22 +1748,371 @@ function convertFunction(node, filename, source) {
590
1748
  kind: /** @type {const} */ ("Parameter"),
591
1749
  location: parameterLocation,
592
1750
  name: parameterName,
593
- type: convertType(declarationType(parameter), `Parameter '${parameterName}'`, parameterLocation, filename, source)
1751
+ type: convertType(declarationType(parameter), `Parameter '${parameterName}'`, parameterLocation, filename, source,
1752
+ recordNames, typeParameterNames)
594
1753
  }
595
1754
 
596
1755
  return withParserRanges(semanticParameter, {name: nodeLocation(parameterNameNode, filename, source)})
597
1756
  })
1757
+ return {
1758
+ location,
1759
+ name,
1760
+ nameLocation: nodeLocation(definition, filename, source),
1761
+ parameters,
1762
+ returnType: convertReturnType(declarationType(node), `Function '${name}' return`, location, filename, source,
1763
+ recordNames, typeParameterNames),
1764
+ ...(semanticTypeParameters ? {typeParameters: semanticTypeParameters} : {})
1765
+ }
1766
+ }
1767
+
1768
+ /**
1769
+ * Converts one Java function body using already-proved module signatures.
1770
+ * @param {import("@lezer/common").SyntaxNode} node - Method declaration.
1771
+ * @param {JavaFunctionSignature} signature - Preconverted function signature.
1772
+ * @param {Map<string, JavaFunctionSignature>} functions - Module function signatures.
1773
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
1774
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration>} records - Record declarations by identity.
1775
+ * @param {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Reference classes by identity.
1776
+ * @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Error declarations by source name.
1777
+ * @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errors - Error declarations by identity.
1778
+ * @param {string} filename - Source filename.
1779
+ * @param {string} source - Complete source.
1780
+ * @returns {import("../semantic/types.js").FunctionDeclaration} Semantic function.
1781
+ */
1782
+ function convertFunction(node, signature, functions, recordNames, records, classes, errorNames, errors, filename, source) {
598
1783
  const block = requiredChild(node, "Block", filename, source)
599
- const body = convertBlock(block, filename, source)
1784
+ const context = {
1785
+ bindings: new Map(signature.parameters.map((parameter) => [parameter.name, parameter.type])),
1786
+ classes,
1787
+ errorNames,
1788
+ erasedBindingNames: new Set(),
1789
+ errors,
1790
+ functions,
1791
+ recordNames,
1792
+ records,
1793
+ returnType: signature.returnType,
1794
+ typeParameters: new Map((signature.typeParameters ?? []).map((parameter) => [parameter.name, parameter]))
1795
+ }
600
1796
 
601
1797
  return withParserRanges({
602
- body,
1798
+ body: convertBlock(block, filename, source, context),
603
1799
  kind: "FunctionDeclaration",
604
- location,
605
- name,
606
- parameters,
607
- returnType: convertType(declarationType(node), `Function '${name}' return`, location, filename, source)
608
- }, {name: nodeLocation(definition, filename, source)})
1800
+ location: signature.location,
1801
+ name: signature.name,
1802
+ parameters: signature.parameters,
1803
+ returnType: signature.returnType,
1804
+ ...(signature.typeParameters ? {typeParameters: signature.typeParameters} : {})
1805
+ }, {name: signature.nameLocation})
1806
+ }
1807
+
1808
+ /**
1809
+ * Converts one exact final RuntimeException subclass.
1810
+ * @param {import("@lezer/common").SyntaxNode} node - Java class declaration.
1811
+ * @param {import("../semantic/types.js").ErrorDeclaration} declaration - Predeclared semantic error.
1812
+ * @param {string} filename - Source filename.
1813
+ * @param {string} source - Complete source.
1814
+ * @param {boolean} [programError] - Whether public program visibility is admitted.
1815
+ * @returns {import("../semantic/types.js").ErrorDeclaration} Semantic error declaration.
1816
+ */
1817
+ function convertJavaError(node, declaration, filename, source, programError = false) {
1818
+ const location = nodeLocation(node, filename, source)
1819
+ const modifiers = node.getChild("Modifiers")
1820
+ const modifierText = modifiers ? structuralChildren(modifiers).map((modifier) => modifier.name).join(" ") : ""
1821
+ const superclass = node.getChild("Superclass")
1822
+ const superclassChildren = superclass ? structuralChildren(superclass) : []
1823
+ const parent = superclassChildren.at(-1)
1824
+ const body = node.getChild("ClassBody")
1825
+ const members = body ? structuralChildren(body) : []
1826
+ const constructor = members[0]
1827
+ const parameters = constructor?.getChild("FormalParameters")
1828
+ const parameterNodes = parameters ? structuralChildren(parameters) : []
1829
+ const parameter = parameterNodes[0]
1830
+ const parameterType = parameter ? declarationType(parameter) : null
1831
+ const parameterName = parameter?.getChild("Definition")
1832
+ const constructorBody = constructor?.getChild("ConstructorBody")
1833
+ const constructorStatements = constructorBody ? structuralChildren(constructorBody) : []
1834
+ const superCall = constructorStatements[0]
1835
+ const superArguments = superCall?.getChild("ArgumentList")
1836
+ const superValues = superArguments ? structuralChildren(superArguments) : []
1837
+
1838
+ if ((programError ? !["public final", "final"].includes(modifierText) : modifierText != "final") ||
1839
+ !superclass || superclassChildren.length != 2 || parent?.name != "TypeName" || nodeText(parent, source) != "RuntimeException" ||
1840
+ node.getChild("SuperInterfaces") || node.getChild("TypeParameters") || !body || members.length != 1 ||
1841
+ constructor?.name != "ConstructorDeclaration" ||
1842
+ (programError ? nodeText(constructor.getChild("Modifiers") ?? constructor, source) != "public" : constructor.getChild("Modifiers")) ||
1843
+ constructor.getChild("Throws") ||
1844
+ nodeText(constructor.getChild("Definition") ?? node, source) != declaration.name || parameterNodes.length != 1 ||
1845
+ parameter?.name != "FormalParameter" || nodeText(parameterType ?? node, source) != "String" ||
1846
+ nodeText(parameterName ?? node, source) != "message" || !constructorBody || constructorStatements.length != 1 ||
1847
+ superCall?.name != "ExplicitConstructorInvocation" || !superCall.getChild("super") || superValues.length != 1 ||
1848
+ superValues[0].name != "Identifier" || nodeText(superValues[0], source) != "message") {
1849
+ return unsupportedSyntax("java", "noncanonical typed error declaration", location)
1850
+ }
1851
+
1852
+ return withParserRanges(declaration, {
1853
+ name: nodeLocation(requiredChild(node, "Definition", filename, source), filename, source),
1854
+ type: nodeLocation(parent, filename, source)
1855
+ })
1856
+ }
1857
+
1858
+ /**
1859
+ * Reads one package-private Java reference constructor or instance-method signature.
1860
+ * @param {import("@lezer/common").SyntaxNode} node - Constructor or method declaration.
1861
+ * @param {import("../semantic/types.js").ClassDeclaration} declaration - Declaring class.
1862
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} nominalNames - Visible nominal declarations.
1863
+ * @param {boolean} constructor - Whether this is the sole constructor.
1864
+ * @param {string} filename - Source filename.
1865
+ * @param {string} source - Complete source.
1866
+ * @returns {{location: import("../semantic/types.js").SourceLocation, name: string, nameLocation: import("../semantic/types.js").SourceLocation, parameters: import("../semantic/types.js").Parameter[], returnType?: import("../semantic/types.js").SemanticFunctionReturnType}} Exact signature.
1867
+ */
1868
+ function convertJavaClassCallableSignature(node, declaration, nominalNames, constructor, filename, source) {
1869
+ const location = nodeLocation(node, filename, source)
1870
+ const definition = requiredChild(node, "Definition", filename, source)
1871
+ const name = nodeText(definition, source)
1872
+ const parametersNode = requiredChild(node, "FormalParameters", filename, source)
1873
+ const parameterNodes = structuralChildren(parametersNode)
1874
+ const modifiers = node.getChild("Modifiers")
1875
+
1876
+ if (modifiers || node.getChild("Throws") || node.getChild("TypeParameters") ||
1877
+ constructor != (node.name == "ConstructorDeclaration") || constructor && name != declaration.name) {
1878
+ return unsupportedSyntax("java", "noncanonical reference class callable", location)
1879
+ }
1880
+ if (!constructor && referenceMethodHooks.has(name)) {
1881
+ return unsupportedSyntax("java", `reserved reference method '${name}'`, nodeLocation(definition, filename, source))
1882
+ }
1883
+ const parameters = parameterNodes.map((parameter) => {
1884
+ const parameterLocation = nodeLocation(parameter, filename, source)
1885
+ const children = structuralChildren(parameter)
1886
+ const parameterNameNode = parameter.getChild("Definition")
1887
+
1888
+ if (parameter.name != "FormalParameter" || children.length != 2 || parameter.getChild("Modifiers") ||
1889
+ parameter.getChild("Dimension") || !parameterNameNode) {
1890
+ return unsupportedSyntax("java", "unsupported reference class parameter", parameterLocation)
1891
+ }
1892
+ const parameterName = nodeText(parameterNameNode, source)
1893
+
1894
+ return withParserRanges({kind: /** @type {const} */ ("Parameter"), location: parameterLocation, name: parameterName,
1895
+ type: convertType(declarationType(parameter), `Parameter '${parameterName}'`, parameterLocation, filename, source, nominalNames)},
1896
+ {name: nodeLocation(parameterNameNode, filename, source)})
1897
+ })
1898
+
1899
+ return {location, name: constructor ? "constructor" : name, nameLocation: nodeLocation(definition, filename, source), parameters,
1900
+ ...(constructor ? {} : {returnType: convertReturnType(declarationType(node), `Method '${name}' return`, location,
1901
+ filename, source, nominalNames)})}
1902
+ }
1903
+
1904
+ /**
1905
+ * Converts the bounded final/private-field Java reference-class profile.
1906
+ * @param {import("@lezer/common").SyntaxNode} node - Class declaration.
1907
+ * @param {import("../semantic/types.js").ClassDeclaration} declaration - Predeclared class identity.
1908
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} nominalNames - Visible nominal declarations.
1909
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration>} records - Records by identity.
1910
+ * @param {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Classes by identity.
1911
+ * @param {Map<string, JavaFunctionSignature>} functions - Top-level signatures.
1912
+ * @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Errors by name.
1913
+ * @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errors - Errors by identity.
1914
+ * @param {string} filename - Source filename.
1915
+ * @param {string} source - Complete source.
1916
+ * @returns {import("../semantic/types.js").ClassDeclaration} Semantic class.
1917
+ */
1918
+ function convertJavaReferenceClass(node, declaration, nominalNames, records, classes, functions, errorNames, errors,
1919
+ filename, source) {
1920
+ const location = nodeLocation(node, filename, source)
1921
+ const modifiers = node.getChild("Modifiers")
1922
+ const modifierNames = modifiers ? structuralChildren(modifiers).map((modifier) => modifier.name) : []
1923
+ const body = requiredChild(node, "ClassBody", filename, source)
1924
+ const members = structuralChildren(body)
1925
+ const fieldNodes = members.filter((member) => member.name == "FieldDeclaration")
1926
+ const constructorNodes = members.filter((member) => member.name == "ConstructorDeclaration")
1927
+ const methodNodes = members.filter((member) => member.name == "MethodDeclaration")
1928
+
1929
+ if (modifierNames.join(" ") != "final" || node.getChild("Superclass") || node.getChild("SuperInterfaces") ||
1930
+ node.getChild("TypeParameters") || constructorNodes.length != 1 || fieldNodes.length == 0 ||
1931
+ fieldNodes.length + constructorNodes.length + methodNodes.length != members.length ||
1932
+ members.slice(0, fieldNodes.length).some((member) => member.name != "FieldDeclaration") ||
1933
+ members[fieldNodes.length] != constructorNodes[0] ||
1934
+ members.slice(fieldNodes.length + 1).some((member) => member.name != "MethodDeclaration")) {
1935
+ return unsupportedSyntax("java", "noncanonical reference class declaration", location)
1936
+ }
1937
+ declaration.fields = fieldNodes.map((fieldNode, index) => {
1938
+ const fieldLocation = nodeLocation(fieldNode, filename, source)
1939
+ const fieldModifiers = fieldNode.getChild("Modifiers")
1940
+ const names = fieldModifiers ? structuralChildren(fieldModifiers).map((modifier) => modifier.name) : []
1941
+ const variableNodes = fieldNode.getChildren("VariableDeclarator")
1942
+ const variable = variableNodes[0]
1943
+ const definition = variable ? variable.getChild("Definition") : null
1944
+
1945
+ if (names.join(" ") != "private" || variableNodes.length != 1 || !definition ||
1946
+ structuralChildren(variable).length != 1 || fieldNode.getChild("Dimension")) {
1947
+ return unsupportedSyntax("java", "noncanonical private instance field", fieldLocation)
1948
+ }
1949
+ const name = nodeText(definition, source)
1950
+
1951
+ return withParserRanges({id: `${declaration.id}:field:${index}`, kind: /** @type {const} */ ("PrivateField"),
1952
+ location: fieldLocation, name,
1953
+ type: convertType(declarationType(fieldNode), `Private field '${name}'`, fieldLocation, filename, source, nominalNames)},
1954
+ {name: nodeLocation(definition, filename, source)})
1955
+ })
1956
+ const constructorNode = constructorNodes[0]
1957
+ const constructorSignature = convertJavaClassCallableSignature(constructorNode, declaration, nominalNames, true, filename, source)
1958
+
1959
+ declaration.constructor = withParserRanges({body: /** @type {import("../semantic/types.js").Block} */ ({}),
1960
+ id: `${declaration.id}:constructor`, kind: /** @type {const} */ ("ConstructorDeclaration"),
1961
+ location: constructorSignature.location, parameters: constructorSignature.parameters}, {constructor: constructorSignature.nameLocation})
1962
+ const methodSignatures = methodNodes.map((method) =>
1963
+ convertJavaClassCallableSignature(method, declaration, nominalNames, false, filename, source))
1964
+
1965
+ declaration.methods = methodSignatures.map((signature, index) => withParserRanges({
1966
+ body: /** @type {import("../semantic/types.js").Block} */ ({}), id: `${declaration.id}:method:${index}`,
1967
+ kind: /** @type {const} */ ("MethodDeclaration"), location: signature.location, name: signature.name,
1968
+ parameters: signature.parameters,
1969
+ returnType: /** @type {import("../semantic/types.js").SemanticFunctionReturnType} */ (signature.returnType)
1970
+ }, {name: signature.nameLocation}))
1971
+ const base = {bindings: new Map(), classes, currentClass: declaration, errorNames, erasedBindingNames: new Set(), errors,
1972
+ functions, recordNames: nominalNames, records}
1973
+ const constructorBody = requiredChild(constructorNode, "ConstructorBody", filename, source)
1974
+
1975
+ declaration.constructor.body = convertBlock(constructorBody, filename, source,
1976
+ {...base, bindings: new Map(declaration.constructor.parameters.map((parameter) => [parameter.name, parameter.type])),
1977
+ returnType: {kind: /** @type {const} */ ("TypeReference"), name: /** @type {const} */ ("void")}})
1978
+ for (let index = 0; index < methodNodes.length; index += 1) {
1979
+ const method = declaration.methods[index]
1980
+
1981
+ method.body = convertBlock(requiredChild(methodNodes[index], "Block", filename, source), filename, source,
1982
+ {...base, bindings: new Map(method.parameters.map((parameter) => [parameter.name, parameter.type])), returnType: method.returnType})
1983
+ }
1984
+
1985
+ return withParserRanges(declaration, {name: nodeLocation(requiredChild(node, "Definition", filename, source), filename, source)})
1986
+ }
1987
+
1988
+ /**
1989
+ * Converts one conventional final Java class with exact private-final storage,
1990
+ * constructor initialization, and canonical field-name accessors.
1991
+ * @param {import("@lezer/common").SyntaxNode} node - Class declaration.
1992
+ * @param {import("../semantic/types.js").RecordDeclaration} declaration - Predeclared nominal identity.
1993
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration>} recordNames - Records by source name.
1994
+ * @param {string} filename - Source filename.
1995
+ * @param {string} source - Complete source.
1996
+ * @param {boolean} [programRecord] - Whether the canonical project profile admits public or package-private class visibility.
1997
+ * @returns {import("../semantic/types.js").RecordDeclaration} Semantic record.
1998
+ */
1999
+ function convertJavaRecord(node, declaration, recordNames, filename, source, programRecord = false) {
2000
+ const location = nodeLocation(node, filename, source)
2001
+ const typeParameterNames = new Map((declaration.typeParameters ?? []).map((parameter) => [parameter.name, parameter]))
2002
+ const modifiers = node.getChild("Modifiers")
2003
+ const modifierNames = modifiers ? structuralChildren(modifiers).map((modifier) => modifier.name) : []
2004
+
2005
+ const modifiersText = modifierNames.join(" ")
2006
+
2007
+ if ((programRecord ? !["public final", "final"].includes(modifiersText) : modifiersText != "final") ||
2008
+ node.getChild("Superclass") || node.getChild("SuperInterfaces")) {
2009
+ return unsupportedSyntax("java", "noncanonical record class modifiers", location)
2010
+ }
2011
+ const classBody = requiredChild(node, "ClassBody", filename, source)
2012
+ const members = structuralChildren(classBody)
2013
+ const fields = members.filter((member) => member.name == "FieldDeclaration")
2014
+ const constructors = members.filter((member) => member.name == "ConstructorDeclaration")
2015
+ const accessors = members.filter((member) => member.name == "MethodDeclaration")
2016
+ const unsupported = members.find((member) => !["FieldDeclaration", "ConstructorDeclaration", "MethodDeclaration"].includes(member.name))
2017
+
2018
+ if (unsupported || constructors.length != 1 || accessors.length != fields.length ||
2019
+ members.slice(0, fields.length).some((member) => member.name != "FieldDeclaration") || members[fields.length] != constructors[0] ||
2020
+ members.slice(fields.length + 1).some((member) => member.name != "MethodDeclaration")) {
2021
+ return unsupportedSyntax("java", "record class member order or shape", nodeLocation(unsupported ?? node, filename, source))
2022
+ }
2023
+ declaration.fields = fields.map((fieldNode) => {
2024
+ const fieldLocation = nodeLocation(fieldNode, filename, source)
2025
+ const fieldModifiers = fieldNode.getChild("Modifiers")
2026
+ const names = fieldModifiers ? structuralChildren(fieldModifiers).map((modifier) => modifier.name) : []
2027
+ const variable = requiredChild(fieldNode, "VariableDeclarator", filename, source)
2028
+ const definition = requiredChild(variable, "Definition", filename, source)
2029
+ const variableChildren = structuralChildren(variable)
2030
+
2031
+ if (names.join(" ") != "private final" || variableChildren.length != 1 || fieldNode.getChild("Dimension")) {
2032
+ return unsupportedSyntax("java", "record field outside private final storage profile", fieldLocation)
2033
+ }
2034
+ const field = {
2035
+ kind: /** @type {const} */ ("RecordField"),
2036
+ location: fieldLocation,
2037
+ name: nodeText(definition, source),
2038
+ type: convertType(declarationType(fieldNode), `Record field '${nodeText(definition, source)}'`, fieldLocation, filename, source,
2039
+ recordNames, typeParameterNames)
2040
+ }
2041
+
2042
+ return withParserRanges(field, {name: nodeLocation(definition, filename, source)})
2043
+ })
2044
+ const constructor = constructors[0]
2045
+ const constructorName = requiredChild(constructor, "Definition", filename, source)
2046
+ const parametersNode = requiredChild(constructor, "FormalParameters", filename, source)
2047
+ const parameters = structuralChildren(parametersNode)
2048
+ const body = requiredChild(constructor, "ConstructorBody", filename, source)
2049
+ const assignments = structuralChildren(body)
2050
+
2051
+ const constructorModifiers = constructor.getChild("Modifiers")
2052
+ const constructorModifierNames = constructorModifiers ? structuralChildren(constructorModifiers).map((modifier) => modifier.name) : []
2053
+
2054
+ if (nodeText(constructorName, source) != declaration.name ||
2055
+ constructorModifierNames.join(" ") != (programRecord ? "public" : "") || constructor.getChild("Throws") ||
2056
+ constructor.getChild("TypeParameters") || parameters.length != declaration.fields.length || assignments.length != declaration.fields.length) {
2057
+ return unsupportedSyntax("java", "noncanonical record constructor", nodeLocation(constructor, filename, source))
2058
+ }
2059
+ for (let index = 0; index < declaration.fields.length; index += 1) {
2060
+ const field = declaration.fields[index]
2061
+ const parameter = parameters[index]
2062
+ const parameterName = parameter?.getChild("Definition")
2063
+ const assignment = assignments[index]
2064
+ const assignmentExpression = assignment?.getChild("AssignmentExpression")
2065
+ const assignmentChildren = assignmentExpression ? structuralChildren(assignmentExpression) : []
2066
+ const fieldAccess = assignmentChildren[0]
2067
+ const operator = assignmentChildren[1]
2068
+ const value = assignmentChildren[2]
2069
+ const parameterType = parameter ? convertType(declarationType(parameter), `Record constructor parameter '${field.name}'`,
2070
+ nodeLocation(parameter, filename, source), filename, source, recordNames, typeParameterNames) : undefined
2071
+
2072
+ if (parameter?.name != "FormalParameter" || !parameterName || nodeText(parameterName, source) != field.name ||
2073
+ parameter.getChild("Modifiers") || parameter.getChild("Dimension") || !parameterType || !sameValueType(parameterType, field.type) ||
2074
+ assignment?.name != "ExpressionStatement" || assignmentChildren.length != 3 || fieldAccess?.name != "FieldAccess" ||
2075
+ nodeText(fieldAccess, source) != `this.${field.name}` || operator?.name != "AssignOp" || nodeText(operator, source) != "=" ||
2076
+ value?.name != "Identifier" || nodeText(value, source) != field.name) {
2077
+ return unsupportedSyntax("java", "record field not initialized exactly once", nodeLocation(assignment ?? constructor, filename, source))
2078
+ }
2079
+ }
2080
+ for (let index = 0; index < declaration.fields.length; index += 1) {
2081
+ const field = declaration.fields[index]
2082
+ const accessor = accessors[index]
2083
+ const name = requiredChild(accessor, "Definition", filename, source)
2084
+ const parameters = requiredChild(accessor, "FormalParameters", filename, source)
2085
+ const block = requiredChild(accessor, "Block", filename, source)
2086
+ const statements = structuralChildren(block)
2087
+ const returned = statements[0]?.getChild("FieldAccess")
2088
+ const accessorType = convertType(declarationType(accessor), `Record accessor '${field.name}'`,
2089
+ nodeLocation(accessor, filename, source), filename, source, recordNames, typeParameterNames)
2090
+
2091
+ const accessorModifiers = accessor.getChild("Modifiers")
2092
+ const accessorModifierNames = accessorModifiers ? structuralChildren(accessorModifiers).map((modifier) => modifier.name) : []
2093
+
2094
+ if (accessorModifierNames.join(" ") != (programRecord ? "public" : "") || accessor.getChild("Throws") || accessor.getChild("TypeParameters") ||
2095
+ nodeText(name, source) != field.name || structuralChildren(parameters).length != 0 || statements.length != 1 ||
2096
+ statements[0].name != "ReturnStatement" || !returned || nodeText(returned, source) != `this.${field.name}` ||
2097
+ !sameValueType(accessorType, field.type)) {
2098
+ return unsupportedSyntax("java", "noncanonical record accessor", nodeLocation(accessor, filename, source))
2099
+ }
2100
+ }
2101
+
2102
+ return withParserRanges(declaration, {name: nodeLocation(requiredChild(node, "Definition", filename, source), filename, source)})
2103
+ }
2104
+
2105
+ /**
2106
+ * Detects only the pinned grammar's parser-backed recovery shape for Java record syntax.
2107
+ * @param {import("@lezer/common").SyntaxNode} root - Program root.
2108
+ * @param {string} source - Complete source.
2109
+ * @returns {import("@lezer/common").SyntaxNode | undefined} The parser-owned `record` type token.
2110
+ */
2111
+ function javaRecordRecoveryToken(root, source) {
2112
+ const first = structuralChildren(root)[0]
2113
+ const type = first?.name == "LocalVariableDeclaration" ? first.getChild("TypeName") : null
2114
+
2115
+ return type && nodeText(type, source) == "record" ? type : undefined
609
2116
  }
610
2117
 
611
2118
  /**
@@ -613,9 +2120,10 @@ function convertFunction(node, filename, source) {
613
2120
  * @param {import("@lezer/common").SyntaxNode} node - Java if statement.
614
2121
  * @param {string} filename - Source filename.
615
2122
  * @param {string} source - Complete source.
2123
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
616
2124
  * @returns {import("../semantic/types.js").IfStatement} Semantic branch.
617
2125
  */
618
- function convertIf(node, filename, source) {
2126
+ function convertIf(node, filename, source, context) {
619
2127
  const location = nodeLocation(node, filename, source)
620
2128
  const conditionContainer = requiredChild(node, "ParenthesizedExpression", filename, source)
621
2129
  const conditionNodes = structuralChildren(conditionContainer)
@@ -630,14 +2138,18 @@ function convertIf(node, filename, source) {
630
2138
  return unsupportedSyntax("java", "if without block consequent", location)
631
2139
  }
632
2140
 
2141
+ const semanticCondition = convertExpression(condition, filename, source, context)
633
2142
  let alternate
2143
+ const consequentContext = {...context, bindings: new Map(context.bindings), erasedBindingNames: new Set(context.erasedBindingNames)}
2144
+ const alternateContext = {...context, bindings: new Map(context.bindings), erasedBindingNames: new Set(context.erasedBindingNames)}
634
2145
 
635
- if (alternateNode?.name == "Block") alternate = convertBlock(alternateNode, filename, source)
636
- else if (alternateNode?.name == "IfStatement") {
2146
+ if (alternateNode?.name == "Block") {
2147
+ alternate = convertBlock(alternateNode, filename, source, alternateContext)
2148
+ } else if (alternateNode?.name == "IfStatement") {
637
2149
  alternate = {
638
2150
  kind: /** @type {const} */ ("Block"),
639
2151
  location: nodeLocation(alternateNode, filename, source),
640
- statements: [convertIf(alternateNode, filename, source)]
2152
+ statements: [convertIf(alternateNode, filename, source, alternateContext)]
641
2153
  }
642
2154
  } else if (alternateNode && alternateNode.name != "else") {
643
2155
  return unsupportedSyntax("java", `if alternate ${alternateNode.name}`, nodeLocation(alternateNode, filename, source))
@@ -645,24 +2157,89 @@ function convertIf(node, filename, source) {
645
2157
 
646
2158
  return {
647
2159
  ...(alternate ? {alternate} : {}),
648
- condition: convertExpression(condition, filename, source),
649
- consequent: convertBlock(consequentNode, filename, source),
2160
+ condition: semanticCondition,
2161
+ consequent: convertBlock(consequentNode, filename, source, consequentContext),
650
2162
  kind: "IfStatement",
651
2163
  location
652
2164
  }
653
2165
  }
654
2166
 
2167
+ /**
2168
+ * Converts one exact block-bodied Java pre-condition loop.
2169
+ * @param {import("@lezer/common").SyntaxNode} node - Java WhileStatement.
2170
+ * @param {string} filename - Source filename.
2171
+ * @param {string} source - Complete source.
2172
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
2173
+ * @returns {import("../semantic/types.js").WhileStatement} Semantic loop.
2174
+ */
2175
+ function convertWhile(node, filename, source, context) {
2176
+ const location = nodeLocation(node, filename, source)
2177
+ const direct = directChildren(node)
2178
+ const conditionContainer = node.getChild("ParenthesizedExpression")
2179
+ const body = node.getChild("Block")
2180
+ const conditionNodes = conditionContainer ? structuralChildren(conditionContainer) : []
2181
+
2182
+ if (direct.length != 3 || direct[0].name != "while" || direct[1].name != "ParenthesizedExpression" ||
2183
+ direct[2].name != "Block" || !conditionContainer || !body || conditionNodes.length != 1) {
2184
+ return unsupportedSyntax("java", "while without exact condition and block body", location)
2185
+ }
2186
+ const bodyContext = {
2187
+ ...context,
2188
+ bindings: new Map(context.bindings),
2189
+ erasedBindingNames: new Set(context.erasedBindingNames)
2190
+ }
2191
+
2192
+ return withParserRanges({
2193
+ body: convertBlock(body, filename, source, bodyContext),
2194
+ condition: convertExpression(conditionNodes[0], filename, source, context),
2195
+ kind: /** @type {const} */ ("WhileStatement"),
2196
+ location
2197
+ }, {keyword: nodeLocation(direct[0], filename, source)})
2198
+ }
2199
+
655
2200
  /**
656
2201
  * Converts Java's supported main method.
657
2202
  * @param {import("@lezer/common").SyntaxNode} node - Main method.
658
2203
  * @param {string} filename - Source filename.
659
2204
  * @param {string} source - Complete source.
2205
+ * @param {Map<string, JavaFunctionSignature>} functions - Module function signatures.
2206
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
2207
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration>} records - Record declarations by identity.
2208
+ * @param {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Reference classes by identity.
2209
+ * @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Error declarations by source name.
2210
+ * @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errors - Error declarations by identity.
660
2211
  * @returns {import("../semantic/types.js").EntryPoint} Semantic entry point.
661
2212
  */
662
- function convertEntryPoint(node, filename, source) {
2213
+ function convertEntryPoint(node, filename, source, functions, recordNames, records, classes, errorNames, errors) {
663
2214
  const location = nodeLocation(node, filename, source)
2215
+ const modifiers = node.getChild("Modifiers")
2216
+ const parameters = node.getChild("FormalParameters")
2217
+ const definition = node.getChild("Definition")
2218
+ const returnType = node.getChild("void")
2219
+ const modifierNames = modifiers ? structuralChildren(modifiers).map((modifier) => modifier.name) : []
2220
+ const parameterNodes = parameters ? structuralChildren(parameters) : []
2221
+ const parameter = parameterNodes[0]
2222
+ const parameterType = parameter?.getChild("ArrayType")
2223
+ const parameterName = parameter?.getChild("Definition")
2224
+
2225
+ if (modifierNames.join(" ") != "public static" || !parameters || parameterNodes.length != 1 ||
2226
+ parameter?.name != "FormalParameter" || nodeText(parameterType ?? node, source) != "String[]" ||
2227
+ nodeText(parameterName ?? node, source) != "args" || !definition || nodeText(definition, source) != "main" || !returnType ||
2228
+ node.getChild("TypeParameters") || node.getChild("Throws")) {
2229
+ return unsupportedSyntax("java", "main method signature", location)
2230
+ }
664
2231
  const block = requiredChild(node, "Block", filename, source)
665
- const body = convertBlock(block, filename, source)
2232
+ const body = convertBlock(block, filename, source, {
2233
+ bindings: new Map(),
2234
+ classes,
2235
+ errorNames,
2236
+ erasedBindingNames: new Set(),
2237
+ errors,
2238
+ functions,
2239
+ recordNames,
2240
+ records,
2241
+ returnType: undefined
2242
+ })
666
2243
 
667
2244
  return {body, kind: "EntryPoint", location}
668
2245
  }
@@ -672,9 +2249,10 @@ function convertEntryPoint(node, filename, source) {
672
2249
  * @param {import("@lezer/common").SyntaxNode} statement - Expression statement.
673
2250
  * @param {string} filename - Source filename.
674
2251
  * @param {string} source - Complete source.
2252
+ * @param {JavaConversionContext} context - Typed lexical conversion context.
675
2253
  * @returns {import("../semantic/types.js").PrintStatement} Semantic print.
676
2254
  */
677
- function convertPrint(statement, filename, source) {
2255
+ function convertPrint(statement, filename, source, context) {
678
2256
  const location = nodeLocation(statement, filename, source)
679
2257
 
680
2258
  const printInvocation = statement.getChild("MethodInvocation")
@@ -697,7 +2275,7 @@ function convertPrint(statement, filename, source) {
697
2275
 
698
2276
  const printLocation = nodeLocation(printInvocation, filename, source)
699
2277
 
700
- return {expression: convertExpression(arguments_[0], filename, source), kind: "PrintStatement", location: printLocation}
2278
+ return {expression: convertExpression(arguments_[0], filename, source, context), kind: "PrintStatement", location: printLocation}
701
2279
  }
702
2280
 
703
2281
  /**
@@ -705,13 +2283,18 @@ function convertPrint(statement, filename, source) {
705
2283
  * @param {object} input - Parser input.
706
2284
  * @param {string} input.filename - Source filename.
707
2285
  * @param {string} input.source - Source text.
2286
+ * @param {readonly import("../semantic/types.js").EffectCapabilityDeclaration[]} [input.capabilities] - Compiler-authorized declarations.
2287
+ * @param {{isEntry: boolean, functions: Map<string, import("../semantic/types.js").FunctionDeclaration>, records: Map<string, import("../semantic/types.js").RecordDeclaration>, errors?: Map<string, import("../semantic/types.js").ErrorDeclaration>}} [input.program] - Resolved program imports and entry role.
708
2288
  * @returns {import("../semantic/types.js").SemanticModule} Semantic module.
709
2289
  */
710
- export function parseJava({filename, source}) {
2290
+ export function parseJava({capabilities = [], filename, source, program}) {
711
2291
  const tree = parser.parse(source)
712
2292
  const error = descendants(tree.topNode, "⚠")[0]
713
2293
 
714
2294
  if (error) {
2295
+ const recordToken = javaRecordRecoveryToken(tree.topNode, source)
2296
+
2297
+ if (recordToken) return unsupportedSyntax("java", "Java record declaration under the pinned parser", nodeLocation(recordToken, filename, source))
715
2298
  throw new SemantifoldDiagnostic({
716
2299
  code: "PARSE_ERROR",
717
2300
  language: "java",
@@ -720,16 +2303,341 @@ export function parseJava({filename, source}) {
720
2303
  })
721
2304
  }
722
2305
 
723
- const methods = descendants(tree.topNode, "MethodDeclaration")
724
- const mainMethod = methods.find((method) => nodeText(requiredChild(method, "Definition", filename, source), source) == "main")
2306
+ if (program) return parseJavaProgramModule(tree.topNode, filename, source, program, capabilities)
2307
+
2308
+ const programMembers = task034JavaProgramMembers(structuralChildren(tree.topNode), capabilities, filename, source)
2309
+ const classDeclarations = programMembers.filter((member) => member.name == "ClassDeclaration")
2310
+ const unexpectedProgramMember = programMembers.find((member) => member.name != "ClassDeclaration")
2311
+ const mainClasses = classDeclarations.filter((declaration) => nodeText(requiredChild(declaration, "Definition", filename, source), source) == "Main")
2312
+ const classDeclaration = mainClasses[0]
2313
+
2314
+ if (!classDeclaration || mainClasses.length != 1 || unexpectedProgramMember || classDeclarations.at(-1) != classDeclaration) {
2315
+ return unsupportedSyntax("java", "compilation unit other than record classes followed by one Main class",
2316
+ nodeLocation(unexpectedProgramMember ?? tree.topNode, filename, source))
2317
+ }
2318
+ const className = requiredChild(classDeclaration, "Definition", filename, source)
2319
+ const classModifiers = classDeclaration.getChild("Modifiers")
2320
+ const classModifierNames = classModifiers ? structuralChildren(classModifiers).map((modifier) => modifier.name) : []
2321
+
2322
+ if (nodeText(className, source) != "Main" || classModifierNames.join(" ") != "public final") {
2323
+ return unsupportedSyntax("java", "public final Main class", nodeLocation(classDeclaration, filename, source))
2324
+ }
2325
+ const classBody = requiredChild(classDeclaration, "ClassBody", filename, source)
2326
+ const members = task034JavaMainMembers(structuralChildren(classBody), capabilities, filename, source)
2327
+ const unsupportedMember = members.find((member) => member.name != "MethodDeclaration")
2328
+
2329
+ if (unsupportedMember) return unsupportedSyntax("java", unsupportedMember.name, nodeLocation(unsupportedMember, filename, source))
2330
+ const methods = members
2331
+ const mainMethods = methods.filter((method) => nodeText(requiredChild(method, "Definition", filename, source), source) == "main")
2332
+ const mainMethod = mainMethods[0]
725
2333
  const functionMethods = methods.filter((method) => method != mainMethod)
726
2334
  const location = moduleLocation(filename, source)
727
2335
 
728
2336
  if (functionMethods.length == 0) return unsupportedSyntax("java", "class without a semantic function", location)
729
- if (!mainMethod) return unsupportedSyntax("java", "class without main", location)
2337
+ if (!mainMethod || mainMethods.length != 1) return unsupportedSyntax("java", "class without one unambiguous main", location)
730
2338
 
731
- const functions = functionMethods.map((method) => convertFunction(method, filename, source))
732
- const entryPoint = convertEntryPoint(mainMethod, filename, source)
2339
+ const nominalNodes = classDeclarations.filter((declaration) => declaration != classDeclaration)
2340
+ const errorNodes = nominalNodes.filter((declaration) => {
2341
+ const parent = declaration.getChild("Superclass")
2342
+ return parent && nodeText(parent, source) == "extends RuntimeException"
2343
+ })
2344
+ const referenceClassNodes = nominalNodes.filter((declaration) => !errorNodes.includes(declaration) &&
2345
+ structuralChildren(requiredChild(declaration, "ClassBody", filename, source)).some((member) =>
2346
+ member.name == "FieldDeclaration" && nodeText(member.getChild("Modifiers") ?? member, source) == "private"))
2347
+ const recordNodes = nominalNodes.filter((declaration) => !errorNodes.includes(declaration) && !referenceClassNodes.includes(declaration))
2348
+ const errorDeclarations = errorNodes.map((errorNode, index) => ({
2349
+ id: `error:${index}`,
2350
+ kind: /** @type {const} */ ("ErrorDeclaration"),
2351
+ location: nodeLocation(errorNode, filename, source),
2352
+ name: nodeText(requiredChild(errorNode, "Definition", filename, source), source)
2353
+ }))
2354
+ const recordDeclarations = recordNodes.map((recordNode, index) => {
2355
+ const id = `record:${index}`
2356
+ const typeParameters = convertJavaTypeParameters(recordNode.getChild("TypeParameters"), id, filename, source)
2357
+
2358
+ return {
2359
+ fields: [],
2360
+ id,
2361
+ kind: /** @type {const} */ ("RecordDeclaration"),
2362
+ location: nodeLocation(recordNode, filename, source),
2363
+ name: nodeText(requiredChild(recordNode, "Definition", filename, source), source),
2364
+ ...(typeParameters ? {typeParameters} : {})
2365
+ }
2366
+ })
2367
+ const referenceDeclarations = referenceClassNodes.map((classNode, index) => ({
2368
+ constructor: /** @type {import("../semantic/types.js").ConstructorDeclaration} */ ({}), fields: [], id: `class:${index}`,
2369
+ kind: /** @type {const} */ ("ClassDeclaration"), location: nodeLocation(classNode, filename, source), methods: [],
2370
+ name: nodeText(requiredChild(classNode, "Definition", filename, source), source)
2371
+ }))
2372
+ const recordNames = /** @type {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} */ (
2373
+ new Map(recordDeclarations.map((declaration) => /** @type {const} */ ([declaration.name, declaration]))))
2374
+ for (const resource of capabilities.flatMap(({resources}) => resources)) recordNames.set(resource.name, resource)
2375
+ for (const declaration of referenceDeclarations) recordNames.set(declaration.name, declaration)
2376
+ /** @type {Map<string, import("../semantic/types.js").ErrorDeclaration>} */
2377
+ const errorNames = new Map(errorDeclarations.map((declaration) => /** @type {const} */ ([declaration.name, declaration])))
2378
+ for (const failure of capabilities.flatMap(({failures}) => failures)) {
2379
+ errorNames.set(failure.name, /** @type {import("../semantic/types.js").ErrorDeclaration} */ (/** @type {unknown} */ (failure)))
2380
+ }
2381
+ const errorsById = new Map([...errorNames.values()].map((declaration) => [/** @type {string} */ (declaration.id), declaration]))
2382
+ const errors = errorNodes.map((errorNode, index) => convertJavaError(errorNode, errorDeclarations[index], filename, source))
2383
+ const recordsById = new Map(recordDeclarations.map((declaration) => [/** @type {string} */ (declaration.id), declaration]))
2384
+ const classesById = new Map(referenceDeclarations.map((declaration) => [/** @type {string} */ (declaration.id), declaration]))
2385
+ const records = recordNodes.map((recordNode, index) =>
2386
+ convertJavaRecord(recordNode, recordDeclarations[index],
2387
+ /** @type {Map<string, import("../semantic/types.js").RecordDeclaration>} */ (/** @type {unknown} */ (recordNames)), filename, source))
2388
+ const signatures = functionMethods.map((method, index) =>
2389
+ convertFunctionSignature(method, filename, source, recordNames, false, `function:${index}`, referenceDeclarations.length > 0))
2390
+ /** @type {Map<string, JavaFunctionSignature>} */
2391
+ const functionSignatures = new Map(capabilityFunctionSignatures(capabilities, location))
2392
+ for (const signature of signatures) functionSignatures.set(signature.name, signature)
2393
+ const classes = referenceClassNodes.map((classNode, index) => convertJavaReferenceClass(classNode, referenceDeclarations[index],
2394
+ recordNames, recordsById, classesById, functionSignatures, errorNames, errorsById, filename, source))
2395
+ const functions = functionMethods.map((method, index) =>
2396
+ convertFunction(method, signatures[index], functionSignatures, recordNames, recordsById, classesById,
2397
+ errorNames, errorsById, filename, source))
2398
+ const entryPoint = convertEntryPoint(mainMethod, filename, source, functionSignatures, recordNames, recordsById,
2399
+ classesById, errorNames, errorsById)
2400
+
2401
+ return {entryPoint, functions, kind: "Module", location, ...(errors.length > 0 ? {errors} : {}),
2402
+ ...(classes.length > 0 ? {classes} : {}), ...(records.length > 0 ? {records} : {})}
2403
+ }
2404
+
2405
+ /**
2406
+ * Removes the exact protected top-level support emitted for the built-in probe.
2407
+ * @param {import("@lezer/common").SyntaxNode[]} members - Parsed program members.
2408
+ * @param {readonly import("../semantic/types.js").EffectCapabilityDeclaration[]} capabilities - Authorized declarations.
2409
+ * @param {string} filename - Source filename.
2410
+ * @param {string} source - Complete source.
2411
+ * @returns {import("@lezer/common").SyntaxNode[]} Portable program members.
2412
+ */
2413
+ function task034JavaProgramMembers(members, capabilities, filename, source) {
2414
+ if (!isTask034ResourceProbe(capabilities) || nodeText(members[0], source) != "// semantifold-task034-support") return members
2415
+ if (!hasExactEffectSupport(source, "java", "top")) {
2416
+ return unsupportedSyntax("java", "malformed protected Task 034 top-level support", nodeLocation(members[0], filename, source))
2417
+ }
2418
+ const names = ["ProbeOperationFailure", "ProbeAcquireFailure", "ProbeReadFailure", "ProbeCloseFailure",
2419
+ "ProbeResourceClosed", "ProbeResource"]
2420
+ const matches = names.every((name, index) => members[index + 1]?.name == "ClassDeclaration" &&
2421
+ nodeText(requiredChild(members[index + 1], "Definition", filename, source), source) == name)
2422
+
2423
+ if (!matches || nodeText(members[names.length + 1], source) != "// semantifold-task034-support-top-end") {
2424
+ return unsupportedSyntax("java", "malformed protected Task 034 top-level support", nodeLocation(members[0], filename, source))
2425
+ }
2426
+ return members.slice(names.length + 2)
2427
+ }
2428
+
2429
+ /**
2430
+ * Removes the exact protected Main-member support emitted for the built-in probe.
2431
+ * @param {import("@lezer/common").SyntaxNode[]} members - Parsed Main members.
2432
+ * @param {readonly import("../semantic/types.js").EffectCapabilityDeclaration[]} capabilities - Authorized declarations.
2433
+ * @param {string} filename - Source filename.
2434
+ * @param {string} source - Complete source.
2435
+ * @returns {import("@lezer/common").SyntaxNode[]} Portable Main members.
2436
+ */
2437
+ function task034JavaMainMembers(members, capabilities, filename, source) {
2438
+ if (!isTask034ResourceProbe(capabilities) || nodeText(members[0], source) != "// semantifold-task034-support-members") return members
2439
+ if (!hasExactEffectSupport(source, "java", "members")) {
2440
+ return unsupportedSyntax("java", "malformed protected Task 034 member support", nodeLocation(members[0], filename, source))
2441
+ }
2442
+ const expected = [
2443
+ ["FieldDeclaration", "__semantifoldTask034Trace"], ["MethodDeclaration", "probeEffect"],
2444
+ ["MethodDeclaration", "probeAcquire"], ["MethodDeclaration", "probeRead"],
2445
+ ["MethodDeclaration", "probeClose"], ["MethodDeclaration", "probeTrace"]
2446
+ ]
2447
+ const matches = expected.every(([kind, name], index) => {
2448
+ const member = members[index + 1]
2449
+ if (member?.name != kind) return false
2450
+ if (kind == "FieldDeclaration") return nodeText(member, source).includes(` ${name} =`)
2451
+ return nodeText(requiredChild(member, "Definition", filename, source), source) == name
2452
+ })
2453
+
2454
+ if (!matches || nodeText(members[expected.length + 1], source) != "// semantifold-task034-support-members-end") {
2455
+ return unsupportedSyntax("java", "malformed protected Task 034 member support", nodeLocation(members[0], filename, source))
2456
+ }
2457
+ return members.slice(expected.length + 2)
2458
+ }
2459
+
2460
+ /**
2461
+ * Converts one already-qualified canonical Java compilation unit.
2462
+ * @param {import("@lezer/common").SyntaxNode} root - Error-free Program node.
2463
+ * @param {string} filename - Source filename.
2464
+ * @param {string} source - Complete source text.
2465
+ * @param {{isEntry: boolean, functions: Map<string, import("../semantic/types.js").FunctionDeclaration>, records: Map<string, import("../semantic/types.js").RecordDeclaration>, errors?: Map<string, import("../semantic/types.js").ErrorDeclaration>}} program - Resolved program imports and entry role.
2466
+ * @param {readonly import("../semantic/types.js").EffectCapabilityDeclaration[]} capabilities - Compiler-authorized declarations.
2467
+ * @returns {import("../semantic/types.js").SemanticModule} Semantic module.
2468
+ */
2469
+ function parseJavaProgramModule(root, filename, source, program, capabilities) {
2470
+ inspectJavaModule({filename, source})
2471
+ const classDeclaration = /** @type {import("@lezer/common").SyntaxNode} */ (structuralChildren(root).find((node) => node.name == "ClassDeclaration"))
2472
+ const classModifiers = classDeclaration.getChild("Modifiers")
2473
+ const classModifierNames = classModifiers ? structuralChildren(classModifiers).map(({name}) => name) : []
2474
+ const classBody = requiredChild(classDeclaration, "ClassBody", filename, source)
2475
+ const members = structuralChildren(classBody)
2476
+ const mainMethods = members.filter((method) => method.name == "MethodDeclaration" &&
2477
+ nodeText(requiredChild(method, "Definition", filename, source), source) == "main")
2478
+ const superclass = classDeclaration.getChild("Superclass")
2479
+ const isError = Boolean(superclass && nodeText(superclass, source) == "extends RuntimeException")
2480
+ const isRecord = !isError && members.some((member) => member.name == "FieldDeclaration" || member.name == "ConstructorDeclaration")
2481
+ const location = moduleLocation(filename, source)
2482
+
2483
+ if (program.isEntry && classModifierNames.join(" ") != "public final") {
2484
+ return unsupportedSyntax("java", "selected entry class without public final visibility", nodeLocation(classDeclaration, filename, source))
2485
+ }
2486
+ if (program.isEntry && mainMethods.length != 1) return unsupportedSyntax("java", "selected entry class without one main method", location)
2487
+ if (!program.isEntry && mainMethods.length > 0) return unsupportedSyntax("java", "main method outside the selected entry module", nodeLocation(mainMethods[0], filename, source))
2488
+ if ((isRecord || isError) && mainMethods.length > 0) {
2489
+ return unsupportedSyntax("java", "nominal class containing main", nodeLocation(classDeclaration, filename, source))
2490
+ }
2491
+
2492
+ const errorDeclarations = isError ? [{
2493
+ id: "error:0",
2494
+ kind: /** @type {const} */ ("ErrorDeclaration"),
2495
+ location: nodeLocation(classDeclaration, filename, source),
2496
+ name: nodeText(requiredChild(classDeclaration, "Definition", filename, source), source)
2497
+ }] : []
2498
+
2499
+ const recordTypeParameters = isRecord
2500
+ ? convertJavaTypeParameters(classDeclaration.getChild("TypeParameters"), "record:0", filename, source)
2501
+ : undefined
2502
+ const recordDeclarations = isRecord ? [{
2503
+ fields: [],
2504
+ id: "record:0",
2505
+ kind: /** @type {const} */ ("RecordDeclaration"),
2506
+ location: nodeLocation(classDeclaration, filename, source),
2507
+ name: nodeText(requiredChild(classDeclaration, "Definition", filename, source), source),
2508
+ ...(recordTypeParameters ? {typeParameters: recordTypeParameters} : {})
2509
+ }] : []
2510
+ /** @type {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} */
2511
+ const recordNames = new Map(program.records)
2512
+ const errorNames = new Map(program.errors ?? [])
2513
+
2514
+ for (const resource of capabilities.flatMap(({resources}) => resources)) recordNames.set(resource.name, resource)
2515
+ for (const failure of capabilities.flatMap(({failures}) => failures)) {
2516
+ errorNames.set(failure.name, /** @type {import("../semantic/types.js").ErrorDeclaration} */ (/** @type {unknown} */ (failure)))
2517
+ }
2518
+ for (const declaration of recordDeclarations) recordNames.set(declaration.name, declaration)
2519
+ for (const declaration of errorDeclarations) errorNames.set(declaration.name, declaration)
2520
+ const errorsById = new Map([...errorNames.values()].map((declaration) => [/** @type {string} */ (declaration.id), declaration]))
2521
+ const errors = errorDeclarations.map((declaration) => convertJavaError(classDeclaration, declaration, filename, source, true))
2522
+ const recordsById = new Map([...program.records.values(), ...recordDeclarations]
2523
+ .map((declaration) => [/** @type {string} */ (declaration.id), declaration]))
2524
+ const classesById = new Map()
2525
+ const records = recordDeclarations.map((declaration) =>
2526
+ convertJavaRecord(classDeclaration, declaration,
2527
+ /** @type {Map<string, import("../semantic/types.js").RecordDeclaration>} */ (/** @type {unknown} */ (recordNames)),
2528
+ filename, source, true))
2529
+ const functionMethods = isRecord || isError ? [] : members.filter((member) => member.name == "MethodDeclaration" && !mainMethods.includes(member))
2530
+ const signatures = functionMethods.map((method, index) =>
2531
+ convertFunctionSignature(method, filename, source, recordNames, true, `function:${index}`))
2532
+ /** @type {[string, JavaFunctionSignature][]} */
2533
+ const importedFunctionSignatures = [...program.functions].map(([localName, declaration]) => [localName, {
2534
+ location: declaration.location,
2535
+ name: localName,
2536
+ nameLocation: declaration.location,
2537
+ parameters: declaration.parameters,
2538
+ returnType: declaration.returnType,
2539
+ ...(declaration.typeParameters ? {typeParameters: declaration.typeParameters} : {})
2540
+ }])
2541
+ const functionSignatures = new Map([...capabilityFunctionSignatures(capabilities, location), ...importedFunctionSignatures])
2542
+
2543
+ for (const signature of signatures) functionSignatures.set(signature.name, signature)
2544
+ const functions = functionMethods.map((method, index) =>
2545
+ convertFunction(method, signatures[index], functionSignatures, recordNames, recordsById, classesById,
2546
+ errorNames, errorsById, filename, source))
2547
+ const emptyBlock = {kind: /** @type {const} */ ("Block"), location, statements: []}
2548
+ const entryPoint = program.isEntry
2549
+ ? convertEntryPoint(mainMethods[0], filename, source, functionSignatures, recordNames, recordsById, classesById,
2550
+ errorNames, errorsById)
2551
+ : {body: emptyBlock, kind: /** @type {const} */ ("EntryPoint"), location}
2552
+
2553
+ return {
2554
+ entryPoint,
2555
+ functions,
2556
+ kind: "Module",
2557
+ location,
2558
+ ...(errors.length > 0 ? {errors} : {}),
2559
+ ...(records.length > 0 ? {records} : {})
2560
+ }
2561
+ }
2562
+
2563
+ /**
2564
+ * Reads direct package/import/class nodes from the pinned Lezer grammar.
2565
+ * @param {object} input - Parser input.
2566
+ * @param {string} input.filename - Source filename.
2567
+ * @param {string} input.source - Source text.
2568
+ * @returns {{imports: {importedName: string, localName: string, location: import("../semantic/types.js").SourceLocation, namespace: true, pathLocation: import("../semantic/types.js").SourceLocation, specifier: string, typeOnly: false}[], exports: {exportedName: string, localName: string, location: import("../semantic/types.js").SourceLocation, typeOnly: false}[], nativeName: string}} Parser-owned Java module header.
2569
+ */
2570
+ export function inspectJavaModule({filename, source}) {
2571
+ const tree = parser.parse(source)
2572
+ const error = descendants(tree.topNode, "⚠")[0]
2573
+
2574
+ if (error) {
2575
+ throw new SemantifoldDiagnostic({
2576
+ code: "PARSE_ERROR",
2577
+ language: "java",
2578
+ location: nodeLocation(error, filename, source),
2579
+ message: "Java parser reported invalid syntax."
2580
+ })
2581
+ }
2582
+ const members = structuralChildren(tree.topNode)
2583
+ const packages = members.filter((node) => node.name == "PackageDeclaration")
2584
+ const importsNodes = members.filter((node) => node.name == "ImportDeclaration")
2585
+ const classes = members.filter((node) => node.name == "ClassDeclaration")
2586
+ const invalid = members.find((node) => !["PackageDeclaration", "ImportDeclaration", "ClassDeclaration"].includes(node.name))
2587
+ const packageNode = packages[0]
2588
+ const classNode = classes[0]
2589
+
2590
+ if (!packageNode || packages.length != 1 || !classNode || classes.length != 1 || invalid ||
2591
+ members.indexOf(packageNode) != 0 || members.indexOf(classNode) != members.length - 1) {
2592
+ return unsupportedSyntax("java", "one package followed by imports and one public class",
2593
+ nodeLocation(invalid ?? packages[1] ?? classes[1] ?? tree.topNode, filename, source))
2594
+ }
2595
+ const packageNameNode = packageNode.getChild("ScopedIdentifier") ?? packageNode.getChild("Identifier")
2596
+ const classNameNode = requiredChild(classNode, "Definition", filename, source)
2597
+ const modifiers = classNode.getChild("Modifiers")
2598
+ const modifierNames = modifiers ? structuralChildren(modifiers).map(({name}) => name) : []
2599
+ const packageName = packageNameNode ? nodeText(packageNameNode, source) : ""
2600
+ const className = nodeText(classNameNode, source)
2601
+ const expectedSuffix = `${packageName.replaceAll(".", "/")}/${className}.java`
2602
+
2603
+ if (!packageNameNode || !["public final", "final"].includes(modifierNames.join(" ")) || !filename.endsWith(expectedSuffix)) {
2604
+ return unsupportedSyntax("java", "package/path/public-class contract", nodeLocation(classNode, filename, source))
2605
+ }
2606
+ const imports = importsNodes.map((node) => {
2607
+ const name = node.getChild("ScopedIdentifier") ?? node.getChild("Identifier")
2608
+
2609
+ if (!name || node.getChild("static") || node.getChild("Asterisk")) {
2610
+ return unsupportedSyntax("java", "static or wildcard import", nodeLocation(node, filename, source))
2611
+ }
2612
+ const specifier = nodeText(name, source)
2613
+
2614
+ return {
2615
+ importedName: "*",
2616
+ localName: specifier.split(".").at(-1) ?? "",
2617
+ location: nodeLocation(node, filename, source),
2618
+ namespace: /** @type {const} */ (true),
2619
+ pathLocation: nodeLocation(name, filename, source),
2620
+ specifier,
2621
+ typeOnly: /** @type {const} */ (false)
2622
+ }
2623
+ })
2624
+ const classBody = requiredChild(classNode, "ClassBody", filename, source)
2625
+ const classMembers = structuralChildren(classBody)
2626
+ const recordClass = classMembers.some((member) => member.name == "FieldDeclaration" || member.name == "ConstructorDeclaration")
2627
+ const exports = recordClass
2628
+ ? modifierNames.join(" ") == "public final"
2629
+ ? [{exportedName: className, localName: className, location: nodeLocation(classNameNode, filename, source), typeOnly: /** @type {const} */ (false)}]
2630
+ : []
2631
+ : modifierNames.join(" ") != "public final" ? [] : classMembers.flatMap((member) => {
2632
+ if (member.name != "MethodDeclaration") return []
2633
+ const name = requiredChild(member, "Definition", filename, source)
2634
+ const methodModifiers = member.getChild("Modifiers")
2635
+ const methodModifierNames = methodModifiers ? structuralChildren(methodModifiers).map(({name: modifierName}) => modifierName) : []
2636
+
2637
+ if (nodeText(name, source) == "main" || methodModifierNames.join(" ") == "private static") return []
2638
+
2639
+ return [{exportedName: nodeText(name, source), localName: nodeText(name, source), location: nodeLocation(name, filename, source), typeOnly: /** @type {const} */ (false)}]
2640
+ })
733
2641
 
734
- return {entryPoint, functions, kind: "Module", location}
2642
+ return {exports, imports, nativeName: `${packageName}.${className}`}
735
2643
  }