1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
//! The native type-annotation grammar (NG-A/NG-B/NG-C, issues
//! #1487/#1488/#1489).
//!
//! **One spelling, every position** (`docs/decision-log.md` 2026-07-26
//! "NG-C ruled: `: type` returns everywhere"): a colon introduces a type
//! wherever one can be written — parameters (`fn f(g: Guest)`), bindings
//! (`let x: int = 1;`, `var hp: int = 10`), a `fn`/`flow` return clause
//! after the parameter list (`fn probability(g: Guest): float { … }`), and
//! lambdas (`|g: Guest|: bool { … }`, ratified 2026-07-23). The Rust arrow
//! was rejected: `->` lexes unconditionally as `DIVERT`
//! (`lexer/punctuation.rs`), so one arrow keeps one meaning.
//!
//! Structurally this mirrors the brink dialect's own TM-2 grammar
//! (`brink-syntax/src/parser/types.rs`, `docs/typed-mode-spec.md` §3) node
//! for node, so both frontends' lowering targets the same
//! `brink_ir::hir::TypeExpr` shape. Like that module, this one is **purely
//! syntactic**: any `IDENT` is accepted as a type name or generic head, and
//! recognizing the fixed nominal set (`int`, `float`, `bool`, `string`,
//! `List<L>`, `Map<K, V>`, `Array<T>`, `Option<T>`, `Weighted<T>`,
//! `Handle<K>`, declared struct names, …) is `brink-analyzer`'s job, never
//! this parser's. Non-primitive type names are Uppercase (issue #1552,
//! `docs/decision-log.md` 2026-07-27 "Type-name surface ruled").
//!
//! `L_BRACKET` is one exception to "purely syntactic, no lookahead into the
//! next construct": [`reject_bracket_after_type_name`] (issue #2792) reads
//! it to catch the retracted `Option[T]` spelling. See that function's doc
//! for the one calling position (the lambda's own return annotation) where
//! this module deliberately does NOT make that read — `expr.rs`'s module
//! doc claim that this module "never touches `L_BRACKET`" predates #2792
//! and is stale.
use crate;
use Parser;
/// Whether [`type_name_or_generic`]'s trailing-`[`-after-type-name check
/// (`reject_bracket_after_type_name`) applies to the outermost type an
/// entry point parses. See [`lambda_return_type_annotation`]'s doc for the
/// one position that passes `No`.
/// `true` when a `: type` annotation clause starts at the current position.
///
/// Read-only lookahead (`Parser::at` skips trivia but never `NEWLINE`), so
/// a colon on the *next* line never gets absorbed as an annotation.
pub
/// Parse `: type_expr` into a [`TYPE_ANNOTATION`] node.
///
/// ```text
/// type_annotation = { ":" ~ type_expr }
/// ```
pub
/// Same as [`type_annotation`], except the OUTERMOST type this call parses
/// is exempt from [`reject_bracket_after_type_name`] — used solely by
/// `expr.rs::lambda_expr` for the lambda's own return annotation.
///
/// Issue #2792 review (BLOCKING false positive): every other annotation
/// position (params, `let`, `var`/`const`, struct fields, and the lambda's
/// own *params* via `expr.rs::lambda_param`) is followed by punctuation a
/// type name could never legally start, so a trailing `[` there is
/// unambiguously the retracted `Option[T]` mistake. The lambda's own return
/// annotation is different: it is immediately followed by the lambda
/// *body*, an expression, and `[` legally starts one (the array-literal
/// atom, #1490) — `|x: int|: List<int> [1, 2]` is a fully legal program
/// (return type `List<int>`, body `[1, 2]`), not a mistake. Verified in
/// review: at head `8db452d9` (this PR's own tip before the fix),
/// `var f = |x: int|: List<int> [1, 2]` and `|x: int|: Foo [1, 2]` both
/// regressed from zero errors (base `a9542235`) to the unified diagnostic,
/// with an identical, correct tree either way — the shared check cannot
/// tell "trailing bracket is a mistake" from "trailing bracket starts the
/// next legal construct" by looking at the type alone; only the calling
/// position knows what follows. Only the outermost type is exempted — a
/// generic argument nested inside `<…>` (`List<Option[int]>`) is never
/// followed by an expression position, so it keeps the normal check: the
/// recursive `type_expr` calls inside [`type_name_or_generic`]'s `<…>` loop
/// always go through the default (`Yes`) path, never this one.
pub
/// Parse one type expression — a function type, a generic instantiation, or
/// a bare nominal name.
///
/// ```text
/// type_expr = { type_fn | type_name_or_generic }
/// ```
///
/// Depth-guarded like every other recursive rule in this parser: a
/// pathological `List<List<List<…>>>` records the nesting-depth diagnostic
/// and stops recursing rather than blowing the stack (CLAUDE.md "guard
/// against unbounded growth"). A depth-limited node is left childless — the
/// "absent data is legal" contract every optional AST child in this grammar
/// already has.
pub
/// Parse a bare type name, upgrading it to a [`TYPE_GENERIC`] if a `<`
/// follows on the same line.
///
/// ```text
/// type_name_or_generic = { IDENT ~ ("<" ~ type_expr ~ ("," ~ type_expr)* ~ ">")? }
/// ```
///
/// `reject_trailing_bracket` gates only the check on THIS call's own type
/// name/generic — nested type arguments (the nested `type_expr` calls
/// inside the `<…>` loop below) always go through the default `Yes` path
/// regardless of what the caller passed, per [`lambda_return_type_annotation`]'s
/// doc.
/// Issue #2792: `[` is not the type-argument delimiter — angle brackets are
/// RULED (`docs/decision-log.md` 2026-07-27, retracting the older
/// `Option[T]` spelling), and `[…]` is reserved for the array/sequence
/// literal (#1490). Every position in this grammar that reads a type name
/// funnels through [`type_name_or_generic`] (params, return clauses,
/// lambda params, `let`/`var`/`const` bindings, struct fields), so checking
/// here — right after a `TYPE_NAME`/successfully-closed `TYPE_GENERIC`
/// finishes — gives every one of them the same targeted diagnostic "for
/// free", instead of each position's own incidental fallout from whatever
/// hard `expect` (or worse, none at all) happened to sit next in that
/// position's grammar.
///
/// Before this, only `var`/`const` had an explicit check (#2781/#2785,
/// called separately right after `binding_annotation` in `decl.rs`) — every
/// other position either happened to fail loudly for an unrelated reason
/// (`fn`/`flow` params trip their own `expect(R_PAREN)`; lambda params trip
/// `expect(PIPE)`) with a generic, position-specific message, or — for a
/// lambda's own return annotation — didn't fail at all: `expression(p)`
/// (`parser/expr.rs::lambda_expr`) happily reads a leftover `[…]` as an
/// `ARRAY_LITERAL` lambda body (a silent data drop, CLAUDE.md: "always bugs
/// until proven otherwise" — the exact shape #2781 found for `var`/`const`'s
/// initializer, just in a different position). Centralizing the check here
/// fixes that silent drop for every position EXCEPT the lambda's own return
/// annotation — see [`lambda_return_type_annotation`]'s doc (#2792 review):
/// that one position calls this check with `RejectTrailingBracket::No`
/// instead, because unlike every other annotation position, an expression
/// (the lambda body) legally follows it, and `[` legally starts one. The
/// silent drop for `|y: int|: Option[int] { none }`-shaped mistakes is
/// therefore back at that one position, same as before #2792 — the false
/// positive against legal array-literal bodies (`|x: int|: List<int>
/// [1, 2]`) was judged the worse of the two failure modes.
///
/// Only fires when the `[` sits on the same source line as the type name
/// just parsed — `Parser::at` never crosses a `NEWLINE`, so a legitimate
/// array literal starting fresh on the next line is untouched. Only called
/// when this call's own type finished cleanly (a bare `TYPE_NAME`, or a
/// `TYPE_GENERIC` whose `>` was actually consumed) — see the
/// `generic_closed` guard in [`type_name_or_generic`], which exists to
/// avoid double-reporting the same `[` once from an inner nested type
/// argument's own check and again from an outer generic that never closed.
///
/// Deliberately narrow, matching #2792's scope: this only ever *adds* the
/// targeted diagnostic ahead of whatever recovery already ran at a given
/// position; it never consumes the `[`, so a position with its own hard
/// `expect` immediately afterward (`fn`/`flow` params, lambda params) still
/// separately reports its prior generic message too, and the leftover
/// `[…]` still lands as parser-generated garbage exactly as it did before.
/// Unifying that recovery is a bigger, separate design question (#2792's
/// own text) — out of scope here.
/// Parse `fn(type, …): type` — a function type.
///
/// ```text
/// type_fn = { "fn" ~ "(" ~ (type_expr ~ ("," ~ type_expr)*)? ~ ")" ~ ":" ~ type_expr }
/// ```
///
/// The return clause is required (a `fn(…)` with no `:` records a
/// diagnostic): `brink_ir::hir::TypeExpr::Fn` carries a non-optional
/// return type, matching the brink dialect's own `type_fn`.