Skip to main content

TypeCache

Struct TypeCache 

Source
pub struct TypeCache<'call, V> { /* private fields */ }
Expand description

inferType, memoised on (environment, term) — the cache Lean has and we do not, with the half Lean does not need because its Expr is de Bruijn.

§Why the environment is in the key, and what happens to a cache without it

Term names its variables. Var("x") interns to ONE TermId whatever scope it occurs in, and Context::extend SHADOWS — so under x : Nat that id’s type is Nat and under x : Bool it is Bool. A cache keyed on the term ALONE hands the second scope the first scope’s answer: the kernel certifying a type the term does not have, from an optimisation. W_perf_extra::a_type_cache_keyed_on_the_term_alone_would_certify_the_wrong_type builds exactly that and watches it happen, and the same test watches this key refuse to.

Lean is not exposed to it: its Expr uses de Bruijn indices for bound variables and globally unique FVarIds for free ones, so two occurrences under different binders are DIFFERENT expressions, and inferType may cache on the expression alone. Porting that design across without porting the representation is the mistake this type exists to make unavailable.

§What it costs, and why it still pays

The invariant the key does NOT carry, and why it does not have to. A term’s type also depends on the GLOBAL environment — definitions, inductives, constructors, structures. That is not in the key because it cannot change: every kernel entry point takes &Context, and a shared reference can be neither displaced, nor extended in place, nor popped. One cache must therefore be threaded only through calls that share one &Context’s globals, which is exactly one outermost kernel call — the lifetime KernelCall gives it.

TermStore::env_for restricts the environment to a term’s FREE VARIABLES, which the store already knows in O(1) and hash-conses, so the key costs O(|free vars|) and not O(|context|). A CLOSED subterm — every type, every literal, every global’s body — keys on EnvId::EMPTY under every scope, so it is checked once for the whole call however many scopes it appears under. That is a_shared_subterm_is_checked_once_and_not_once_per_occurrence on the CHECKING side, by construction rather than by a special case.

Implementations§

Source§

impl<'call, V: Clone + Debug> TypeCache<'call, V>

Source

pub fn new(_call: &'call KernelCall) -> Self

An empty cache, borrowing the call it may not outlive.

Source

pub fn get(&mut self, env: EnvId, id: TermId) -> Option<&V>

The type decided for this term under this environment, if it has been asked.

Source

pub fn insert(&mut self, env: EnvId, id: TermId, ty: V)

Record a decision.

Recording the same key twice with a DIFFERENT answer is a bug in the caller — one environment, one term, one type — and it is the empirical tripwire for a cache that has outlived the environment it decided under. It is a debug_assert because it compares through Debug, which every kernel answer has and not every one has PartialEq; the test suite runs in debug, which is where a stale cache would be caught.

Source

pub fn len(&self) -> usize

How many judgements are cached.

Source

pub fn is_empty(&self) -> bool

Whether nothing has been decided.

Source

pub fn hits(&self) -> u64

Lookups answered from the cache — the number that says a shared subterm was checked once.

Source

pub fn misses(&self) -> u64

Lookups that had to be decided.

Source

pub fn clear(&mut self)

Forget every decision.

Auto Trait Implementations§

§

impl<'call, V> Freeze for TypeCache<'call, V>

§

impl<'call, V> RefUnwindSafe for TypeCache<'call, V>
where V: RefUnwindSafe,

§

impl<'call, V> !Send for TypeCache<'call, V>

§

impl<'call, V> !Sync for TypeCache<'call, V>

§

impl<'call, V> Unpin for TypeCache<'call, V>
where V: Unpin,

§

impl<'call, V> UnsafeUnpin for TypeCache<'call, V>

§

impl<'call, V> UnwindSafe for TypeCache<'call, V>
where V: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.