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}