// The type checker's working state: what a typed node carries, and the tables
// names resolve through.
//
// Ported from wax/src/lib-wax/typing_env.ml. Split out of the checker itself so
// the lints and the suggestion pass can read the same state without depending
// on the 13k lines that build it.
///|
/// The same annotation before resolution, with the inference cells intact.
///
/// Resolution collapses distinctions the cells still hold: a flexible numeric
/// literal that has not picked a width, an unknown or unreachable type, an
/// inline anonymous composite type. The editor reads this form because a hover
/// wants to say `number` rather than the `i32` it would default to; the code
/// generator reads the resolved one because an opcode has to pick.
pub type InferredAnnotation = (
Array[@infer.Cell[@infer.InferredType]],
@basic.Location,
)
///|
/// What a resolved reference summarises, for a hover on a name that is not
/// itself an expression.
///
/// Kept as data rather than a rendered string: nothing is formatted until a
/// hover actually asks, so a plain check pays one boxing per reference and no
/// printing at all.
pub(all) enum HoverTarget {
/// A variable's type.
ValueType(@infer.InferredValType)
/// A referenced type's definition.
TypeDef(@ast.SubType)
}
///|
/// A resolved name or label reference: where it was used, where it was defined,
/// and what it resolves to.
///
/// There is more than one definition only under conditional compilation, where
/// a name may be declared in several mutually exclusive branches.
pub(all) struct Reference {
use_ : @basic.Location
definitions : Array[@basic.Location]
hover : HoverTarget?
}
///|
/// Where references are recorded for go-to-definition, or `None` outside the
/// editor -- so an ordinary compile pays nothing for the feature.
type ResolveSink = Array[Reference]?
///|
/// Is this a real source span, rather than a synthesized node's?
///
/// A synthesized node -- an interned function type looked up for a call, a
/// desugared construct -- carries the dummy span, whose offset is the `-1`
/// sentinel rather than `0`. Only genuine source references are recorded.
fn is_source(l : @basic.Location) -> Bool {
l.start.cnum >= 0
}
///|
fn same_span(a : @basic.Location, b : @basic.Location) -> Bool {
a.start.cnum == b.start.cnum && a.end.cnum == b.end.cnum
}
///|
/// The single inference cell an instruction leaves on the stack, or `None` if
/// it leaves none or several.
///
/// The error-free counterpart of the checker's own `expression_type`, which
/// reports "an expression is expected here" and yields an error cell. A lint or
/// a suggestion reads the cell when there is exactly one and stays silent
/// otherwise, rather than emitting the diagnostic the checker already owns.
pub fn[Info] expression_type_opt(
info : (Array[@infer.Cell[@infer.InferredType]], Info),
) -> @infer.Cell[@infer.InferredType]? {
let (cells, _) = info
if cells.length() == 1 {
Some(cells[0])
} else {
None
}
}
///|
/// The bottom reference type, nullable or not.
///
/// `None_` is a built-in abstract bottom heap type, so its internal form
/// carries no index into the type store and needs no context to build -- which
/// is what lets this be a plain value where the checker's own path threads one.
fn ref_none_valtype(nullable~ : Bool) -> @infer.InferredValType {
{
typ: Ref({ nullable, typ: None_ }),
internal: Ref({ nullable, typ: None_ }),
anon_comptype: None,
}
}
///|
/// The `exn` reference type: what an exception object is, and what the
/// instructions that carry one around take.
pub fn ref_exn_valtype(nullable~ : Bool) -> @infer.InferredValType {
{
typ: Ref({ nullable, typ: Exn }),
internal: Ref({ nullable, typ: Exn }),
anon_comptype: None,
}
}
///|
/// The concrete value type a cell stands for on its own, or `None` when it has
/// none yet.
///
/// A still-flexible literal takes its default width -- an integer or a bare
/// number becomes i32, a large one i64, a float f64. `Null` and `UnknownRef`
/// concretize to the nullable and non-nullable bottom reference respectively:
/// the latter because that is the type `null!` produced before `UnknownRef`
/// existed.
///
/// Pure and context-free, unlike the checker's own path, because the only
/// reference it can produce is the built-in bottom.
pub fn standalone_valtype(
ty : @infer.Cell[@infer.InferredType],
) -> @infer.InferredValType? {
match ty.get() {
Valtype(v) => Some(v)
Int | Number => Some(@infer.i32_valtype)
LargeInt => Some(@infer.i64_valtype)
Float => Some(@infer.f64_valtype)
Null => Some(ref_none_valtype(nullable=true))
UnknownRef => Some(ref_none_valtype(nullable=false))
Int8 | Int16 | Unknown | Error | Collecting(_) => None
}
}
///|
/// Record a punned struct-literal field's span -- the bare `x` standing for
/// `x: x`.
///
/// Such a span is both a field name and a variable use, so a rename has to
/// expand it (`x` becomes `x: new`) rather than replace it, and the editor
/// needs to know which spans those are.
pub fn record_pun(
sink : Array[@basic.Location]?,
name_info : @basic.Location,
) -> Unit {
if sink is Some(r) && is_source(name_info) {
r.push(name_info)
}
}
///|
/// Record a struct field access, for member completion: the field's span --
/// possibly partial, since the cursor may be mid-word -- and what it is on.
pub fn record_members(
sink : Array[(@basic.Location, @members.MemberReceiver)]?,
field : @basic.Location,
receiver : @members.MemberReceiver,
) -> Unit {
if sink is Some(r) && is_source(field) {
r.push((field, receiver))
}
}
///|
/// Record a use and the definitions it binds to.
///
/// Synthesized definitions are dropped, and so is the self-reference a name's
/// own declaration makes when it looks itself up: go-to-definition on a
/// definition has nowhere useful to go.
pub fn record_reference(
sink : ResolveSink,
use_ : @basic.Location,
definitions : Array[@basic.Location],
hover? : HoverTarget? = None,
) -> Unit {
guard sink is Some(r) && is_source(use_) else { return }
let kept = definitions.filter(d => is_source(d) && !same_span(d, use_))
if !kept.is_empty() {
r.push({ use_, definitions: kept, hover })
}
}