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}