Skip to main content

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}