lanekeep_types/lib.rs
1//! The bounded type oracle: what a node's type is, and where its name came from.
2//!
3//! This is the "bounded oracle" of the type-aware rules design, and the bound is *depth*
4//! rather than the file. [`TypeScriptOracle`] answers from one parse and nothing else;
5//! [`BuiltinProvider`] wraps it and follows an import out of that file — to a sibling source,
6//! to a `.d.ts`, into `node_modules` — through the caller's [`lanekeep_core::FileAccess`], so
7//! every file it opens is a recorded dependency of the answer. What it still has no notion of
8//! is a *program*: no `tsconfig.json`, no path mapping, no compiler, and a fixed number of
9//! hops rather than a transitive closure.
10//!
11//! It answers `None` whenever it cannot be sure. That is the whole contract: a rule choosing
12//! to be silent on `None` is sound, and one choosing to report on it is the author's decision
13//! rather than the engine's.
14//!
15//! # Why this is its own crate
16//!
17//! It reaches declarations through [`lanekeep_lang::binding::BindingResolver::declaration_of`]
18//! and [`lanekeep_lang::binding::BindingResolver::declares`], two language-neutral questions,
19//! so it never needs a language crate's internals — `lanekeep-lang-js` is a dependency of its
20//! tests alone. Only its knowledge of TypeScript *syntax* is language-specific, and that is
21//! held in one type whose constructor refuses a grammar that does not speak it.
22//!
23//! # What is deliberately absent
24//!
25//! No clock, no environment, no randomness, no `HashMap` iteration, and no filesystem access
26//! that is not a [`lanekeep_core::FileAccess`] read. A cached result computed by this oracle
27//! must still be valid, so nothing here may observe anything the cache key does not cover: the
28//! bytes it was handed, and the tracked reads it made from them.
29
30mod builtin;
31mod declarations;
32mod oracle;
33mod provider;
34mod resolve;
35mod table;
36pub mod tsc;
37mod types;
38
39pub use builtin::BuiltinProvider;
40pub use declarations::{
41 Declaration, ExportTarget, Exported, declared_here, declared_name, find_export,
42};
43pub use oracle::{Followed, ImportResolution, TypeScriptOracle, TypeScriptSupport};
44pub use provider::{BeginRunError, Query, TypeProvider};
45pub use resolve::resolve_specifier;
46pub use types::{Primitive, Symbol, Type};
47
48/// What this oracle *is*, as a digest of every source file that decides an answer.
49///
50/// A cache-key input for whoever wires the oracle up: a result computed by an oracle that no
51/// longer exists must not be served. It is derived rather than hand-maintained, because the
52/// hand-maintained alternative is `lanekeep_js::HOST_API_VERSION`, whose own documentation
53/// says plainly that nothing detects a missed bump.
54///
55/// **Not a hash of the tables.** The `+` arm, the shadow check on builtin calls and the
56/// recursion bound are logic rather than table rows, and an oracle whose `+` arm is corrected
57/// gives different answers from an identical table. Hashing the data alone would
58/// under-invalidate on exactly the changes most likely to matter, which is the asymmetric
59/// failure the cache key exists to prevent.
60///
61/// This over-invalidates instead: editing a comment in this crate discards every cached
62/// type-aware result. That is the trade `hash_ruleset` already makes for rule source, and
63/// for the same reason — over-invalidation costs a recompute, under-invalidation reports a
64/// wrong answer and gives no sign that it did.
65///
66/// The grammar is not folded in here. A run's cache key already carries a structural digest of
67/// every registered grammar, so a TypeScript grammar bump invalidates through that term;
68/// hashing it twice would be one place too many.
69#[must_use]
70pub fn oracle_identity() -> [u8; 32] {
71 // Written by `build.rs`, which walks `src/` so that a file added but not listed cannot
72 // be a silent gap.
73 lanekeep_lang::decode_hex32(env!("LANEKEEP_TYPES_ORACLE_HASH"))
74}