lanekeep_types/oracle.rs
1//! The oracle itself: construction, dispatch, and the bound that makes it terminate.
2
3use std::cell::Cell;
4use std::fmt;
5use std::sync::Arc;
6
7use lanekeep_core::FilePath;
8use lanekeep_lang::Language;
9use lanekeep_lang::binding::{Binding, BindingResolver, ImportedName};
10use tree_sitter::{Node, Tree};
11
12use crate::declarations::ExportTarget;
13use crate::table;
14use crate::types::{Primitive, Symbol, Type};
15
16/// What an oracle asks its host when a name comes from another file.
17///
18/// A trait rather than a concrete provider, so this crate's layering holds: the oracle reads
19/// **one** tree and nothing else, and every question about *which other file* and *how deep*
20/// belongs to the value that owns the declaration cache and the budget. An oracle with no
21/// implementation attached answers exactly what it answered before cross-file resolution
22/// existed, which is what keeps `TypeScriptOracle::new` a within-file oracle.
23///
24/// Every method takes the *importing* file, because a relative specifier means nothing
25/// without one, and a `depth` already spent, because a bound reset at every file boundary is
26/// not a bound.
27pub trait ImportResolution {
28 /// The type an imported *value* has, computed in its declaring file's own context.
29 fn imported_value_type(
30 &self,
31 from: &FilePath,
32 module: &str,
33 name: &ImportedName,
34 depth: u32,
35 ) -> Option<Type>;
36
37 /// The type an imported *type alias* names, when the imported name is one.
38 ///
39 /// Deliberately not "the type of the imported type". An imported class or interface keeps
40 /// its own nominal identity and its use-site symbol — replacing it with whatever its
41 /// declaration file says would drop the module the name was imported from, which is the
42 /// one field `lanekeep/no-restricted-types` matches on. Only an alias is transparent,
43 /// exactly as a same-file `type Amount = number` already is.
44 ///
45 /// Returns [`Followed`] rather than `Option<Type>` because the caller's fallback depends
46 /// on *why* there is no type: a name that simply is not an alias keeps its own nominal
47 /// identity (as it always has), but a name that *is* an alias whose chain was cut by
48 /// `MAX_DEPTH` must not — falling back there would answer with an intermediate file's
49 /// own nominal type, a confident guess rather than the honest "unknown" a cut chain
50 /// deserves. See `Followed`'s own documentation.
51 fn imported_alias_type(
52 &self,
53 from: &FilePath,
54 module: &str,
55 name: &ImportedName,
56 depth: u32,
57 ) -> Followed;
58
59 /// What calling an imported function yields.
60 fn imported_return_type(
61 &self,
62 from: &FilePath,
63 module: &str,
64 name: &ImportedName,
65 depth: u32,
66 ) -> Option<Type>;
67
68 /// Where an imported name is actually declared, after every re-export.
69 fn imported_export(
70 &self,
71 from: &FilePath,
72 module: &str,
73 name: &ImportedName,
74 ) -> Option<ExportTarget>;
75}
76
77/// Node kinds the dispatch below reads, which the constructor requires the grammar to know.
78///
79/// Derived from the dispatch rather than written beside it: a kind added to `type_of`
80/// without being added here would be read from a grammar that may not have it. Keeping the
81/// two in one place is what stops them drifting.
82const REQUIRED_KINDS: &[&str] = &[
83 "predefined_type",
84 "type_annotation",
85 "type_identifier",
86 "union_type",
87 "literal_type",
88 "type_alias_declaration",
89 "type_parameter",
90 "identifier",
91 "required_parameter",
92 "optional_parameter",
93 "variable_declarator",
94 // Not a type node, and read all the same: a `comment` is a *named* child of a
95 // `union_type`, so the union arm has to name it in order to skip it. See there.
96 "comment",
97 "string",
98 "template_string",
99 "true",
100 "false",
101 "null",
102 "undefined",
103 "number",
104 "parenthesized_expression",
105 "binary_expression",
106 "unary_expression",
107 "call_expression",
108 // The declaration walk's own vocabulary (`declarations.rs`). A grammar without these
109 // cannot answer a cross-file question, and probing for them here is what keeps the
110 // provider from opening a file it has no way to read.
111 "export_statement",
112 "export_clause",
113 "export_specifier",
114 "namespace_export",
115 "ambient_declaration",
116 "lexical_declaration",
117 "variable_declaration",
118 "function_signature",
119 "function_declaration",
120 "generator_function_declaration",
121 "class_declaration",
122 "abstract_class_declaration",
123 "interface_declaration",
124 "enum_declaration",
125 "module",
126 "internal_module",
127 "class_heritage",
128 "extends_clause",
129 "extends_type_clause",
130 "import_statement",
131];
132
133/// What following an imported name across the file boundary, as a type alias, found.
134///
135/// A plain `Option<Type>` cannot tell two failure shapes apart, and `named_type`'s fallback
136/// has to answer them differently: "this name is not an alias at all" keeps its own nominal
137/// identity, exactly as it always has, while "this name is an alias, but the chain following
138/// it was cut by `MAX_DEPTH`" must answer nothing — see addendum B of task 4.16. The
139/// distinction has to survive an arbitrary number of cross-file hops, because the bound can
140/// be spent several files away from the frame that first asked; every hop threads this enum
141/// rather than collapsing it back to `Option` until the walk has fully unwound.
142#[derive(Debug, Clone, PartialEq, Eq)]
143pub enum Followed {
144 /// The type the alias names.
145 Type(Type),
146 /// The name is an alias, but `MAX_DEPTH` cut the chain before it resolved to a type.
147 Exhausted,
148 /// The name does not name a type alias at all.
149 NotAnAlias,
150}
151
152/// How far the oracle will follow a chain before giving up.
153///
154/// Two things make the recursion unbounded otherwise: `type A = B; type B = A`, and chains
155/// of initializers. Exceeding the bound is indistinguishable from not knowing, which is
156/// already a first-class answer, so nothing needs to be reported when it happens.
157///
158/// Fixed rather than measured. A bound that depended on elapsed time would put the clock in
159/// the cache key.
160pub(crate) const MAX_DEPTH: u32 = 16;
161
162/// A type oracle for one parsed TypeScript file.
163pub struct TypeScriptOracle<'t> {
164 tree: &'t Tree,
165 source: &'t str,
166 resolver: Arc<dyn BindingResolver>,
167 /// Which file this parse is of, when the caller could say.
168 ///
169 /// Required for cross-file resolution and for nothing else, which is why it is optional:
170 /// a within-file question does not need to know where the file lives, and demanding one
171 /// would make every existing caller supply a value it has no use for.
172 file: Option<&'t FilePath>,
173 imports: Option<&'t dyn ImportResolution>,
174 /// Set the moment this oracle gives up on [`MAX_DEPTH`], when a caller asked to be told.
175 ///
176 /// The bound answers a bare `None`, which is indistinguishable from "there is no type
177 /// here" — and a caller threading a depth it has already spent needs the difference: an
178 /// answer the bound truncated describes the *prefix* the caller walked, not the node it
179 /// asked about, so it must not be memoized against that node. A `Cell` rather than a
180 /// return-type change because the bound is checked in four recursive arms several frames
181 /// below any public method, exactly the shape `Imports`' own flag exists for. `None` when
182 /// nobody asked, which is every within-file caller.
183 exhausted: Option<&'t Cell<bool>>,
184}
185
186/// Hand-written because `Arc<dyn BindingResolver>` is not `Debug` — the trait answers
187/// identifier questions, not requests to describe itself, and requiring every implementor
188/// to add one for the sake of this impl is not worth it. The same reasoning, and the same
189/// fix, as `LanguageRegistry` in `lanekeep-lang`.
190///
191/// `tree` has no such problem — `Tree`'s own `Debug` delegates to the root `Node`'s,
192/// which prints one line (measured: `{Tree {Node program (0, 0) - (0, 12)}}`) rather than
193/// the whole parse tree, so it costs nothing to include.
194///
195/// `source` is the one field deliberately summarized rather than printed. It is a whole
196/// file, and a `Debug` that puts a file into every line it appears in is not one anybody
197/// can read; its length identifies which file this is for as well as the bytes would.
198/// Same call as `LanguageRegistry`, which prints its keys and not the languages behind
199/// them.
200impl fmt::Debug for TypeScriptOracle<'_> {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 f.debug_struct("TypeScriptOracle")
203 .field("tree", &self.tree)
204 .field("source_len", &self.source.len())
205 .field("has_imports", &self.imports.is_some())
206 .finish_non_exhaustive()
207 }
208}
209
210/// A grammar confirmed to speak TypeScript, and the resolver that goes with it.
211///
212/// Separate from the oracle because probing is 8.4 µs of a 9.2 µs construction — 23
213/// `id_for_node_kind` calls, each a linear scan over a 383-kind table. Paying that once per
214/// run rather than once per query is what keeps the type surface from costing thirty host
215/// crossings on every call, against a crossing §15.1 measures at ~302 ns.
216#[derive(Clone)]
217pub struct TypeScriptSupport {
218 resolver: Arc<dyn BindingResolver>,
219}
220
221impl fmt::Debug for TypeScriptSupport {
222 /// Hand-written because `Arc<dyn BindingResolver>` is not `Debug`, the same reason and
223 /// the same shape as `LanguageRegistry`'s.
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 f.debug_struct("TypeScriptSupport").finish_non_exhaustive()
226 }
227}
228
229impl TypeScriptSupport {
230 /// Confirm a grammar has the vocabulary the oracle reads, and take its resolver.
231 ///
232 /// `None` in two cases, both of which would otherwise produce confident nonsense rather
233 /// than an error. A grammar that does not know the node kinds this oracle reads is not
234 /// TypeScript, whatever it calls itself. And a language with no resolver cannot say where
235 /// a name was declared, so the oracle could type no identifier at all — which would look
236 /// exactly like a file with nothing to say about it.
237 #[must_use]
238 pub fn probe(language: &dyn Language) -> Option<Self> {
239 let grammar = language.grammar();
240 if !REQUIRED_KINDS
241 .iter()
242 .all(|kind| grammar.id_for_node_kind(kind, true) != 0)
243 {
244 return None;
245 }
246 Some(Self {
247 resolver: language.resolver()?,
248 })
249 }
250
251 /// The resolver the probe took.
252 ///
253 /// Handed to every [`crate::declarations::Declaration`] this support's provider parses,
254 /// and to the walks over the asking file's own tree, so that "which statement declares
255 /// this name" is answered by the one resolver the run was probed with — through the
256 /// trait, never through a language crate this one would otherwise have to name.
257 pub(crate) fn resolver(&self) -> &Arc<dyn BindingResolver> {
258 &self.resolver
259 }
260}
261
262impl<'t> TypeScriptOracle<'t> {
263 /// Build an oracle for one parsed file.
264 ///
265 /// Cheap by construction: everything expensive happened in [`TypeScriptSupport::probe`].
266 /// That is what lets a caller build one of these per query rather than per run.
267 #[must_use]
268 pub fn new(support: &TypeScriptSupport, tree: &'t Tree, source: &'t str) -> Self {
269 Self {
270 tree,
271 source,
272 resolver: Arc::clone(&support.resolver),
273 file: None,
274 imports: None,
275 exhausted: None,
276 }
277 }
278
279 /// Let this oracle follow a name into the file that declares it.
280 ///
281 /// Without it every arm behaves exactly as it did before cross-file resolution existed —
282 /// an import is a name with a module and no type — which is what makes a within-file
283 /// oracle still a thing this crate can hand out.
284 #[must_use]
285 pub fn with_imports(mut self, file: &'t FilePath, imports: &'t dyn ImportResolution) -> Self {
286 self.file = Some(file);
287 self.imports = Some(imports);
288 self
289 }
290
291 /// Let this oracle report that its depth bound — rather than the program — is why it
292 /// answered nothing.
293 ///
294 /// For a caller that threads a depth it has already spent and memoizes what comes back.
295 /// A bare `None` cannot say this on its own — it is what the bound and an untypeable
296 /// node both answer — and the bound is checked several frames below any public method, so
297 /// the flag is the channel rather than a return type.
298 #[must_use]
299 pub fn with_exhaustion(mut self, exhausted: &'t Cell<bool>) -> Self {
300 self.exhausted = Some(exhausted);
301 self
302 }
303
304 /// Answer nothing, and say the bound is why.
305 fn exhaust<T>(&self) -> Option<T> {
306 if let Some(flag) = self.exhausted {
307 flag.set(true);
308 }
309 None
310 }
311
312 /// The type of `node`, starting from a depth already spent.
313 ///
314 /// For a provider that has followed an import: the recursion crosses files, and a bound
315 /// reset at every boundary is not a bound at all.
316 #[must_use]
317 pub fn type_of_from(&self, node: Node<'t>, depth: u32) -> Option<Type> {
318 self.type_of_at(node, depth)
319 }
320
321 /// The type a declaration gives the name it declares, from a depth already spent.
322 #[must_use]
323 pub fn declaration_type_from(&self, declaration: Node<'t>, depth: u32) -> Option<Type> {
324 self.declaration_type(declaration, depth)
325 }
326
327 /// The type a `type_annotation` (or a bare type node) denotes, from a depth already spent.
328 ///
329 /// Steps through the `type_annotation` wrapper, then types the node in *type* position —
330 /// `number` comes back as [`Primitive::Number`], not a nominal named `number`, which is
331 /// what separates it from [`Self::type_named_by`]. For the provider, typing a member's
332 /// declared type in the file that declares the member.
333 #[must_use]
334 pub fn annotation_type_from(&self, annotation: Node<'t>, depth: u32) -> Option<Type> {
335 self.annotation_type(annotation_child(annotation)?, depth)
336 }
337
338 /// The return type of `node`, from a depth already spent. See [`Self::type_of_from`].
339 #[must_use]
340 pub fn return_type_from(&self, node: Node<'t>, depth: u32) -> Option<Type> {
341 self.return_type_at(node, depth)
342 }
343
344 /// The type a name *in type position* denotes: an alias followed, a nominal otherwise.
345 ///
346 /// [`Self::type_of`] cannot stand in for it. In expression position an `identifier` is a
347 /// value, so `class A extends B {}`'s `B` would be typed as whatever value `B` holds —
348 /// which for a class declaration is nothing at all — rather than as the type it names.
349 #[must_use]
350 pub fn type_named_by(&self, node: Node<'t>) -> Option<Type> {
351 self.named_type(node, 0)
352 }
353
354 /// The type of the expression at `node`, or `None` when the oracle cannot be sure.
355 ///
356 /// `None` is an answer rather than a failure. A rule that stays silent on it reports
357 /// only what was established, which is the posture every rule built on this oracle is
358 /// expected to take.
359 #[must_use]
360 pub fn type_of(&self, node: Node<'t>) -> Option<Type> {
361 self.type_of_at(node, 0)
362 }
363
364 /// Where the name at `node` came from, or `None` if nothing in this file declares it.
365 ///
366 /// Distinct from [`Self::type_of`] and useful where that returns nothing: an imported
367 /// value has no type this oracle can read, and still has a name and a module — which is
368 /// exactly what a rule distinguishing one library's `Decimal` from a local class needs.
369 ///
370 /// Answers in type position as well as expression position, because the resolver does.
371 #[must_use]
372 pub fn symbol_of(&self, node: Node<'t>) -> Option<Symbol> {
373 self.symbol_at(node)
374 }
375
376 /// What calling the function at `node` yields.
377 ///
378 /// Separate from [`Self::type_of`] rather than folded into it, and the reason is the
379 /// vocabulary rather than the plumbing: a function declaration is not an expression, and
380 /// giving `type_of` a signature type would mean a `Type::Function` variant every rule
381 /// asking a simpler question would then have to unpack. There is exactly one question
382 /// rules ask about a function, so there is exactly one method.
383 ///
384 /// Accepts a call expression (whose callee is resolved), a function-like declaration, or
385 /// an identifier bound to one.
386 ///
387 /// A *generic* call whose signature returns a bare type parameter answers `None`, not the
388 /// type the call site would instantiate it to: `useMemo(() => 0n, [])`, whose signature
389 /// returns `T`, is `None` here rather than `bigint`. Instantiating a parameter from an
390 /// argument's type is inference this oracle does not do — the `tsc` provider does it and
391 /// answers `bigint`. The honest label for what this one cannot see is the same `None` every
392 /// unread thing gets, never a nominal named `T`: a rule may branch on `None`, whereas a
393 /// `text`-only `T` would be a non-`undefined` answer carrying no field to branch on. The
394 /// type parameter itself is dropped by [`Self::type_named_by`] for the same reason.
395 #[must_use]
396 pub fn return_type_of(&self, node: Node<'t>) -> Option<Type> {
397 self.return_type_at(node, 0)
398 }
399
400 fn return_type_at(&self, node: Node<'t>, depth: u32) -> Option<Type> {
401 if depth >= MAX_DEPTH {
402 return self.exhaust();
403 }
404 let next = depth.saturating_add(1);
405
406 match node.kind() {
407 "call_expression" => self.return_type_at(node.child_by_field_name("function")?, next),
408 "identifier" => {
409 if let Some(Binding::Import { module, name }) =
410 self.resolver.resolve(self.tree, self.source, node)
411 && let (Some(file), Some(imports)) = (self.file, self.imports)
412 {
413 return imports.imported_return_type(file, &module, &name, next);
414 }
415 let declaration = self.resolver.declaration_of(self.tree, self.source, node)?;
416 self.return_type_at(declaration, next)
417 }
418 // `const rate = () => 1` binds the function to a name; the declarator's value is
419 // the function. An annotated declarator is deliberately not read as a signature —
420 // that would be a function *type*, which this oracle says nothing about.
421 "variable_declarator" => self.return_type_at(node.child_by_field_name("value")?, next),
422 "function_declaration"
423 | "generator_function_declaration"
424 | "function_signature"
425 | "function_expression"
426 // The expression form: `const g = function*() {...}`. `is_function_like` has
427 // always listed it; this dispatch had not, so a call to a generator bound this
428 // way fell through to `_ => None` despite the oracle treating it as function-like
429 // everywhere else — addendum A1/A2 of task 4.16.
430 | "generator_function"
431 | "arrow_function"
432 | "method_definition"
433 | "method_signature"
434 | "abstract_method_signature" => self.signature_return(node, next),
435 _ => None,
436 }
437 }
438
439 /// The return type of a function-like node: its annotation, or what its body returns.
440 ///
441 /// The annotation wins wherever both are present, on the same reasoning
442 /// [`Self::declaration_type`] prefers one: the annotation is what the program means, and
443 /// answering from the body would describe a mistake rather than a declaration.
444 fn signature_return(&self, node: Node<'t>, depth: u32) -> Option<Type> {
445 if let Some(annotation) = node.child_by_field_name("return_type") {
446 // `asserts_annotation` and `type_predicate_annotation` are the other two kinds
447 // this field can hold (`node-types.json`); `annotation_child` hands back whatever
448 // is there and the annotation vocabulary answers `None` for both, which is right —
449 // `x is Foo` is not a type any rule built on this oracle asks about.
450 return self.annotation_type(annotation_child(annotation)?, depth);
451 }
452
453 // An `async` function's value is a `Promise<…>` and a generator's is a `Generator<…>`,
454 // and this oracle has no variant that can say either — no type arguments, no
455 // `Promise`. With no annotation there is nothing here able to name the wrapper, so the
456 // body's `return` type is not the call's type: answering `number` for
457 // `async function rate() { return 1 }` would be a claim a rule can compare against a
458 // `number` and be wrong about every time, with nothing in the answer to say a wrapper
459 // was dropped. The annotation path above is untouched — the refusal is about the
460 // absence of an annotation rather than about `async`.
461 if wraps_its_return(node) {
462 return None;
463 }
464
465 let body = node.child_by_field_name("body")?;
466 if body.kind() != "statement_block" {
467 // A concise arrow body is the returned expression itself.
468 return self.type_of_at(body, depth);
469 }
470
471 let mut returns = Vec::new();
472 collect_returns(body, &mut returns);
473 if returns.is_empty() {
474 // No `return` at all. `void` would be a guess, and this oracle has no variant for
475 // it — see `Primitive`'s own documentation on why `any` and `unknown` are absent
476 // for the same reason.
477 return None;
478 }
479
480 // Every member or none, exactly as a union annotation is read: a member that could
481 // not be typed leaves an answer byte-identical to a complete one, with nothing left
482 // to say something was lost.
483 let members: Vec<Type> = returns
484 .into_iter()
485 .map(|returned| match returned {
486 // A bare `return;` yields `undefined`, which is a member rather than a gap.
487 None => Some(Type::Primitive(Primitive::Undefined)),
488 Some(expression) => self.type_of_at(expression, depth),
489 })
490 .collect::<Option<Vec<Type>>>()?;
491 Type::union(members)
492 }
493
494 fn type_of_at(&self, node: Node<'t>, depth: u32) -> Option<Type> {
495 if depth >= MAX_DEPTH {
496 return self.exhaust();
497 }
498
499 match node.kind() {
500 "string" | "template_string" => Some(Type::Primitive(Primitive::String)),
501 "true" | "false" => Some(Type::Primitive(Primitive::Boolean)),
502 "null" => Some(Type::Primitive(Primitive::Null)),
503 "undefined" => Some(Type::Primitive(Primitive::Undefined)),
504
505 // A bigint literal parses as `number`; the trailing `n` is the only thing that
506 // distinguishes it, so this reads the text rather than trusting the kind.
507 "number" => Some(Type::Primitive(if self.text(node).ends_with('n') {
508 Primitive::BigInt
509 } else {
510 Primitive::Number
511 })),
512
513 "parenthesized_expression" => {
514 self.type_of_at(node.named_child(0)?, depth.saturating_add(1))
515 }
516
517 "binary_expression" => {
518 let next = depth.saturating_add(1);
519 let operator = self.operator_of(node)?;
520 let left_node = node.child_by_field_name("left")?;
521 let right = self.primitive_of(node.child_by_field_name("right")?, next);
522 // `a ?? b` keeps `a` whenever `a` is neither `null` nor `undefined`, so its
523 // left contributes `NonNullable<typeof a>`: the left is typed with the nullish
524 // arms stripped. Every other operator reads the left as it stands.
525 let left = if operator == "??" {
526 self.non_nullish_primitive_of(left_node, next)
527 } else {
528 self.primitive_of(left_node, next)
529 };
530 table::binary(operator, left, right).map(Type::Primitive)
531 }
532
533 "unary_expression" => table::unary(self.operator_of(node)?).map(Type::Primitive),
534
535 "call_expression" => {
536 let callee = node.child_by_field_name("function")?;
537 // Only a *bare* global counts. A member call like `Number.parseFloat(x)`
538 // is not in the table, and a callee that resolves to a local binding is
539 // somebody's own function that happens to share a name.
540 if callee.kind() != "identifier" {
541 return None;
542 }
543 if self
544 .resolver
545 .resolve(self.tree, self.source, callee)
546 .is_some()
547 {
548 return None;
549 }
550 table::builtin_call(self.text(callee)).map(Type::Primitive)
551 }
552
553 "type_annotation" => {
554 self.annotation_type(node.named_child(0)?, depth.saturating_add(1))
555 }
556 "predefined_type" | "union_type" | "literal_type" | "type_identifier" => {
557 self.annotation_type(node, depth)
558 }
559
560 "identifier" => {
561 // An imported value's declaration is in another file. With resolution
562 // attached, that file is opened and the declaration typed in its own context;
563 // without it, this is the `None` it always was.
564 //
565 // Asked here rather than in `declaration_type`'s `import_statement` arm — the
566 // seam the design named — because the module specifier and *which* export was
567 // imported are what `resolve` answers, and the `import_statement` node alone
568 // does not say which of its specifiers bound this use.
569 if let Some(Binding::Import { module, name }) =
570 self.resolver.resolve(self.tree, self.source, node)
571 && let (Some(file), Some(imports)) = (self.file, self.imports)
572 {
573 return imports.imported_value_type(
574 file,
575 &module,
576 &name,
577 depth.saturating_add(1),
578 );
579 }
580 let declaration = self.resolver.declaration_of(self.tree, self.source, node)?;
581 self.declaration_type(declaration, depth.saturating_add(1))
582 }
583
584 "member_expression" | "subscript_expression" => self.member_access(node, depth),
585
586 _ => None,
587 }
588 }
589
590 /// The type of a property access (`a.b`, `a?.b`) or a string-literal subscript (`a["b"]`),
591 /// when the base's type is declared **in this same file**.
592 ///
593 /// Resolution is by node, not by [`Type`]: it follows the base's annotation node to the
594 /// declaration it names, walks that declaration's body for the member, and reads the
595 /// member's own annotation node. A [`Type`] cannot stand in as the intermediate, because an
596 /// object type (`type T = { … }`, or an inline `{ … }` member) has no [`Type`] variant at
597 /// all — the reachable-by-`Type` receivers would be only interfaces and classes, and the
598 /// idiomatic object-literal alias would answer nothing.
599 ///
600 /// A cross-file base answers `None` here, deliberately: a type reference imported from
601 /// another file is resolved by the provider, which owns the
602 /// [`FileAccess`](lanekeep_core::FileAccess) a crossing needs and threads the file each hop
603 /// stands in — see `BuiltinProvider`'s member walk. With no provider attached this is the
604 /// within-file answer, matching every other arm.
605 ///
606 /// `undefined` is added when this link is optional (`a?.b`), when the member is optional
607 /// (`b?: T`), or when the receiver's own type carried `null`/`undefined` — the last is what
608 /// propagates `a?.b.c`'s short-circuit through the tail, since the `optional_chain` marker
609 /// sits only on the inner link.
610 fn member_access(&self, node: Node<'t>, depth: u32) -> Option<Type> {
611 let (member_inner, nullish) = self.member_site(node, depth)?;
612 with_optional(
613 self.annotation_type(member_inner, depth.saturating_add(1))?,
614 nullish,
615 )
616 }
617
618 /// The type *node* a property access or subscript denotes, and whether the path to it
619 /// short-circuits to `undefined`.
620 ///
621 /// Returns the member's annotation node rather than its [`Type`] so that a chain reads
622 /// through it: the next link's receiver is this node.
623 fn member_site(&self, node: Node<'t>, depth: u32) -> Option<(Node<'t>, bool)> {
624 if depth >= MAX_DEPTH {
625 return self.exhaust();
626 }
627 let object = node.child_by_field_name("object")?;
628 let (receiver, path_nullish) = self.receiver_type_node(object, depth.saturating_add(1))?;
629 let receiver_nullish = type_contains_nullish(receiver);
630 let container = self.resolve_to_container(receiver, depth.saturating_add(1))?;
631 let member = member_name(self.source, node)?;
632 let (annotation, member_optional) = member_annotation(self.source, container, &member)?;
633 Some((
634 annotation_child(annotation)?,
635 path_nullish || receiver_nullish || optional_access(node) || member_optional,
636 ))
637 }
638
639 /// The type node an expression is annotated with, and whether the path to it short-circuits.
640 ///
641 /// A property access or subscript is itself a member site; a binding is resolved through
642 /// [`Self::annotated_type_node`]; parentheses are transparent.
643 fn receiver_type_node(&self, expr: Node<'t>, depth: u32) -> Option<(Node<'t>, bool)> {
644 match expr.kind() {
645 "member_expression" | "subscript_expression" => self.member_site(expr, depth),
646 "parenthesized_expression" => {
647 self.receiver_type_node(expr.named_child(0)?, depth.saturating_add(1))
648 }
649 _ => self.annotated_type_node(expr),
650 }
651 }
652
653 /// The type node a bound name is annotated with, and whether that binding is nullable.
654 ///
655 /// A binding with no annotation gives nothing — this milestone does not infer a variable's
656 /// type from its initializer for the purpose of a member read. Exposed for the provider,
657 /// which resolves the base of a cross-file chain in the asking file before folding the rest.
658 #[must_use]
659 pub fn annotated_type_node(&self, expr: Node<'t>) -> Option<(Node<'t>, bool)> {
660 match expr.kind() {
661 "parenthesized_expression" => self.annotated_type_node(expr.named_child(0)?),
662 "identifier" => {
663 let declaration = self.resolver.declaration_of(self.tree, self.source, expr)?;
664 let nullish = declaration.kind() == "optional_parameter";
665 Some((binding_annotation(declaration)?, nullish))
666 }
667 _ => None,
668 }
669 }
670
671 /// The member container a type node denotes, following same-file aliases and stripping a
672 /// nullable union's `null`/`undefined` arms.
673 ///
674 /// An imported type reference answers `None`: this oracle opens no files, and the provider
675 /// resolves the crossing instead.
676 fn resolve_to_container(&self, type_node: Node<'t>, depth: u32) -> Option<Node<'t>> {
677 if depth >= MAX_DEPTH {
678 return self.exhaust();
679 }
680 match type_node.kind() {
681 "object_type" => Some(type_node),
682 "parenthesized_type" => {
683 self.resolve_to_container(type_node.named_child(0)?, depth.saturating_add(1))
684 }
685 "union_type" => {
686 self.resolve_to_container(sole_non_nullish_arm(type_node)?, depth.saturating_add(1))
687 }
688 "type_identifier" | "generic_type" => {
689 let name = type_name_node(type_node)?;
690 if matches!(
691 self.resolver.resolve(self.tree, self.source, name),
692 Some(Binding::Import { .. })
693 ) {
694 return None;
695 }
696 // Scope-aware, like `named_type`: a `type`/`interface`/`class` declared inside a
697 // function shadows a same-named module-level one, and a top-level lookup would
698 // read the wrong declaration's members — a confident wrong answer. `resolve`
699 // above rules out imports first, so this only ever resolves a local.
700 let declaration = self.resolver.declaration_of(self.tree, self.source, name)?;
701 if declaration.has_error() {
702 return None;
703 }
704 match declaration.kind() {
705 "type_alias_declaration" => self.resolve_to_container(
706 declaration.child_by_field_name("value")?,
707 depth.saturating_add(1),
708 ),
709 _ => declaration_body(declaration),
710 }
711 }
712 _ => None,
713 }
714 }
715
716 /// The type a declaration gives the name it declares.
717 ///
718 /// An annotation is preferred over an initializer wherever both are present, because
719 /// the annotation is what the program means: `const x: string = parseFloat(s)` is a
720 /// type error, and answering `number` for it would describe the mistake rather than the
721 /// declaration.
722 ///
723 /// A declaration that binds through a *pattern* gives nothing at all. Both arms below
724 /// hold a type for the thing being destructured and none for the names taken out of
725 /// it, and the two are not the same type — reading either the annotation or the
726 /// initializer would hand every name the whole thing's type. See [`binds_one_name`].
727 fn declaration_type(&self, declaration: Node<'t>, depth: u32) -> Option<Type> {
728 if depth >= MAX_DEPTH {
729 return self.exhaust();
730 }
731 let next = depth.saturating_add(1);
732
733 match declaration.kind() {
734 "required_parameter" | "optional_parameter" => {
735 if !binds_one_name(declaration, "pattern") {
736 return None;
737 }
738 // The `type` field is the `type_annotation` wrapper; the parameter node
739 // itself is not one, so it has to be read before unwrapping. An unannotated
740 // parameter has no `type` field and gives nothing, which is correct — this
741 // milestone does not infer a parameter's type from its call sites.
742 let annotation = declaration.child_by_field_name("type")?;
743 self.annotation_type(annotation_child(annotation)?, next)
744 }
745
746 "variable_declarator" => {
747 if !binds_one_name(declaration, "name") {
748 return None;
749 }
750 if let Some(annotation) = declaration.child_by_field_name("type") {
751 return self.annotation_type(annotation_child(annotation)?, next);
752 }
753 self.type_of_at(declaration.child_by_field_name("value")?, next)
754 }
755
756 // An import's declaration is in another file, which this oracle does not open.
757 // A function or class declaration names a callable or a constructor rather than
758 // a value with a type this milestone reasons about. A `type_parameter` is
759 // whatever the call site chose, which this oracle does not see.
760 _ => None,
761 }
762 }
763
764 /// A node's type, when it is a primitive and nothing else.
765 ///
766 /// The operator table reasons about primitives, and a nominal or a union on either side
767 /// of an arithmetic operator is something it has no row for.
768 fn primitive_of(&self, node: Node<'t>, depth: u32) -> Option<Primitive> {
769 match self.type_of_at(node, depth)? {
770 Type::Primitive(primitive) => Some(primitive),
771 Type::Nominal { .. } | Type::Union(_) => None,
772 }
773 }
774
775 /// A node's primitive with `null`/`undefined` dropped, for the left of `??`.
776 ///
777 /// This is `NonNullable<T>` narrowed to the table's vocabulary. `a ?? b` reduces to `a`'s
778 /// type only when `a` is present, so the nullish arms of the left never reach the result and
779 /// are removed before the table sees it — which is what lets `number | undefined` on the
780 /// left agree with a `number` fallback.
781 ///
782 /// A single primitive answers itself unless it is `null`/`undefined` alone, whose
783 /// non-nullish part is `never` and has no table row. A union answers its one remaining
784 /// primitive after the nullish arms are dropped; a union that still holds a nominal, or more
785 /// than one primitive, answers `None`, because the table reasons about single primitives and
786 /// nothing else.
787 fn non_nullish_primitive_of(&self, node: Node<'t>, depth: u32) -> Option<Primitive> {
788 fn is_nullish(primitive: Primitive) -> bool {
789 matches!(primitive, Primitive::Null | Primitive::Undefined)
790 }
791 match self.type_of_at(node, depth)? {
792 Type::Primitive(primitive) if !is_nullish(primitive) => Some(primitive),
793 Type::Union(members) => {
794 let mut sole = None;
795 for member in members {
796 match member {
797 Type::Primitive(primitive) if is_nullish(primitive) => {}
798 Type::Primitive(primitive) => {
799 if sole.is_some() {
800 return None;
801 }
802 sole = Some(primitive);
803 }
804 // A nominal arm survives the nullish strip but is not a primitive the
805 // table can answer, so the whole thing is unknown.
806 _ => return None,
807 }
808 }
809 sole
810 }
811 _ => None,
812 }
813 }
814
815 /// The type a type-level node denotes.
816 ///
817 /// Separate from [`Self::type_of_at`] because the two vocabularies barely overlap: a
818 /// `number` in expression position is a literal and in type position is a keyword. One
819 /// match arm handling both would have to disambiguate by parent, which is the kind of
820 /// thing that is right until somebody nests it.
821 fn annotation_type(&self, node: Node<'t>, depth: u32) -> Option<Type> {
822 if depth >= MAX_DEPTH {
823 return self.exhaust();
824 }
825
826 match node.kind() {
827 // Matched on text, not kind: `any` and `unknown` parse identically to `number`.
828 // Both give nothing, deliberately — `any` is the absence of a claim, and
829 // `void` and `never` are types no rule built on this oracle asks about.
830 //
831 // There is no `bigint` row, and its absence is the measurement rather than an
832 // oversight: this grammar does not lex `bigint` as a `predefined_type` at all.
833 // The `type_identifier` arm below is where it is answered, and
834 // `each_predefined_type_annotation_is_its_primitive` is what would redden if a
835 // grammar bump moved it here.
836 "predefined_type" => match self.text(node) {
837 "number" => Some(Type::Primitive(Primitive::Number)),
838 "string" => Some(Type::Primitive(Primitive::String)),
839 "boolean" => Some(Type::Primitive(Primitive::Boolean)),
840 "symbol" => Some(Type::Primitive(Primitive::Symbol)),
841 _ => None,
842 },
843
844 // Every member or none.
845 //
846 // A member the oracle cannot type used to be dropped, on the reasoning that
847 // `number | Foo<T>` still tells a rule asking "can this be a number" something
848 // true. It does not: what came back was a bare `Primitive(Number)`, identical
849 // in every byte to a declared `number`, with nothing left to say a member had
850 // been lost. A rule reporting "this is typed `number`" then fires on
851 // `amount: number | Decimal` and accuses correct code.
852 //
853 // A `comment` is a *named* child of a `union_type` — measured:
854 // `number /* c */ | string` gives `(union_type (predefined_type) (comment)
855 // (predefined_type))` — so it has to be skipped by name. Left in, it would be
856 // an untypeable member, and a comment written inside an annotation would
857 // silence the whole union.
858 "union_type" => {
859 let next = depth.saturating_add(1);
860 let mut cursor = node.walk();
861 let members: Vec<Type> = node
862 .children(&mut cursor)
863 .filter(|child| child.is_named() && child.kind() != "comment")
864 .map(|member| self.annotation_type(member, next))
865 .collect::<Option<Vec<Type>>>()?;
866 Type::union(members)
867 }
868
869 // A literal type wraps the literal itself, so the expression side answers it.
870 "literal_type" => self.type_of_at(node.named_child(0)?, depth.saturating_add(1)),
871
872 // `bigint` is the one primitive-type keyword this grammar does not lex as a
873 // `predefined_type` — verified against tree-sitter-typescript 0.23 with a parse
874 // probe: `let x: bigint;` produces a `type_identifier` node reading "bigint",
875 // where `number`, `string`, `boolean`, `symbol`, `any` and `unknown` all produce
876 // `predefined_type`. Matched on text for the same reason the arm above matches
877 // on text rather than kind — but the resolver gets first say: `class bigint {}`
878 // shadows the primitive exactly as a local `parseFloat` shadows the builtin
879 // conversion in `type_of_at`, so the check has to run before the shortcut, not
880 // after `named_type` would have caught it anyway.
881 "type_identifier" => {
882 if self.text(node) == "bigint"
883 && self
884 .resolver
885 .resolve(self.tree, self.source, node)
886 .is_none()
887 {
888 return Some(Type::Primitive(Primitive::BigInt));
889 }
890 self.named_type(node, depth)
891 }
892
893 // Generic, conditional, mapped, function and object types. Each would need an
894 // abstraction this oracle does not have, and guessing is worse than silence.
895 _ => None,
896 }
897 }
898
899 /// A type named by an identifier: a same-file alias followed, or a nominal type.
900 ///
901 /// An alias is followed because `type Amount = number` means a rule asking "is this a
902 /// number" should hear yes. An imported alias is followed too, through the
903 /// [`ImportResolution`] hook, when one is installed; with none installed it stays nominal,
904 /// since there is nothing here to cross the file boundary with.
905 ///
906 /// A *type parameter* is the one declaration that is neither. `Nominal` is a claim —
907 /// that this is a distinct named type — and `f<number>(1)` makes it false, so the `T`
908 /// in `function f<T>(x: T)` gives nothing at all. Which is also why the resolver has to
909 /// see type parameters in the first place: before it did, the scope walk escaped
910 /// outward and `type A = number; function f<A>(x: A)` typed `x` as `number`.
911 fn named_type(&self, node: Node<'t>, depth: u32) -> Option<Type> {
912 let name = self.text(node);
913 if name.is_empty() {
914 return None;
915 }
916
917 if let Some(declaration) = self.resolver.declaration_of(self.tree, self.source, node) {
918 if declaration.kind() == "type_parameter" {
919 return None;
920 }
921 if declaration.kind() == "type_alias_declaration"
922 && let Some(value) = declaration.child_by_field_name("value")
923 {
924 return self.annotation_type(value, depth.saturating_add(1));
925 }
926 }
927
928 // An imported *alias* is followed across the boundary exactly as a same-file one is
929 // above: `export type Amount = number` means a rule asking "is this a number" should
930 // hear yes wherever the alias was written. Everything else keeps its own nominal
931 // identity and gains only a better `symbol` — see `ImportResolution`'s own doc for
932 // why replacing an imported class with its declaration would be a false positive
933 // rather than a better answer.
934 //
935 // `Exhausted` answers `None` rather than falling to the nominal case below: the name
936 // *is* an alias, and a chain the bound cut is unknown, never a guess — see
937 // `Followed`'s own documentation.
938 if let Some(Binding::Import {
939 module,
940 name: imported,
941 }) = self.resolver.resolve(self.tree, self.source, node)
942 && let (Some(file), Some(imports)) = (self.file, self.imports)
943 {
944 match imports.imported_alias_type(file, &module, &imported, depth.saturating_add(1)) {
945 Followed::Type(aliased) => return Some(aliased),
946 Followed::Exhausted => return self.exhaust(),
947 Followed::NotAnAlias => {}
948 }
949 }
950
951 Some(Type::Nominal {
952 name: name.to_owned(),
953 symbol: self.symbol_at(node),
954 })
955 }
956
957 /// Where the name at `node` came from, when the resolver can say.
958 ///
959 /// `exported` is the name the *declaring* module uses. With resolution attached it is
960 /// followed through every re-export to the file that declares the thing, so
961 /// `import Big from 'decimal.js'` reports `Big`'s real declared name rather than the
962 /// placeholder `default` — which is what lets a rule compare against a required export
963 /// name without accusing a conforming default import. Without resolution, or when the
964 /// declaration file is unreadable, it falls back to what the import statement itself
965 /// says.
966 fn symbol_at(&self, node: Node<'t>) -> Option<Symbol> {
967 let name = self.text(node);
968 if name.is_empty() {
969 return None;
970 }
971 let (module, exported) = match self.resolver.resolve(self.tree, self.source, node)? {
972 Binding::Import {
973 module,
974 name: imported,
975 } => {
976 let declared = self
977 .file
978 .zip(self.imports)
979 .and_then(|(file, imports)| imports.imported_export(file, &module, &imported))
980 .map(|target| target.name);
981 let exported = declared.or(match &imported {
982 // Copied even when no rename happened: the consumer compares
983 // `exported === require.name`, and a `None`-when-unrenamed contract makes
984 // a forgotten fallback a silent false negative on every plain import.
985 ImportedName::Named(exported) => Some(exported.clone()),
986 ImportedName::Default => Some("default".to_owned()),
987 // `import * as D` binds the module object; there is no one exported name.
988 ImportedName::Namespace => None,
989 });
990 (Some(module), exported)
991 }
992 Binding::Local(_) => (None, None),
993 };
994 Some(Symbol {
995 name: name.to_owned(),
996 module,
997 exported,
998 })
999 }
1000
1001 /// The operator token of a binary or unary expression.
1002 ///
1003 /// `operator` is a real field on both node kinds, same as `left`, `right` and
1004 /// `function` beside it — the token it points to is an anonymous *node* (there is no
1005 /// dedicated `+` or `typeof` kind), but anonymous-ness is a property of the node, not
1006 /// of whether a field names it. The two are independent, and it is only the former that
1007 /// is true here.
1008 fn operator_of(&self, node: Node<'t>) -> Option<&'t str> {
1009 node.child_by_field_name("operator")
1010 .map(|child| self.text(child))
1011 }
1012
1013 /// The source text of a node.
1014 fn text(&self, node: Node<'t>) -> &'t str {
1015 self.source.get(node.byte_range()).unwrap_or("")
1016 }
1017}
1018
1019/// Whether a declaration binds exactly one name, rather than destructuring.
1020///
1021/// The resolver answers `declaration_of` with the whole declaration for every name a
1022/// pattern binds, so `const { rate }: Money = order` hands back the same declarator for
1023/// `rate` that `const order: Money = row` hands back for `order`. Nothing further down
1024/// distinguishes them, and both the annotation and the initializer describe the thing
1025/// being taken apart rather than any name taken out of it: without this guard,
1026/// `const s = String(q); const { length } = s` types `length` as `string`, and
1027/// `function f({ rate }: Money)` types `rate` as `Money`. Both are confidently wrong,
1028/// which is worse than the `None` this produces instead.
1029///
1030/// Measured against tree-sitter-typescript: the named field is an `identifier` for a plain
1031/// binding — including `let a!: number`, whose definite-assignment `!` does not change the
1032/// kind — and an `object_pattern`, `array_pattern` or `rest_pattern` for the rest. So the
1033/// test is for the one shape that is not a pattern, not against a list of the ones that
1034/// are; a pattern kind this file has never heard of still fails it.
1035///
1036/// Typing a destructured name needs property lookup on the pattern's type, which is a
1037/// later milestone's capability rather than a gap here.
1038fn binds_one_name(declaration: Node<'_>, field: &str) -> bool {
1039 declaration
1040 .child_by_field_name(field)
1041 .is_some_and(|bound| bound.kind() == "identifier")
1042}
1043
1044/// Whether a function-like node's call yields a wrapper around what its body returns.
1045///
1046/// `async` and `*` are anonymous tokens rather than fields — the grammar writes them bare, the
1047/// same way `export default`'s `default` is written — so this reads the children rather than
1048/// asking for a field that does not exist. Both spellings of a generator are covered: the
1049/// dedicated `generator_function*` kinds and a `method_definition` or arrow carrying the token.
1050fn wraps_its_return(node: Node<'_>) -> bool {
1051 let mut cursor = node.walk();
1052 node.children(&mut cursor)
1053 .any(|child| !child.is_named() && matches!(child.kind(), "async" | "*"))
1054}
1055
1056/// Every `return` in this body, skipping the ones that belong to a nested function.
1057///
1058/// `None` for a bare `return;`. Nested functions are skipped because their returns are
1059/// somebody else's: `function f() { const g = () => 'a'; return 1; }` returns a number, and a
1060/// walk that took every `return_statement` under the body would answer `number | string`.
1061///
1062/// A stack rather than a cursor recursion, and children pushed in reverse so the walk visits
1063/// them in source order — the union is canonicalized afterwards, so this is about a
1064/// reproducible *failure* message rather than about the answer.
1065fn collect_returns<'t>(node: Node<'t>, out: &mut Vec<Option<Node<'t>>>) {
1066 let mut stack = vec![node];
1067 while let Some(current) = stack.pop() {
1068 if current.kind() == "return_statement" {
1069 out.push(current.named_child(0));
1070 continue;
1071 }
1072 if current.id() != node.id() && is_function_like(current) {
1073 continue;
1074 }
1075 let mut cursor = current.walk();
1076 let children: Vec<Node<'t>> = current.children(&mut cursor).collect();
1077 stack.extend(children.into_iter().rev());
1078 }
1079}
1080
1081/// Whether a node introduces a function of its own.
1082fn is_function_like(node: Node<'_>) -> bool {
1083 matches!(
1084 node.kind(),
1085 "function_declaration"
1086 | "generator_function_declaration"
1087 | "function_signature"
1088 | "function_expression"
1089 | "generator_function"
1090 | "arrow_function"
1091 | "method_definition"
1092 | "method_signature"
1093 | "abstract_method_signature"
1094 )
1095}
1096
1097/// The type inside a `type_annotation` wrapper.
1098///
1099/// A parameter's `type` field is the `type_annotation` node, not the type itself, so every
1100/// caller reading an annotation has to step through it. One place to get that wrong is
1101/// better than four.
1102pub(crate) fn annotation_child(node: Node<'_>) -> Option<Node<'_>> {
1103 if node.kind() == "type_annotation" {
1104 node.named_child(0)
1105 } else {
1106 Some(node)
1107 }
1108}
1109
1110/// The static member a property access or subscript names.
1111///
1112/// The property identifier of `a.b` / `a?.b`, or the string literal of `a["b"]` / `a?.["b"]`.
1113/// A dynamic subscript — `a[i]`, `a[0]`, `a[k + 1]` — names no member the oracle can resolve
1114/// without an element-type representation it does not have, and yields `None`. A private field
1115/// (`a.#x`, a `private_property_identifier`) is not an interface member and yields `None` too.
1116pub(crate) fn member_name(source: &str, node: Node<'_>) -> Option<String> {
1117 match node.kind() {
1118 "member_expression" => {
1119 let property = node.child_by_field_name("property")?;
1120 (property.kind() == "property_identifier")
1121 .then(|| source.get(property.byte_range()).map(str::to_owned))
1122 .flatten()
1123 }
1124 "subscript_expression" => {
1125 let index = node.child_by_field_name("index")?;
1126 if index.kind() != "string" {
1127 return None;
1128 }
1129 string_literal_value(source.get(index.byte_range())?)
1130 }
1131 _ => None,
1132 }
1133}
1134
1135/// The value of a single-quoted or double-quoted string literal, quotes stripped.
1136///
1137/// `None` when the text carries an escape: decoding one to match a member name is more than a
1138/// property key ever needs, so the conservative answer is no member rather than a wrong one.
1139fn string_literal_value(text: &str) -> Option<String> {
1140 let inner = text
1141 .strip_prefix('"')
1142 .and_then(|rest| rest.strip_suffix('"'))
1143 .or_else(|| {
1144 text.strip_prefix('\'')
1145 .and_then(|rest| rest.strip_suffix('\''))
1146 })?;
1147 (!inner.contains('\\')).then(|| inner.to_owned())
1148}
1149
1150/// Whether a property access or subscript is optional (`a?.b`, `a?.["b"]`).
1151///
1152/// `optional_chain` is a field on both `member_expression` and `subscript_expression`, present
1153/// only on the link that carries the `?.`.
1154pub(crate) fn optional_access(node: Node<'_>) -> bool {
1155 node.child_by_field_name("optional_chain").is_some()
1156}
1157
1158/// The type node a bound name is annotated with: a parameter's or variable's `type`, unwrapped
1159/// from its `type_annotation`.
1160///
1161/// A binding that destructures gives nothing, for the reason [`binds_one_name`] documents; one
1162/// with no annotation gives nothing too, since an initializer's type is not what this reads.
1163pub(crate) fn binding_annotation(declaration: Node<'_>) -> Option<Node<'_>> {
1164 let field = match declaration.kind() {
1165 "required_parameter" | "optional_parameter" => "pattern",
1166 "variable_declarator" => "name",
1167 _ => return None,
1168 };
1169 if !binds_one_name(declaration, field) {
1170 return None;
1171 }
1172 annotation_child(declaration.child_by_field_name("type")?)
1173}
1174
1175/// Whether a type node denotes `null` or `undefined`.
1176///
1177/// In *type* position both are wrapped in a `literal_type` — `T | undefined` parses as
1178/// `(union_type (type_identifier) (literal_type (undefined)))` — so the bare kinds alone never
1179/// match a real annotation. The unwrapped forms are accepted too, harmlessly.
1180fn is_nullish_type(type_node: Node<'_>) -> bool {
1181 match type_node.kind() {
1182 "null" | "undefined" => true,
1183 "literal_type" => type_node
1184 .named_child(0)
1185 .is_some_and(|inner| matches!(inner.kind(), "null" | "undefined")),
1186 _ => false,
1187 }
1188}
1189
1190/// Whether a type node is, or is a union containing, `null` or `undefined`.
1191///
1192/// One level of union, which is what a nullable annotation writes; a nested union is rare and
1193/// the conservative miss only ever drops an `undefined` the answer would have carried.
1194pub(crate) fn type_contains_nullish(type_node: Node<'_>) -> bool {
1195 if is_nullish_type(type_node) {
1196 return true;
1197 }
1198 if type_node.kind() == "union_type" {
1199 let mut cursor = type_node.walk();
1200 return type_node.named_children(&mut cursor).any(is_nullish_type);
1201 }
1202 false
1203}
1204
1205/// The single non-nullish arm of a union type, or `None` when it has zero or several.
1206///
1207/// `Order | undefined` denotes `Order` for a member read; `A | B` denotes no single receiver.
1208/// A `comment` is a named child of a `union_type`, so it is skipped by kind the way
1209/// [`Self::annotation_type`](TypeScriptOracle::annotation_type)'s union arm skips it.
1210pub(crate) fn sole_non_nullish_arm(union_type: Node<'_>) -> Option<Node<'_>> {
1211 let mut cursor = union_type.walk();
1212 let mut arm = None;
1213 for child in union_type.named_children(&mut cursor) {
1214 if is_nullish_type(child) || child.kind() == "comment" {
1215 continue;
1216 }
1217 if arm.is_some() {
1218 return None;
1219 }
1220 arm = Some(child);
1221 }
1222 arm
1223}
1224
1225/// The name node of a type reference: the reference itself for a `type_identifier`, or the
1226/// `name` field for a `generic_type` (`Foo<T>`), whose type arguments this crate drops.
1227pub(crate) fn type_name_node(type_node: Node<'_>) -> Option<Node<'_>> {
1228 match type_node.kind() {
1229 "type_identifier" => Some(type_node),
1230 "generic_type" => type_node.child_by_field_name("name"),
1231 _ => None,
1232 }
1233}
1234
1235/// The body an interface or class exposes members through.
1236///
1237/// An alias is not here: it is followed to its right-hand side first, which may be an object
1238/// type, another named type, or a union — a distinction the body of a declaration cannot make.
1239pub(crate) fn declaration_body(declaration: Node<'_>) -> Option<Node<'_>> {
1240 matches!(
1241 declaration.kind(),
1242 "interface_declaration" | "class_declaration" | "abstract_class_declaration"
1243 )
1244 .then(|| declaration.child_by_field_name("body"))
1245 .flatten()
1246}
1247
1248/// The annotation of the member named `member`, and whether that member is optional (`b?: T`).
1249///
1250/// Reads an `interface_body`, an `object_type` or a `class_body` — their members are positional
1251/// named children, not reached through a field. A member with no annotation (a class field with
1252/// only an initializer, a method) has no type to read, so a matching name without a `type`
1253/// field yields `None`. The optional `?` is an anonymous token, not a field, so it is found by
1254/// scanning children.
1255pub(crate) fn member_annotation<'t>(
1256 source: &str,
1257 container: Node<'t>,
1258 member: &str,
1259) -> Option<(Node<'t>, bool)> {
1260 let mut cursor = container.walk();
1261 for child in container.named_children(&mut cursor) {
1262 if !matches!(
1263 child.kind(),
1264 "property_signature" | "public_field_definition"
1265 ) {
1266 continue;
1267 }
1268 let Some(name) = child.child_by_field_name("name") else {
1269 continue;
1270 };
1271 if name.kind() != "property_identifier" || source.get(name.byte_range()) != Some(member) {
1272 continue;
1273 }
1274 let annotation = child.child_by_field_name("type")?;
1275 return Some((annotation, has_optional_token(child)));
1276 }
1277 None
1278}
1279
1280/// Whether an optional-member marker (`?`) is present, as in `b?: T`.
1281fn has_optional_token(node: Node<'_>) -> bool {
1282 let mut cursor = node.walk();
1283 node.children(&mut cursor).any(|child| child.kind() == "?")
1284}
1285
1286/// A member type, with `| undefined` added when the access short-circuits.
1287///
1288/// [`Type::union`] flattens and dedups, so an already-nullable member type gains nothing when
1289/// `optional` is set. It returns `None` only for an empty input, which this never passes.
1290pub(crate) fn with_optional(ty: Type, optional: bool) -> Option<Type> {
1291 if optional {
1292 Type::union(vec![ty, Type::Primitive(Primitive::Undefined)])
1293 } else {
1294 Some(ty)
1295 }
1296}