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 return type of `node`, from a depth already spent. See [`Self::type_of_from`].
328    #[must_use]
329    pub fn return_type_from(&self, node: Node<'t>, depth: u32) -> Option<Type> {
330        self.return_type_at(node, depth)
331    }
332
333    /// The type a name *in type position* denotes: an alias followed, a nominal otherwise.
334    ///
335    /// [`Self::type_of`] cannot stand in for it. In expression position an `identifier` is a
336    /// value, so `class A extends B {}`'s `B` would be typed as whatever value `B` holds —
337    /// which for a class declaration is nothing at all — rather than as the type it names.
338    #[must_use]
339    pub fn type_named_by(&self, node: Node<'t>) -> Option<Type> {
340        self.named_type(node, 0)
341    }
342
343    /// The type of the expression at `node`, or `None` when the oracle cannot be sure.
344    ///
345    /// `None` is an answer rather than a failure. A rule that stays silent on it reports
346    /// only what was established, which is the posture every rule built on this oracle is
347    /// expected to take.
348    #[must_use]
349    pub fn type_of(&self, node: Node<'t>) -> Option<Type> {
350        self.type_of_at(node, 0)
351    }
352
353    /// Where the name at `node` came from, or `None` if nothing in this file declares it.
354    ///
355    /// Distinct from [`Self::type_of`] and useful where that returns nothing: an imported
356    /// value has no type this oracle can read, and still has a name and a module — which is
357    /// exactly what a rule distinguishing one library's `Decimal` from a local class needs.
358    ///
359    /// Answers in type position as well as expression position, because the resolver does.
360    #[must_use]
361    pub fn symbol_of(&self, node: Node<'t>) -> Option<Symbol> {
362        self.symbol_at(node)
363    }
364
365    /// What calling the function at `node` yields.
366    ///
367    /// Separate from [`Self::type_of`] rather than folded into it, and the reason is the
368    /// vocabulary rather than the plumbing: a function declaration is not an expression, and
369    /// giving `type_of` a signature type would mean a `Type::Function` variant every rule
370    /// asking a simpler question would then have to unpack. There is exactly one question
371    /// rules ask about a function, so there is exactly one method.
372    ///
373    /// Accepts a call expression (whose callee is resolved), a function-like declaration, or
374    /// an identifier bound to one.
375    #[must_use]
376    pub fn return_type_of(&self, node: Node<'t>) -> Option<Type> {
377        self.return_type_at(node, 0)
378    }
379
380    fn return_type_at(&self, node: Node<'t>, depth: u32) -> Option<Type> {
381        if depth >= MAX_DEPTH {
382            return self.exhaust();
383        }
384        let next = depth.saturating_add(1);
385
386        match node.kind() {
387            "call_expression" => self.return_type_at(node.child_by_field_name("function")?, next),
388            "identifier" => {
389                if let Some(Binding::Import { module, name }) =
390                    self.resolver.resolve(self.tree, self.source, node)
391                    && let (Some(file), Some(imports)) = (self.file, self.imports)
392                {
393                    return imports.imported_return_type(file, &module, &name, next);
394                }
395                let declaration = self.resolver.declaration_of(self.tree, self.source, node)?;
396                self.return_type_at(declaration, next)
397            }
398            // `const rate = () => 1` binds the function to a name; the declarator's value is
399            // the function. An annotated declarator is deliberately not read as a signature —
400            // that would be a function *type*, which this oracle says nothing about.
401            "variable_declarator" => self.return_type_at(node.child_by_field_name("value")?, next),
402            "function_declaration"
403            | "generator_function_declaration"
404            | "function_signature"
405            | "function_expression"
406            // The expression form: `const g = function*() {...}`. `is_function_like` has
407            // always listed it; this dispatch had not, so a call to a generator bound this
408            // way fell through to `_ => None` despite the oracle treating it as function-like
409            // everywhere else — addendum A1/A2 of task 4.16.
410            | "generator_function"
411            | "arrow_function"
412            | "method_definition"
413            | "method_signature"
414            | "abstract_method_signature" => self.signature_return(node, next),
415            _ => None,
416        }
417    }
418
419    /// The return type of a function-like node: its annotation, or what its body returns.
420    ///
421    /// The annotation wins wherever both are present, on the same reasoning
422    /// [`Self::declaration_type`] prefers one: the annotation is what the program means, and
423    /// answering from the body would describe a mistake rather than a declaration.
424    fn signature_return(&self, node: Node<'t>, depth: u32) -> Option<Type> {
425        if let Some(annotation) = node.child_by_field_name("return_type") {
426            // `asserts_annotation` and `type_predicate_annotation` are the other two kinds
427            // this field can hold (`node-types.json`); `annotation_child` hands back whatever
428            // is there and the annotation vocabulary answers `None` for both, which is right —
429            // `x is Foo` is not a type any rule built on this oracle asks about.
430            return self.annotation_type(annotation_child(annotation)?, depth);
431        }
432
433        // An `async` function's value is a `Promise<…>` and a generator's is a `Generator<…>`,
434        // and this oracle has no variant that can say either — no type arguments, no
435        // `Promise`. With no annotation there is nothing here able to name the wrapper, so the
436        // body's `return` type is not the call's type: answering `number` for
437        // `async function rate() { return 1 }` would be a claim a rule can compare against a
438        // `number` and be wrong about every time, with nothing in the answer to say a wrapper
439        // was dropped. The annotation path above is untouched — the refusal is about the
440        // absence of an annotation rather than about `async`.
441        if wraps_its_return(node) {
442            return None;
443        }
444
445        let body = node.child_by_field_name("body")?;
446        if body.kind() != "statement_block" {
447            // A concise arrow body is the returned expression itself.
448            return self.type_of_at(body, depth);
449        }
450
451        let mut returns = Vec::new();
452        collect_returns(body, &mut returns);
453        if returns.is_empty() {
454            // No `return` at all. `void` would be a guess, and this oracle has no variant for
455            // it — see `Primitive`'s own documentation on why `any` and `unknown` are absent
456            // for the same reason.
457            return None;
458        }
459
460        // Every member or none, exactly as a union annotation is read: a member that could
461        // not be typed leaves an answer byte-identical to a complete one, with nothing left
462        // to say something was lost.
463        let members: Vec<Type> = returns
464            .into_iter()
465            .map(|returned| match returned {
466                // A bare `return;` yields `undefined`, which is a member rather than a gap.
467                None => Some(Type::Primitive(Primitive::Undefined)),
468                Some(expression) => self.type_of_at(expression, depth),
469            })
470            .collect::<Option<Vec<Type>>>()?;
471        Type::union(members)
472    }
473
474    fn type_of_at(&self, node: Node<'t>, depth: u32) -> Option<Type> {
475        if depth >= MAX_DEPTH {
476            return self.exhaust();
477        }
478
479        match node.kind() {
480            "string" | "template_string" => Some(Type::Primitive(Primitive::String)),
481            "true" | "false" => Some(Type::Primitive(Primitive::Boolean)),
482            "null" => Some(Type::Primitive(Primitive::Null)),
483            "undefined" => Some(Type::Primitive(Primitive::Undefined)),
484
485            // A bigint literal parses as `number`; the trailing `n` is the only thing that
486            // distinguishes it, so this reads the text rather than trusting the kind.
487            "number" => Some(Type::Primitive(if self.text(node).ends_with('n') {
488                Primitive::BigInt
489            } else {
490                Primitive::Number
491            })),
492
493            "parenthesized_expression" => {
494                self.type_of_at(node.named_child(0)?, depth.saturating_add(1))
495            }
496
497            "binary_expression" => {
498                let next = depth.saturating_add(1);
499                let left = self.primitive_of(node.child_by_field_name("left")?, next);
500                let right = self.primitive_of(node.child_by_field_name("right")?, next);
501                table::binary(self.operator_of(node)?, left, right).map(Type::Primitive)
502            }
503
504            "unary_expression" => table::unary(self.operator_of(node)?).map(Type::Primitive),
505
506            "call_expression" => {
507                let callee = node.child_by_field_name("function")?;
508                // Only a *bare* global counts. A member call like `Number.parseFloat(x)`
509                // is not in the table, and a callee that resolves to a local binding is
510                // somebody's own function that happens to share a name.
511                if callee.kind() != "identifier" {
512                    return None;
513                }
514                if self
515                    .resolver
516                    .resolve(self.tree, self.source, callee)
517                    .is_some()
518                {
519                    return None;
520                }
521                table::builtin_call(self.text(callee)).map(Type::Primitive)
522            }
523
524            "type_annotation" => {
525                self.annotation_type(node.named_child(0)?, depth.saturating_add(1))
526            }
527            "predefined_type" | "union_type" | "literal_type" | "type_identifier" => {
528                self.annotation_type(node, depth)
529            }
530
531            "identifier" => {
532                // An imported value's declaration is in another file. With resolution
533                // attached, that file is opened and the declaration typed in its own context;
534                // without it, this is the `None` it always was.
535                //
536                // Asked here rather than in `declaration_type`'s `import_statement` arm — the
537                // seam the design named — because the module specifier and *which* export was
538                // imported are what `resolve` answers, and the `import_statement` node alone
539                // does not say which of its specifiers bound this use.
540                if let Some(Binding::Import { module, name }) =
541                    self.resolver.resolve(self.tree, self.source, node)
542                    && let (Some(file), Some(imports)) = (self.file, self.imports)
543                {
544                    return imports.imported_value_type(
545                        file,
546                        &module,
547                        &name,
548                        depth.saturating_add(1),
549                    );
550                }
551                let declaration = self.resolver.declaration_of(self.tree, self.source, node)?;
552                self.declaration_type(declaration, depth.saturating_add(1))
553            }
554
555            _ => None,
556        }
557    }
558
559    /// The type a declaration gives the name it declares.
560    ///
561    /// An annotation is preferred over an initializer wherever both are present, because
562    /// the annotation is what the program means: `const x: string = parseFloat(s)` is a
563    /// type error, and answering `number` for it would describe the mistake rather than the
564    /// declaration.
565    ///
566    /// A declaration that binds through a *pattern* gives nothing at all. Both arms below
567    /// hold a type for the thing being destructured and none for the names taken out of
568    /// it, and the two are not the same type — reading either the annotation or the
569    /// initializer would hand every name the whole thing's type. See [`binds_one_name`].
570    fn declaration_type(&self, declaration: Node<'t>, depth: u32) -> Option<Type> {
571        if depth >= MAX_DEPTH {
572            return self.exhaust();
573        }
574        let next = depth.saturating_add(1);
575
576        match declaration.kind() {
577            "required_parameter" | "optional_parameter" => {
578                if !binds_one_name(declaration, "pattern") {
579                    return None;
580                }
581                // The `type` field is the `type_annotation` wrapper; the parameter node
582                // itself is not one, so it has to be read before unwrapping. An unannotated
583                // parameter has no `type` field and gives nothing, which is correct — this
584                // milestone does not infer a parameter's type from its call sites.
585                let annotation = declaration.child_by_field_name("type")?;
586                self.annotation_type(annotation_child(annotation)?, next)
587            }
588
589            "variable_declarator" => {
590                if !binds_one_name(declaration, "name") {
591                    return None;
592                }
593                if let Some(annotation) = declaration.child_by_field_name("type") {
594                    return self.annotation_type(annotation_child(annotation)?, next);
595                }
596                self.type_of_at(declaration.child_by_field_name("value")?, next)
597            }
598
599            // An import's declaration is in another file, which this oracle does not open.
600            // A function or class declaration names a callable or a constructor rather than
601            // a value with a type this milestone reasons about. A `type_parameter` is
602            // whatever the call site chose, which this oracle does not see.
603            _ => None,
604        }
605    }
606
607    /// A node's type, when it is a primitive and nothing else.
608    ///
609    /// The operator table reasons about primitives, and a nominal or a union on either side
610    /// of an arithmetic operator is something it has no row for.
611    fn primitive_of(&self, node: Node<'t>, depth: u32) -> Option<Primitive> {
612        match self.type_of_at(node, depth)? {
613            Type::Primitive(primitive) => Some(primitive),
614            Type::Nominal { .. } | Type::Union(_) => None,
615        }
616    }
617
618    /// The type a type-level node denotes.
619    ///
620    /// Separate from [`Self::type_of_at`] because the two vocabularies barely overlap: a
621    /// `number` in expression position is a literal and in type position is a keyword. One
622    /// match arm handling both would have to disambiguate by parent, which is the kind of
623    /// thing that is right until somebody nests it.
624    fn annotation_type(&self, node: Node<'t>, depth: u32) -> Option<Type> {
625        if depth >= MAX_DEPTH {
626            return self.exhaust();
627        }
628
629        match node.kind() {
630            // Matched on text, not kind: `any` and `unknown` parse identically to `number`.
631            // Both give nothing, deliberately — `any` is the absence of a claim, and
632            // `void` and `never` are types no rule built on this oracle asks about.
633            //
634            // There is no `bigint` row, and its absence is the measurement rather than an
635            // oversight: this grammar does not lex `bigint` as a `predefined_type` at all.
636            // The `type_identifier` arm below is where it is answered, and
637            // `each_predefined_type_annotation_is_its_primitive` is what would redden if a
638            // grammar bump moved it here.
639            "predefined_type" => match self.text(node) {
640                "number" => Some(Type::Primitive(Primitive::Number)),
641                "string" => Some(Type::Primitive(Primitive::String)),
642                "boolean" => Some(Type::Primitive(Primitive::Boolean)),
643                "symbol" => Some(Type::Primitive(Primitive::Symbol)),
644                _ => None,
645            },
646
647            // Every member or none.
648            //
649            // A member the oracle cannot type used to be dropped, on the reasoning that
650            // `number | Foo<T>` still tells a rule asking "can this be a number" something
651            // true. It does not: what came back was a bare `Primitive(Number)`, identical
652            // in every byte to a declared `number`, with nothing left to say a member had
653            // been lost. A rule reporting "this is typed `number`" then fires on
654            // `amount: number | Decimal` and accuses correct code.
655            //
656            // A `comment` is a *named* child of a `union_type` — measured:
657            // `number /* c */ | string` gives `(union_type (predefined_type) (comment)
658            // (predefined_type))` — so it has to be skipped by name. Left in, it would be
659            // an untypeable member, and a comment written inside an annotation would
660            // silence the whole union.
661            "union_type" => {
662                let next = depth.saturating_add(1);
663                let mut cursor = node.walk();
664                let members: Vec<Type> = node
665                    .children(&mut cursor)
666                    .filter(|child| child.is_named() && child.kind() != "comment")
667                    .map(|member| self.annotation_type(member, next))
668                    .collect::<Option<Vec<Type>>>()?;
669                Type::union(members)
670            }
671
672            // A literal type wraps the literal itself, so the expression side answers it.
673            "literal_type" => self.type_of_at(node.named_child(0)?, depth.saturating_add(1)),
674
675            // `bigint` is the one primitive-type keyword this grammar does not lex as a
676            // `predefined_type` — verified against tree-sitter-typescript 0.23 with a parse
677            // probe: `let x: bigint;` produces a `type_identifier` node reading "bigint",
678            // where `number`, `string`, `boolean`, `symbol`, `any` and `unknown` all produce
679            // `predefined_type`. Matched on text for the same reason the arm above matches
680            // on text rather than kind — but the resolver gets first say: `class bigint {}`
681            // shadows the primitive exactly as a local `parseFloat` shadows the builtin
682            // conversion in `type_of_at`, so the check has to run before the shortcut, not
683            // after `named_type` would have caught it anyway.
684            "type_identifier" => {
685                if self.text(node) == "bigint"
686                    && self
687                        .resolver
688                        .resolve(self.tree, self.source, node)
689                        .is_none()
690                {
691                    return Some(Type::Primitive(Primitive::BigInt));
692                }
693                self.named_type(node, depth)
694            }
695
696            // Generic, conditional, mapped, function and object types. Each would need an
697            // abstraction this oracle does not have, and guessing is worse than silence.
698            _ => None,
699        }
700    }
701
702    /// A type named by an identifier: a same-file alias followed, or a nominal type.
703    ///
704    /// An alias is followed because `type Amount = number` means a rule asking "is this a
705    /// number" should hear yes. An imported alias is followed too, through the
706    /// [`ImportResolution`] hook, when one is installed; with none installed it stays nominal,
707    /// since there is nothing here to cross the file boundary with.
708    ///
709    /// A *type parameter* is the one declaration that is neither. `Nominal` is a claim —
710    /// that this is a distinct named type — and `f<number>(1)` makes it false, so the `T`
711    /// in `function f<T>(x: T)` gives nothing at all. Which is also why the resolver has to
712    /// see type parameters in the first place: before it did, the scope walk escaped
713    /// outward and `type A = number; function f<A>(x: A)` typed `x` as `number`.
714    fn named_type(&self, node: Node<'t>, depth: u32) -> Option<Type> {
715        let name = self.text(node);
716        if name.is_empty() {
717            return None;
718        }
719
720        if let Some(declaration) = self.resolver.declaration_of(self.tree, self.source, node) {
721            if declaration.kind() == "type_parameter" {
722                return None;
723            }
724            if declaration.kind() == "type_alias_declaration"
725                && let Some(value) = declaration.child_by_field_name("value")
726            {
727                return self.annotation_type(value, depth.saturating_add(1));
728            }
729        }
730
731        // An imported *alias* is followed across the boundary exactly as a same-file one is
732        // above: `export type Amount = number` means a rule asking "is this a number" should
733        // hear yes wherever the alias was written. Everything else keeps its own nominal
734        // identity and gains only a better `symbol` — see `ImportResolution`'s own doc for
735        // why replacing an imported class with its declaration would be a false positive
736        // rather than a better answer.
737        //
738        // `Exhausted` answers `None` rather than falling to the nominal case below: the name
739        // *is* an alias, and a chain the bound cut is unknown, never a guess — see
740        // `Followed`'s own documentation.
741        if let Some(Binding::Import {
742            module,
743            name: imported,
744        }) = self.resolver.resolve(self.tree, self.source, node)
745            && let (Some(file), Some(imports)) = (self.file, self.imports)
746        {
747            match imports.imported_alias_type(file, &module, &imported, depth.saturating_add(1)) {
748                Followed::Type(aliased) => return Some(aliased),
749                Followed::Exhausted => return self.exhaust(),
750                Followed::NotAnAlias => {}
751            }
752        }
753
754        Some(Type::Nominal {
755            name: name.to_owned(),
756            symbol: self.symbol_at(node),
757        })
758    }
759
760    /// Where the name at `node` came from, when the resolver can say.
761    ///
762    /// `exported` is the name the *declaring* module uses. With resolution attached it is
763    /// followed through every re-export to the file that declares the thing, so
764    /// `import Big from 'decimal.js'` reports `Big`'s real declared name rather than the
765    /// placeholder `default` — which is what lets a rule compare against a required export
766    /// name without accusing a conforming default import. Without resolution, or when the
767    /// declaration file is unreadable, it falls back to what the import statement itself
768    /// says.
769    fn symbol_at(&self, node: Node<'t>) -> Option<Symbol> {
770        let name = self.text(node);
771        if name.is_empty() {
772            return None;
773        }
774        let (module, exported) = match self.resolver.resolve(self.tree, self.source, node)? {
775            Binding::Import {
776                module,
777                name: imported,
778            } => {
779                let declared = self
780                    .file
781                    .zip(self.imports)
782                    .and_then(|(file, imports)| imports.imported_export(file, &module, &imported))
783                    .map(|target| target.name);
784                let exported = declared.or(match &imported {
785                    // Copied even when no rename happened: the consumer compares
786                    // `exported === require.name`, and a `None`-when-unrenamed contract makes
787                    // a forgotten fallback a silent false negative on every plain import.
788                    ImportedName::Named(exported) => Some(exported.clone()),
789                    ImportedName::Default => Some("default".to_owned()),
790                    // `import * as D` binds the module object; there is no one exported name.
791                    ImportedName::Namespace => None,
792                });
793                (Some(module), exported)
794            }
795            Binding::Local(_) => (None, None),
796        };
797        Some(Symbol {
798            name: name.to_owned(),
799            module,
800            exported,
801        })
802    }
803
804    /// The operator token of a binary or unary expression.
805    ///
806    /// `operator` is a real field on both node kinds, same as `left`, `right` and
807    /// `function` beside it — the token it points to is an anonymous *node* (there is no
808    /// dedicated `+` or `typeof` kind), but anonymous-ness is a property of the node, not
809    /// of whether a field names it. The two are independent, and it is only the former that
810    /// is true here.
811    fn operator_of(&self, node: Node<'t>) -> Option<&'t str> {
812        node.child_by_field_name("operator")
813            .map(|child| self.text(child))
814    }
815
816    /// The source text of a node.
817    fn text(&self, node: Node<'t>) -> &'t str {
818        self.source.get(node.byte_range()).unwrap_or("")
819    }
820}
821
822/// Whether a declaration binds exactly one name, rather than destructuring.
823///
824/// The resolver answers `declaration_of` with the whole declaration for every name a
825/// pattern binds, so `const { rate }: Money = order` hands back the same declarator for
826/// `rate` that `const order: Money = row` hands back for `order`. Nothing further down
827/// distinguishes them, and both the annotation and the initializer describe the thing
828/// being taken apart rather than any name taken out of it: without this guard,
829/// `const s = String(q); const { length } = s` types `length` as `string`, and
830/// `function f({ rate }: Money)` types `rate` as `Money`. Both are confidently wrong,
831/// which is worse than the `None` this produces instead.
832///
833/// Measured against tree-sitter-typescript: the named field is an `identifier` for a plain
834/// binding — including `let a!: number`, whose definite-assignment `!` does not change the
835/// kind — and an `object_pattern`, `array_pattern` or `rest_pattern` for the rest. So the
836/// test is for the one shape that is not a pattern, not against a list of the ones that
837/// are; a pattern kind this file has never heard of still fails it.
838///
839/// Typing a destructured name needs property lookup on the pattern's type, which is a
840/// later milestone's capability rather than a gap here.
841fn binds_one_name(declaration: Node<'_>, field: &str) -> bool {
842    declaration
843        .child_by_field_name(field)
844        .is_some_and(|bound| bound.kind() == "identifier")
845}
846
847/// Whether a function-like node's call yields a wrapper around what its body returns.
848///
849/// `async` and `*` are anonymous tokens rather than fields — the grammar writes them bare, the
850/// same way `export default`'s `default` is written — so this reads the children rather than
851/// asking for a field that does not exist. Both spellings of a generator are covered: the
852/// dedicated `generator_function*` kinds and a `method_definition` or arrow carrying the token.
853fn wraps_its_return(node: Node<'_>) -> bool {
854    let mut cursor = node.walk();
855    node.children(&mut cursor)
856        .any(|child| !child.is_named() && matches!(child.kind(), "async" | "*"))
857}
858
859/// Every `return` in this body, skipping the ones that belong to a nested function.
860///
861/// `None` for a bare `return;`. Nested functions are skipped because their returns are
862/// somebody else's: `function f() { const g = () => 'a'; return 1; }` returns a number, and a
863/// walk that took every `return_statement` under the body would answer `number | string`.
864///
865/// A stack rather than a cursor recursion, and children pushed in reverse so the walk visits
866/// them in source order — the union is canonicalized afterwards, so this is about a
867/// reproducible *failure* message rather than about the answer.
868fn collect_returns<'t>(node: Node<'t>, out: &mut Vec<Option<Node<'t>>>) {
869    let mut stack = vec![node];
870    while let Some(current) = stack.pop() {
871        if current.kind() == "return_statement" {
872            out.push(current.named_child(0));
873            continue;
874        }
875        if current.id() != node.id() && is_function_like(current) {
876            continue;
877        }
878        let mut cursor = current.walk();
879        let children: Vec<Node<'t>> = current.children(&mut cursor).collect();
880        stack.extend(children.into_iter().rev());
881    }
882}
883
884/// Whether a node introduces a function of its own.
885fn is_function_like(node: Node<'_>) -> bool {
886    matches!(
887        node.kind(),
888        "function_declaration"
889            | "generator_function_declaration"
890            | "function_signature"
891            | "function_expression"
892            | "generator_function"
893            | "arrow_function"
894            | "method_definition"
895            | "method_signature"
896            | "abstract_method_signature"
897    )
898}
899
900/// The type inside a `type_annotation` wrapper.
901///
902/// A parameter's `type` field is the `type_annotation` node, not the type itself, so every
903/// caller reading an annotation has to step through it. One place to get that wrong is
904/// better than four.
905fn annotation_child(node: Node<'_>) -> Option<Node<'_>> {
906    if node.kind() == "type_annotation" {
907        node.named_child(0)
908    } else {
909        Some(node)
910    }
911}