Skip to main content

Module facts

Module facts 

Source
Expand description

What the checker worked out about each expression.

The type checker settles a type for every expression it walks and, until now, threw each one away as soon as the surrounding form had been checked against it. Everything downstream that needed one had to work it out again — and a pass that walks a tree without the checker’s tables cannot, so it guessed from the shape of the source instead. ADR 0019 states the rule this module exists to make keepable: the lowering reads the checker’s answers rather than recomputing them, so the two cannot disagree.

§Recording is not deciding

Nothing here participates in checking. A fact is written after the checker has settled it and is read by nobody during the walk, so adding one changes no diagnostic. That is the property this table is worth having only if it keeps.

§Keys are dense integers, so a lookup is a load

An expression is named by the file it was parsed from and its ExprId, which cove_syntax::number::number_unit hands out as 0..n over one file with no gaps. Both halves are therefore an index into a Vec rather than a hash of anything, which is what lets the checker afford a push per expression.

§An unknown is an answer

Facts::ty answers None only for an expression the checker never walked. An expression it walked and could say nothing about answers Some(Ty::Unknown(..)), because “the checker abstained” and “I never asked” are different facts and a consumer specialising on one must not act on the other.

§A name a call resolves is not an expression the checker types

The table is total over a function’s expressions with one exception, and it is the checker’s shape rather than an omission here. A callee is walked only when the call goes through a value: f(1) where f is a binding evaluates f, and so does a call through a field holding a closure. A callee that instead names a declaration — a function, a struct being initialized, an enum case, a type’s associated function, or a method reached through a receiver — is resolved against the checker’s tables and never given a type, because several of those have none to be given: Point in Point(x: 0.0) names a type, not a value.

So ty answers None for those, and that answer carries the distinction rather than losing it: a callee with a recorded type is a call through a value, and a callee without one is a call to a declaration — which Facts::target then names.

§A declaration’s boundary is a fact too, and it is a small one

An expression is not the only thing the checker settles. It also resolves every declaration’s signature — what each parameter is, what the receiver is, what comes back — and a consumer that has to know where a parameter lives, or which stack an answer comes back on, is asking about the signature rather than about any expression inside the body. Re-deriving it from the source would be the same mistake in a new place: a -> module.Thing written in one module and read in another is a name whose meaning only the checker holds.

Facts::signature is that table, and it is keyed differently from the expression tables on purpose — see Facts::signature for why a hash is the right shape here and the wrong one there.

Structs§

Facts
What the checker worked out about each expression.
MethodTarget
The declaration a call resolved to, named the way the package names it.
Signature
A declaration’s boundary, as the checker resolved it for that body.