Skip to main content

bynk_check/
hints.rs

1//! v0.27 (ADR 0056): the inlay-hint sink.
2//!
3//! A curated set of inferred-type hints harvested from the checker as it
4//! computes each binding's type — `let` / `let <-` bindings and lambda
5//! parameters whose annotation is absent. The sink mirrors [`RefSink`]
6//! (`index.rs`): it is a `&mut` parameter threaded through the checker
7//! entry points, NOT part of the `Ok(TypedCommons)` payload `check_record`
8//! drops on error — so hints persist through a transient type error at
9//! every site the checker still reaches.
10//!
11//! Labels are pre-rendered (`": " + Ty::display()`) so no `Ty` leaks
12//! through the public surface; spans are bare byte offsets into the file
13//! the sink was attributed to via [`HintSink::enter_file`].
14//!
15//! [`RefSink`]: crate::index::RefSink
16
17use bynk_syntax::span::Span;
18use std::collections::HashMap;
19use std::path::{Path, PathBuf};
20
21/// v0.39 (ADR 0072): the inlay-hint kind, which drives the LSP rendering —
22/// anchor side, `InlayHintKind`, and padding.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum HintKind {
25    /// An inferred type, rendered after a name (`x`**`: Int`**, or a generic
26    /// call's `identity`**`[Int]`**). Anchored at the span's end.
27    Type,
28    /// A parameter name at a call argument (**`count:`** `5`). Anchored at the
29    /// argument span's start.
30    Parameter,
31}
32
33/// One inlay hint: a pre-rendered label at a span, plus its [`HintKind`].
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct Hint {
36    pub span: Span,
37    pub label: String,
38    pub kind: HintKind,
39}
40
41/// Project-relative source path → that file's hints, span-ordered.
42pub type FileHints = HashMap<PathBuf, Vec<Hint>>;
43
44/// Records inferred-type hints per file. A fresh sink records nothing
45/// until [`enter_file`](Self::enter_file) attributes it.
46#[derive(Debug, Default)]
47pub struct HintSink {
48    files: FileHints,
49    file: Option<PathBuf>,
50    /// Set for synthetic (toolchain-injected) and test/integration files:
51    /// hints are discarded — neither surfaces in an editor.
52    muted: bool,
53}
54
55impl HintSink {
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    /// Enter a per-file recording context.
61    pub fn enter_file(&mut self, file: &Path, muted: bool) {
62        self.file = Some(file.to_path_buf());
63        self.muted = muted;
64    }
65
66    /// Record an inferred-**type** hint at `span` (binding types, generic-call
67    /// type arguments). Dropped when muted or before any `enter_file`.
68    pub fn record(&mut self, span: Span, label: String) {
69        self.push(span, label, HintKind::Type);
70    }
71
72    /// v0.39 (ADR 0072): record a **parameter-name** hint at a call argument's
73    /// span (`count:` before `5`).
74    pub fn record_param(&mut self, span: Span, label: String) {
75        self.push(span, label, HintKind::Parameter);
76    }
77
78    fn push(&mut self, span: Span, label: String, kind: HintKind) {
79        if self.muted {
80            return;
81        }
82        let Some(file) = &self.file else {
83            return;
84        };
85        self.files
86            .entry(file.clone())
87            .or_default()
88            .push(Hint { span, label, kind });
89    }
90
91    /// Drain the recorded hints, each file's entries ordered by span start.
92    pub fn take_files(&mut self) -> FileHints {
93        let mut files = std::mem::take(&mut self.files);
94        for hints in files.values_mut() {
95            hints.sort_by_key(|h| (h.span.start, h.span.end));
96        }
97        files
98    }
99}