Skip to main content

decl_lang/
lsp.rs

1//! decl-lsp (docs/tooling/03_lsp.md): the language server over stdio — a
2//! port of the reference implementation's lsp.ts. Every answer comes from
3//! the same checker, inference, and engine as the command line, driven
4//! through the session object (session.rs) with the open buffers
5//! overriding the disk; positions come from the source ranges every AST
6//! node carries, and the types and resolutions recorded while the checker
7//! runs (infer.rs hooks). Messages are handled strictly in order, and the
8//! server exits when its input closes.
9use crate::ast::*;
10use crate::checker::{check_module, CheckHooks};
11use crate::fmt::{format, u16len};
12use crate::infer::{resolve_in, std_path, type_text, Target, Ty, STD};
13use crate::module::Module;
14use crate::parse::parse_source;
15use crate::semantics::{
16    is_rec, json_str, parse_path, read_json, rec_members, seg_text, Diag, MKind, RTk, Seg,
17    SlotState, Value, RT,
18};
19use crate::session::{fmt_diag, BindSource, Mode, Op, Run, Session};
20use regex::Regex;
21use std::cell::RefCell;
22use std::collections::HashMap;
23use std::io::{Read, Write};
24use std::path::{Path, PathBuf};
25use std::rc::Rc;
26
27// ---------------- JSON out (key order kept) ----------------
28#[derive(Clone)]
29enum J {
30    Null,
31    Bool(bool),
32    Num(i64),
33    Str(String),
34    Arr(Vec<J>),
35    Obj(Vec<(String, J)>),
36}
37impl J {
38    fn obj(pairs: Vec<(&str, J)>) -> J {
39        J::Obj(pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
40    }
41    fn s(v: impl Into<String>) -> J {
42        J::Str(v.into())
43    }
44    fn text(&self) -> String {
45        match self {
46            J::Null => "null".into(),
47            J::Bool(b) => b.to_string(),
48            J::Num(n) => n.to_string(),
49            J::Str(s) => json_str(s),
50            J::Arr(items) => format!(
51                "[{}]",
52                items.iter().map(J::text).collect::<Vec<_>>().join(",")
53            ),
54            J::Obj(es) => format!(
55                "{{{}}}",
56                es.iter()
57                    .map(|(k, v)| format!("{}:{}", json_str(k), v.text()))
58                    .collect::<Vec<_>>()
59                    .join(",")
60            ),
61        }
62    }
63}
64
65// ---------------- JSON in (over the runtime's Value) ----------------
66fn get<'a>(v: &'a Value, key: &str) -> Option<&'a Value> {
67    match v {
68        Value::JObj(es) => es.iter().find(|(k, _)| k == key).map(|(_, x)| x),
69        _ => None,
70    }
71}
72fn as_str(v: Option<&Value>) -> Option<&str> {
73    match v {
74        Some(Value::Str(s)) => Some(s.as_str()),
75        _ => None,
76    }
77}
78fn as_usize(v: Option<&Value>) -> Option<usize> {
79    match v {
80        Some(Value::Int(i)) => i.to_string().parse().ok(),
81        Some(Value::Float(f)) => Some(*f as usize),
82        _ => None,
83    }
84}
85fn as_bool(v: Option<&Value>) -> Option<bool> {
86    match v {
87        Some(Value::Bool(b)) => Some(*b),
88        _ => None,
89    }
90}
91fn json_of(v: &Value) -> String {
92    match v {
93        Value::Null | Value::Undef | Value::Absent => "null".into(),
94        Value::Bool(b) => b.to_string(),
95        Value::Int(i) => i.to_string(),
96        Value::Float(f) => crate::semantics::js_num_str(*f),
97        Value::Str(s) => json_str(s),
98        Value::JArr(items) => format!(
99            "[{}]",
100            items.iter().map(json_of).collect::<Vec<_>>().join(",")
101        ),
102        Value::JObj(es) => format!(
103            "{{{}}}",
104            es.iter()
105                .map(|(k, x)| format!("{}:{}", json_str(k), json_of(x)))
106                .collect::<Vec<_>>()
107                .join(",")
108        ),
109        other => json_str(&format!("{other:?}")),
110    }
111}
112
113// ---------------- transport ----------------
114fn send(body: &str) {
115    let mut out = std::io::stdout().lock();
116    let _ = write!(out, "Content-Length: {}\r\n\r\n{}", body.len(), body);
117    let _ = out.flush();
118}
119fn reply(id: Option<&Value>, result: J) {
120    if let Some(id) = id {
121        send(&format!(
122            "{{\"jsonrpc\":\"2.0\",\"id\":{},\"result\":{}}}",
123            json_of(id),
124            result.text()
125        ));
126    }
127}
128fn notify(method: &str, params: J) {
129    send(&format!(
130        "{{\"jsonrpc\":\"2.0\",\"method\":{},\"params\":{}}}",
131        json_str(method),
132        params.text()
133    ));
134}
135
136// ---------------- documents ----------------
137pub fn path_of(uri: &str) -> PathBuf {
138    let raw = uri.strip_prefix("file://").unwrap_or(uri);
139    let raw = raw.split(['?', '#']).next().unwrap_or(raw);
140    let bytes = raw.as_bytes();
141    let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
142    let mut i = 0;
143    while i < bytes.len() {
144        if bytes[i] == b'%' && i + 2 < bytes.len() && i + 2 < bytes.len() {
145            if let Ok(v) = u8::from_str_radix(&raw[i + 1..i + 3], 16) {
146                out.push(v);
147                i += 3;
148                continue;
149            }
150        }
151        out.push(bytes[i]);
152        i += 1;
153    }
154    PathBuf::from(String::from_utf8_lossy(&out).to_string())
155}
156pub fn uri_of(path: &Path) -> String {
157    let s = path.to_string_lossy();
158    let mut out = String::from("file://");
159    for b in s.bytes() {
160        if b.is_ascii_alphanumeric() || b"-._~/".contains(&b) {
161            out.push(b as char);
162        } else {
163            out.push_str(&format!("%{:02X}", b));
164        }
165    }
166    out
167}
168
169#[derive(Clone, Copy)]
170struct Pos {
171    line: usize,
172    character: usize,
173}
174fn range_json(l: Loc) -> J {
175    J::obj(vec![
176        (
177            "start",
178            J::obj(vec![
179                ("line", J::Num(l.sl as i64)),
180                ("character", J::Num(l.sc as i64)),
181            ]),
182        ),
183        (
184            "end",
185            J::obj(vec![
186                ("line", J::Num(l.el as i64)),
187                ("character", J::Num(l.ec as i64)),
188            ]),
189        ),
190    ])
191}
192fn contains(l: Loc, p: Pos) -> bool {
193    (l.sl < p.line || (l.sl == p.line && l.sc <= p.character))
194        && (p.line < l.el || (p.line == l.el && p.character <= l.ec))
195}
196fn span(l: Loc) -> usize {
197    (l.el - l.sl) * 100000 + l.ec.saturating_sub(l.sc)
198}
199
200// ---- columns: UTF-16 units (the protocol's, and the reference's) <-> bytes ----
201fn u16_col(line: &str, byte: usize) -> usize {
202    u16len(line.get(..byte).unwrap_or(line))
203}
204fn byte_col(line: &str, col: usize) -> usize {
205    let mut units = 0;
206    for (i, ch) in line.char_indices() {
207        if units >= col {
208            return i;
209        }
210        units += ch.len_utf16();
211    }
212    line.len()
213}
214/// JS `line.indexOf(needle, from)` in UTF-16 columns
215fn find16(line: &str, needle: &str, from: usize) -> Option<usize> {
216    let b = byte_col(line, from);
217    line.get(b..)?.find(needle).map(|i| u16_col(line, b + i))
218}
219/// JS `line.lastIndexOf(needle, from)` in UTF-16 columns
220fn rfind16(line: &str, needle: &str, from: usize) -> Option<usize> {
221    let b = byte_col(line, from);
222    let mut end = (b + needle.len()).min(line.len());
223    while !line.is_char_boundary(end) {
224        end -= 1;
225    }
226    line[..end].rfind(needle).map(|i| u16_col(line, i))
227}
228fn slice16(line: &str, a: usize, b: usize) -> &str {
229    let (ba, bb) = (byte_col(line, a), byte_col(line, b));
230    line.get(ba..bb.max(ba)).unwrap_or("")
231}
232
233// ---------------- analysis ----------------
234// one analysis per open document: its universe (the document as entry),
235// and for every module the checker's tables — the type of every
236// expression and what every name denotes
237struct Tables {
238    types: HashMap<usize, Ty>,
239    res: HashMap<usize, Option<Target>>,
240}
241fn key_of(e: &Rc<Expr>) -> usize {
242    Rc::as_ptr(e) as *const u8 as usize
243}
244struct Analysis {
245    text: String,
246    session: Session,
247    run: Run,
248    tables: RefCell<HashMap<PathBuf, Rc<Tables>>>,
249}
250
251struct State {
252    /// uri -> text, in open order
253    docs: Vec<(String, String)>,
254    /// path -> text (open buffers override the disk)
255    overlay: HashMap<PathBuf, String>,
256    analyses: HashMap<String, Rc<Analysis>>,
257    /// the last analysis of a document that parsed (completion while typing)
258    last_good: HashMap<String, Rc<Analysis>>,
259    /// `decl.inputs`: input name -> document file
260    inputs: Vec<(String, String)>,
261    /// `decl.inlayHints.*`
262    hint_types: bool,
263    hint_parameter_names: bool,
264    hint_values: bool,
265    hint_units: bool,
266    hint_context_variables: bool,
267    /// a client that accepts work-done progress sees an analysis as a progress item (03_lsp.md §14)
268    progress_supported: bool,
269    progress_seq: usize,
270}
271impl Default for State {
272    fn default() -> Self {
273        State {
274            docs: vec![],
275            overlay: HashMap::new(),
276            analyses: HashMap::new(),
277            last_good: HashMap::new(),
278            inputs: vec![],
279            hint_types: true,
280            hint_parameter_names: true,
281            hint_values: false,
282            hint_units: true,
283            hint_context_variables: false,
284            progress_supported: false,
285            progress_seq: 0,
286        }
287    }
288}
289impl State {
290    fn set(&mut self, uri: &str, text: String) {
291        if let Some(d) = self.docs.iter_mut().find(|(u, _)| u == uri) {
292            d.1 = text.clone();
293        } else {
294            self.docs.push((uri.to_string(), text.clone()));
295        }
296        self.overlay.insert(path_of(uri), text);
297    }
298    fn text(&self, uri: &str) -> Option<&String> {
299        self.docs.iter().find(|(u, _)| u == uri).map(|(_, t)| t)
300    }
301    fn analysis_of(&mut self, uri: &str) -> Option<Rc<Analysis>> {
302        let text = self.text(uri)?.clone();
303        if let Some(a) = self.analyses.get(uri) {
304            if a.text == text {
305                return Some(a.clone());
306            }
307        }
308        let path = path_of(uri);
309        if !parse_source(&text).errors.is_empty() {
310            return None;
311        }
312        let session = Session::with_overlay(Some(&path.to_string_lossy()), Some(&self.overlay));
313        let title = format!(
314            "Decl: evaluating {}",
315            path.file_name()
316                .map(|f| f.to_string_lossy().to_string())
317                .unwrap_or_default()
318        );
319        let token = if self.progress_supported {
320            self.progress_seq += 1;
321            let token = format!("decl-{}", self.progress_seq);
322            // the request's id is the sequence number: an integer, which every client accepts (Neovim rejects a string)
323            send(&format!("{{\"jsonrpc\":\"2.0\",\"id\":{},\"method\":\"window/workDoneProgress/create\",\"params\":{{\"token\":{}}}}}", self.progress_seq, json_str(&token)));
324            notify(
325                "$/progress",
326                J::obj(vec![
327                    ("token", J::s(token.clone())),
328                    (
329                        "value",
330                        J::obj(vec![
331                            ("kind", J::s("begin")),
332                            ("title", J::s(title)),
333                            ("cancellable", J::Bool(false)),
334                        ]),
335                    ),
336                ]),
337            );
338            Some(token)
339        } else {
340            None
341        };
342        let run = session.run(Mode::Full);
343        if let Some(token) = token {
344            notify(
345                "$/progress",
346                J::obj(vec![
347                    ("token", J::s(token)),
348                    ("value", J::obj(vec![("kind", J::s("end"))])),
349                ]),
350            );
351        }
352        let a = Rc::new(Analysis {
353            text,
354            session,
355            run,
356            tables: RefCell::new(HashMap::new()),
357        });
358        self.analyses.insert(uri.to_string(), a.clone());
359        self.last_good.insert(uri.to_string(), a.clone());
360        Some(a)
361    }
362}
363fn tables_of(a: &Analysis, m: &Rc<Module>) -> Rc<Tables> {
364    if let Some(t) = a.tables.borrow().get(&m.path) {
365        return t.clone();
366    }
367    let types: Rc<RefCell<HashMap<usize, Ty>>> = Rc::new(RefCell::new(HashMap::new()));
368    let res: Rc<RefCell<HashMap<usize, Option<Target>>>> = Rc::new(RefCell::new(HashMap::new()));
369    let hooks = CheckHooks {
370        record: Some({
371            let types = types.clone();
372            Rc::new(move |e: &Rc<Expr>, ty: &Ty| {
373                types.borrow_mut().insert(key_of(e), ty.clone());
374            })
375        }),
376        resolve_hook: Some({
377            let res = res.clone();
378            Rc::new(move |e: &Rc<Expr>, target: Option<Target>| {
379                res.borrow_mut().insert(key_of(e), target);
380            })
381        }),
382    };
383    check_module(&m.decls, Some(m.env.clone()), Some(&hooks));
384    let t = Rc::new(Tables {
385        types: types.borrow().clone(),
386        res: res.borrow().clone(),
387    });
388    a.tables.borrow_mut().insert(m.path.clone(), t.clone());
389    t
390}
391fn module_of(a: &Analysis, path: &Path) -> Option<Rc<Module>> {
392    a.run.modules.iter().find(|m| m.path == path).cloned()
393}
394fn text_of(st: &State, m: &Module) -> String {
395    st.overlay
396        .get(&m.path)
397        .cloned()
398        .unwrap_or_else(|| read_text(&m.path))
399}
400fn read_text(path: &Path) -> String {
401    std::fs::read_to_string(path).unwrap_or_default()
402}
403
404// ---------------- diagnostics ----------------
405fn anchor_for(src: &str, message: &str) -> Loc {
406    let names = Regex::new(r"[A-Za-z_][A-Za-z0-9_.]*").unwrap();
407    let lines: Vec<&str> = src.split('\n').collect();
408    for m in names.find_iter(message) {
409        let n = m.as_str();
410        if [
411            "error", "in", "the", "a", "is", "not", "std", "module", "import", "type", "name",
412        ]
413        .contains(&n)
414        {
415            continue;
416        }
417        let re = Regex::new(&format!(r"\b{}\b", regex::escape(n))).unwrap();
418        for (i, line) in lines.iter().enumerate() {
419            if let Some(mm) = re.find(line) {
420                let a = u16_col(line, mm.start());
421                return Loc {
422                    sl: i,
423                    sc: a,
424                    el: i,
425                    ec: a + u16len(n),
426                };
427            }
428        }
429    }
430    Loc {
431        sl: 0,
432        sc: 0,
433        el: 0,
434        ec: u16len(lines.first().copied().unwrap_or("")).max(1),
435    }
436}
437// the source position of a document path: the literal the path leads to
438// in the root's declaration, or the deepest literal on the way
439fn loc_of_path(decls: &[Decl], segs: &[Seg]) -> Option<Loc> {
440    let root = seg_text(segs.first()?);
441    let decl = decls.iter().find(|d| matches!(&d.body, DeclBody::Output { name, .. } | DeclBody::Input { name, .. } if *name == root))?;
442    let mut best = decl.loc?;
443    let mut e: Option<Rc<Expr>> = match &decl.body {
444        DeclBody::Output { expr, .. } => Some(expr.clone()),
445        DeclBody::Input { fallback, .. } => fallback.clone(),
446        _ => None,
447    };
448    for s in &segs[1..] {
449        let Some(mut cur) = e.clone() else { break };
450        if let Expr::Paren(x) = &*cur {
451            cur = x.clone();
452        }
453        let next: Option<Rc<Expr>> = match (&*cur, s) {
454            (Expr::Obj(entries), Seg::Name(k)) | (Expr::Obj(entries), Seg::Key(k)) => entries
455                .iter()
456                .find(|(kk, _)| kk == k)
457                .map(|(_, v)| v.clone()),
458            (Expr::Arr(items), Seg::Idx(i)) => items.get(*i).map(|(_, v)| v.clone()),
459            (Expr::With { base, .. }, _) => {
460                e = Some(base.clone());
461                continue;
462            }
463            _ => None,
464        };
465        let Some(next) = next else { break };
466        if let Some(l) = expr_loc(&next) {
467            best = l;
468        }
469        e = Some(next);
470    }
471    Some(best)
472}
473fn severity_of(s: &str) -> i64 {
474    match s {
475        "error" => 1,
476        "warning" => 2,
477        _ => 3,
478    }
479}
480fn diag_json(loc: Loc, d: &Diag) -> J {
481    let mut item = vec![
482        ("range", range_json(loc)),
483        ("severity", J::Num(severity_of(&d.severity))),
484        ("source", J::s("decl")),
485    ];
486    if d.code.is_some() || d.id.is_some() {
487        item.push((
488            "code",
489            J::s(d.id.clone().or_else(|| d.code.clone()).unwrap_or_default()),
490        ));
491    }
492    item.push((
493        "message",
494        J::s(if d.path.is_empty() {
495            d.message.clone()
496        } else {
497            format!("{} (at {})", d.message, d.path)
498        }),
499    ));
500    J::obj(item)
501}
502
503fn analyze(st: &mut State, uri: &str) {
504    let src = st.text(uri).cloned().unwrap_or_default();
505    let path = path_of(uri);
506    let mut out: Vec<J> = vec![];
507    let parsed = parse_source(&src);
508    if !parsed.errors.is_empty() {
509        for (row, col) in &parsed.errors {
510            out.push(J::obj(vec![
511                (
512                    "range",
513                    range_json(Loc {
514                        sl: *row,
515                        sc: *col,
516                        el: *row,
517                        ec: col + 1,
518                    }),
519                ),
520                ("severity", J::Num(1)),
521                ("source", J::s("decl")),
522                ("code", J::s("E2001")),
523                ("message", J::s("syntax error")),
524            ]));
525        }
526    } else if let Some(a) = st.analysis_of(uri) {
527        let r = &a.run;
528        for d in &r.load_diags {
529            // a loading problem is anchored to the import it concerns when one is named
530            let imp = parsed.decls.iter().find(|x| match &x.body {
531                DeclBody::Import { from, .. } | DeclBody::ReExport { from, .. } => {
532                    x.loc.is_some()
533                        && d.message.contains(
534                            from.strip_prefix("./")
535                                .unwrap_or(from)
536                                .strip_suffix(".decl")
537                                .unwrap_or(from.strip_prefix("./").unwrap_or(from)),
538                        )
539                }
540                _ => false,
541            });
542            out.push(diag_json(
543                imp.and_then(|x| x.loc)
544                    .unwrap_or_else(|| anchor_for(&src, &d.message)),
545                d,
546            ));
547        }
548        for (file, d) in &r.checks {
549            // a borrowed Path against the PathBuf: no allocation, and std's oldest comparison impl
550            if std::path::Path::new(file) != path {
551                continue;
552            }
553            out.push(diag_json(
554                d.loc.unwrap_or_else(|| anchor_for(&src, &d.message)),
555                d,
556            ));
557        }
558        for d in &r.diags {
559            if d.severity == "information" {
560                continue;
561            }
562            let segs = if d.path.is_empty() {
563                None
564            } else {
565                parse_path(&d.path, "").ok()
566            };
567            let Some(loc) = segs.and_then(|s| loc_of_path(&parsed.decls, &s)) else {
568                continue;
569            }; // a root declared elsewhere: its own module's business
570            out.push(diag_json(loc, d));
571        }
572    }
573    notify(
574        "textDocument/publishDiagnostics",
575        J::obj(vec![("uri", J::s(uri)), ("diagnostics", J::Arr(out))]),
576    );
577}
578
579// ---------------- positions -> nodes ----------------
580#[derive(Clone)]
581enum NodeRef<'a> {
582    Decl(&'a Decl),
583    Member(&'a MemberAst),
584    Type(&'a TypeAst),
585    Expr(&'a Rc<Expr>),
586}
587impl<'a> NodeRef<'a> {
588    fn loc(&self) -> Option<Loc> {
589        match self {
590            NodeRef::Decl(d) => d.loc,
591            NodeRef::Member(m) => m.loc(),
592            NodeRef::Type(t) => t.loc(),
593            NodeRef::Expr(e) => expr_loc(e),
594        }
595    }
596}
597struct Hit<'a> {
598    node: NodeRef<'a>,
599    loc: Loc,
600    parents: Vec<NodeRef<'a>>,
601}
602struct Finder<'a> {
603    pos: Pos,
604    best: Option<Hit<'a>>,
605}
606impl<'a> Finder<'a> {
607    // the reference visits object values in key order and keeps the
608    // innermost node containing the position (a later equal span wins)
609    fn enter(&mut self, node: NodeRef<'a>, parents: &[NodeRef<'a>]) -> Vec<NodeRef<'a>> {
610        let own = node.loc().filter(|l| contains(*l, self.pos));
611        if let Some(l) = own {
612            if self
613                .best
614                .as_ref()
615                .map(|b| span(l) <= span(b.loc))
616                .unwrap_or(true)
617            {
618                self.best = Some(Hit {
619                    node: node.clone(),
620                    loc: l,
621                    parents: parents.to_vec(),
622                });
623            }
624            let mut p = parents.to_vec();
625            p.push(node);
626            p
627        } else {
628            parents.to_vec()
629        }
630    }
631    fn decls(&mut self, decls: &'a [Decl]) {
632        for d in decls {
633            self.decl(d, &[]);
634        }
635    }
636    fn decl(&mut self, d: &'a Decl, parents: &[NodeRef<'a>]) {
637        let p = self.enter(NodeRef::Decl(d), parents);
638        match &d.body {
639            DeclBody::Type {
640                params, ty, tail, ..
641            } => {
642                for pr in params {
643                    if let Some(t) = &pr.ty {
644                        self.ty(t, &p);
645                    }
646                }
647                self.ty(ty, &p);
648                if let Some(t) = tail {
649                    self.tail(t, &p);
650                }
651            }
652            DeclBody::Const { ty, expr, .. } => {
653                if let Some(t) = ty {
654                    self.ty(t, &p);
655                }
656                self.expr(expr, &p);
657            }
658            DeclBody::Func {
659                params, ret, body, ..
660            } => {
661                for pr in params {
662                    if let Some(t) = &pr.ty {
663                        self.ty(t, &p);
664                    }
665                }
666                if let Some(t) = ret {
667                    self.ty(t, &p);
668                }
669                self.expr(body, &p);
670            }
671            DeclBody::Output { ty, expr, .. } => {
672                self.ty(ty, &p);
673                self.expr(expr, &p);
674            }
675            DeclBody::Input { ty, fallback, .. } => {
676                self.ty(ty, &p);
677                if let Some(f) = fallback {
678                    self.expr(f, &p);
679                }
680            }
681            DeclBody::Diagnostic {
682                params, template, ..
683            } => {
684                for pr in params {
685                    if let Some(t) = &pr.ty {
686                        self.ty(t, &p);
687                    }
688                }
689                self.template(template, &p);
690            }
691            DeclBody::Unit {
692                factor: Some(f), ..
693            } => {
694                self.expr(f, &p);
695            }
696            DeclBody::Unit { factor: None, .. } => {}
697            DeclBody::Dimension { .. } | DeclBody::Import { .. } | DeclBody::ReExport { .. } => {}
698        }
699    }
700    fn tail(&mut self, t: &'a Tail, p: &Vec<NodeRef<'a>>) {
701        match t {
702            Tail::Inline { template, .. } => self.template(template, p),
703            Tail::Ref { args, .. } => {
704                for a in args {
705                    self.expr(a, p);
706                }
707            }
708        }
709    }
710    fn template(&mut self, parts: &'a [TPart], p: &Vec<NodeRef<'a>>) {
711        for part in parts {
712            if let TPart::Expr(x) = part {
713                self.expr(x, p);
714            }
715        }
716    }
717    fn ty(&mut self, t: &'a TypeAst, parents: &[NodeRef<'a>]) {
718        let p = self.enter(NodeRef::Type(t), parents);
719        match t {
720            TypeAst::Record { members, .. } => {
721                for m in members {
722                    self.member(m, &p);
723                }
724            }
725            TypeAst::Map { key, val, .. } => {
726                self.ty(key, &p);
727                self.ty(val, &p);
728            }
729            TypeAst::Array { elem, .. } => self.ty(elem, &p),
730            TypeAst::Union { arms, .. } | TypeAst::Isect { arms, .. } => {
731                for a in arms {
732                    self.ty(a, &p);
733                }
734            }
735            TypeAst::Func { params, ret, .. } => {
736                for a in params {
737                    self.ty(a, &p);
738                }
739                self.ty(ret, &p);
740            }
741            TypeAst::Named {
742                args, preds, ext, ..
743            } => {
744                for a in args {
745                    self.ty(a, &p);
746                }
747                for x in preds.iter().flatten() {
748                    self.expr(x, &p);
749                }
750                if let Some(x) = ext {
751                    self.ty(x, &p);
752                }
753            }
754            TypeAst::Prim { .. }
755            | TypeAst::Lit { .. }
756            | TypeAst::Range { .. }
757            | TypeAst::Pattern { .. } => {}
758        }
759    }
760    fn member(&mut self, m: &'a MemberAst, parents: &[NodeRef<'a>]) {
761        let p = self.enter(NodeRef::Member(m), parents);
762        match m {
763            MemberAst::Value { ty, dflt, .. } => {
764                self.ty(ty, &p);
765                if let Some(d) = dflt {
766                    self.expr(d, &p);
767                }
768            }
769            MemberAst::Derived { ty, expr, .. } => {
770                if let Some(t) = ty {
771                    self.ty(t, &p);
772                }
773                self.expr(expr, &p);
774            }
775            MemberAst::Context { ty, .. } => self.ty(ty, &p),
776            MemberAst::Assert { cond, tail, .. } => {
777                self.expr(cond, &p);
778                if let Some(t) = tail {
779                    self.tail(t, &p);
780                }
781            }
782            MemberAst::When { cond, body, .. } => {
783                self.expr(cond, &p);
784                for b in body {
785                    self.member(b, &p);
786                }
787            }
788        }
789    }
790    fn expr(&mut self, e: &'a Rc<Expr>, parents: &[NodeRef<'a>]) {
791        let p = self.enter(NodeRef::Expr(e), parents);
792        match &**e {
793            Expr::Template(parts) => self.template(parts, &p),
794            Expr::Obj(entries) => {
795                for (_, v) in entries {
796                    self.expr(v, &p);
797                }
798            }
799            Expr::Arr(items) => {
800                for (_, v) in items {
801                    self.expr(v, &p);
802                }
803            }
804            Expr::Comp { head, clauses } => {
805                self.expr(head, &p);
806                self.clauses(clauses, &p);
807            }
808            Expr::MapComp { key, val, clauses } => {
809                self.expr(key, &p);
810                self.expr(val, &p);
811                self.clauses(clauses, &p);
812            }
813            Expr::Bin { l, r, .. } => {
814                self.expr(l, &p);
815                self.expr(r, &p);
816            }
817            Expr::Un { x, .. } | Expr::Paren(x) => self.expr(x, &p),
818            Expr::If { c, t, f } => {
819                self.expr(c, &p);
820                self.expr(t, &p);
821                self.expr(f, &p);
822            }
823            Expr::Lambda { body, .. } => self.expr(body, &p),
824            Expr::Call { fun, args } => {
825                self.expr(fun, &p);
826                for a in args {
827                    self.expr(a, &p);
828                }
829            }
830            Expr::Member { x, .. } => self.expr(x, &p),
831            Expr::Index { x, i } => {
832                self.expr(x, &p);
833                self.expr(i, &p);
834            }
835            Expr::With { base, patch } => {
836                self.expr(base, &p);
837                self.expr(patch, &p);
838            }
839            Expr::Match { subject, arms } => {
840                self.expr(subject, &p);
841                for a in arms {
842                    if let Some(t) = &a.ty {
843                        self.ty(t, &p);
844                    }
845                    self.expr(&a.body, &p);
846                }
847            }
848            Expr::Lit(_)
849            | Expr::UnitLit { .. }
850            | Expr::Name(_)
851            | Expr::Ctx(_)
852            | Expr::Referrers { .. }
853            | Expr::Pattern(_) => {}
854        }
855    }
856    fn clauses(&mut self, clauses: &'a [ForClause], p: &Vec<NodeRef<'a>>) {
857        for c in clauses {
858            self.expr(&c.iter, p);
859            for f in &c.filters {
860                self.expr(f, p);
861            }
862        }
863    }
864}
865fn node_at<'a>(decls: &'a [Decl], pos: Pos) -> Option<Hit<'a>> {
866    let mut f = Finder { pos, best: None };
867    f.decls(decls);
868    f.best
869}
870
871fn decl_kind(d: &Decl) -> &'static str {
872    match &d.body {
873        DeclBody::Type { .. } => "type",
874        DeclBody::Const { .. } => "const",
875        DeclBody::Func { .. } => "func",
876        DeclBody::Output { .. } => "output",
877        DeclBody::Input { .. } => "input",
878        DeclBody::Diagnostic { .. } => "diagnostic",
879        DeclBody::Dimension { .. } => "dimension",
880        DeclBody::Unit { .. } => "unit",
881        DeclBody::Import { .. } => "import",
882        DeclBody::ReExport { .. } => "re_export",
883    }
884}
885
886// the range of a declaration's name token (the declaration site)
887fn name_range(text: &str, decl: &Decl, name: &str) -> Loc {
888    let loc = decl.loc.unwrap();
889    let lines: Vec<&str> = text.split('\n').collect();
890    let re = Regex::new(&format!(r"\b{}\b", regex::escape(name))).unwrap();
891    let mut i = loc.sl;
892    while i <= loc.el && i < lines.len() {
893        let from = if i == loc.sl {
894            byte_col(lines[i], loc.sc)
895        } else {
896            0
897        };
898        if let Some(m) = re.find_at(lines[i], from) {
899            let a = u16_col(lines[i], m.start());
900            return Loc {
901                sl: i,
902                sc: a,
903                el: i,
904                ec: a + u16len(name),
905            };
906        }
907        i += 1;
908    }
909    loc
910}
911fn member_range(text: &str, member: &MemberAst, name: &str) -> Loc {
912    let loc = member.loc().unwrap();
913    let line = text.split('\n').nth(loc.sl).unwrap_or("");
914    match find16(line, name, loc.sc) {
915        Some(i) => Loc {
916            sl: loc.sl,
917            sc: i,
918            el: loc.sl,
919            ec: i + u16len(name),
920        },
921        None => loc,
922    }
923}
924
925// ---------------- what is under the cursor ----------------
926#[derive(Clone)]
927struct Site {
928    kind: String,
929    module: Rc<Module>,
930    decl: Option<usize>, // the declaration's identity (its address)
931    decl_loc: Option<Loc>,
932    member_loc: Option<Loc>,
933    range: Loc,
934    name: String,
935}
936fn decl_id(d: &Decl) -> usize {
937    d as *const Decl as usize
938}
939
940// the declaration a target denotes, as a site in its module
941fn site_of_target(st: &State, a: &Analysis, t: Option<&Target>) -> Option<Site> {
942    let t = t?;
943    let env = t.env.as_ref()?;
944    let m = a
945        .run
946        .modules
947        .iter()
948        .find(|x| Rc::ptr_eq(&x.env, env))?
949        .clone();
950    let text = text_of(st, &m);
951    let decl = m.decls.iter().find(|d| {
952        d.name() == Some(t.name.as_str())
953            && d.loc.is_some()
954            && !matches!(d.body, DeclBody::Import { .. })
955    })?;
956    Some(Site {
957        kind: decl_kind(decl).to_string(),
958        module: m.clone(),
959        decl: Some(decl_id(decl)),
960        decl_loc: decl.loc,
961        member_loc: None,
962        range: name_range(&text, decl, &t.name),
963        name: t.name.clone(),
964    })
965}
966fn rec_name(rt: Option<&RT>) -> Option<String> {
967    let rt = rt?;
968    match &rt.k {
969        RTk::Rec(_) => rt.name.borrow().clone(),
970        RTk::Pred { base, .. } => base.name.borrow().clone(),
971        _ => None,
972    }
973}
974fn record_members(ty: &TypeAst) -> &[MemberAst] {
975    match ty {
976        TypeAst::Record { members, .. } => members,
977        TypeAst::Named { ext: Some(x), .. } => match &**x {
978            TypeAst::Record { members, .. } => members,
979            _ => &[],
980        },
981        _ => &[],
982    }
983}
984fn member_site(
985    st: &State,
986    a: &Analysis,
987    m: &Rc<Module>,
988    rt: Option<&RT>,
989    member: &str,
990) -> Option<Site> {
991    // the member's declaring type, extension chains followed (§4)
992    let mut seen: Vec<String> = vec![];
993    let mut type_name = rec_name(rt);
994    while let Some(tn) = type_name.clone() {
995        if seen.contains(&tn) {
996            break;
997        }
998        seen.push(tn.clone());
999        let target = resolve_in(&m.env, &tn);
1000        let site = site_of_target(st, a, target.as_ref())?;
1001        let sm = site.module.clone();
1002        let decl = sm
1003            .decls
1004            .iter()
1005            .find(|d| decl_id(d) == site.decl.unwrap_or(0))?;
1006        let DeclBody::Type { ty, .. } = &decl.body else {
1007            return None;
1008        };
1009        let members = record_members(ty);
1010        if let Some(mem) = members.iter().find(|x| x.name() == Some(member)) {
1011            if mem.loc().is_some() {
1012                return Some(Site {
1013                    kind: "member".into(),
1014                    module: sm.clone(),
1015                    decl: Some(decl_id(decl)),
1016                    decl_loc: decl.loc,
1017                    member_loc: mem.loc(),
1018                    range: member_range(&text_of(st, &sm), mem, member),
1019                    name: member.to_string(),
1020                });
1021            }
1022        }
1023        type_name = match ty {
1024            TypeAst::Named { name, .. } => Some(name.clone()),
1025            _ => None,
1026        };
1027    }
1028    None
1029}
1030
1031struct SiteAt<'a> {
1032    site: Option<Site>,
1033    ty: Option<Ty>,
1034    hit: Option<Hit<'a>>,
1035    module: Rc<Module>,
1036}
1037fn ns_export_site(st: &State, a: &Analysis, m: &Module, ns: &str, name: &str) -> Option<Site> {
1038    let nss = m.env.namespaces.borrow();
1039    let (_, exports) = nss.get(ns)?;
1040    let ex = exports.borrow().get(name).cloned()?;
1041    site_of_target(st, a, resolve_in(&ex.env, &ex.name).as_ref())
1042}
1043fn site_at<'a>(st: &State, a: &'a Analysis, uri: &str, pos: Pos) -> Option<SiteAt<'a>> {
1044    let m = module_of(a, &path_of(uri))?;
1045    let mi = a.run.modules.iter().position(|x| Rc::ptr_eq(x, &m))?;
1046    let decls: &'a [Decl] = &a.run.modules[mi].decls;
1047    let Some(hit) = node_at(decls, pos) else {
1048        return Some(SiteAt {
1049            site: None,
1050            ty: None,
1051            hit: None,
1052            module: m,
1053        });
1054    };
1055    let t = tables_of(a, &m);
1056    match &hit.node {
1057        NodeRef::Expr(e) => {
1058            let ty = t.types.get(&key_of(e)).cloned();
1059            match &***e {
1060                Expr::Name(n) => {
1061                    let target = match t.res.get(&key_of(e)) {
1062                        Some(r) => r.clone(),
1063                        None => resolve_in(&m.env, n),
1064                    };
1065                    Some(SiteAt {
1066                        site: site_of_target(st, a, target.as_ref()),
1067                        ty,
1068                        hit: Some(hit),
1069                        module: m,
1070                    })
1071                }
1072                Expr::Member { x, name, .. } => {
1073                    if let Expr::Name(xn) = &**x {
1074                        if m.env.namespaces.borrow().contains_key(xn) {
1075                            let site = ns_export_site(st, a, &m, xn, name);
1076                            return Some(SiteAt {
1077                                site,
1078                                ty,
1079                                hit: Some(hit),
1080                                module: m,
1081                            });
1082                        }
1083                    }
1084                    let xt = t.types.get(&key_of(x)).cloned();
1085                    let site =
1086                        member_site(st, a, &m, xt.as_ref().and_then(|t| t.rt.as_ref()), name);
1087                    Some(SiteAt {
1088                        site,
1089                        ty,
1090                        hit: Some(hit),
1091                        module: m,
1092                    })
1093                }
1094                _ => Some(SiteAt {
1095                    site: None,
1096                    ty,
1097                    hit: Some(hit),
1098                    module: m,
1099                }),
1100            }
1101        }
1102        NodeRef::Type(TypeAst::Named { name, .. }) => {
1103            let mut parts = name.splitn(2, '.');
1104            let head = parts.next().unwrap_or("");
1105            let tail = parts.next();
1106            let target = match tail {
1107                Some(tail) if m.env.namespaces.borrow().contains_key(head) => {
1108                    let site = ns_export_site(st, a, &m, head, tail);
1109                    return Some(SiteAt {
1110                        site,
1111                        ty: None,
1112                        hit: Some(hit),
1113                        module: m,
1114                    });
1115                }
1116                _ => resolve_in(&m.env, head),
1117            };
1118            Some(SiteAt {
1119                site: site_of_target(st, a, target.as_ref()),
1120                ty: None,
1121                hit: Some(hit),
1122                module: m,
1123            })
1124        }
1125        NodeRef::Member(mem) if mem.name().is_some() => {
1126            // the member's own declaration
1127            let name = mem.name().unwrap().to_string();
1128            let decl = hit.parents.iter().find_map(|p| {
1129                if let NodeRef::Decl(d) = p {
1130                    Some(*d)
1131                } else {
1132                    None
1133                }
1134            });
1135            let site = decl.map(|d| Site {
1136                kind: "member".into(),
1137                module: m.clone(),
1138                decl: Some(decl_id(d)),
1139                decl_loc: d.loc,
1140                member_loc: mem.loc(),
1141                range: member_range(&text_of(st, &m), mem, &name),
1142                name,
1143            });
1144            Some(SiteAt {
1145                site,
1146                ty: None,
1147                hit: Some(hit),
1148                module: m,
1149            })
1150        }
1151        NodeRef::Decl(d) if d.name().is_some() => {
1152            let name = d.name().unwrap().to_string();
1153            let r = name_range(&text_of(st, &m), d, &name);
1154            if contains(r, pos) {
1155                let site = Site {
1156                    kind: decl_kind(d).to_string(),
1157                    module: m.clone(),
1158                    decl: Some(decl_id(d)),
1159                    decl_loc: d.loc,
1160                    member_loc: None,
1161                    range: r,
1162                    name,
1163                };
1164                return Some(SiteAt {
1165                    site: Some(site),
1166                    ty: None,
1167                    hit: Some(hit),
1168                    module: m,
1169                });
1170            }
1171            Some(SiteAt {
1172                site: None,
1173                ty: None,
1174                hit: Some(hit),
1175                module: m,
1176            })
1177        }
1178        _ => Some(SiteAt {
1179            site: None,
1180            ty: None,
1181            hit: Some(hit),
1182            module: m,
1183        }),
1184    }
1185}
1186
1187// ---------------- hover ----------------
1188fn decl_text(st: &State, site: &Site) -> Vec<String> {
1189    let text = text_of(st, &site.module);
1190    let lines: Vec<&str> = text.split('\n').collect();
1191    let doc_re = Regex::new(r"^\s*///").unwrap();
1192    let doc_above = |sl: usize| -> Vec<String> {
1193        let mut from = sl;
1194        let mut out: Vec<String> = vec![];
1195        while from > 0 && doc_re.is_match(lines[from - 1]) {
1196            from -= 1;
1197            out.insert(0, lines[from].trim().to_string());
1198        }
1199        out
1200    };
1201    if let Some(l) = site.member_loc {
1202        let mut out = doc_above(l.sl);
1203        let body: Vec<String> = if l.sl == l.el {
1204            vec![slice16(lines.get(l.sl).copied().unwrap_or(""), l.sc, l.ec).to_string()]
1205        } else {
1206            let mut b = vec![slice16(lines[l.sl], l.sc, u16len(lines[l.sl])).to_string()];
1207            b.extend(lines[l.sl + 1..l.el].iter().map(|x| x.to_string()));
1208            b.push(slice16(lines[l.el], 0, l.ec).to_string());
1209            b
1210        };
1211        out.extend(
1212            body.iter()
1213                .map(|x| x.trim().to_string())
1214                .filter(|x| !x.is_empty()),
1215        );
1216        return out;
1217    }
1218    let l = site.decl_loc.unwrap();
1219    let mut out = doc_above(l.sl);
1220    let body: Vec<String> = lines[l.sl..=l.el.min(lines.len() - 1)]
1221        .iter()
1222        .map(|x| x.to_string())
1223        .collect();
1224    if body.len() > 12 {
1225        out.extend(body[..11].iter().cloned());
1226        out.push("    …".into());
1227        out.push(body[body.len() - 1].clone());
1228    } else {
1229        out.extend(body);
1230    }
1231    out
1232}
1233fn hover(st: &mut State, uri: &str, pos: Pos) -> J {
1234    let Some(a) = st.analysis_of(uri) else {
1235        return J::Null;
1236    };
1237    let Some(s) = site_at(st, &a, uri, pos) else {
1238        return J::Null;
1239    };
1240    let mut parts: Vec<String> = vec![];
1241    if let Some(site) = &s.site {
1242        let lines = decl_text(st, site);
1243        let doc: Vec<String> = lines
1244            .iter()
1245            .filter(|l| l.starts_with("///"))
1246            .map(|l| {
1247                l.strip_prefix("///")
1248                    .map(|r| r.strip_prefix(' ').unwrap_or(r))
1249                    .unwrap_or(l)
1250                    .to_string()
1251            })
1252            .collect();
1253        let code: Vec<&String> = lines.iter().filter(|l| !l.starts_with("///")).collect();
1254        if !doc.is_empty() {
1255            parts.push(doc.join("\n"));
1256        }
1257        parts.push(format!(
1258            "```decl\n{}\n```",
1259            code.iter()
1260                .map(|x| x.as_str())
1261                .collect::<Vec<_>>()
1262                .join("\n")
1263        ));
1264    }
1265    if let Some(ty) = &s.ty {
1266        parts.push(format!(
1267            "`{}{}`",
1268            type_text(ty.rt.as_ref()),
1269            if ty.abs { "?" } else { "" }
1270        ));
1271    }
1272    if parts.is_empty() {
1273        return J::Null;
1274    }
1275    let contents = J::obj(vec![
1276        ("kind", J::s("markdown")),
1277        ("value", J::s(parts.join("\n\n"))),
1278    ]);
1279    match s.hit.as_ref().map(|h| h.loc) {
1280        Some(l) => J::obj(vec![("contents", contents), ("range", range_json(l))]),
1281        None => J::obj(vec![("contents", contents)]),
1282    }
1283}
1284
1285// ---------------- navigation ----------------
1286fn location(m: &Module, loc: Loc) -> J {
1287    J::obj(vec![
1288        ("uri", J::s(uri_of(&m.path))),
1289        ("range", range_json(loc)),
1290    ])
1291}
1292fn definition(st: &mut State, uri: &str, pos: Pos) -> J {
1293    let Some(a) = st.analysis_of(uri) else {
1294        return J::Null;
1295    };
1296    match site_at(st, &a, uri, pos).and_then(|s| s.site) {
1297        Some(site) => location(&site.module, site.range),
1298        None => J::Null,
1299    }
1300}
1301// the named types a declared type spells (`T`, `T[]`, `A | B`, `T?`, `ref<T>`, `map<K, V>`): whatever they resolve to
1302fn named_types_of(ast: &TypeAst, out: &mut Vec<String>) {
1303    match ast {
1304        TypeAst::Named { name, args, .. } => {
1305            out.push(name.clone());
1306            for arg in args {
1307                named_types_of(arg, out);
1308            }
1309        }
1310        TypeAst::Array { elem, .. } => named_types_of(elem, out),
1311        TypeAst::Union { arms, .. } | TypeAst::Isect { arms, .. } => {
1312            for arm in arms {
1313                named_types_of(arm, out);
1314            }
1315        }
1316        TypeAst::Map { val, .. } => named_types_of(val, out),
1317        _ => {}
1318    }
1319}
1320// the same over a resolved type: record names, through refs, arrays, maps, unions
1321fn named_types_of_rt(rt: Option<&RT>, out: &mut Vec<String>) {
1322    let Some(rt) = rt else { return };
1323    match &rt.k {
1324        RTk::Rec(_) => {
1325            if let Some(n) = rt.name.borrow().clone() {
1326                if !n.starts_with('{') {
1327                    out.push(n);
1328                }
1329            }
1330        }
1331        RTk::Pred { base, .. } => named_types_of_rt(Some(base), out),
1332        RTk::Ref(target) => named_types_of_rt(Some(target), out),
1333        RTk::Arr { elem, .. } => named_types_of_rt(Some(elem), out),
1334        RTk::Map { val, .. } => named_types_of_rt(Some(val), out),
1335        RTk::Union(arms) => {
1336            for arm in arms {
1337                named_types_of_rt(Some(arm), out);
1338            }
1339        }
1340        _ => {}
1341    }
1342}
1343// the type a member declares, found again by name in its declaring type's body
1344fn member_type_ast(m: &Module, site: &Site) -> Option<TypeAst> {
1345    if site.kind != "member" {
1346        return None;
1347    }
1348    let d = decl_by_id(m, site.decl)?;
1349    record_body_of(d).map(record_members).and_then(|members| {
1350        members
1351            .iter()
1352            .find(|x| x.name() == Some(site.name.as_str()))
1353            .and_then(|x| match x {
1354                MemberAst::Value { ty, .. } | MemberAst::Context { ty, .. } => Some(ty.clone()),
1355                MemberAst::Derived { ty, .. } => ty.clone(),
1356                _ => None,
1357            })
1358    })
1359}
1360fn type_definition(st: &mut State, uri: &str, pos: Pos) -> J {
1361    let Some(a) = st.analysis_of(uri) else {
1362        return J::Null;
1363    };
1364    let Some(s) = site_at(st, &a, uri, pos) else {
1365        return J::Null;
1366    };
1367    // the declared type first (a member, an output or input, a constant's
1368    // annotation): the named types it spells, whatever they resolve to —
1369    // an alias of a literal union has a declaration too; else the
1370    // expression's inferred type
1371    let mut names: Vec<String> = vec![];
1372    let mut env = s.module.env.clone();
1373    if let Some(site) = &s.site {
1374        let sm = site.module.clone();
1375        let d = decl_by_id(&sm, site.decl);
1376        let ast: Option<TypeAst> = member_type_ast(&sm, site).or_else(|| {
1377            d.and_then(|d| match &d.body {
1378                DeclBody::Output { ty, .. } | DeclBody::Input { ty, .. } => Some(ty.clone()),
1379                DeclBody::Const { ty: Some(ty), .. } => Some(ty.clone()),
1380                _ => None,
1381            })
1382        });
1383        if let Some(ast) = ast {
1384            named_types_of(&ast, &mut names);
1385            env = sm.env.clone();
1386        } else if let Some(DeclBody::Const { expr, .. }) = d.map(|d| &d.body) {
1387            let rt = tables_of(&a, &sm)
1388                .types
1389                .get(&key_of(expr))
1390                .and_then(|x| x.rt.clone());
1391            named_types_of_rt(rt.as_ref(), &mut names);
1392        }
1393    }
1394    if names.is_empty() {
1395        if let Some(NodeRef::Expr(e)) = s.hit.as_ref().map(|h| h.node.clone()) {
1396            if let Expr::Member { x, name, .. } = &**e {
1397                // a member access: the member's declared type, where it is declared
1398                let xt = tables_of(&a, &s.module)
1399                    .types
1400                    .get(&key_of(x))
1401                    .and_then(|t| t.rt.clone());
1402                if let Some(ms) = member_site(st, &a, &s.module, xt.as_ref(), name) {
1403                    if let Some(ast) = member_type_ast(&ms.module, &ms) {
1404                        named_types_of(&ast, &mut names);
1405                        env = ms.module.env.clone();
1406                    }
1407                }
1408            }
1409        }
1410    }
1411    if names.is_empty() {
1412        named_types_of_rt(s.ty.as_ref().and_then(|t| t.rt.as_ref()), &mut names);
1413    }
1414    let mut seen: Vec<(PathBuf, usize, usize)> = vec![];
1415    let mut locs: Vec<J> = vec![];
1416    for n in names {
1417        let (head, tail) = match n.split_once('.') {
1418            Some((h, t)) => (h.to_string(), Some(t.to_string())),
1419            None => (n.clone(), None),
1420        };
1421        let target = match &tail {
1422            Some(t) if env.namespaces.borrow().contains_key(&head) => {
1423                let ex = env
1424                    .namespaces
1425                    .borrow()
1426                    .get(&head)
1427                    .and_then(|(_, exports)| exports.borrow().get(t).cloned());
1428                ex.and_then(|ex| resolve_in(&ex.env, &ex.name))
1429            }
1430            _ => resolve_in(&env, &head),
1431        };
1432        let Some(site) = site_of_target(st, &a, target.as_ref()) else {
1433            continue;
1434        };
1435        let key = (site.module.path.clone(), site.range.sl, site.range.sc);
1436        if seen.contains(&key) {
1437            continue;
1438        }
1439        seen.push(key);
1440        locs.push(location(&site.module, site.range));
1441    }
1442    match locs.len() {
1443        0 => J::Null,
1444        1 => locs.pop().unwrap(),
1445        _ => J::Arr(locs),
1446    }
1447}
1448fn same(x: Option<&Site>, target: &Site) -> bool {
1449    match x {
1450        Some(x) => {
1451            Rc::ptr_eq(&x.module, &target.module)
1452                && x.name == target.name
1453                && x.kind == target.kind
1454                && (x.kind != "member" || x.decl == target.decl)
1455        }
1456        None => false,
1457    }
1458}
1459fn member_token_loc(text: &str, e: &Rc<Expr>, name: &str) -> Loc {
1460    let l = expr_loc(e).unwrap();
1461    let line = text.split('\n').nth(l.el).unwrap_or("");
1462    match rfind16(line, name, l.ec) {
1463        Some(i) => Loc {
1464            sl: l.el,
1465            sc: i,
1466            el: l.el,
1467            ec: i + u16len(name),
1468        },
1469        None => l,
1470    }
1471}
1472fn type_name_loc(l: Loc, offset: usize, name: &str) -> Loc {
1473    Loc {
1474        sl: l.sl,
1475        sc: l.sc + offset,
1476        el: l.sl,
1477        ec: l.sc + offset + u16len(name),
1478    }
1479}
1480fn import_item_loc(text: &str, d: &Decl, name: &str) -> Loc {
1481    let l = d.loc.unwrap();
1482    let line = text.split('\n').nth(l.sl).unwrap_or("");
1483    match find16(line, name, l.sc) {
1484        Some(i) => Loc {
1485            sl: l.sl,
1486            sc: i,
1487            el: l.sl,
1488            ec: i + u16len(name),
1489        },
1490        None => l,
1491    }
1492}
1493// every reference to a site across the universe: name and member nodes
1494// that resolve to the same declaration, plus the declaration itself
1495fn references(
1496    st: &mut State,
1497    uri: &str,
1498    pos: Pos,
1499    include_declaration: bool,
1500) -> Vec<(Rc<Module>, Loc)> {
1501    let Some(a) = st.analysis_of(uri) else {
1502        return vec![];
1503    };
1504    let Some(target) = site_at(st, &a, uri, pos).and_then(|s| s.site) else {
1505        return vec![];
1506    };
1507    let mut out: Vec<(Rc<Module>, Loc)> = vec![];
1508    for m in a.run.modules.clone() {
1509        let t = tables_of(&a, &m);
1510        let text = text_of(st, &m);
1511        // every expression node, in the reference's traversal order
1512        let mut exprs: Vec<Rc<Expr>> = vec![];
1513        let mut types: Vec<&TypeAst> = vec![];
1514        for d in &m.decls {
1515            collect_decl(d, &mut exprs, &mut types);
1516        }
1517        for x in &exprs {
1518            if expr_loc(x).is_none() {
1519                continue;
1520            }
1521            match &**x {
1522                Expr::Name(n) => {
1523                    let tg = match t.res.get(&key_of(x)) {
1524                        Some(r) => r.clone(),
1525                        None => resolve_in(&m.env, n),
1526                    };
1527                    if same(site_of_target(st, &a, tg.as_ref()).as_ref(), &target) {
1528                        out.push((m.clone(), expr_loc(x).unwrap()));
1529                    }
1530                }
1531                Expr::Member { x: xx, name, .. } => {
1532                    let site = match &**xx {
1533                        Expr::Name(xn) if m.env.namespaces.borrow().contains_key(xn) => {
1534                            ns_export_site(st, &a, &m, xn, name)
1535                        }
1536                        _ => member_site(
1537                            st,
1538                            &a,
1539                            &m,
1540                            t.types.get(&key_of(xx)).and_then(|t| t.rt.as_ref()),
1541                            name,
1542                        ),
1543                    };
1544                    if same(site.as_ref(), &target) {
1545                        out.push((m.clone(), member_token_loc(&text, x, name)));
1546                    }
1547                }
1548                _ => {}
1549            }
1550        }
1551        for ty in types {
1552            let TypeAst::Named {
1553                name, loc: Some(l), ..
1554            } = ty
1555            else {
1556                continue;
1557            };
1558            let mut parts = name.splitn(2, '.');
1559            let head = parts.next().unwrap_or("");
1560            let tail = parts.next();
1561            let site = match tail {
1562                Some(tail) if m.env.namespaces.borrow().contains_key(head) => {
1563                    ns_export_site(st, &a, &m, head, tail)
1564                }
1565                _ => site_of_target(st, &a, resolve_in(&m.env, head).as_ref()),
1566            };
1567            if same(site.as_ref(), &target) {
1568                out.push((
1569                    m.clone(),
1570                    type_name_loc(
1571                        *l,
1572                        if tail.is_some() { u16len(head) + 1 } else { 0 },
1573                        tail.unwrap_or(head),
1574                    ),
1575                ));
1576            }
1577        }
1578        // import items naming the declaration
1579        for d in &m.decls {
1580            let (names, has_loc) = match &d.body {
1581                DeclBody::Import {
1582                    names: Some(names), ..
1583                } => (names, d.loc.is_some()),
1584                DeclBody::ReExport { names, .. } => (names, d.loc.is_some()),
1585                _ => continue,
1586            };
1587            if !has_loc {
1588                continue;
1589            }
1590            for it in names {
1591                let local = it.alias.clone().unwrap_or_else(|| it.name.clone());
1592                let im = m.env.imports.borrow().get(&local).cloned();
1593                if let Some(im) = im {
1594                    if same(
1595                        site_of_target(st, &a, resolve_in(&im.env, &im.name).as_ref()).as_ref(),
1596                        &target,
1597                    ) {
1598                        out.push((m.clone(), import_item_loc(&text, d, &it.name)));
1599                    }
1600                }
1601            }
1602        }
1603    }
1604    if include_declaration {
1605        out.insert(0, (target.module.clone(), target.range));
1606    }
1607    let mut seen: Vec<(PathBuf, usize, usize)> = vec![];
1608    let mut kept: Vec<(Rc<Module>, Loc)> = vec![];
1609    for (m, l) in out {
1610        let k = (m.path.clone(), l.sl, l.sc);
1611        if seen.contains(&k) {
1612            continue;
1613        }
1614        seen.push(k);
1615        kept.push((m, l));
1616    }
1617    kept.sort_by(|p, q| {
1618        let (pp, qp) = (
1619            p.0.path.to_string_lossy().to_string(),
1620            q.0.path.to_string_lossy().to_string(),
1621        );
1622        pp.cmp(&qp)
1623            .then(p.1.sl.cmp(&q.1.sl))
1624            .then(p.1.sc.cmp(&q.1.sc))
1625    });
1626    kept
1627}
1628// the expression and type nodes of a declaration, in the reference's traversal order
1629fn collect_decl<'a>(d: &'a Decl, exprs: &mut Vec<Rc<Expr>>, types: &mut Vec<&'a TypeAst>) {
1630    let mut ty = |t: &'a TypeAst| collect_type(t, exprs, types);
1631    match &d.body {
1632        DeclBody::Type {
1633            params,
1634            ty: t,
1635            tail,
1636            ..
1637        } => {
1638            for p in params {
1639                if let Some(pt) = &p.ty {
1640                    collect_type(pt, exprs, types);
1641                }
1642            }
1643            collect_type(t, exprs, types);
1644            if let Some(tl) = tail {
1645                collect_tail(tl, exprs, types);
1646            }
1647        }
1648        DeclBody::Const { ty: t, expr, .. } => {
1649            if let Some(t) = t {
1650                ty(t);
1651            }
1652            collect_expr(expr, exprs, types);
1653        }
1654        DeclBody::Func {
1655            params, ret, body, ..
1656        } => {
1657            for p in params {
1658                if let Some(pt) = &p.ty {
1659                    collect_type(pt, exprs, types);
1660                }
1661            }
1662            if let Some(r) = ret {
1663                collect_type(r, exprs, types);
1664            }
1665            collect_expr(body, exprs, types);
1666        }
1667        DeclBody::Output { ty: t, expr, .. } => {
1668            collect_type(t, exprs, types);
1669            collect_expr(expr, exprs, types);
1670        }
1671        DeclBody::Input {
1672            ty: t, fallback, ..
1673        } => {
1674            collect_type(t, exprs, types);
1675            if let Some(f) = fallback {
1676                collect_expr(f, exprs, types);
1677            }
1678        }
1679        DeclBody::Diagnostic {
1680            params, template, ..
1681        } => {
1682            for p in params {
1683                if let Some(pt) = &p.ty {
1684                    collect_type(pt, exprs, types);
1685                }
1686            }
1687            collect_template(template, exprs, types);
1688        }
1689        DeclBody::Unit {
1690            factor: Some(f), ..
1691        } => {
1692            collect_expr(f, exprs, types);
1693        }
1694        _ => {}
1695    }
1696}
1697fn collect_tail<'a>(t: &'a Tail, exprs: &mut Vec<Rc<Expr>>, types: &mut Vec<&'a TypeAst>) {
1698    match t {
1699        Tail::Inline { template, .. } => collect_template(template, exprs, types),
1700        Tail::Ref { args, .. } => args.iter().for_each(|a| collect_expr(a, exprs, types)),
1701    }
1702}
1703fn collect_template<'a>(
1704    parts: &'a [TPart],
1705    exprs: &mut Vec<Rc<Expr>>,
1706    types: &mut Vec<&'a TypeAst>,
1707) {
1708    for p in parts {
1709        if let TPart::Expr(x) = p {
1710            collect_expr(x, exprs, types);
1711        }
1712    }
1713}
1714fn collect_type<'a>(t: &'a TypeAst, exprs: &mut Vec<Rc<Expr>>, types: &mut Vec<&'a TypeAst>) {
1715    types.push(t);
1716    match t {
1717        TypeAst::Record { members, .. } => {
1718            members.iter().for_each(|m| collect_member(m, exprs, types))
1719        }
1720        TypeAst::Map { key, val, .. } => {
1721            collect_type(key, exprs, types);
1722            collect_type(val, exprs, types);
1723        }
1724        TypeAst::Array { elem, .. } => collect_type(elem, exprs, types),
1725        TypeAst::Union { arms, .. } | TypeAst::Isect { arms, .. } => {
1726            arms.iter().for_each(|a| collect_type(a, exprs, types))
1727        }
1728        TypeAst::Func { params, ret, .. } => {
1729            params.iter().for_each(|a| collect_type(a, exprs, types));
1730            collect_type(ret, exprs, types);
1731        }
1732        TypeAst::Named {
1733            args, preds, ext, ..
1734        } => {
1735            args.iter().for_each(|a| collect_type(a, exprs, types));
1736            preds
1737                .iter()
1738                .flatten()
1739                .for_each(|x| collect_expr(x, exprs, types));
1740            if let Some(x) = ext {
1741                collect_type(x, exprs, types);
1742            }
1743        }
1744        _ => {}
1745    }
1746}
1747fn collect_member<'a>(m: &'a MemberAst, exprs: &mut Vec<Rc<Expr>>, types: &mut Vec<&'a TypeAst>) {
1748    match m {
1749        MemberAst::Value { ty, dflt, .. } => {
1750            collect_type(ty, exprs, types);
1751            if let Some(d) = dflt {
1752                collect_expr(d, exprs, types);
1753            }
1754        }
1755        MemberAst::Derived { ty, expr, .. } => {
1756            if let Some(t) = ty {
1757                collect_type(t, exprs, types);
1758            }
1759            collect_expr(expr, exprs, types);
1760        }
1761        MemberAst::Context { ty, .. } => collect_type(ty, exprs, types),
1762        MemberAst::Assert { cond, tail, .. } => {
1763            collect_expr(cond, exprs, types);
1764            if let Some(t) = tail {
1765                collect_tail(t, exprs, types);
1766            }
1767        }
1768        MemberAst::When { cond, body, .. } => {
1769            collect_expr(cond, exprs, types);
1770            body.iter().for_each(|b| collect_member(b, exprs, types));
1771        }
1772    }
1773}
1774fn collect_expr<'a>(e: &'a Rc<Expr>, exprs: &mut Vec<Rc<Expr>>, types: &mut Vec<&'a TypeAst>) {
1775    exprs.push(e.clone());
1776    let mut go = |x: &'a Rc<Expr>| collect_expr(x, exprs, types);
1777    match &**e {
1778        Expr::Template(parts) => {
1779            for p in parts {
1780                if let TPart::Expr(x) = p {
1781                    go(x);
1782                }
1783            }
1784        }
1785        Expr::Obj(entries) => entries.iter().for_each(|(_, v)| go(v)),
1786        Expr::Arr(items) => items.iter().for_each(|(_, v)| go(v)),
1787        Expr::Comp { head, clauses } => {
1788            go(head);
1789            for c in clauses {
1790                go(&c.iter);
1791                c.filters.iter().for_each(&mut go);
1792            }
1793        }
1794        Expr::MapComp { key, val, clauses } => {
1795            go(key);
1796            go(val);
1797            for c in clauses {
1798                go(&c.iter);
1799                c.filters.iter().for_each(&mut go);
1800            }
1801        }
1802        Expr::Bin { l, r, .. } => {
1803            go(l);
1804            go(r);
1805        }
1806        Expr::Un { x, .. } | Expr::Paren(x) => go(x),
1807        Expr::If { c, t, f } => {
1808            go(c);
1809            go(t);
1810            go(f);
1811        }
1812        Expr::Lambda { body, .. } => go(body),
1813        Expr::Call { fun, args } => {
1814            go(fun);
1815            args.iter().for_each(&mut go);
1816        }
1817        Expr::Member { x, .. } => go(x),
1818        Expr::Index { x, i } => {
1819            go(x);
1820            go(i);
1821        }
1822        Expr::With { base, patch } => {
1823            go(base);
1824            go(patch);
1825        }
1826        Expr::Match { subject, arms } => {
1827            go(subject);
1828            for a in arms {
1829                if let Some(t) = &a.ty {
1830                    collect_type(t, exprs, types);
1831                }
1832                collect_expr(&a.body, exprs, types);
1833            }
1834        }
1835        _ => {}
1836    }
1837}
1838
1839// ---------------- completion ----------------
1840fn completion(st: &mut State, uri: &str, pos: Pos) -> J {
1841    let a = st.analysis_of(uri);
1842    let Some(text) = st.text(uri).cloned() else {
1843        return J::obj(vec![
1844            ("isIncomplete", J::Bool(false)),
1845            ("items", J::Arr(vec![])),
1846        ]);
1847    };
1848    let line = text.split('\n').nth(pos.line).unwrap_or("");
1849    let prefix = &line[..byte_col(line, pos.character)];
1850    // while the text does not parse, the scope is the last one that did
1851    let fresh;
1852    let session: &Session = match a.as_ref().or_else(|| st.last_good.get(uri)) {
1853        Some(x) => &x.session,
1854        None => {
1855            fresh = Session::with_overlay(Some(&path_of(uri).to_string_lossy()), Some(&st.overlay));
1856            &fresh
1857        }
1858    };
1859    let items: Vec<J> = session
1860        .complete(prefix, &[])
1861        .iter()
1862        .map(|c| {
1863            let (label, detail) = match c.find("  ") {
1864                Some(i) => (&c[..i], Some(&c[i + 2..])),
1865                None => (c.as_str(), None),
1866            };
1867            let kind = match detail {
1868                Some(d) => {
1869                    if d.starts_with("derived")
1870                        || d.starts_with("required")
1871                        || d.starts_with("optional")
1872                        || d.starts_with("defaulted")
1873                    {
1874                        5
1875                    } else {
1876                        6
1877                    }
1878                }
1879                None => {
1880                    if label
1881                        .chars()
1882                        .next()
1883                        .map(|c| c.is_ascii_uppercase())
1884                        .unwrap_or(false)
1885                    {
1886                        7
1887                    } else if label.starts_with('$') {
1888                        14
1889                    } else {
1890                        6
1891                    }
1892                }
1893            };
1894            let mut item = vec![("label", J::s(label)), ("kind", J::Num(kind))];
1895            if let Some(d) = detail {
1896                item.push(("detail", J::s(d)));
1897            }
1898            J::obj(item)
1899        })
1900        .collect();
1901    J::obj(vec![
1902        ("isIncomplete", J::Bool(false)),
1903        ("items", J::Arr(items)),
1904    ])
1905}
1906
1907// ---------------- symbols, folding, formatting ----------------
1908fn symbol_kind(d: &Decl) -> Option<i64> {
1909    Some(match &d.body {
1910        DeclBody::Type { .. } => 5,
1911        DeclBody::Const { .. } => 14,
1912        DeclBody::Func { .. } => 12,
1913        DeclBody::Output { .. }
1914        | DeclBody::Input { .. }
1915        | DeclBody::Dimension { .. }
1916        | DeclBody::Unit { .. } => 13,
1917        DeclBody::Diagnostic { .. } => 24,
1918        _ => return None,
1919    })
1920}
1921fn document_symbols(st: &State, uri: &str) -> J {
1922    let Some(text) = st.text(uri) else {
1923        return J::Arr(vec![]);
1924    };
1925    let parsed = parse_source(text);
1926    if !parsed.errors.is_empty() {
1927        return J::Arr(vec![]);
1928    }
1929    let mut out: Vec<J> = vec![];
1930    for d in &parsed.decls {
1931        let (Some(loc), Some(name), Some(kind)) = (d.loc, d.name(), symbol_kind(d)) else {
1932            continue;
1933        };
1934        let mut sym = vec![
1935            ("name", J::s(name)),
1936            ("kind", J::Num(kind)),
1937            ("range", range_json(loc)),
1938            ("selectionRange", range_json(name_range(text, d, name))),
1939        ];
1940        if let DeclBody::Type { ty, .. } = &d.body {
1941            let children: Vec<J> = record_members(ty)
1942                .iter()
1943                .filter(|m| m.loc().is_some() && m.name().is_some())
1944                .map(|m| {
1945                    let n = m.name().unwrap();
1946                    let (label, kind) = match m {
1947                        MemberAst::Assert { .. } => (format!("assert {n}"), 24),
1948                        MemberAst::Derived { hidden: true, .. } => (format!("{n}$"), 7),
1949                        _ => (n.to_string(), 7),
1950                    };
1951                    J::obj(vec![
1952                        ("name", J::s(label)),
1953                        ("kind", J::Num(kind)),
1954                        ("range", range_json(m.loc().unwrap())),
1955                        ("selectionRange", range_json(member_range(text, m, n))),
1956                    ])
1957                })
1958                .collect();
1959            if !children.is_empty() {
1960                sym.push(("children", J::Arr(children)));
1961            }
1962        }
1963        out.push(J::obj(sym));
1964    }
1965    J::Arr(out)
1966}
1967fn folding_ranges(st: &State, uri: &str) -> J {
1968    let Some(text) = st.text(uri) else {
1969        return J::Arr(vec![]);
1970    };
1971    let parsed = parse_source(text);
1972    if !parsed.errors.is_empty() {
1973        return J::Arr(vec![]);
1974    }
1975    let mut ranges: Vec<(usize, usize)> = vec![];
1976    for d in &parsed.decls {
1977        fold_decl(d, &mut ranges);
1978    }
1979    let mut seen: Vec<(usize, usize)> = vec![];
1980    let mut out: Vec<J> = vec![];
1981    for r in ranges {
1982        if seen.contains(&r) {
1983            continue;
1984        }
1985        seen.push(r);
1986        out.push(J::obj(vec![
1987            ("startLine", J::Num(r.0 as i64)),
1988            ("endLine", J::Num(r.1 as i64)),
1989            ("kind", J::s("region")),
1990        ]));
1991    }
1992    J::Arr(out)
1993}
1994fn fold_push(loc: Option<Loc>, out: &mut Vec<(usize, usize)>) {
1995    if let Some(l) = loc {
1996        if l.el > l.sl {
1997            out.push((l.sl, l.el));
1998        }
1999    }
2000}
2001fn fold_decl(d: &Decl, out: &mut Vec<(usize, usize)>) {
2002    fold_push(d.loc, out);
2003    let mut exprs: Vec<Rc<Expr>> = vec![];
2004    let mut types: Vec<&TypeAst> = vec![];
2005    // the reference's generic pre-order visit: the same order collect_* keep,
2006    // interleaving members (visited through their types) where they occur
2007    fold_walk_decl(d, out);
2008    let _ = (&mut exprs, &mut types);
2009}
2010fn fold_walk_decl(d: &Decl, out: &mut Vec<(usize, usize)>) {
2011    match &d.body {
2012        DeclBody::Type {
2013            params, ty, tail, ..
2014        } => {
2015            for p in params {
2016                if let Some(t) = &p.ty {
2017                    fold_type(t, out);
2018                }
2019            }
2020            fold_type(ty, out);
2021            if let Some(t) = tail {
2022                fold_tail(t, out);
2023            }
2024        }
2025        DeclBody::Const { ty, expr, .. } => {
2026            if let Some(t) = ty {
2027                fold_type(t, out);
2028            }
2029            fold_expr(expr, out);
2030        }
2031        DeclBody::Func {
2032            params, ret, body, ..
2033        } => {
2034            for p in params {
2035                if let Some(t) = &p.ty {
2036                    fold_type(t, out);
2037                }
2038            }
2039            if let Some(t) = ret {
2040                fold_type(t, out);
2041            }
2042            fold_expr(body, out);
2043        }
2044        DeclBody::Output { ty, expr, .. } => {
2045            fold_type(ty, out);
2046            fold_expr(expr, out);
2047        }
2048        DeclBody::Input { ty, fallback, .. } => {
2049            fold_type(ty, out);
2050            if let Some(f) = fallback {
2051                fold_expr(f, out);
2052            }
2053        }
2054        DeclBody::Diagnostic {
2055            params, template, ..
2056        } => {
2057            for p in params {
2058                if let Some(t) = &p.ty {
2059                    fold_type(t, out);
2060                }
2061            }
2062            fold_template(template, out);
2063        }
2064        DeclBody::Unit {
2065            factor: Some(f), ..
2066        } => {
2067            fold_expr(f, out);
2068        }
2069        _ => {}
2070    }
2071}
2072fn fold_tail(t: &Tail, out: &mut Vec<(usize, usize)>) {
2073    match t {
2074        Tail::Inline { template, .. } => fold_template(template, out),
2075        Tail::Ref { args, .. } => args.iter().for_each(|a| fold_expr(a, out)),
2076    }
2077}
2078fn fold_template(parts: &[TPart], out: &mut Vec<(usize, usize)>) {
2079    for p in parts {
2080        if let TPart::Expr(x) = p {
2081            fold_expr(x, out);
2082        }
2083    }
2084}
2085fn fold_type(t: &TypeAst, out: &mut Vec<(usize, usize)>) {
2086    if let TypeAst::Record { .. } = t {
2087        fold_push(t.loc(), out);
2088    }
2089    match t {
2090        TypeAst::Record { members, .. } => members.iter().for_each(|m| fold_member(m, out)),
2091        TypeAst::Map { key, val, .. } => {
2092            fold_type(key, out);
2093            fold_type(val, out);
2094        }
2095        TypeAst::Array { elem, .. } => fold_type(elem, out),
2096        TypeAst::Union { arms, .. } | TypeAst::Isect { arms, .. } => {
2097            arms.iter().for_each(|a| fold_type(a, out))
2098        }
2099        TypeAst::Func { params, ret, .. } => {
2100            params.iter().for_each(|a| fold_type(a, out));
2101            fold_type(ret, out);
2102        }
2103        TypeAst::Named {
2104            args, preds, ext, ..
2105        } => {
2106            args.iter().for_each(|a| fold_type(a, out));
2107            preds.iter().flatten().for_each(|x| fold_expr(x, out));
2108            if let Some(x) = ext {
2109                fold_type(x, out);
2110            }
2111        }
2112        _ => {}
2113    }
2114}
2115fn fold_member(m: &MemberAst, out: &mut Vec<(usize, usize)>) {
2116    if let MemberAst::When { .. } = m {
2117        fold_push(m.loc(), out);
2118    }
2119    match m {
2120        MemberAst::Value { ty, dflt, .. } => {
2121            fold_type(ty, out);
2122            if let Some(d) = dflt {
2123                fold_expr(d, out);
2124            }
2125        }
2126        MemberAst::Derived { ty, expr, .. } => {
2127            if let Some(t) = ty {
2128                fold_type(t, out);
2129            }
2130            fold_expr(expr, out);
2131        }
2132        MemberAst::Context { ty, .. } => fold_type(ty, out),
2133        MemberAst::Assert { cond, tail, .. } => {
2134            fold_expr(cond, out);
2135            if let Some(t) = tail {
2136                fold_tail(t, out);
2137            }
2138        }
2139        MemberAst::When { cond, body, .. } => {
2140            fold_expr(cond, out);
2141            body.iter().for_each(|b| fold_member(b, out));
2142        }
2143    }
2144}
2145fn fold_expr(e: &Rc<Expr>, out: &mut Vec<(usize, usize)>) {
2146    if matches!(&**e, Expr::Obj(_) | Expr::Arr(_) | Expr::Match { .. }) {
2147        fold_push(expr_loc(e), out);
2148    }
2149    match &**e {
2150        Expr::Template(parts) => fold_template(parts, out),
2151        Expr::Obj(entries) => entries.iter().for_each(|(_, v)| fold_expr(v, out)),
2152        Expr::Arr(items) => items.iter().for_each(|(_, v)| fold_expr(v, out)),
2153        Expr::Comp { head, clauses } => {
2154            fold_expr(head, out);
2155            for c in clauses {
2156                fold_expr(&c.iter, out);
2157                c.filters.iter().for_each(|f| fold_expr(f, out));
2158            }
2159        }
2160        Expr::MapComp { key, val, clauses } => {
2161            fold_expr(key, out);
2162            fold_expr(val, out);
2163            for c in clauses {
2164                fold_expr(&c.iter, out);
2165                c.filters.iter().for_each(|f| fold_expr(f, out));
2166            }
2167        }
2168        Expr::Bin { l, r, .. } => {
2169            fold_expr(l, out);
2170            fold_expr(r, out);
2171        }
2172        Expr::Un { x, .. } | Expr::Paren(x) => fold_expr(x, out),
2173        Expr::If { c, t, f } => {
2174            fold_expr(c, out);
2175            fold_expr(t, out);
2176            fold_expr(f, out);
2177        }
2178        Expr::Lambda { body, .. } => fold_expr(body, out),
2179        Expr::Call { fun, args } => {
2180            fold_expr(fun, out);
2181            args.iter().for_each(|a| fold_expr(a, out));
2182        }
2183        Expr::Member { x, .. } => fold_expr(x, out),
2184        Expr::Index { x, i } => {
2185            fold_expr(x, out);
2186            fold_expr(i, out);
2187        }
2188        Expr::With { base, patch } => {
2189            fold_expr(base, out);
2190            fold_expr(patch, out);
2191        }
2192        Expr::Match { subject, arms } => {
2193            fold_expr(subject, out);
2194            for a in arms {
2195                if let Some(t) = &a.ty {
2196                    fold_type(t, out);
2197                }
2198                fold_expr(&a.body, out);
2199            }
2200        }
2201        _ => {}
2202    }
2203}
2204fn formatting(st: &State, uri: &str) -> J {
2205    let Some(text) = st.text(uri) else {
2206        return J::Arr(vec![]);
2207    };
2208    let Ok(out) = format(text) else {
2209        return J::Arr(vec![]);
2210    };
2211    if &out == text {
2212        return J::Arr(vec![]);
2213    }
2214    let lines: Vec<&str> = text.split('\n').collect();
2215    let last = lines.len() - 1;
2216    J::Arr(vec![J::obj(vec![
2217        (
2218            "range",
2219            range_json(Loc {
2220                sl: 0,
2221                sc: 0,
2222                el: last,
2223                ec: u16len(lines[last]),
2224            }),
2225        ),
2226        ("newText", J::s(out)),
2227    ])])
2228}
2229
2230// ---------------- rename ----------------
2231fn prepare_rename(st: &mut State, uri: &str, pos: Pos) -> J {
2232    let a = st.analysis_of(uri);
2233    let s = a.as_ref().and_then(|a| site_at(st, a, uri, pos));
2234    if s.as_ref().map(|s| s.site.is_none()).unwrap_or(true) {
2235        // a local variable: its binding and uses
2236        let Some(locs) = local_ranges(st, uri, pos) else {
2237            return J::Null;
2238        };
2239        let Some(here) = locs.iter().find(|l| contains(**l, pos)) else {
2240            return J::Null;
2241        };
2242        let text = st.text(uri).cloned().unwrap_or_default();
2243        let line = text.split('\n').nth(here.sl).unwrap_or("");
2244        return J::obj(vec![
2245            ("range", range_json(*here)),
2246            ("placeholder", J::s(slice16(line, here.sc, here.ec))),
2247        ]);
2248    }
2249    let s = s.unwrap();
2250    let (Some(site), Some(hit)) = (&s.site, &s.hit) else {
2251        return J::Null;
2252    };
2253    let loc = match &hit.node {
2254        NodeRef::Expr(e) => match &***e {
2255            Expr::Member { name, .. } => member_token_loc(&text_of(st, &s.module), e, name),
2256            _ => hit.loc,
2257        },
2258        NodeRef::Type(t) => {
2259            let name = match t {
2260                TypeAst::Named { name, .. } => name.as_str(),
2261                _ => "",
2262            };
2263            let offset = name.find('.').map(|i| i + 1).unwrap_or(0);
2264            type_name_loc(hit.loc, offset, name.rsplit('.').next().unwrap_or(name))
2265        }
2266        NodeRef::Decl(_) | NodeRef::Member(_) => site.range,
2267    };
2268    J::obj(vec![
2269        ("range", range_json(loc)),
2270        ("placeholder", J::s(site.name.clone())),
2271    ])
2272}
2273fn rename(st: &mut State, uri: &str, pos: Pos, new_name: &str) -> J {
2274    let refs = references(st, uri, pos, true);
2275    if refs.is_empty() {
2276        let Some(locs) = local_ranges(st, uri, pos) else {
2277            return J::Null;
2278        };
2279        if locs.is_empty() {
2280            return J::Null;
2281        }
2282        let edits: Vec<J> = locs
2283            .iter()
2284            .map(|l| J::obj(vec![("range", range_json(*l)), ("newText", J::s(new_name))]))
2285            .collect();
2286        return J::obj(vec![(
2287            "changes",
2288            J::Obj(vec![(uri.to_string(), J::Arr(edits))]),
2289        )]);
2290    }
2291    let mut changes: Vec<(String, Vec<J>)> = vec![];
2292    for (m, l) in refs {
2293        let u = uri_of(&m.path);
2294        let edit = J::obj(vec![("range", range_json(l)), ("newText", J::s(new_name))]);
2295        match changes.iter_mut().find(|(k, _)| *k == u) {
2296            Some(e) => e.1.push(edit),
2297            None => changes.push((u, vec![edit])),
2298        }
2299    }
2300    J::obj(vec![(
2301        "changes",
2302        J::Obj(changes.into_iter().map(|(k, v)| (k, J::Arr(v))).collect()),
2303    )])
2304}
2305
2306// ---------------- lenses and commands ----------------
2307fn code_lenses(st: &State, uri: &str) -> J {
2308    let Some(text) = st.text(uri) else {
2309        return J::Arr(vec![]);
2310    };
2311    let parsed = parse_source(text);
2312    if !parsed.errors.is_empty() {
2313        return J::Arr(vec![]);
2314    }
2315    let mut out: Vec<J> = vec![];
2316    for d in &parsed.decls {
2317        let Some(l) = d.loc else { continue };
2318        let (title, command, name) = match &d.body {
2319            DeclBody::Output { name, .. } => ("evaluate", "decl.evaluate", name),
2320            DeclBody::Input { name, .. } => ("validate", "decl.validate", name),
2321            _ => continue,
2322        };
2323        out.push(J::obj(vec![
2324            (
2325                "range",
2326                range_json(Loc {
2327                    sl: l.sl,
2328                    sc: l.sc,
2329                    el: l.sl,
2330                    ec: l.sc,
2331                }),
2332            ),
2333            (
2334                "command",
2335                J::obj(vec![
2336                    ("title", J::s(title)),
2337                    ("command", J::s(command)),
2338                    ("arguments", J::Arr(vec![J::s(uri), J::s(name.clone())])),
2339                ]),
2340            ),
2341        ]));
2342    }
2343    J::Arr(out)
2344}
2345fn execute_command(st: &mut State, command: &str, args: Option<&Value>) -> J {
2346    let items: Vec<&Value> = match args {
2347        Some(Value::JArr(a)) => a.iter().collect(),
2348        _ => vec![],
2349    };
2350    let Some(uri) = as_str(items.first().copied()) else {
2351        return J::Null;
2352    };
2353    let root = as_str(items.get(1).copied()).map(|s| s.to_string());
2354    let path = path_of(uri);
2355    let mut session = Session::with_overlay(Some(&path.to_string_lossy()), Some(&st.overlay));
2356    let dir = path.parent().map(|p| p.to_path_buf()).unwrap_or_default();
2357    for (name, file) in st.inputs.clone() {
2358        let abs = std::path::absolute(dir.join(&file)).unwrap_or_else(|_| dir.join(&file));
2359        let _ = session.apply(Op::Bind {
2360            name,
2361            src: BindSource::File {
2362                file: file.clone(),
2363                text: read_text(&abs),
2364            },
2365        });
2366    }
2367    let names: Vec<String> = root.iter().cloned().collect();
2368    let diags_of = |run: &Run, extra: &[Diag]| -> J {
2369        let mut all: Vec<&Diag> = run.load_diags.iter().collect();
2370        all.extend(run.checks.iter().map(|(_, d)| d));
2371        all.extend(extra.iter());
2372        J::Arr(all.iter().map(|d| J::s(fmt_diag(d, None))).collect())
2373    };
2374    match command {
2375        "decl.evaluate" => {
2376            let Ok((run, ds, _exported)) = session.evaluate(&names) else {
2377                return J::Null;
2378            };
2379            let diagnostics = diags_of(&run, &run.diags);
2380            if let Some(r) = root {
2381                let doc = ds.first().and_then(|(_, j)| j.clone());
2382                return J::obj(vec![
2383                    ("root", J::s(r)),
2384                    ("document", doc.map(J::s).unwrap_or(J::Null)),
2385                    ("diagnostics", diagnostics),
2386                ]);
2387            }
2388            let all = if run.eng.is_some() && ds.iter().all(|(_, j)| j.is_some()) {
2389                Some(format!(
2390                    "{{{}}}",
2391                    ds.iter()
2392                        .map(|(n, j)| format!("{}:{}", json_str(n), j.clone().unwrap()))
2393                        .collect::<Vec<_>>()
2394                        .join(",")
2395                ))
2396            } else {
2397                None
2398            };
2399            J::obj(vec![
2400                ("root", J::Null),
2401                ("document", all.map(J::s).unwrap_or(J::Null)),
2402                ("diagnostics", diagnostics),
2403            ])
2404        }
2405        "decl.validate" => {
2406            let Ok((run, verdicts, diags)) = session.validate(&names) else {
2407                return J::Null;
2408            };
2409            let vs: Vec<J> = verdicts
2410                .iter()
2411                .map(|(n, e, w)| {
2412                    J::obj(vec![
2413                        ("name", J::s(n.clone())),
2414                        ("errors", J::Num(*e as i64)),
2415                        ("warnings", J::Num(*w as i64)),
2416                    ])
2417                })
2418                .collect();
2419            J::obj(vec![
2420                ("verdicts", J::Arr(vs)),
2421                ("diagnostics", diags_of(&run, &diags)),
2422            ])
2423        }
2424        "decl.trace" => match root {
2425            Some(r) => match session.trace(&r) {
2426                Ok(lines) => J::obj(vec![(
2427                    "lines",
2428                    J::Arr(lines.into_iter().map(J::s).collect()),
2429                )]),
2430                Err(_) => J::Null,
2431            },
2432            None => J::Null,
2433        },
2434        "decl.showSyntaxTree" => syntax_tree(st, uri),
2435        "decl.reloadWorkspace" => {
2436            st.analyses.clear();
2437            let uris: Vec<String> = st.docs.iter().map(|(u, _)| u.clone()).collect();
2438            for u in uris {
2439                analyze(st, &u);
2440            }
2441            J::Null
2442        }
2443        _ => J::Null,
2444    }
2445}
2446
2447// ---------------- signature help ----------------
2448fn after(l: Loc, p: Pos) -> bool {
2449    p.line > l.el || (p.line == l.el && p.character >= l.ec)
2450}
2451fn src_of(text: &str, l: Loc) -> String {
2452    let lines: Vec<&str> = text.split('\n').collect();
2453    let line = |i: usize| lines.get(i).copied().unwrap_or("");
2454    if l.sl == l.el {
2455        return slice16(line(l.sl), l.sc, l.ec).to_string();
2456    }
2457    let mut parts = vec![slice16(line(l.sl), l.sc, u16len(line(l.sl))).to_string()];
2458    parts.extend(lines[l.sl + 1..l.el].iter().map(|x| x.to_string()));
2459    parts.push(slice16(line(l.el), 0, l.ec).to_string());
2460    parts.join("\n")
2461}
2462fn sig_json(label: String, params: Vec<String>, active: usize) -> J {
2463    J::obj(vec![
2464        (
2465            "signatures",
2466            J::Arr(vec![J::obj(vec![
2467                ("label", J::s(label)),
2468                (
2469                    "parameters",
2470                    J::Arr(
2471                        params
2472                            .into_iter()
2473                            .map(|p| J::obj(vec![("label", J::s(p))]))
2474                            .collect(),
2475                    ),
2476                ),
2477            ])]),
2478        ),
2479        ("activeSignature", J::Num(0)),
2480        ("activeParameter", J::Num(active as i64)),
2481    ])
2482}
2483fn decl_by_id(m: &Module, id: Option<usize>) -> Option<&Decl> {
2484    m.decls.iter().find(|d| decl_id(d) == id.unwrap_or(0))
2485}
2486fn signature_help(st: &mut State, uri: &str, pos: Pos) -> J {
2487    let a = match st.analysis_of(uri) {
2488        Some(a) => a,
2489        None => match st.last_good.get(uri) {
2490            Some(a) => a.clone(),
2491            None => return J::Null,
2492        },
2493    };
2494    let Some(m) = module_of(&a, &path_of(uri)) else {
2495        return J::Null;
2496    };
2497    let Some(mi) = a.run.modules.iter().position(|x| Rc::ptr_eq(x, &m)) else {
2498        return J::Null;
2499    };
2500    let Some(hit) = node_at(&a.run.modules[mi].decls, pos) else {
2501        return J::Null;
2502    };
2503    let mut chain: Vec<NodeRef> = hit.parents.clone();
2504    chain.push(hit.node.clone());
2505    let calls: Vec<&Rc<Expr>> = chain
2506        .iter()
2507        .rev()
2508        .filter_map(|n| match n {
2509            NodeRef::Expr(e) if matches!(&***e, Expr::Call { .. }) => Some(*e),
2510            _ => None,
2511        })
2512        .collect();
2513    for c in calls {
2514        let Expr::Call { fun, args } = &**c else {
2515            continue;
2516        };
2517        let Some(fl) = expr_loc(fun) else { continue };
2518        if !after(fl, pos) {
2519            continue;
2520        }
2521        let mut active = 0usize;
2522        for (i, arg) in args.iter().enumerate() {
2523            if let Some(al) = expr_loc(arg) {
2524                if after(al, pos) {
2525                    active = i + 1;
2526                } else if contains(al, pos) {
2527                    active = i;
2528                }
2529            }
2530        }
2531        if let Expr::Name(n) = &**fun {
2532            let target = resolve_in(&m.env, n);
2533            let Some(site) = site_of_target(st, &a, target.as_ref()) else {
2534                return J::Null;
2535            };
2536            let sm = site.module.clone();
2537            let Some(decl) = decl_by_id(&sm, site.decl) else {
2538                return J::Null;
2539            };
2540            let DeclBody::Func {
2541                name, params, ret, ..
2542            } = &decl.body
2543            else {
2544                return J::Null;
2545            };
2546            let text = text_of(st, &sm);
2547            let ps: Vec<String> = params
2548                .iter()
2549                .map(|p| {
2550                    format!(
2551                        "{}: {}",
2552                        p.name,
2553                        p.ty.as_ref()
2554                            .and_then(|t| t.loc())
2555                            .map(|l| src_of(&text, l))
2556                            .unwrap_or_else(|| "…".into())
2557                    )
2558                })
2559                .collect();
2560            let r = ret
2561                .as_ref()
2562                .and_then(|t| t.loc())
2563                .map(|l| format!(": {}", src_of(&text, l)))
2564                .unwrap_or_default();
2565            let n = ps.len();
2566            return sig_json(
2567                format!("{name}({}){r}", ps.join(", ")),
2568                ps,
2569                active.min(n.saturating_sub(1)),
2570            );
2571        }
2572        if let Some(sp) = std_path(fun) {
2573            if let Some(e) = STD.iter().find(|e| e.0 == sp) {
2574                let ps: Vec<String> = (1..=e.1).map(|i| format!("a{i}")).collect();
2575                let n = ps.len();
2576                return sig_json(
2577                    format!("std.{sp}({})", ps.join(", ")),
2578                    ps,
2579                    active.min(n.saturating_sub(1)),
2580                );
2581            }
2582        }
2583        return J::Null;
2584    }
2585    J::Null
2586}
2587
2588// ---------------- workspace symbols, selection ranges ----------------
2589fn workspace_symbols(st: &State, query: &str) -> J {
2590    let q = query.to_lowercase();
2591    let mut out: Vec<(String, String, J)> = vec![];
2592    let mut seen: Vec<PathBuf> = vec![];
2593    for a in st.last_good.values() {
2594        for m in &a.run.modules {
2595            if seen.contains(&m.path) {
2596                continue;
2597            }
2598            seen.push(m.path.clone());
2599            let text = text_of(st, m);
2600            for d in &m.decls {
2601                let (Some(_), Some(name), Some(kind)) = (d.loc, d.name(), symbol_kind(d)) else {
2602                    continue;
2603                };
2604                if !name.to_lowercase().contains(&q) {
2605                    continue;
2606                }
2607                out.push((
2608                    name.to_string(),
2609                    uri_of(&m.path),
2610                    J::obj(vec![
2611                        ("name", J::s(name)),
2612                        ("kind", J::Num(kind)),
2613                        ("location", location(m, name_range(&text, d, name))),
2614                    ]),
2615                ));
2616            }
2617        }
2618    }
2619    out.sort_by(|x, y| x.0.cmp(&y.0).then(x.1.cmp(&y.1)));
2620    J::Arr(out.into_iter().map(|x| x.2).collect())
2621}
2622fn pos_json(p: Pos) -> J {
2623    J::obj(vec![
2624        ("line", J::Num(p.line as i64)),
2625        ("character", J::Num(p.character as i64)),
2626    ])
2627}
2628fn selection_ranges(st: &State, uri: &str, positions: &[Pos]) -> J {
2629    let Some(text) = st.text(uri) else {
2630        return J::Arr(vec![]);
2631    };
2632    let parsed = parse_source(text);
2633    let point = |p: Pos| {
2634        J::obj(vec![(
2635            "range",
2636            J::obj(vec![("start", pos_json(p)), ("end", pos_json(p))]),
2637        )])
2638    };
2639    if !parsed.errors.is_empty() {
2640        return J::Arr(positions.iter().map(|p| point(*p)).collect());
2641    }
2642    J::Arr(
2643        positions
2644            .iter()
2645            .map(|p| {
2646                let Some(hit) = node_at(&parsed.decls, *p) else {
2647                    return point(*p);
2648                };
2649                // the chain, innermost first; rebuilt from the outside in
2650                let mut chain: Vec<Loc> = vec![hit.loc];
2651                chain.extend(hit.parents.iter().rev().filter_map(|n| n.loc()));
2652                let mut sel: Option<J> = None;
2653                for l in chain.iter().rev() {
2654                    sel = Some(match sel {
2655                        Some(s) => J::obj(vec![("range", range_json(*l)), ("parent", s)]),
2656                        None => J::obj(vec![("range", range_json(*l))]),
2657                    });
2658                }
2659                sel.unwrap_or_else(|| point(*p))
2660            })
2661            .collect(),
2662    )
2663}
2664
2665// ---------------- semantic tokens ----------------
2666const TOKEN_TYPES: [&str; 6] = [
2667    "type",
2668    "property",
2669    "function",
2670    "variable",
2671    "namespace",
2672    "parameter",
2673];
2674const TOKEN_MODS: [&str; 8] = [
2675    "declaration",
2676    "required",
2677    "optional",
2678    "defaulted",
2679    "derived",
2680    "hidden",
2681    "unresolved",
2682    "readonly",
2683];
2684const T_TYPE: i64 = 0;
2685const T_PROPERTY: i64 = 1;
2686const T_FUNCTION: i64 = 2;
2687const T_VARIABLE: i64 = 3;
2688const T_NAMESPACE: i64 = 4;
2689const T_PARAMETER: i64 = 5;
2690const M_DECLARATION: i64 = 1;
2691const M_REQUIRED: i64 = 2;
2692const M_OPTIONAL: i64 = 4;
2693const M_DEFAULTED: i64 = 8;
2694const M_DERIVED: i64 = 16;
2695const M_HIDDEN: i64 = 32;
2696const M_UNRESOLVED: i64 = 64;
2697const M_READONLY: i64 = 128;
2698fn member_mods(kind: MKind, hidden: bool) -> i64 {
2699    (match kind {
2700        MKind::Der => M_DERIVED,
2701        MKind::Dflt => M_DEFAULTED,
2702        MKind::Opt => M_OPTIONAL,
2703        MKind::Req => M_REQUIRED,
2704    }) | if hidden { M_HIDDEN } else { 0 }
2705}
2706fn member_kind_of(rt: Option<&RT>, name: &str) -> Option<(MKind, bool)> {
2707    let rt = rt?;
2708    let r: RT = match &rt.k {
2709        RTk::Pred { base, .. } => base.clone(),
2710        RTk::Ref(t) => t.clone(),
2711        _ => rt.clone(),
2712    };
2713    rec_members(&r)
2714        .into_iter()
2715        .find(|m| m.name == name)
2716        .map(|m| (m.kind, m.hidden))
2717}
2718fn param_loc(text: &str, decl: &Decl, name: &str) -> Option<Loc> {
2719    let l = decl.loc?;
2720    let line = text.split('\n').nth(l.sl).unwrap_or("");
2721    let open = find16(line, "(", l.sc)?;
2722    let re = Regex::new(&format!(r"\b{}\b", regex::escape(name))).unwrap();
2723    let m = re.find_at(line, byte_col(line, open))?;
2724    let a = u16_col(line, m.start());
2725    Some(Loc {
2726        sl: l.sl,
2727        sc: a,
2728        el: l.sl,
2729        ec: a + u16len(name),
2730    })
2731}
2732struct TokenWalk<'a> {
2733    st: &'a State,
2734    a: &'a Analysis,
2735    m: &'a Rc<Module>,
2736    text: String,
2737    t: Rc<Tables>,
2738    toks: Vec<(Loc, i64, i64)>,
2739}
2740impl<'a> TokenWalk<'a> {
2741    fn push(&mut self, l: Loc, ty: i64, mods: i64) {
2742        if l.sl == l.el && l.ec > l.sc {
2743            self.toks.push((l, ty, mods));
2744        }
2745    }
2746    fn decl(&mut self, d: &Decl) {
2747        let mut in_func: Vec<String> = vec![];
2748        if let (Some(_), Some(name)) = (d.loc, d.name()) {
2749            let r = name_range(&self.text, d, name);
2750            let ty = match &d.body {
2751                DeclBody::Type { .. } | DeclBody::Dimension { .. } | DeclBody::Unit { .. } => {
2752                    T_TYPE
2753                }
2754                DeclBody::Func { .. } | DeclBody::Diagnostic { .. } => T_FUNCTION,
2755                _ => T_VARIABLE,
2756            };
2757            self.push(
2758                r,
2759                ty,
2760                M_DECLARATION
2761                    | if matches!(d.body, DeclBody::Const { .. }) {
2762                        M_READONLY
2763                    } else {
2764                        0
2765                    },
2766            );
2767            if let DeclBody::Func { params, .. } = &d.body {
2768                in_func = params.iter().map(|p| p.name.clone()).collect();
2769                for p in params {
2770                    if let Some(pl) = param_loc(&self.text, d, &p.name) {
2771                        self.push(pl, T_PARAMETER, M_DECLARATION);
2772                    }
2773                }
2774            }
2775        }
2776        match &d.body {
2777            DeclBody::Type {
2778                params, ty, tail, ..
2779            } => {
2780                for p in params {
2781                    if let Some(t) = &p.ty {
2782                        self.ty(t, &in_func);
2783                    }
2784                }
2785                self.ty(ty, &in_func);
2786                if let Some(t) = tail {
2787                    self.tail(t, &in_func);
2788                }
2789            }
2790            DeclBody::Const { ty, expr, .. } => {
2791                if let Some(t) = ty {
2792                    self.ty(t, &in_func);
2793                }
2794                self.expr(expr, &in_func);
2795            }
2796            DeclBody::Func {
2797                params, ret, body, ..
2798            } => {
2799                for p in params {
2800                    if let Some(t) = &p.ty {
2801                        self.ty(t, &in_func);
2802                    }
2803                }
2804                if let Some(t) = ret {
2805                    self.ty(t, &in_func);
2806                }
2807                self.expr(body, &in_func);
2808            }
2809            DeclBody::Output { ty, expr, .. } => {
2810                self.ty(ty, &in_func);
2811                self.expr(expr, &in_func);
2812            }
2813            DeclBody::Input { ty, fallback, .. } => {
2814                self.ty(ty, &in_func);
2815                if let Some(f) = fallback {
2816                    self.expr(f, &in_func);
2817                }
2818            }
2819            DeclBody::Diagnostic {
2820                params, template, ..
2821            } => {
2822                for p in params {
2823                    if let Some(t) = &p.ty {
2824                        self.ty(t, &in_func);
2825                    }
2826                }
2827                self.template(template, &in_func);
2828            }
2829            DeclBody::Unit {
2830                factor: Some(f), ..
2831            } => {
2832                self.expr(f, &in_func);
2833            }
2834            _ => {}
2835        }
2836    }
2837    fn tail(&mut self, t: &Tail, in_func: &[String]) {
2838        match t {
2839            Tail::Inline { template, .. } => self.template(template, in_func),
2840            Tail::Ref { args, .. } => {
2841                for a in args {
2842                    self.expr(a, in_func);
2843                }
2844            }
2845        }
2846    }
2847    fn template(&mut self, parts: &[TPart], in_func: &[String]) {
2848        for p in parts {
2849            if let TPart::Expr(x) = p {
2850                self.expr(x, in_func);
2851            }
2852        }
2853    }
2854    fn ty(&mut self, t: &TypeAst, in_func: &[String]) {
2855        if let TypeAst::Named {
2856            name, loc: Some(l), ..
2857        } = t
2858        {
2859            let mut parts = name.splitn(2, '.');
2860            let head = parts.next().unwrap_or("");
2861            match parts.next() {
2862                Some(tail) => {
2863                    self.push(type_name_loc(*l, 0, head), T_NAMESPACE, 0);
2864                    self.push(type_name_loc(*l, u16len(head) + 1, tail), T_TYPE, 0);
2865                }
2866                None => {
2867                    if !["map", "ref", "quantity"].contains(&head) {
2868                        let mods = if resolve_in(&self.m.env, head).is_some() {
2869                            0
2870                        } else {
2871                            M_UNRESOLVED
2872                        };
2873                        self.push(type_name_loc(*l, 0, head), T_TYPE, mods);
2874                    }
2875                }
2876            }
2877        }
2878        match t {
2879            TypeAst::Record { members, .. } => {
2880                for m in members {
2881                    self.member(m, in_func);
2882                }
2883            }
2884            TypeAst::Map { key, val, .. } => {
2885                self.ty(key, in_func);
2886                self.ty(val, in_func);
2887            }
2888            TypeAst::Array { elem, .. } => self.ty(elem, in_func),
2889            TypeAst::Union { arms, .. } | TypeAst::Isect { arms, .. } => {
2890                for a in arms {
2891                    self.ty(a, in_func);
2892                }
2893            }
2894            TypeAst::Func { params, ret, .. } => {
2895                for a in params {
2896                    self.ty(a, in_func);
2897                }
2898                self.ty(ret, in_func);
2899            }
2900            TypeAst::Named {
2901                args, preds, ext, ..
2902            } => {
2903                for a in args {
2904                    self.ty(a, in_func);
2905                }
2906                for x in preds.iter().flatten() {
2907                    self.expr(x, in_func);
2908                }
2909                if let Some(x) = ext {
2910                    self.ty(x, in_func);
2911                }
2912            }
2913            _ => {}
2914        }
2915    }
2916    fn member(&mut self, m: &MemberAst, in_func: &[String]) {
2917        match m {
2918            MemberAst::Value {
2919                name,
2920                opt,
2921                dflt,
2922                loc: Some(_),
2923                ..
2924            } => {
2925                let kind = if dflt.is_some() {
2926                    MKind::Dflt
2927                } else if *opt {
2928                    MKind::Opt
2929                } else {
2930                    MKind::Req
2931                };
2932                let r = member_range(&self.text, m, name);
2933                self.push(r, T_PROPERTY, M_DECLARATION | member_mods(kind, false));
2934            }
2935            MemberAst::Derived {
2936                name,
2937                hidden,
2938                loc: Some(_),
2939                ..
2940            } => {
2941                let r = member_range(&self.text, m, name);
2942                self.push(
2943                    r,
2944                    T_PROPERTY,
2945                    M_DECLARATION | member_mods(MKind::Der, *hidden),
2946                );
2947            }
2948            _ => {}
2949        }
2950        match m {
2951            MemberAst::Value { ty, dflt, .. } => {
2952                self.ty(ty, in_func);
2953                if let Some(d) = dflt {
2954                    self.expr(d, in_func);
2955                }
2956            }
2957            MemberAst::Derived { ty, expr, .. } => {
2958                if let Some(t) = ty {
2959                    self.ty(t, in_func);
2960                }
2961                self.expr(expr, in_func);
2962            }
2963            MemberAst::Context { ty, .. } => self.ty(ty, in_func),
2964            MemberAst::Assert { cond, tail, .. } => {
2965                self.expr(cond, in_func);
2966                if let Some(t) = tail {
2967                    self.tail(t, in_func);
2968                }
2969            }
2970            MemberAst::When { cond, body, .. } => {
2971                self.expr(cond, in_func);
2972                for b in body {
2973                    self.member(b, in_func);
2974                }
2975            }
2976        }
2977    }
2978    fn expr(&mut self, e: &Rc<Expr>, in_func: &[String]) {
2979        let mut scope: Vec<String> = in_func.to_vec();
2980        if let Some(l) = expr_loc(e) {
2981            match &**e {
2982                Expr::Name(n) => {
2983                    let target = match self.t.res.get(&key_of(e)) {
2984                        Some(r) => r.clone(),
2985                        None => resolve_in(&self.m.env, n),
2986                    };
2987                    if n == "std" {
2988                        self.push(l, T_NAMESPACE, 0);
2989                    } else if in_func.contains(n)
2990                        || target.as_ref().map(|t| t.kind == "var").unwrap_or(false)
2991                    {
2992                        self.push(l, T_PARAMETER, 0);
2993                    } else {
2994                        match target {
2995                            None => self.push(l, T_VARIABLE, M_UNRESOLVED),
2996                            Some(t) => {
2997                                let ty = match t.kind {
2998                                    "func" => T_FUNCTION,
2999                                    "namespace" => T_NAMESPACE,
3000                                    "type" => T_TYPE,
3001                                    _ => T_VARIABLE,
3002                                };
3003                                self.push(l, ty, if t.kind == "const" { M_READONLY } else { 0 });
3004                            }
3005                        }
3006                    }
3007                }
3008                Expr::Member { x, name, .. } => {
3009                    let ml = member_token_loc(&self.text, e, name);
3010                    if let Some(sp) = std_path(e) {
3011                        self.push(
3012                            ml,
3013                            if STD.iter().any(|s| s.0 == sp) {
3014                                T_FUNCTION
3015                            } else {
3016                                T_NAMESPACE
3017                            },
3018                            0,
3019                        );
3020                    } else if let Expr::Name(xn) = &**x {
3021                        if self.m.env.namespaces.borrow().contains_key(xn) {
3022                            let nss = self.m.env.namespaces.borrow();
3023                            let ex = nss
3024                                .get(xn)
3025                                .and_then(|(_, exports)| exports.borrow().get(name).cloned());
3026                            let tg = ex.and_then(|ex| resolve_in(&ex.env, &ex.name));
3027                            let ty = match tg.as_ref().map(|t| t.kind) {
3028                                Some("func") => T_FUNCTION,
3029                                Some("type") => T_TYPE,
3030                                _ => T_VARIABLE,
3031                            };
3032                            self.push(ml, ty, if tg.is_some() { 0 } else { M_UNRESOLVED });
3033                        } else {
3034                            let mk = member_kind_of(
3035                                self.t.types.get(&key_of(x)).and_then(|t| t.rt.as_ref()),
3036                                name,
3037                            );
3038                            self.push(
3039                                ml,
3040                                T_PROPERTY,
3041                                mk.map(|(k, h)| member_mods(k, h)).unwrap_or(0),
3042                            );
3043                        }
3044                    } else {
3045                        let mk = member_kind_of(
3046                            self.t.types.get(&key_of(x)).and_then(|t| t.rt.as_ref()),
3047                            name,
3048                        );
3049                        self.push(
3050                            ml,
3051                            T_PROPERTY,
3052                            mk.map(|(k, h)| member_mods(k, h)).unwrap_or(0),
3053                        );
3054                    }
3055                }
3056                Expr::Lambda { params, .. } => scope.extend(params.iter().cloned()),
3057                Expr::Comp { clauses, .. } | Expr::MapComp { clauses, .. } => {
3058                    scope.extend(clauses.iter().map(|c| c.v.clone()))
3059                }
3060                _ => {}
3061            }
3062        }
3063        let s = &scope;
3064        match &**e {
3065            Expr::Template(parts) => self.template(parts, s),
3066            Expr::Obj(entries) => {
3067                for (_, v) in entries {
3068                    self.expr(v, s);
3069                }
3070            }
3071            Expr::Arr(items) => {
3072                for (_, v) in items {
3073                    self.expr(v, s);
3074                }
3075            }
3076            Expr::Comp { head, clauses } => {
3077                self.expr(head, s);
3078                for c in clauses {
3079                    self.expr(&c.iter, s);
3080                    for f in &c.filters {
3081                        self.expr(f, s);
3082                    }
3083                }
3084            }
3085            Expr::MapComp { key, val, clauses } => {
3086                self.expr(key, s);
3087                self.expr(val, s);
3088                for c in clauses {
3089                    self.expr(&c.iter, s);
3090                    for f in &c.filters {
3091                        self.expr(f, s);
3092                    }
3093                }
3094            }
3095            Expr::Bin { l, r, .. } => {
3096                self.expr(l, s);
3097                self.expr(r, s);
3098            }
3099            Expr::Un { x, .. } | Expr::Paren(x) => self.expr(x, s),
3100            Expr::If { c, t, f } => {
3101                self.expr(c, s);
3102                self.expr(t, s);
3103                self.expr(f, s);
3104            }
3105            Expr::Lambda { body, .. } => self.expr(body, s),
3106            Expr::Call { fun, args } => {
3107                self.expr(fun, s);
3108                for a in args {
3109                    self.expr(a, s);
3110                }
3111            }
3112            Expr::Member { x, .. } => self.expr(x, s),
3113            Expr::Index { x, i } => {
3114                self.expr(x, s);
3115                self.expr(i, s);
3116            }
3117            Expr::With { base, patch } => {
3118                self.expr(base, s);
3119                self.expr(patch, s);
3120            }
3121            Expr::Match { subject, arms } => {
3122                self.expr(subject, s);
3123                for a in arms {
3124                    if let Some(t) = &a.ty {
3125                        self.ty(t, s);
3126                    }
3127                    self.expr(&a.body, s);
3128                }
3129            }
3130            _ => {}
3131        }
3132    }
3133}
3134fn semantic_tokens(st: &mut State, uri: &str) -> J {
3135    let empty = J::obj(vec![("data", J::Arr(vec![]))]);
3136    let Some(a) = st.analysis_of(uri) else {
3137        return empty;
3138    };
3139    let Some(m) = module_of(&a, &path_of(uri)) else {
3140        return empty;
3141    };
3142    let t = tables_of(&a, &m);
3143    let text = text_of(st, &m);
3144    let mut w = TokenWalk {
3145        st,
3146        a: &a,
3147        m: &m,
3148        text,
3149        t,
3150        toks: vec![],
3151    };
3152    let _ = w.st;
3153    let _ = w.a;
3154    for d in &m.decls {
3155        w.decl(d);
3156    }
3157    let mut toks = w.toks;
3158    toks.sort_by(|p, q| p.0.sl.cmp(&q.0.sl).then(p.0.sc.cmp(&q.0.sc)));
3159    let mut data: Vec<J> = vec![];
3160    let (mut pl, mut pc) = (0usize, 0usize);
3161    for (l, ty, mods) in toks {
3162        let dl = l.sl - pl;
3163        let dc: i64 = if dl == 0 {
3164            l.sc as i64 - pc as i64
3165        } else {
3166            l.sc as i64
3167        };
3168        if dl == 0 && dc < 0 {
3169            continue; // overlapping tokens: the first wins
3170        }
3171        data.extend([
3172            J::Num(dl as i64),
3173            J::Num(dc),
3174            J::Num((l.ec - l.sc) as i64),
3175            J::Num(ty),
3176            J::Num(mods),
3177        ]);
3178        pl = l.sl;
3179        pc = l.sc;
3180    }
3181    J::obj(vec![("data", J::Arr(data))])
3182}
3183
3184// ---------------- inlay hints ----------------
3185struct HintWalk<'a> {
3186    st: &'a State,
3187    a: &'a Analysis,
3188    m: &'a Rc<Module>,
3189    text: String,
3190    t: Rc<Tables>,
3191    range: (Pos, Pos),
3192    /// types, parameter names, units, values, context variables
3193    hints: (bool, bool, bool, bool, bool),
3194    out: Vec<(Pos, J)>,
3195}
3196impl<'a> HintWalk<'a> {
3197    fn in_range(&self, p: Pos) -> bool {
3198        p.line >= self.range.0.line && p.line <= self.range.1.line
3199    }
3200    fn hint(&mut self, p: Pos, label: String, kind: Option<i64>, pad_left: bool, pad_right: bool) {
3201        let mut item = vec![("position", pos_json(p)), ("label", J::s(label))];
3202        if let Some(k) = kind {
3203            item.push(("kind", J::Num(k)));
3204        }
3205        if pad_right {
3206            item.push(("paddingRight", J::Bool(true)));
3207        }
3208        if pad_left {
3209            item.push(("paddingLeft", J::Bool(true)));
3210        }
3211        self.out.push((p, J::obj(item)));
3212    }
3213    fn decl(&mut self, d: &Decl) {
3214        if let DeclBody::Const {
3215            name,
3216            ty: None,
3217            expr,
3218        } = &d.body
3219        {
3220            if self.hints.0 && d.loc.is_some() {
3221                if let Some(rt) = self.t.types.get(&key_of(expr)).and_then(|t| t.rt.clone()) {
3222                    let r = name_range(&self.text, d, name);
3223                    let p = Pos {
3224                        line: r.el,
3225                        character: r.ec,
3226                    };
3227                    if self.in_range(p) {
3228                        self.hint(
3229                            p,
3230                            format!(": {}", type_text(Some(&rt))),
3231                            Some(1),
3232                            false,
3233                            false,
3234                        );
3235                    }
3236                }
3237            }
3238        }
3239        if let DeclBody::Output { name, expr, .. } = &d.body {
3240            if self.hints.3 && d.loc.is_some() {
3241                if let (Some(eng), Some(entry)) = (self.a.run.eng.clone(), self.a.run.entry.clone())
3242                {
3243                    let root = entry
3244                        .env
3245                        .roots
3246                        .borrow()
3247                        .borrow()
3248                        .iter()
3249                        .find(|(n, _)| n == name)
3250                        .map(|(_, v)| v.clone());
3251                    if let Some(v) = root {
3252                        self.values(&v, expr, name, &eng);
3253                    }
3254                }
3255            }
3256        }
3257        match &d.body {
3258            DeclBody::Type {
3259                params, ty, tail, ..
3260            } => {
3261                for p in params {
3262                    if let Some(t) = &p.ty {
3263                        self.ty(t);
3264                    }
3265                }
3266                self.ty(ty);
3267                if let Some(t) = tail {
3268                    self.tail(t);
3269                }
3270            }
3271            DeclBody::Const { ty, expr, .. } => {
3272                if let Some(t) = ty {
3273                    self.ty(t);
3274                }
3275                self.expr(expr);
3276            }
3277            DeclBody::Func {
3278                params, ret, body, ..
3279            } => {
3280                for p in params {
3281                    if let Some(t) = &p.ty {
3282                        self.ty(t);
3283                    }
3284                }
3285                if let Some(t) = ret {
3286                    self.ty(t);
3287                }
3288                self.expr(body);
3289            }
3290            DeclBody::Output { ty, expr, .. } => {
3291                self.ty(ty);
3292                self.expr(expr);
3293            }
3294            DeclBody::Input { ty, fallback, .. } => {
3295                self.ty(ty);
3296                if let Some(f) = fallback {
3297                    self.expr(f);
3298                }
3299            }
3300            DeclBody::Diagnostic {
3301                params, template, ..
3302            } => {
3303                for p in params {
3304                    if let Some(t) = &p.ty {
3305                        self.ty(t);
3306                    }
3307                }
3308                self.template(template);
3309            }
3310            DeclBody::Unit {
3311                factor: Some(f), ..
3312            } => {
3313                self.expr(f);
3314            }
3315            _ => {}
3316        }
3317    }
3318    // the evaluated derived members of a literal, at the end of the literal
3319    fn values(&mut self, v: &Value, e: &Rc<Expr>, root: &str, eng: &crate::engine::Engine) {
3320        match (v, &**e) {
3321            (Value::Rec(inst), Expr::Obj(entries)) => {
3322                let inst = inst.borrow();
3323                let mut parts: Vec<String> = vec![];
3324                for mem in rec_members(&inst.rt) {
3325                    let Some((_, s)) = inst.slots.iter().find(|(n, _)| *n == mem.name) else {
3326                        continue;
3327                    };
3328                    if mem.kind != MKind::Der || s.hidden || s.state != SlotState::Ok {
3329                        continue;
3330                    }
3331                    let txt = eng.serialize(&s.value, root, false);
3332                    let shown = if u16len(&txt) > 40 {
3333                        format!("{}…", slice16(&txt, 0, 37))
3334                    } else {
3335                        txt
3336                    };
3337                    parts.push(format!("{} = {}", mem.name, shown));
3338                }
3339                if let Some(l) = expr_loc(e) {
3340                    let p = Pos {
3341                        line: l.el,
3342                        character: l.ec,
3343                    };
3344                    if !parts.is_empty() && self.in_range(p) {
3345                        self.hint(p, format!("// {}", parts.join(", ")), None, true, false);
3346                    }
3347                }
3348                let children: Vec<(Value, Rc<Expr>)> = entries
3349                    .iter()
3350                    .filter_map(|(k, val)| {
3351                        inst.slots
3352                            .iter()
3353                            .find(|(n, _)| n == k)
3354                            .filter(|(_, s)| s.state == SlotState::Ok)
3355                            .map(|(_, s)| (s.value.clone(), val.clone()))
3356                    })
3357                    .collect();
3358                drop(inst);
3359                for (cv, ce) in children {
3360                    self.values(&cv, &ce, root, eng);
3361                }
3362            }
3363            (Value::Arr(arr), Expr::Arr(items)) => {
3364                let vals: Vec<Value> = arr.borrow().items.clone();
3365                for (i, it) in vals.iter().enumerate() {
3366                    if let Some((_, ce)) = items.get(i) {
3367                        self.values(it, ce, root, eng);
3368                    }
3369                }
3370            }
3371            (Value::Map(map), Expr::Obj(entries)) => {
3372                for (k, val) in entries {
3373                    let cv = map.borrow().get(k).cloned();
3374                    if let Some(cv) = cv {
3375                        self.values(&cv, val, root, eng);
3376                    }
3377                }
3378            }
3379            _ => {}
3380        }
3381    }
3382    fn tail(&mut self, t: &Tail) {
3383        match t {
3384            Tail::Inline { template, .. } => self.template(template),
3385            Tail::Ref { args, .. } => {
3386                for a in args {
3387                    self.expr(a);
3388                }
3389            }
3390        }
3391    }
3392    fn template(&mut self, parts: &[TPart]) {
3393        for p in parts {
3394            if let TPart::Expr(x) = p {
3395                self.expr(x);
3396            }
3397        }
3398    }
3399    fn ty(&mut self, t: &TypeAst) {
3400        match t {
3401            TypeAst::Record { members, .. } => {
3402                for m in members {
3403                    self.member(m);
3404                }
3405            }
3406            TypeAst::Map { key, val, .. } => {
3407                self.ty(key);
3408                self.ty(val);
3409            }
3410            TypeAst::Array { elem, .. } => self.ty(elem),
3411            TypeAst::Union { arms, .. } | TypeAst::Isect { arms, .. } => {
3412                for a in arms {
3413                    self.ty(a);
3414                }
3415            }
3416            TypeAst::Func { params, ret, .. } => {
3417                for a in params {
3418                    self.ty(a);
3419                }
3420                self.ty(ret);
3421            }
3422            TypeAst::Named {
3423                args, preds, ext, ..
3424            } => {
3425                for a in args {
3426                    self.ty(a);
3427                }
3428                for x in preds.iter().flatten() {
3429                    self.expr(x);
3430                }
3431                if let Some(x) = ext {
3432                    self.ty(x);
3433                }
3434            }
3435            _ => {}
3436        }
3437    }
3438    fn member(&mut self, m: &MemberAst) {
3439        if let MemberAst::Derived {
3440            name,
3441            ty: None,
3442            expr,
3443            hidden,
3444            loc: Some(_),
3445        } = m
3446        {
3447            if self.hints.0 {
3448                if let Some(rt) = self.t.types.get(&key_of(expr)).and_then(|t| t.rt.clone()) {
3449                    let r = member_range(&self.text, m, name);
3450                    let p = Pos {
3451                        line: r.el,
3452                        character: r.ec + if *hidden { 1 } else { 0 },
3453                    };
3454                    if self.in_range(p) {
3455                        self.hint(
3456                            p,
3457                            format!(": {}", type_text(Some(&rt))),
3458                            Some(1),
3459                            false,
3460                            false,
3461                        );
3462                    }
3463                }
3464            }
3465        }
3466        match m {
3467            MemberAst::Value { ty, dflt, .. } => {
3468                self.ty(ty);
3469                if let Some(d) = dflt {
3470                    self.expr(d);
3471                }
3472            }
3473            MemberAst::Derived { ty, expr, .. } => {
3474                if let Some(t) = ty {
3475                    self.ty(t);
3476                }
3477                self.expr(expr);
3478            }
3479            MemberAst::Context { ty, .. } => self.ty(ty),
3480            MemberAst::Assert { cond, tail, .. } => {
3481                self.expr(cond);
3482                if let Some(t) = tail {
3483                    self.tail(t);
3484                }
3485            }
3486            MemberAst::When { cond, body, .. } => {
3487                self.expr(cond);
3488                for b in body {
3489                    self.member(b);
3490                }
3491            }
3492        }
3493    }
3494    fn expr(&mut self, e: &Rc<Expr>) {
3495        match &**e {
3496            Expr::Call { fun, args } if self.hints.1 => {
3497                if let Expr::Name(n) = &**fun {
3498                    let target = match self.t.res.get(&key_of(fun)) {
3499                        Some(r) => r.clone(),
3500                        None => resolve_in(&self.m.env, n),
3501                    };
3502                    if let Some(site) = site_of_target(self.st, self.a, target.as_ref()) {
3503                        let sm = site.module.clone();
3504                        if let Some(DeclBody::Func { params, .. }) =
3505                            decl_by_id(&sm, site.decl).map(|d| &d.body)
3506                        {
3507                            for (i, arg) in args.iter().enumerate() {
3508                                if let (Some(p), Some(al)) = (params.get(i), expr_loc(arg)) {
3509                                    let pos = Pos {
3510                                        line: al.sl,
3511                                        character: al.sc,
3512                                    };
3513                                    if self.in_range(pos) {
3514                                        self.hint(
3515                                            pos,
3516                                            format!("{}:", p.name),
3517                                            Some(2),
3518                                            false,
3519                                            true,
3520                                        );
3521                                    }
3522                                }
3523                            }
3524                        }
3525                    }
3526                }
3527            }
3528            Expr::Ctx(name)
3529                if self.hints.4 && (name == "$parent" || name == "$root" || name == "$key") =>
3530            {
3531                // the bound the enclosing type declares for the variable
3532                if let Some(l) = expr_loc(e) {
3533                    let decl = self.m.decls.iter().find(|d| {
3534                        matches!(d.body, DeclBody::Type { .. })
3535                            && d.loc
3536                                .map(|dl| dl.sl <= l.sl && l.el <= dl.el)
3537                                .unwrap_or(false)
3538                    });
3539                    let tl =
3540                        decl.and_then(record_body_of)
3541                            .map(record_members)
3542                            .and_then(|members| {
3543                                members.iter().find_map(|mm| match mm {
3544                                    MemberAst::Context { variable, ty, .. } if variable == name => {
3545                                        ty.loc()
3546                                    }
3547                                    _ => None,
3548                                })
3549                            });
3550                    let p = Pos {
3551                        line: l.el,
3552                        character: l.ec,
3553                    };
3554                    if let Some(tl) = tl {
3555                        if self.in_range(p) {
3556                            self.hint(
3557                                p,
3558                                format!(": {}", src_of(&self.text, tl)),
3559                                Some(1),
3560                                false,
3561                                false,
3562                            );
3563                        }
3564                    }
3565                }
3566            }
3567            Expr::UnitLit { num, unit } if self.hints.2 => {
3568                if let (Ok((key, to_base)), Some(l)) = (self.m.env.unit_info(unit), expr_loc(e)) {
3569                    let base = self
3570                        .m
3571                        .env
3572                        .base_unit_of
3573                        .borrow()
3574                        .get(&key)
3575                        .cloned()
3576                        .unwrap_or(key);
3577                    let p = Pos {
3578                        line: l.el,
3579                        character: l.ec,
3580                    };
3581                    if base != *unit && self.in_range(p) {
3582                        self.hint(
3583                            p,
3584                            format!("= {} {}", crate::semantics::js_num_str(num * to_base), base),
3585                            None,
3586                            true,
3587                            false,
3588                        );
3589                    }
3590                }
3591            }
3592            _ => {}
3593        }
3594        match &**e {
3595            Expr::Template(parts) => self.template(parts),
3596            Expr::Obj(entries) => {
3597                for (_, v) in entries {
3598                    self.expr(v);
3599                }
3600            }
3601            Expr::Arr(items) => {
3602                for (_, v) in items {
3603                    self.expr(v);
3604                }
3605            }
3606            Expr::Comp { head, clauses } => {
3607                self.expr(head);
3608                for c in clauses {
3609                    self.expr(&c.iter);
3610                    for f in &c.filters {
3611                        self.expr(f);
3612                    }
3613                }
3614            }
3615            Expr::MapComp { key, val, clauses } => {
3616                self.expr(key);
3617                self.expr(val);
3618                for c in clauses {
3619                    self.expr(&c.iter);
3620                    for f in &c.filters {
3621                        self.expr(f);
3622                    }
3623                }
3624            }
3625            Expr::Bin { l, r, .. } => {
3626                self.expr(l);
3627                self.expr(r);
3628            }
3629            Expr::Un { x, .. } | Expr::Paren(x) => self.expr(x),
3630            Expr::If { c, t, f } => {
3631                self.expr(c);
3632                self.expr(t);
3633                self.expr(f);
3634            }
3635            Expr::Lambda { body, .. } => self.expr(body),
3636            Expr::Call { fun, args } => {
3637                self.expr(fun);
3638                for a in args {
3639                    self.expr(a);
3640                }
3641            }
3642            Expr::Member { x, .. } => self.expr(x),
3643            Expr::Index { x, i } => {
3644                self.expr(x);
3645                self.expr(i);
3646            }
3647            Expr::With { base, patch } => {
3648                self.expr(base);
3649                self.expr(patch);
3650            }
3651            Expr::Match { subject, arms } => {
3652                self.expr(subject);
3653                for a in arms {
3654                    if let Some(t) = &a.ty {
3655                        self.ty(t);
3656                    }
3657                    self.expr(&a.body);
3658                }
3659            }
3660            _ => {}
3661        }
3662    }
3663}
3664fn inlay_hints(st: &mut State, uri: &str, range: (Pos, Pos)) -> J {
3665    let Some(a) = st.analysis_of(uri) else {
3666        return J::Arr(vec![]);
3667    };
3668    let Some(m) = module_of(&a, &path_of(uri)) else {
3669        return J::Arr(vec![]);
3670    };
3671    let t = tables_of(&a, &m);
3672    let text = text_of(st, &m);
3673    let hints = (
3674        st.hint_types,
3675        st.hint_parameter_names,
3676        st.hint_units,
3677        st.hint_values,
3678        st.hint_context_variables,
3679    );
3680    let mut w = HintWalk {
3681        st,
3682        a: &a,
3683        m: &m,
3684        text,
3685        t,
3686        range,
3687        hints,
3688        out: vec![],
3689    };
3690    for d in &m.decls {
3691        w.decl(d);
3692    }
3693    let mut out = w.out;
3694    out.sort_by(|p, q| {
3695        p.0.line
3696            .cmp(&q.0.line)
3697            .then(p.0.character.cmp(&q.0.character))
3698    });
3699    J::Arr(out.into_iter().map(|x| x.1).collect())
3700}
3701
3702// ---------------- hierarchies ----------------
3703fn hierarchy_item(m: &Module, d: &Decl, text: &str) -> J {
3704    let name = d.name().unwrap_or("");
3705    J::obj(vec![
3706        ("name", J::s(name)),
3707        ("kind", J::Num(symbol_kind(d).unwrap_or(13))),
3708        ("uri", J::s(uri_of(&m.path))),
3709        ("range", range_json(d.loc.unwrap())),
3710        ("selectionRange", range_json(name_range(text, d, name))),
3711    ])
3712}
3713fn prepare_hierarchy(st: &mut State, uri: &str, pos: Pos, want: &str) -> J {
3714    let Some(a) = st.analysis_of(uri) else {
3715        return J::Null;
3716    };
3717    let Some(site) = site_at(st, &a, uri, pos).and_then(|s| s.site) else {
3718        return J::Null;
3719    };
3720    if site.decl.is_none() || site.kind != want {
3721        return J::Null;
3722    }
3723    let sm = site.module.clone();
3724    let Some(decl) = decl_by_id(&sm, site.decl) else {
3725        return J::Null;
3726    };
3727    J::Arr(vec![hierarchy_item(&sm, decl, &text_of(st, &sm))])
3728}
3729fn module_of_uri(st: &State, uri: &str) -> Option<(Rc<Analysis>, Rc<Module>)> {
3730    for a in st.last_good.values() {
3731        if let Some(m) = a.run.modules.iter().find(|x| uri_of(&x.path) == uri) {
3732            return Some((a.clone(), m.clone()));
3733        }
3734    }
3735    None
3736}
3737fn decl_containing(m: &Module, loc: Loc) -> Option<&Decl> {
3738    m.decls.iter().find(|d| {
3739        d.loc
3740            .map(|dl| dl.sl <= loc.sl && loc.el <= dl.el)
3741            .unwrap_or(false)
3742            && d.name().is_some()
3743    })
3744}
3745fn item_uri_line(item: Option<&Value>) -> Option<(String, usize)> {
3746    let item = item?;
3747    let uri = as_str(get(item, "uri"))?.to_string();
3748    let line = as_usize(
3749        get(item, "range")
3750            .and_then(|r| get(r, "start"))
3751            .and_then(|s| get(s, "line")),
3752    )?;
3753    Some((uri, line))
3754}
3755fn incoming_calls(st: &State, item: Option<&Value>) -> J {
3756    let Some((uri, line)) = item_uri_line(item) else {
3757        return J::Arr(vec![]);
3758    };
3759    let Some((a, _)) = module_of_uri(st, &uri) else {
3760        return J::Arr(vec![]);
3761    };
3762    let mut out: Vec<J> = vec![];
3763    for m in a.run.modules.clone() {
3764        let t = tables_of(&a, &m);
3765        let text = text_of(st, &m);
3766        let mut by_caller: Vec<(usize, Vec<Loc>)> = vec![];
3767        for d in &m.decls {
3768            let mut exprs: Vec<Rc<Expr>> = vec![];
3769            let mut types: Vec<&TypeAst> = vec![];
3770            collect_decl(d, &mut exprs, &mut types);
3771            for x in &exprs {
3772                let Expr::Call { fun, .. } = &**x else {
3773                    continue;
3774                };
3775                let Expr::Name(n) = &**fun else { continue };
3776                let Some(fl) = expr_loc(fun) else { continue };
3777                let tg = match t.res.get(&key_of(fun)) {
3778                    Some(r) => r.clone(),
3779                    None => resolve_in(&m.env, n),
3780                };
3781                let Some(site) = site_of_target(st, &a, tg.as_ref()) else {
3782                    continue;
3783                };
3784                if site.decl.is_none()
3785                    || uri_of(&site.module.path) != uri
3786                    || site.decl_loc.map(|l| l.sl) != Some(line)
3787                {
3788                    continue;
3789                }
3790                if let Some(caller) = decl_containing(&m, fl) {
3791                    let id = decl_id(caller);
3792                    match by_caller.iter_mut().find(|(k, _)| *k == id) {
3793                        Some(e) => e.1.push(fl),
3794                        None => by_caller.push((id, vec![fl])),
3795                    }
3796                }
3797            }
3798        }
3799        for (id, locs) in by_caller {
3800            let Some(caller) = decl_by_id(&m, Some(id)) else {
3801                continue;
3802            };
3803            out.push(J::obj(vec![
3804                ("from", hierarchy_item(&m, caller, &text)),
3805                (
3806                    "fromRanges",
3807                    J::Arr(locs.into_iter().map(range_json).collect()),
3808                ),
3809            ]));
3810        }
3811    }
3812    J::Arr(out)
3813}
3814fn outgoing_calls(st: &State, item: Option<&Value>) -> J {
3815    let Some((uri, line)) = item_uri_line(item) else {
3816        return J::Arr(vec![]);
3817    };
3818    let Some((a, m)) = module_of_uri(st, &uri) else {
3819        return J::Arr(vec![]);
3820    };
3821    let t = tables_of(&a, &m);
3822    let Some(decl) = m.decls.iter().find(|d| d.loc.map(|l| l.sl) == Some(line)) else {
3823        return J::Arr(vec![]);
3824    };
3825    let mut exprs: Vec<Rc<Expr>> = vec![];
3826    let mut types: Vec<&TypeAst> = vec![];
3827    collect_decl(decl, &mut exprs, &mut types);
3828    let mut by_callee: Vec<(String, J, Vec<Loc>)> = vec![];
3829    for x in &exprs {
3830        let Expr::Call { fun, .. } = &**x else {
3831            continue;
3832        };
3833        let Expr::Name(n) = &**fun else { continue };
3834        let Some(fl) = expr_loc(fun) else { continue };
3835        let tg = match t.res.get(&key_of(fun)) {
3836            Some(r) => r.clone(),
3837            None => resolve_in(&m.env, n),
3838        };
3839        let Some(site) = site_of_target(st, &a, tg.as_ref()) else {
3840            continue;
3841        };
3842        if site.kind != "func" {
3843            continue;
3844        }
3845        let sm = site.module.clone();
3846        let Some(callee) = decl_by_id(&sm, site.decl) else {
3847            continue;
3848        };
3849        let key = format!(
3850            "{}:{}",
3851            sm.path.to_string_lossy(),
3852            callee.loc.map(|l| l.sl).unwrap_or(0)
3853        );
3854        match by_callee.iter_mut().find(|(k, _, _)| *k == key) {
3855            Some(e) => e.2.push(fl),
3856            None => by_callee.push((
3857                key,
3858                hierarchy_item(&sm, callee, &text_of(st, &sm)),
3859                vec![fl],
3860            )),
3861        }
3862    }
3863    J::Arr(
3864        by_callee
3865            .into_iter()
3866            .map(|(_, to, locs)| {
3867                J::obj(vec![
3868                    ("to", to),
3869                    (
3870                        "fromRanges",
3871                        J::Arr(locs.into_iter().map(range_json).collect()),
3872                    ),
3873                ])
3874            })
3875            .collect(),
3876    )
3877}
3878fn supertypes(st: &State, item: Option<&Value>) -> J {
3879    let Some((uri, line)) = item_uri_line(item) else {
3880        return J::Arr(vec![]);
3881    };
3882    let Some((a, m)) = module_of_uri(st, &uri) else {
3883        return J::Arr(vec![]);
3884    };
3885    let base = m.decls.iter().find_map(|d| match &d.body {
3886        DeclBody::Type {
3887            ty: TypeAst::Named {
3888                name, ext: Some(_), ..
3889            },
3890            ..
3891        } if d.loc.map(|l| l.sl) == Some(line) => Some(name.clone()),
3892        _ => None,
3893    });
3894    let Some(base) = base else {
3895        return J::Arr(vec![]);
3896    };
3897    let Some(site) = site_of_target(st, &a, resolve_in(&m.env, &base).as_ref()) else {
3898        return J::Arr(vec![]);
3899    };
3900    let sm = site.module.clone();
3901    match decl_by_id(&sm, site.decl) {
3902        Some(d) => J::Arr(vec![hierarchy_item(&sm, d, &text_of(st, &sm))]),
3903        None => J::Arr(vec![]),
3904    }
3905}
3906fn subtypes(st: &State, item: Option<&Value>) -> J {
3907    let Some((uri, line)) = item_uri_line(item) else {
3908        return J::Arr(vec![]);
3909    };
3910    let Some((a, _)) = module_of_uri(st, &uri) else {
3911        return J::Arr(vec![]);
3912    };
3913    let mut out: Vec<J> = vec![];
3914    for m in a.run.modules.clone() {
3915        for d in &m.decls {
3916            let DeclBody::Type {
3917                ty: TypeAst::Named {
3918                    name, ext: Some(_), ..
3919                },
3920                ..
3921            } = &d.body
3922            else {
3923                continue;
3924            };
3925            if d.loc.is_none() {
3926                continue;
3927            }
3928            let Some(site) = site_of_target(st, &a, resolve_in(&m.env, name).as_ref()) else {
3929                continue;
3930            };
3931            if site.decl.is_some()
3932                && uri_of(&site.module.path) == uri
3933                && site.decl_loc.map(|l| l.sl) == Some(line)
3934            {
3935                out.push(hierarchy_item(&m, d, &text_of(st, &m)));
3936            }
3937        }
3938    }
3939    J::Arr(out)
3940}
3941
3942// ---------------- code actions ----------------
3943fn js_value_string(v: &Value) -> String {
3944    match v {
3945        Value::Str(s) => json_str(s),
3946        Value::Int(i) => i.to_string(),
3947        Value::Float(f) => crate::semantics::js_num_str(*f),
3948        Value::Bool(b) => b.to_string(),
3949        _ => "null".into(),
3950    }
3951}
3952// the record body of a type declaration: its own, or its extension's
3953trait ConstExprLoc {
3954    fn const_expr_loc(&self) -> Option<Loc>;
3955}
3956impl ConstExprLoc for DeclBody {
3957    fn const_expr_loc(&self) -> Option<Loc> {
3958        match self {
3959            DeclBody::Const { expr, .. } => expr_loc(expr),
3960            _ => None,
3961        }
3962    }
3963}
3964fn record_body_of(d: &Decl) -> Option<&TypeAst> {
3965    let DeclBody::Type { ty, .. } = &d.body else {
3966        return None;
3967    };
3968    match ty {
3969        TypeAst::Record { .. } => Some(ty),
3970        TypeAst::Named { ext: Some(x), .. } => match &**x {
3971            TypeAst::Record { .. } => Some(&**x),
3972            _ => None,
3973        },
3974        _ => None,
3975    }
3976}
3977fn record_parts(t: &TypeAst) -> Option<(&[MemberAst], Loc, bool)> {
3978    match t {
3979        TypeAst::Record {
3980            members,
3981            open,
3982            loc: Some(l),
3983        } => Some((members, *l, *open)),
3984        _ => None,
3985    }
3986}
3987fn safe_resolve(m: &Module, type_name: &str) -> Option<RT> {
3988    m.env
3989        .resolve(
3990            &TypeAst::Named {
3991                name: type_name.to_string(),
3992                args: vec![],
3993                preds: None,
3994                ext: None,
3995                loc: None,
3996            },
3997            None,
3998        )
3999        .ok()
4000}
4001fn placeholder_for(rt: Option<&RT>) -> String {
4002    let Some(rt) = rt else { return "null".into() };
4003    let r: RT = match &rt.k {
4004        RTk::Pred { base, .. } => base.clone(),
4005        _ => rt.clone(),
4006    };
4007    match &r.k {
4008        RTk::Prim(name) => match name.as_str() {
4009            "string" => "\"\"".into(),
4010            "int" => "0".into(),
4011            "float" => "0.0".into(),
4012            "bool" => "false".into(),
4013            _ => "null".into(),
4014        },
4015        RTk::Lit(v) => js_value_string(v),
4016        RTk::Range { lo, .. } => js_value_string(lo),
4017        RTk::Rec(_) => "{ }".into(),
4018        RTk::Arr { .. } => "[]".into(),
4019        RTk::Map { .. } => "{}".into(),
4020        RTk::Union(arms) => placeholder_for(arms.first()),
4021        _ => "null".into(),
4022    }
4023}
4024fn value_to_j(v: &Value) -> J {
4025    match v {
4026        Value::Null | Value::Undef | Value::Absent => J::Null,
4027        Value::Bool(b) => J::Bool(*b),
4028        Value::Int(i) => J::Num(i.to_string().parse().unwrap_or(0)),
4029        Value::Float(f) => J::Str(crate::semantics::js_num_str(*f)),
4030        Value::Str(s) => J::s(s.clone()),
4031        Value::JArr(items) => J::Arr(items.iter().map(value_to_j).collect()),
4032        Value::JObj(es) => J::Obj(es.iter().map(|(k, x)| (k.clone(), value_to_j(x))).collect()),
4033        other => J::s(format!("{other:?}")),
4034    }
4035}
4036// `path.resolve(dir, spec)`: the joined path, `.` and `..` folded lexically
4037fn resolve_lexical(dir: &Path, spec: &str) -> PathBuf {
4038    let joined = if spec.starts_with('/') {
4039        PathBuf::from(spec)
4040    } else {
4041        dir.join(spec)
4042    };
4043    let mut parts: Vec<String> = vec![];
4044    for c in joined.to_string_lossy().split('/') {
4045        match c {
4046            "" | "." => {}
4047            ".." => {
4048                parts.pop();
4049            }
4050            x => parts.push(x.to_string()),
4051        }
4052    }
4053    PathBuf::from(format!("/{}", parts.join("/")))
4054}
4055fn relative_path(from_dir: &Path, to: &Path) -> String {
4056    let f: Vec<&str> = from_dir
4057        .to_str()
4058        .unwrap_or("")
4059        .split('/')
4060        .filter(|x| !x.is_empty())
4061        .collect();
4062    let t: Vec<&str> = to
4063        .to_str()
4064        .unwrap_or("")
4065        .split('/')
4066        .filter(|x| !x.is_empty())
4067        .collect();
4068    let mut i = 0;
4069    while i < f.len() && i < t.len() && f[i] == t[i] {
4070        i += 1;
4071    }
4072    let mut parts: Vec<&str> = f[i..].iter().map(|_| "..").collect();
4073    parts.extend(t[i..].iter().copied());
4074    parts.join("/")
4075}
4076fn require_rel(from: &Path, to: &Path) -> String {
4077    let rel = relative_path(from.parent().unwrap_or(Path::new("/")), to);
4078    if rel.starts_with('.') {
4079        rel.strip_prefix("./").unwrap_or(&rel).to_string()
4080    } else {
4081        rel
4082    }
4083}
4084// the modules that export a name: the universe's, the other open
4085// documents' universes, then the .decl files beside the module
4086fn exporters_of(st: &State, a: &Analysis, m: &Module, name: &str) -> Vec<PathBuf> {
4087    let mut out: Vec<PathBuf> = vec![];
4088    let mut seen: Vec<PathBuf> = vec![m.path.clone()];
4089    let consider = |md: &Module, out: &mut Vec<PathBuf>, seen: &mut Vec<PathBuf>| {
4090        if !seen.contains(&md.path) && md.exports.borrow().contains_key(name) {
4091            seen.push(md.path.clone());
4092            out.push(md.path.clone());
4093        }
4094    };
4095    for md in &a.run.modules {
4096        consider(md, &mut out, &mut seen);
4097    }
4098    for other in st.last_good.values() {
4099        for md in &other.run.modules {
4100            consider(md, &mut out, &mut seen);
4101        }
4102    }
4103    let dir = m.path.parent().map(|p| p.to_path_buf()).unwrap_or_default();
4104    let mut names: Vec<String> = std::fs::read_dir(&dir)
4105        .map(|rd| {
4106            rd.flatten()
4107                .map(|e| e.file_name().to_string_lossy().to_string())
4108                .collect()
4109        })
4110        .unwrap_or_default();
4111    names.sort();
4112    for f in names {
4113        if !f.ends_with(".decl") {
4114            continue;
4115        }
4116        let p = resolve_lexical(&dir, &f);
4117        if seen.contains(&p) {
4118            continue;
4119        }
4120        let text = st.overlay.get(&p).cloned().unwrap_or_else(|| read_text(&p));
4121        let parsed = parse_source(&text);
4122        if !parsed.errors.is_empty() {
4123            continue;
4124        }
4125        if parsed.decls.iter().any(|d| {
4126            d.exported && d.name() == Some(name) && !matches!(d.body, DeclBody::Import { .. })
4127        }) {
4128            seen.push(p.clone());
4129            out.push(p);
4130        }
4131    }
4132    out
4133}
4134fn edit_json(l: Loc, new_text: &str) -> J {
4135    J::obj(vec![("range", range_json(l)), ("newText", J::s(new_text))])
4136}
4137fn insert_json(p: Pos, new_text: &str) -> J {
4138    edit_json(
4139        Loc {
4140            sl: p.line,
4141            sc: p.character,
4142            el: p.line,
4143            ec: p.character,
4144        },
4145        new_text,
4146    )
4147}
4148fn action_edits_json(
4149    title: String,
4150    kind: &str,
4151    diagnostic: Option<J>,
4152    preferred: bool,
4153    uri: &str,
4154    edits: Vec<J>,
4155) -> J {
4156    let mut item = vec![("title", J::s(title)), ("kind", J::s(kind))];
4157    if let Some(d) = diagnostic {
4158        item.push(("diagnostics", J::Arr(vec![d])));
4159    }
4160    if preferred {
4161        item.push(("isPreferred", J::Bool(true)));
4162    }
4163    item.push((
4164        "edit",
4165        J::obj(vec![(
4166            "changes",
4167            J::Obj(vec![(uri.to_string(), J::Arr(edits))]),
4168        )]),
4169    ));
4170    J::obj(item)
4171}
4172fn action_json(
4173    title: String,
4174    kind: &str,
4175    diagnostic: Option<J>,
4176    preferred: bool,
4177    uri: &str,
4178    edit: J,
4179) -> J {
4180    action_edits_json(title, kind, diagnostic, preferred, uri, vec![edit])
4181}
4182fn diag_pos(d: &Value, which: &str) -> Pos {
4183    let p = get(d, "range").and_then(|r| get(r, which));
4184    Pos {
4185        line: as_usize(p.and_then(|s| get(s, "line"))).unwrap_or(0),
4186        character: as_usize(p.and_then(|s| get(s, "character"))).unwrap_or(0),
4187    }
4188}
4189fn chain_of<'a>(hit: &Hit<'a>) -> Vec<NodeRef<'a>> {
4190    let mut chain: Vec<NodeRef> = vec![hit.node.clone()];
4191    chain.extend(hit.parents.iter().rev().cloned());
4192    chain
4193}
4194fn chain_expr<'a>(chain: &[NodeRef<'a>], pred: impl Fn(&Expr) -> bool) -> Option<Rc<Expr>> {
4195    chain.iter().find_map(|n| match n {
4196        NodeRef::Expr(e) if pred(e) => Some((*e).clone()),
4197        _ => None,
4198    })
4199}
4200// every expression under a node, in the reference's pre-order
4201fn exprs_under<'a>(n: &NodeRef<'a>) -> Vec<Rc<Expr>> {
4202    let mut exprs: Vec<Rc<Expr>> = vec![];
4203    let mut types: Vec<&TypeAst> = vec![];
4204    match n {
4205        NodeRef::Decl(d) => collect_decl(d, &mut exprs, &mut types),
4206        NodeRef::Member(m) => collect_member(m, &mut exprs, &mut types),
4207        NodeRef::Type(t) => collect_type(t, &mut exprs, &mut types),
4208        NodeRef::Expr(e) => collect_expr(e, &mut exprs, &mut types),
4209    }
4210    exprs
4211}
4212// a literal's type widened to its primitive: a declaration wants `bool`, not `true`
4213fn widen(rt: &RT) -> RT {
4214    match &rt.k {
4215        RTk::Lit(v) => {
4216            let name = match v {
4217                Value::Str(_) => "string",
4218                Value::Bool(_) => "bool",
4219                Value::Int(_) => "int",
4220                Value::Float(_) => "float",
4221                _ => "null",
4222            };
4223            crate::semantics::ty(RTk::Prim(name.to_string()))
4224        }
4225        _ => rt.clone(),
4226    }
4227}
4228fn is_logical(op: &str) -> bool {
4229    op == "&&" || op == "||"
4230}
4231// the mixed `??` operand: the first binary node (pre-order) whose operator
4232// mixes with its parent's, whichever side it is on
4233fn mixed_in_expr(e: &Rc<Expr>, parent_op: Option<&str>) -> Option<Rc<Expr>> {
4234    if let Expr::Bin { op, .. } = &**e {
4235        if let (Some(pop), Some(_)) = (parent_op, expr_loc(e)) {
4236            if (op == "??" && is_logical(pop)) || (is_logical(op) && pop == "??") {
4237                return Some(e.clone());
4238            }
4239        }
4240    }
4241    let op: Option<&str> = match &**e {
4242        Expr::Bin { op, .. } => Some(op.as_str()),
4243        _ => None,
4244    };
4245    let kids: Vec<Rc<Expr>> = match &**e {
4246        Expr::Template(parts) => parts
4247            .iter()
4248            .filter_map(|p| {
4249                if let TPart::Expr(x) = p {
4250                    Some(x.clone())
4251                } else {
4252                    None
4253                }
4254            })
4255            .collect(),
4256        Expr::Obj(entries) => entries.iter().map(|(_, v)| v.clone()).collect(),
4257        Expr::Arr(items) => items.iter().map(|(_, v)| v.clone()).collect(),
4258        Expr::Comp { head, clauses } => std::iter::once(head.clone())
4259            .chain(
4260                clauses
4261                    .iter()
4262                    .flat_map(|c| std::iter::once(c.iter.clone()).chain(c.filters.iter().cloned())),
4263            )
4264            .collect(),
4265        Expr::MapComp { key, val, clauses } => vec![key.clone(), val.clone()]
4266            .into_iter()
4267            .chain(
4268                clauses
4269                    .iter()
4270                    .flat_map(|c| std::iter::once(c.iter.clone()).chain(c.filters.iter().cloned())),
4271            )
4272            .collect(),
4273        Expr::Bin { l, r, .. } => vec![l.clone(), r.clone()],
4274        Expr::Un { x, .. } | Expr::Paren(x) => vec![x.clone()],
4275        Expr::If { c, t, f } => vec![c.clone(), t.clone(), f.clone()],
4276        Expr::Lambda { body, .. } => vec![body.clone()],
4277        Expr::Call { fun, args } => std::iter::once(fun.clone())
4278            .chain(args.iter().cloned())
4279            .collect(),
4280        Expr::Member { x, .. } => vec![x.clone()],
4281        Expr::Index { x, i } => vec![x.clone(), i.clone()],
4282        Expr::With { base, patch } => vec![base.clone(), patch.clone()],
4283        Expr::Match { subject, arms } => std::iter::once(subject.clone())
4284            .chain(arms.iter().map(|a| a.body.clone()))
4285            .collect(),
4286        _ => vec![],
4287    };
4288    for k in kids {
4289        if let Some(t) = mixed_in_expr(&k, op) {
4290            return Some(t);
4291        }
4292    }
4293    None
4294}
4295fn mixed_in_node(n: &NodeRef) -> Option<Rc<Expr>> {
4296    match n {
4297        NodeRef::Expr(e) => mixed_in_expr(e, None),
4298        NodeRef::Decl(d) => {
4299            let tops: Vec<Rc<Expr>> = match &d.body {
4300                DeclBody::Const { expr, .. } | DeclBody::Output { expr, .. } => vec![expr.clone()],
4301                DeclBody::Input { fallback, .. } => fallback.iter().cloned().collect(),
4302                DeclBody::Func { body, .. } => vec![body.clone()],
4303                DeclBody::Unit { factor, .. } => factor.iter().cloned().collect(),
4304                _ => vec![],
4305            };
4306            tops.iter().find_map(|e| mixed_in_expr(e, None))
4307        }
4308        NodeRef::Member(m) => match m {
4309            MemberAst::Derived { expr, .. } => mixed_in_expr(expr, None),
4310            MemberAst::Value { dflt: Some(d), .. } => mixed_in_expr(d, None),
4311            MemberAst::Assert { cond, .. } | MemberAst::When { cond, .. } => {
4312                mixed_in_expr(cond, None)
4313            }
4314            _ => None,
4315        },
4316        NodeRef::Type(_) => None,
4317    }
4318}
4319// does an expression read any name (a constant expression reads none)?
4320fn mentions_name(e: &Rc<Expr>) -> bool {
4321    let mut exprs: Vec<Rc<Expr>> = vec![];
4322    let mut types: Vec<&TypeAst> = vec![];
4323    collect_expr(e, &mut exprs, &mut types);
4324    matches!(&**e, Expr::Name(_) | Expr::Ctx(_) | Expr::Referrers { .. })
4325        || exprs
4326            .iter()
4327            .any(|x| matches!(&**x, Expr::Name(_) | Expr::Ctx(_) | Expr::Referrers { .. }))
4328}
4329fn leading_spaces(line: &str) -> String {
4330    line.chars().take_while(|c| *c == ' ').collect()
4331}
4332// ---------------- on-type formatting ----------------
4333// `\n`: the new line takes the previous line's indentation, one level
4334// deeper after an opening bracket or a continuation point (§2.9); `}`:
4335// the line dedents to the opening brace's line
4336fn on_type_formatting(st: &State, uri: &str, pos: Pos, ch: &str) -> J {
4337    let Some(text) = st.text(uri) else {
4338        return J::Arr(vec![]);
4339    };
4340    let lines: Vec<&str> = text.split('\n').collect();
4341    let line = |i: usize| lines.get(i).copied().unwrap_or("");
4342    let indent_of = |s: &str| s.chars().take_while(|c| *c == ' ').count();
4343    let edit = |l: usize, have: usize, want: usize| {
4344        if have == want {
4345            J::Arr(vec![])
4346        } else {
4347            J::Arr(vec![edit_json(
4348                Loc {
4349                    sl: l,
4350                    sc: 0,
4351                    el: l,
4352                    ec: have,
4353                },
4354                &" ".repeat(want),
4355            )])
4356        }
4357    };
4358    if ch == "\n" {
4359        let prev = if pos.line >= 1 {
4360            line(pos.line - 1)
4361        } else {
4362            ""
4363        };
4364        let cur = line(pos.line);
4365        let body = Regex::new(r"//.*$")
4366            .unwrap()
4367            .replace(prev, "")
4368            .trim_end()
4369            .to_string();
4370        let mut want = indent_of(prev);
4371        if Regex::new(r"[{\[(]$").unwrap().is_match(&body)
4372            || Regex::new(r"(?:[+\-*/%<>=!&|?:,]|\bthen|\belse|\bin|\bwith|=>)$")
4373                .unwrap()
4374                .is_match(&body)
4375        {
4376            want += 4;
4377        }
4378        if Regex::new(r"^\s*[}\])]").unwrap().is_match(cur) {
4379            want = want.saturating_sub(4);
4380        }
4381        return edit(pos.line, indent_of(cur), want);
4382    }
4383    if ch == "}" || ch == "]" || ch == ")" {
4384        let cur = line(pos.line);
4385        if cur.trim() != ch {
4386            return J::Arr(vec![]);
4387        }
4388        let close = ch.chars().next().unwrap();
4389        let open = match ch {
4390            "}" => '{',
4391            "]" => '[',
4392            _ => '(',
4393        };
4394        // the opening bracket's line: scan back with a depth count
4395        let mut depth = 0i64;
4396        for l in (0..=pos.line).rev() {
4397            let s = line(l);
4398            let chars: Vec<char> = s.chars().collect();
4399            let end = if l == pos.line {
4400                cur.find(ch).map(|b| cur[..b].chars().count()).unwrap_or(0)
4401            } else {
4402                chars.len()
4403            };
4404            for i in (0..end).rev() {
4405                let c = chars[i];
4406                if c == close {
4407                    depth += 1;
4408                } else if c == open {
4409                    if depth == 0 {
4410                        return edit(pos.line, indent_of(cur), indent_of(s));
4411                    }
4412                    depth -= 1;
4413                }
4414            }
4415        }
4416        return J::Arr(vec![]);
4417    }
4418    J::Arr(vec![])
4419}
4420
4421fn code_actions(st: &mut State, uri: &str, range: (Pos, Pos), diagnostics: &[Value]) -> J {
4422    let Some(text) = st.text(uri).cloned() else {
4423        return J::Arr(vec![]);
4424    };
4425    let a = st.analysis_of(uri);
4426    let mut out: Vec<J> = vec![];
4427    let parsed = parse_source(&text);
4428    let lines: Vec<&str> = text.split('\n').collect();
4429    let (Some(a), true) = (a, parsed.errors.is_empty()) else {
4430        return J::Arr(out);
4431    };
4432    let Some(m) = module_of(&a, &path_of(uri)) else {
4433        return J::Arr(out);
4434    };
4435    let t = tables_of(&a, &m);
4436    let re_unknown = Regex::new(r"^unknown name ([A-Za-z_][A-Za-z0-9_]*)").unwrap();
4437    let re_undeclared = Regex::new(
4438        r"^member ([A-Za-z_][A-Za-z0-9_]*) is not declared on ([A-Za-z_][A-Za-z0-9_]*)$",
4439    )
4440    .unwrap();
4441    let re_missing = Regex::new(r"^required member ([A-Za-z_][A-Za-z0-9_]*) missing").unwrap();
4442    let re_ctx_undeclared =
4443        Regex::new(r"^(\$[a-z]+) used without a context declaration in ([A-Za-z_][A-Za-z0-9_]*)$")
4444            .unwrap();
4445    let re_ctx_ref =
4446        Regex::new(r"^(\$[a-z]+) declaration must be ref<\.\.\.> \(([A-Za-z_][A-Za-z0-9_]*)\)$")
4447            .unwrap();
4448    let re_override = Regex::new(r"^(?:illegal member-kind transition for|override widens inherited member) ([A-Za-z_][A-Za-z0-9_]*)[^(]*\(([A-Za-z_][A-Za-z0-9_]*)\)$").unwrap();
4449    let re_union =
4450        Regex::new(r"^record union arms not discriminable in ([A-Za-z_][A-Za-z0-9_]*)$").unwrap();
4451    let re_restated =
4452        Regex::new(r"^derived member ([A-Za-z_][A-Za-z0-9_]*) restated with a differing value")
4453            .unwrap();
4454    let trailing_comma = Regex::new(r",\s*$").unwrap();
4455    // the fixes of the diagnostics that touch the range (a client may send more)
4456    let touches = |d: &Value| {
4457        get(d, "range").is_some()
4458            && !(diag_pos(d, "end").line < range.0.line || diag_pos(d, "start").line > range.1.line)
4459    };
4460    for d in diagnostics.iter().filter(|d| touches(d)) {
4461        let message = as_str(get(d, "message")).unwrap_or("").to_string();
4462        let dpos = diag_pos(d, "start");
4463        if let Some(cap) = re_unknown.captures(&message) {
4464            let name = cap[1].to_string();
4465            for other in exporters_of(st, &a, &m, &name) {
4466                let mut spec = format!("./{}", require_rel(&m.path, &other));
4467                let dir = m.path.parent().map(|p| p.to_path_buf()).unwrap_or_default();
4468                let existing = parsed.decls.iter().find(|x| match &x.body {
4469                    DeclBody::Import {
4470                        from,
4471                        names: Some(_),
4472                        ..
4473                    } => resolve_lexical(&dir, from) == other,
4474                    _ => false,
4475                });
4476                let edit = match existing {
4477                    Some(x) => {
4478                        let l = x.loc.unwrap();
4479                        let line = lines.get(l.sl).copied().unwrap_or("");
4480                        let close = find16(line, "}", l.sc).map(|c| c as i64).unwrap_or(-1);
4481                        if let DeclBody::Import { from, .. } = &x.body {
4482                            spec = from.clone();
4483                        }
4484                        edit_json(
4485                            Loc {
4486                                sl: l.sl,
4487                                sc: close.max(0) as usize,
4488                                el: l.sl,
4489                                ec: close.max(0) as usize,
4490                            },
4491                            &format!(", {name} "),
4492                        )
4493                    }
4494                    None => {
4495                        let last_import = parsed.decls.iter().rev().find(|x| {
4496                            matches!(x.body, DeclBody::Import { .. } | DeclBody::ReExport { .. })
4497                        });
4498                        let at = last_import
4499                            .and_then(|x| x.loc)
4500                            .map(|l| l.el + 1)
4501                            .unwrap_or(0);
4502                        edit_json(
4503                            Loc {
4504                                sl: at,
4505                                sc: 0,
4506                                el: at,
4507                                ec: 0,
4508                            },
4509                            &format!("import {{ {name} }} from \"{spec}\"\n"),
4510                        )
4511                    }
4512                };
4513                out.push(action_json(
4514                    format!("import {name} from \"{spec}\""),
4515                    "quickfix",
4516                    Some(value_to_j(d)),
4517                    true,
4518                    uri,
4519                    edit,
4520                ));
4521            }
4522            // a namespace import that exports it: qualify the name (namespaces in declaration order)
4523            let mut nss: Vec<String> = vec![];
4524            for x in &parsed.decls {
4525                if let DeclBody::Import { ns: Some(ns), .. } = &x.body {
4526                    if !nss.contains(ns) {
4527                        nss.push(ns.clone());
4528                    }
4529                }
4530            }
4531            for ns in nss {
4532                let exports = m.env.namespaces.borrow().get(&ns).map(|(_, ex)| ex.clone());
4533                let Some(exports) = exports else { continue };
4534                if !exports.borrow().contains_key(&name) {
4535                    continue;
4536                }
4537                let Some(hit) = node_at(&parsed.decls, dpos) else {
4538                    continue;
4539                };
4540                let chain = chain_of(&hit);
4541                let n = chain_expr(&chain, |x| matches!(x, Expr::Name(nm) if *nm == name));
4542                if let Some(l) = n.as_ref().and_then(expr_loc) {
4543                    out.push(action_json(
4544                        format!("qualify as {ns}.{name}"),
4545                        "quickfix",
4546                        Some(value_to_j(d)),
4547                        false,
4548                        uri,
4549                        edit_json(l, &format!("{ns}.{name}")),
4550                    ));
4551                }
4552            }
4553        }
4554        if let Some(cap) = re_undeclared.captures(&message) {
4555            // declare the member on the type, with the supplied value's inferred type
4556            let (name, type_name) = (cap[1].to_string(), cap[2].to_string());
4557            let site = site_of_target(st, &a, resolve_in(&m.env, &type_name).as_ref());
4558            if let Some(site) = site {
4559                let sm = site.module.clone();
4560                let body: Option<(&[MemberAst], Loc)> =
4561                    decl_by_id(&sm, site.decl).and_then(|decl| match &decl.body {
4562                        DeclBody::Type { ty, .. } => match ty {
4563                            TypeAst::Record {
4564                                members,
4565                                loc: Some(l),
4566                                ..
4567                            } => Some((members.as_slice(), *l)),
4568                            TypeAst::Named { ext: Some(ext), .. } => match &**ext {
4569                                TypeAst::Record {
4570                                    members,
4571                                    loc: Some(l),
4572                                    ..
4573                                } => Some((members.as_slice(), *l)),
4574                                _ => None,
4575                            },
4576                            _ => None,
4577                        },
4578                        _ => None,
4579                    });
4580                if let Some((members, body_loc)) = body {
4581                    let hit = node_at(&parsed.decls, dpos);
4582                    let mut entry: Option<Rc<Expr>> = None;
4583                    if let Some(hit) = &hit {
4584                        let chain = chain_of(hit);
4585                        if let Some(obj) = chain_expr(&chain, |x| matches!(x, Expr::Obj(_))) {
4586                            if let Expr::Obj(entries) = &*obj {
4587                                entry = entries
4588                                    .iter()
4589                                    .find(|(k, _)| *k == name)
4590                                    .map(|(_, v)| v.clone());
4591                            }
4592                        }
4593                        if entry.is_none() {
4594                            for o in exprs_under(&hit.node) {
4595                                if let Expr::Obj(entries) = &*o {
4596                                    if let Some((_, v)) = entries.iter().find(|(k, _)| *k == name) {
4597                                        entry = Some(v.clone());
4598                                        break;
4599                                    }
4600                                }
4601                            }
4602                        }
4603                    }
4604                    let ty = entry
4605                        .as_ref()
4606                        .and_then(|v| t.types.get(&key_of(v)))
4607                        .and_then(|x| x.rt.clone());
4608                    let member_type = ty
4609                        .map(|rt| type_text(Some(&widen(&rt))))
4610                        .unwrap_or_else(|| "any".into());
4611                    let last = members.last().and_then(|x| x.loc());
4612                    let (at, new_text) = match last {
4613                        Some(l) => (
4614                            Pos {
4615                                line: l.el,
4616                                character: l.ec,
4617                            },
4618                            format!("\n    {name}: {member_type}"),
4619                        ),
4620                        None => (
4621                            Pos {
4622                                line: body_loc.sl,
4623                                character: body_loc.sc + 1,
4624                            },
4625                            format!(" {name}: {member_type}"),
4626                        ),
4627                    };
4628                    out.push(action_json(
4629                        format!("declare {name}: {member_type} on {type_name}"),
4630                        "quickfix",
4631                        Some(value_to_j(d)),
4632                        true,
4633                        &uri_of(&sm.path),
4634                        insert_json(at, &new_text),
4635                    ));
4636                }
4637            }
4638        }
4639        if message.starts_with("member access on a maybe-absent expression") {
4640            if let Some(hit) = node_at(&parsed.decls, dpos) {
4641                let chain = chain_of(&hit);
4642                if let Some(n) =
4643                    chain_expr(&chain, |x| matches!(x, Expr::Member { safe: false, .. }))
4644                {
4645                    if let (Expr::Member { name, .. }, Some(_)) = (&*n, expr_loc(&n)) {
4646                        let tok = member_token_loc(&text, &n, name);
4647                        out.push(action_json(
4648                            "use ?.".into(),
4649                            "quickfix",
4650                            Some(value_to_j(d)),
4651                            true,
4652                            uri,
4653                            edit_json(
4654                                Loc {
4655                                    sl: tok.sl,
4656                                    sc: tok.sc - 1,
4657                                    el: tok.sl,
4658                                    ec: tok.sc,
4659                                },
4660                                "?.",
4661                            ),
4662                        ));
4663                    }
4664                }
4665            }
4666        }
4667        if message.starts_with("maybe-absent expression consumed") {
4668            if let Some(hit) = node_at(&parsed.decls, dpos) {
4669                if let NodeRef::Expr(e) = &hit.node {
4670                    if let Some(l) = expr_loc(e) {
4671                        out.push(action_json(
4672                            "supply a fallback with ??".into(),
4673                            "quickfix",
4674                            Some(value_to_j(d)),
4675                            false,
4676                            uri,
4677                            insert_json(
4678                                Pos {
4679                                    line: l.el,
4680                                    character: l.ec,
4681                                },
4682                                " ?? null",
4683                            ),
4684                        ));
4685                    }
4686                }
4687            }
4688        }
4689        if message.starts_with("`??` mixed with") {
4690            // parenthesize the `??` operand of the mixed expression
4691            if let Some(hit) = node_at(&parsed.decls, dpos) {
4692                if let Some(target) = mixed_in_node(&hit.node) {
4693                    let l = expr_loc(&target).unwrap();
4694                    out.push(action_edits_json(
4695                        "parenthesize the ?? expression".into(),
4696                        "quickfix",
4697                        Some(value_to_j(d)),
4698                        true,
4699                        uri,
4700                        vec![
4701                            insert_json(
4702                                Pos {
4703                                    line: l.sl,
4704                                    character: l.sc,
4705                                },
4706                                "(",
4707                            ),
4708                            insert_json(
4709                                Pos {
4710                                    line: l.el,
4711                                    character: l.ec,
4712                                },
4713                                ")",
4714                            ),
4715                        ],
4716                    ));
4717                }
4718            }
4719        }
4720        if message.starts_with("`match` is not exhaustive") {
4721            if let Some(hit) = node_at(&parsed.decls, dpos) {
4722                let chain = chain_of(&hit);
4723                if let Some(n) = chain_expr(&chain, |x| matches!(x, Expr::Match { .. })) {
4724                    let Expr::Match {
4725                        subject,
4726                        arms: match_arms,
4727                    } = &*n
4728                    else {
4729                        unreachable!()
4730                    };
4731                    let subject_rt = t.types.get(&key_of(subject)).and_then(|x| x.rt.clone());
4732                    let arms: Vec<String> = match subject_rt.as_ref().map(|r| &r.k) {
4733                        Some(RTk::Union(us)) => us
4734                            .iter()
4735                            .filter_map(|r| {
4736                                if let RTk::Rec(_) = &r.k {
4737                                    rec_name(Some(r))
4738                                } else {
4739                                    None
4740                                }
4741                            })
4742                            .collect(),
4743                        _ => vec![],
4744                    };
4745                    let covered: Vec<String> = match_arms
4746                        .iter()
4747                        .map(|arm| match &arm.ty {
4748                            Some(TypeAst::Named { name, .. }) => name.clone(),
4749                            _ => String::new(),
4750                        })
4751                        .collect();
4752                    let missing: Vec<String> =
4753                        arms.into_iter().filter(|x| !covered.contains(x)).collect();
4754                    if let (Some(l), false) = (expr_loc(&n), missing.is_empty()) {
4755                        let at = Pos {
4756                            line: l.el,
4757                            character: l.ec - 1,
4758                        };
4759                        let indent = format!(
4760                            "{}    ",
4761                            leading_spaces(lines.get(l.sl).copied().unwrap_or(""))
4762                        );
4763                        let new_text = format!(
4764                            "{}{}",
4765                            missing
4766                                .iter()
4767                                .map(|x| format!("{indent}(v: {x}) => null\n"))
4768                                .collect::<String>(),
4769                            &indent[4..]
4770                        );
4771                        out.push(action_json(
4772                            format!(
4773                                "add the missing arm{}: {}",
4774                                if missing.len() > 1 { "s" } else { "" },
4775                                missing.join(", ")
4776                            ),
4777                            "quickfix",
4778                            Some(value_to_j(d)),
4779                            true,
4780                            uri,
4781                            insert_json(at, &new_text),
4782                        ));
4783                    }
4784                }
4785            }
4786        }
4787        if let Some(cap) = re_ctx_undeclared.captures(&message) {
4788            // declare the context variable on the type: `$parent: ref<{ ... }>`, `$root: ref<{ ... }>`, `$key: string`
4789            let (variable, type_name) = (cap[1].to_string(), cap[2].to_string());
4790            if let Some(site) = site_of_target(st, &a, resolve_in(&m.env, &type_name).as_ref()) {
4791                let sm = site.module.clone();
4792                if let Some((members, body_loc, _)) = decl_by_id(&sm, site.decl)
4793                    .and_then(record_body_of)
4794                    .and_then(record_parts)
4795                {
4796                    let bound = if variable == "$key" {
4797                        "string"
4798                    } else {
4799                        "ref<{ ... }>"
4800                    };
4801                    let first = members.first().and_then(|x| x.loc());
4802                    let at = match first {
4803                        Some(l) => Pos {
4804                            line: l.sl,
4805                            character: l.sc,
4806                        },
4807                        None => Pos {
4808                            line: body_loc.sl,
4809                            character: body_loc.sc + 1,
4810                        },
4811                    };
4812                    let new_text = match first {
4813                        Some(l) if l.sl > body_loc.sl => {
4814                            format!("{variable}: {bound}\n{}", " ".repeat(l.sc))
4815                        }
4816                        Some(_) => format!("{variable}: {bound}, "),
4817                        None => format!(" {variable}: {bound}, "),
4818                    };
4819                    out.push(action_json(
4820                        format!("declare {variable}: {bound} on {type_name}"),
4821                        "quickfix",
4822                        Some(value_to_j(d)),
4823                        true,
4824                        &uri_of(&sm.path),
4825                        insert_json(at, &new_text),
4826                    ));
4827                }
4828            }
4829        }
4830        if let Some(cap) = re_ctx_ref.captures(&message) {
4831            // the declared bound wrapped in ref<…>
4832            let (variable, type_name) = (cap[1].to_string(), cap[2].to_string());
4833            if let Some(site) = site_of_target(st, &a, resolve_in(&m.env, &type_name).as_ref()) {
4834                let sm = site.module.clone();
4835                let tl = decl_by_id(&sm, site.decl)
4836                    .and_then(record_body_of)
4837                    .map(record_members)
4838                    .and_then(|members| {
4839                        members.iter().find_map(|x| match x {
4840                            MemberAst::Context {
4841                                variable: v, ty, ..
4842                            } if *v == variable => ty.loc(),
4843                            _ => None,
4844                        })
4845                    });
4846                if let Some(tl) = tl {
4847                    let src = src_of(&text_of(st, &sm), tl);
4848                    out.push(action_json(
4849                        format!("declare {variable} as ref<{src}>"),
4850                        "quickfix",
4851                        Some(value_to_j(d)),
4852                        true,
4853                        &uri_of(&sm.path),
4854                        edit_json(tl, &format!("ref<{src}>")),
4855                    ));
4856                }
4857            }
4858        }
4859        if let Some(cap) = re_override.captures(&message) {
4860            // the override replaced by the parent's declaration of the member
4861            let (member, type_name) = (cap[1].to_string(), cap[2].to_string());
4862            if let Some(site) = site_of_target(st, &a, resolve_in(&m.env, &type_name).as_ref()) {
4863                let sm = site.module.clone();
4864                if let Some(decl) = decl_by_id(&sm, site.decl) {
4865                    let own = record_body_of(decl)
4866                        .map(record_members)
4867                        .and_then(|members| {
4868                            members
4869                                .iter()
4870                                .find(|x| x.name() == Some(member.as_str()) && x.loc().is_some())
4871                        })
4872                        .and_then(|x| x.loc());
4873                    let base = match &decl.body {
4874                        DeclBody::Type {
4875                            ty: TypeAst::Named { name, .. },
4876                            ..
4877                        } => Some(name.clone()),
4878                        _ => None,
4879                    };
4880                    let parent = base.and_then(|b| {
4881                        member_site(st, &a, &sm, safe_resolve(&sm, &b).as_ref(), &member)
4882                    });
4883                    if let (Some(own_loc), Some(parent)) = (own, parent) {
4884                        if let Some(pl) = parent.member_loc {
4885                            let parent_text = trailing_comma
4886                                .replace(&src_of(&text_of(st, &parent.module), pl), "")
4887                                .to_string();
4888                            out.push(action_json(
4889                                format!("use the parent's declaration: {parent_text}"),
4890                                "quickfix",
4891                                Some(value_to_j(d)),
4892                                true,
4893                                &uri_of(&sm.path),
4894                                edit_json(own_loc, &parent_text),
4895                            ));
4896                        }
4897                    }
4898                }
4899            }
4900        }
4901        if let Some(cap) = re_union.captures(&message) {
4902            // a literal-typed `kind` member on every arm that is a local record type
4903            let uname = cap[1].to_string();
4904            if let Some(site) = site_of_target(st, &a, resolve_in(&m.env, &uname).as_ref()) {
4905                let sm = site.module.clone();
4906                let arms: Vec<String> = match decl_by_id(&sm, site.decl).map(|d| &d.body) {
4907                    Some(DeclBody::Type {
4908                        ty: TypeAst::Union { arms, .. },
4909                        ..
4910                    }) => arms
4911                        .iter()
4912                        .filter_map(|t| match t {
4913                            TypeAst::Named { name, .. } => Some(name.clone()),
4914                            _ => None,
4915                        })
4916                        .collect(),
4917                    _ => vec![],
4918                };
4919                let mut changes: Vec<(String, Vec<J>)> = vec![];
4920                for arm in arms {
4921                    let Some(as_) = site_of_target(st, &a, resolve_in(&sm.env, &arm).as_ref())
4922                    else {
4923                        continue;
4924                    };
4925                    let am = as_.module.clone();
4926                    let Some((members, body_loc, _)) = decl_by_id(&am, as_.decl)
4927                        .and_then(record_body_of)
4928                        .and_then(record_parts)
4929                    else {
4930                        continue;
4931                    };
4932                    if members.iter().any(|x| x.name() == Some("kind")) {
4933                        continue;
4934                    }
4935                    let first = members.first().and_then(|x| x.loc());
4936                    let at = match first {
4937                        Some(l) => Pos {
4938                            line: l.sl,
4939                            character: l.sc,
4940                        },
4941                        None => Pos {
4942                            line: body_loc.sl,
4943                            character: body_loc.sc + 1,
4944                        },
4945                    };
4946                    let new_text = match first {
4947                        Some(l) if l.sl > body_loc.sl => {
4948                            format!("kind: \"{arm}\"\n{}", " ".repeat(l.sc))
4949                        }
4950                        Some(_) => format!("kind: \"{arm}\", "),
4951                        None => format!(" kind: \"{arm}\", "),
4952                    };
4953                    let u = uri_of(&am.path);
4954                    match changes.iter_mut().find(|(k, _)| *k == u) {
4955                        Some((_, v)) => v.push(insert_json(at, &new_text)),
4956                        None => changes.push((u, vec![insert_json(at, &new_text)])),
4957                    }
4958                }
4959                if !changes.is_empty() {
4960                    out.push(J::obj(vec![
4961                        (
4962                            "title",
4963                            J::s(format!("add a discriminant `kind` to the arms of {uname}")),
4964                        ),
4965                        ("kind", J::s("quickfix")),
4966                        ("diagnostics", J::Arr(vec![value_to_j(d)])),
4967                        ("isPreferred", J::Bool(true)),
4968                        (
4969                            "edit",
4970                            J::obj(vec![(
4971                                "changes",
4972                                J::Obj(changes.into_iter().map(|(k, v)| (k, J::Arr(v))).collect()),
4973                            )]),
4974                        ),
4975                    ]));
4976                }
4977            }
4978        }
4979        if let Some(cap) = re_restated.captures(&message) {
4980            // a document supplies it: make it defaulted (`x?: T = e`) where it is declared with a type
4981            let member = cap[1].to_string();
4982            for decl in &m.decls {
4983                let Some(members) = record_body_of(decl).map(record_members) else {
4984                    continue;
4985                };
4986                let Some(own) = members.iter().find(|x| matches!(x, MemberAst::Derived { name, ty: Some(_), loc: Some(_), .. } if *name == member)) else { continue };
4987                let r = member_range(&text, own, &member);
4988                out.push(action_json(
4989                    format!(
4990                        "make {}.{member} defaulted (x?: T = e)",
4991                        decl.name().unwrap_or("")
4992                    ),
4993                    "quickfix",
4994                    Some(value_to_j(d)),
4995                    false,
4996                    uri,
4997                    insert_json(
4998                        Pos {
4999                            line: r.el,
5000                            character: r.ec,
5001                        },
5002                        "?",
5003                    ),
5004                ));
5005            }
5006        }
5007        if let Some(cap) = re_missing.captures(&message) {
5008            let name = cap[1].to_string();
5009            // the construction: the literal at the diagnostic, or the root's literal when the diagnostic names the declaration
5010            let Some(hit) = node_at(&parsed.decls, dpos) else {
5011                continue;
5012            };
5013            let chain = chain_of(&hit);
5014            let mut obj: Option<Rc<Expr>> = chain_expr(&chain, |x| matches!(x, Expr::Obj(_)));
5015            if obj.is_none() {
5016                obj = chain
5017                    .iter()
5018                    .filter_map(|n| match n {
5019                        NodeRef::Decl(d) => Some(*d),
5020                        _ => None,
5021                    })
5022                    .filter_map(|d| match &d.body {
5023                        DeclBody::Output { expr, .. } => Some(expr.clone()),
5024                        DeclBody::Input { fallback, .. } => fallback.clone(),
5025                        DeclBody::Const { expr, .. } => Some(expr.clone()),
5026                        _ => None,
5027                    })
5028                    .find(|e| matches!(&**e, Expr::Obj(_)));
5029            }
5030            let Some(obj) = obj else { continue };
5031            // the literal's type: its declared position (a root's annotation), else what inference recorded
5032            let owner = chain.iter().find_map(|n| match n {
5033                NodeRef::Decl(d) => match &d.body {
5034                    DeclBody::Output { ty, .. } | DeclBody::Input { ty, .. } => Some(ty.clone()),
5035                    _ => None,
5036                },
5037                _ => None,
5038            });
5039            let rt: Option<RT> = match owner {
5040                Some(ty) => m.env.resolve(&ty, None).ok(),
5041                None => t.types.get(&key_of(&obj)).and_then(|x| x.rt.clone()),
5042            };
5043            let mem = rt
5044                .as_ref()
5045                .and_then(|r| rec_members(r).into_iter().find(|x| x.name == name));
5046            let value = placeholder_for(mem.as_ref().and_then(|x| x.ty.as_ref()));
5047            let Expr::Obj(entries) = &*obj else { continue };
5048            let edit = match entries.last().and_then(|(_, v)| expr_loc(v)) {
5049                Some(vl) => insert_json(
5050                    Pos {
5051                        line: vl.el,
5052                        character: vl.ec,
5053                    },
5054                    &format!(", {name}: {value}"),
5055                ),
5056                None => {
5057                    let ol = expr_loc(&obj).unwrap();
5058                    insert_json(
5059                        Pos {
5060                            line: ol.sl,
5061                            character: ol.sc + 1,
5062                        },
5063                        &format!(" {name}: {value}"),
5064                    )
5065                }
5066            };
5067            out.push(action_json(
5068                format!("add {name}: {value}"),
5069                "quickfix",
5070                Some(value_to_j(d)),
5071                true,
5072                uri,
5073                edit,
5074            ));
5075        }
5076    }
5077    // assists at the range
5078    let Some(hit) = node_at(&parsed.decls, range.0) else {
5079        return J::Arr(out);
5080    };
5081    let chain = chain_of(&hit);
5082    let mut one = |title: String, kind: &str, edits: Vec<J>| {
5083        out.push(J::obj(vec![
5084            ("title", J::s(title)),
5085            ("kind", J::s(kind)),
5086            (
5087                "edit",
5088                J::obj(vec![(
5089                    "changes",
5090                    J::Obj(vec![(uri.to_string(), J::Arr(edits))]),
5091                )]),
5092            ),
5093        ]))
5094    };
5095    // annotate an unannotated derived member or constant with its inferred type
5096    for n in &chain {
5097        let (expr, r, hidden) = match n {
5098            NodeRef::Member(
5099                mm @ MemberAst::Derived {
5100                    name,
5101                    ty: None,
5102                    expr,
5103                    hidden,
5104                    ..
5105                },
5106            ) => (expr.clone(), member_range(&text, mm, name), *hidden),
5107            NodeRef::Decl(d) => match &d.body {
5108                DeclBody::Const {
5109                    name,
5110                    ty: None,
5111                    expr,
5112                } => (expr.clone(), name_range(&text, d, name), false),
5113                _ => continue,
5114            },
5115            _ => continue,
5116        };
5117        if let Some(rt) = t.types.get(&key_of(&expr)).and_then(|x| x.rt.clone()) {
5118            let tt = type_text(Some(&rt));
5119            let at = Pos {
5120                line: r.el,
5121                character: r.ec + if hidden { 1 } else { 0 },
5122            };
5123            one(
5124                format!("annotate: {tt}"),
5125                "refactor.rewrite",
5126                vec![insert_json(at, &format!(": {tt}"))],
5127            );
5128        }
5129        break;
5130    }
5131    // convert a member's kind: derived <-> hidden, defaulted <-> derived, optional <-> required
5132    let member = chain.iter().find_map(|n| match n {
5133        NodeRef::Member(
5134            mm @ (MemberAst::Derived { loc: Some(_), .. } | MemberAst::Value { loc: Some(_), .. }),
5135        ) => Some(*mm),
5136        _ => None,
5137    });
5138    if let Some(mm) = member {
5139        let name = mm.name().unwrap_or("");
5140        let r = member_range(&text, mm, name);
5141        let after_name = Pos {
5142            line: r.el,
5143            character: r.ec,
5144        };
5145        let remove_next = edit_json(
5146            Loc {
5147                sl: r.el,
5148                sc: r.ec,
5149                el: r.el,
5150                ec: r.ec + 1,
5151            },
5152            "",
5153        );
5154        match mm {
5155            MemberAst::Derived { hidden, ty, .. } => {
5156                if *hidden {
5157                    one(
5158                        "make visible (derived)".into(),
5159                        "refactor.rewrite",
5160                        vec![remove_next.clone()],
5161                    );
5162                } else {
5163                    one(
5164                        "make hidden (x$)".into(),
5165                        "refactor.rewrite",
5166                        vec![insert_json(after_name, "$")],
5167                    );
5168                }
5169                if ty.is_some() {
5170                    one(
5171                        "make defaulted (x?: T = e)".into(),
5172                        "refactor.rewrite",
5173                        vec![insert_json(after_name, "?")],
5174                    );
5175                }
5176            }
5177            MemberAst::Value { dflt: Some(_), .. } => one(
5178                "make derived (x: T = e)".into(),
5179                "refactor.rewrite",
5180                vec![remove_next],
5181            ),
5182            MemberAst::Value { opt: true, .. } => one(
5183                "make required".into(),
5184                "refactor.rewrite",
5185                vec![remove_next],
5186            ),
5187            _ => one(
5188                "make optional".into(),
5189                "refactor.rewrite",
5190                vec![insert_json(after_name, "?")],
5191            ),
5192        }
5193    }
5194    // generate: export, an output or input skeleton for a type, the fixture header
5195    let decl = chain.iter().find_map(|n| match n {
5196        NodeRef::Decl(d) => Some(*d),
5197        _ => None,
5198    });
5199    if let Some(d) = decl {
5200        if let (Some(l), Some(name), false) = (d.loc, d.name(), d.exported) {
5201            if !matches!(d.body, DeclBody::Import { .. } | DeclBody::ReExport { .. }) {
5202                one(
5203                    format!("export {name}"),
5204                    "refactor.rewrite",
5205                    vec![insert_json(
5206                        Pos {
5207                            line: l.sl,
5208                            character: l.sc,
5209                        },
5210                        "export ",
5211                    )],
5212                );
5213            }
5214        }
5215        if let DeclBody::Type { name, ty, .. } = &d.body {
5216            if let Ok(rt) = m.env.resolve(ty, None) {
5217                if is_rec(&rt) {
5218                    let req: Vec<String> = rec_members(&rt)
5219                        .iter()
5220                        .filter(|x| x.kind == MKind::Req)
5221                        .map(|x| format!("{}: {}", x.name, placeholder_for(x.ty.as_ref())))
5222                        .collect();
5223                    let last = lines.last().copied().unwrap_or("");
5224                    let end = Pos {
5225                        line: lines.len() - 1,
5226                        character: u16len(last),
5227                    };
5228                    let lead = if last.is_empty() { "" } else { "\n" };
5229                    let mut chars = name.chars();
5230                    let lower = match chars.next() {
5231                        Some(c) => format!("{}{}", c.to_lowercase(), chars.as_str()),
5232                        None => String::new(),
5233                    };
5234                    one(
5235                        format!("generate an output of {name}"),
5236                        "refactor.rewrite",
5237                        vec![insert_json(
5238                            end,
5239                            &format!(
5240                                "{lead}output {lower}: {name} = {{ {}{}}}\n",
5241                                req.join(", "),
5242                                if req.is_empty() { "" } else { " " }
5243                            ),
5244                        )],
5245                    );
5246                    one(
5247                        format!("generate an input of {name}"),
5248                        "refactor.rewrite",
5249                        vec![insert_json(end, &format!("{lead}input {lower}: {name}\n"))],
5250                    );
5251                }
5252            }
5253        }
5254    }
5255    if !diagnostics.is_empty()
5256        && !lines
5257            .first()
5258            .copied()
5259            .unwrap_or("")
5260            .starts_with("// @expect-")
5261    {
5262        let first = diagnostics
5263            .iter()
5264            .find(|d| matches!(get(d, "severity"), Some(Value::Int(i)) if i.to_string() == "1"))
5265            .unwrap_or(&diagnostics[0]);
5266        let code = match get(first, "code") {
5267            Some(Value::Str(c)) => c.clone(),
5268            Some(Value::Int(i)) => i.to_string(),
5269            Some(Value::Float(f)) => crate::semantics::js_num_str(*f),
5270            _ => String::new(),
5271        };
5272        let assert_id = Regex::new(r"^[A-Z][A-Za-z0-9_]*\.").unwrap();
5273        let phase = if code.starts_with("E1") || code.starts_with("E2") {
5274            "parsing"
5275        } else if code.starts_with("E5") || code.starts_with("E6") || assert_id.is_match(&code) {
5276            "binding"
5277        } else {
5278            "checking"
5279        };
5280        one(
5281            "generate the fixture header (@expect-phase / @expect-error)".into(),
5282            "refactor.rewrite",
5283            vec![insert_json(
5284                Pos {
5285                    line: 0,
5286                    character: 0,
5287                },
5288                &format!("// @expect-phase: {phase}\n// @expect-error: {code}\n"),
5289            )],
5290        );
5291    }
5292    // fill the missing required members of a literal
5293    if let Some(obj) = chain_expr(&chain, |x| matches!(x, Expr::Obj(_))) {
5294        if let (Some(ol), Expr::Obj(entries)) = (expr_loc(&obj), &*obj) {
5295            let owner = chain.iter().find_map(|n| match n {
5296                NodeRef::Decl(d) => match &d.body {
5297                    DeclBody::Output { ty, expr, .. } if Rc::ptr_eq(expr, &obj) => Some(ty.clone()),
5298                    DeclBody::Input { ty, .. } => Some(ty.clone()),
5299                    _ => None,
5300                },
5301                _ => None,
5302            });
5303            // the reference: `owner && owner.expr === obj` — an input's fallback is never `expr`
5304            let owner = match (
5305                &owner,
5306                chain.iter().find_map(|n| match n {
5307                    NodeRef::Decl(d) => Some(*d),
5308                    _ => None,
5309                }),
5310            ) {
5311                (Some(_), Some(d)) if matches!(&d.body, DeclBody::Input { .. }) => None,
5312                _ => owner,
5313            };
5314            let rt: Option<RT> = match owner {
5315                Some(ty) => m.env.resolve(&ty, None).ok(),
5316                None => t.types.get(&key_of(&obj)).and_then(|x| x.rt.clone()),
5317            };
5318            if let Some(rt) = rt.filter(is_rec) {
5319                let have: Vec<&str> = entries.iter().map(|(k, _)| k.as_str()).collect();
5320                let missing: Vec<crate::semantics::Member> = rec_members(&rt)
5321                    .into_iter()
5322                    .filter(|x| x.kind == MKind::Req && !have.contains(&x.name.as_str()))
5323                    .collect();
5324                if !missing.is_empty() {
5325                    let fill = missing
5326                        .iter()
5327                        .map(|x| format!("{}: {}", x.name, placeholder_for(x.ty.as_ref())))
5328                        .collect::<Vec<_>>()
5329                        .join(", ");
5330                    let edit = match entries.last().and_then(|(_, v)| expr_loc(v)) {
5331                        Some(vl) => insert_json(
5332                            Pos {
5333                                line: vl.el,
5334                                character: vl.ec,
5335                            },
5336                            &format!(", {fill}"),
5337                        ),
5338                        None => insert_json(
5339                            Pos {
5340                                line: ol.sl,
5341                                character: ol.sc + 1,
5342                            },
5343                            &format!(" {fill}"),
5344                        ),
5345                    };
5346                    one(
5347                        format!(
5348                            "fill the required members: {}",
5349                            missing
5350                                .iter()
5351                                .map(|x| x.name.clone())
5352                                .collect::<Vec<_>>()
5353                                .join(", ")
5354                        ),
5355                        "refactor.rewrite",
5356                        vec![edit],
5357                    );
5358                }
5359            }
5360        }
5361    }
5362    // inline a constant: its expression at every use, the declaration gone
5363    if let Some(d) = decl {
5364        if let (DeclBody::Const { name, expr, .. }, Some(dl), Some(el)) =
5365            (&d.body, d.loc, d.body.const_expr_loc())
5366        {
5367            let nr = name_range(&text, d, name);
5368            let refs: Vec<Loc> = references(
5369                st,
5370                uri,
5371                Pos {
5372                    line: nr.sl,
5373                    character: nr.sc,
5374                },
5375                false,
5376            )
5377            .into_iter()
5378            .filter(|(rm, _)| rm.path == m.path)
5379            .map(|(_, l)| l)
5380            .collect();
5381            if !refs.is_empty() {
5382                let src = src_of(&text, el);
5383                let plain = matches!(
5384                    &**expr,
5385                    Expr::Name(_)
5386                        | Expr::Lit(_)
5387                        | Expr::UnitLit { .. }
5388                        | Expr::Call { .. }
5389                        | Expr::Member { .. }
5390                        | Expr::Paren(_)
5391                );
5392                let new_text = if plain {
5393                    src.clone()
5394                } else {
5395                    format!("({src})")
5396                };
5397                let mut edits: Vec<J> = refs.iter().map(|l| edit_json(*l, &new_text)).collect();
5398                edits.push(edit_json(
5399                    Loc {
5400                        sl: dl.sl,
5401                        sc: 0,
5402                        el: dl.el + 1,
5403                        ec: 0,
5404                    },
5405                    "",
5406                ));
5407                one(format!("inline {name}"), "refactor.inline", edits);
5408            }
5409        }
5410    }
5411    // extract an inline record type into a named type
5412    let own_body: Option<*const TypeAst> =
5413        decl.and_then(record_body_of).map(|t| t as *const TypeAst);
5414    let inline_record: Option<Loc> = chain
5415        .iter()
5416        .find_map(|n| match n {
5417            NodeRef::Type(t @ TypeAst::Record { loc: Some(l), .. })
5418                if own_body != Some(*t as *const TypeAst) =>
5419            {
5420                Some(*l)
5421            }
5422            _ => None,
5423        })
5424        .or_else(|| {
5425            chain.iter().find_map(|n| match n {
5426                NodeRef::Member(MemberAst::Value {
5427                    ty: TypeAst::Record { loc: Some(l), .. },
5428                    ..
5429                }) => Some(*l),
5430                _ => None,
5431            })
5432        });
5433    if let (Some(rl), Some(dl)) = (inline_record, decl.and_then(|d| d.loc)) {
5434        one(
5435            "extract to a named type".into(),
5436            "refactor.extract",
5437            vec![
5438                insert_json(
5439                    Pos {
5440                        line: dl.sl,
5441                        character: 0,
5442                    },
5443                    &format!("type Extracted = {}\n", src_of(&text, rl)),
5444                ),
5445                edit_json(rl, "Extracted"),
5446            ],
5447        );
5448    }
5449    // a unit literal in its base unit
5450    if let Some(ul) = chain_expr(&chain, |x| matches!(x, Expr::UnitLit { .. })) {
5451        if let (Expr::UnitLit { num, unit }, Some(l)) = (&*ul, expr_loc(&ul)) {
5452            if let Ok((key, to_base)) = m.env.unit_info(unit) {
5453                let base = m
5454                    .env
5455                    .base_unit_of
5456                    .borrow()
5457                    .get(&key)
5458                    .cloned()
5459                    .unwrap_or(key);
5460                if base != *unit {
5461                    let converted =
5462                        format!("{}{base}", crate::semantics::js_num_str(num * to_base));
5463                    one(
5464                        format!("convert to {converted}"),
5465                        "refactor.rewrite",
5466                        vec![edit_json(l, &converted)],
5467                    );
5468                }
5469            }
5470        }
5471    }
5472    // reorder a record's members into the canonical order
5473    let type_decl = chain.iter().find_map(|n| match n {
5474        NodeRef::Decl(d) if matches!(d.body, DeclBody::Type { .. }) => Some(*d),
5475        _ => None,
5476    });
5477    if let Some((members, bl, open)) = type_decl.and_then(record_body_of).and_then(record_parts) {
5478        if members.len() > 1 && members.iter().all(|x| x.loc().is_some()) {
5479            let rank = |x: &MemberAst| match x {
5480                MemberAst::Context { .. } => 0,
5481                MemberAst::Value { dflt: Some(_), .. } => 3,
5482                MemberAst::Value { opt: true, .. } => 2,
5483                MemberAst::Value { .. } => 1,
5484                MemberAst::Derived { hidden: true, .. } => 5,
5485                MemberAst::Derived { .. } => 4,
5486                _ => 6,
5487            };
5488            let mut sorted: Vec<(usize, &MemberAst)> = members.iter().enumerate().collect();
5489            sorted.sort_by_key(|(i, x)| (rank(x), *i));
5490            if sorted.iter().enumerate().any(|(i, (j, _))| i != *j) {
5491                let lead = leading_spaces(lines.get(bl.sl).copied().unwrap_or(""));
5492                let indent = format!("{lead}    ");
5493                let body: Vec<String> = sorted
5494                    .iter()
5495                    .map(|(_, x)| {
5496                        format!(
5497                            "{indent}{}",
5498                            trailing_comma.replace(&src_of(&text, x.loc().unwrap()), "")
5499                        )
5500                    })
5501                    .collect();
5502                let trailing = if open {
5503                    format!("\n{indent}...")
5504                } else {
5505                    String::new()
5506                };
5507                one(
5508                    "reorder the members canonically".into(),
5509                    "refactor.rewrite",
5510                    vec![edit_json(
5511                        bl,
5512                        &format!("{{\n{}{trailing}\n{lead}}}", body.join("\n")),
5513                    )],
5514                );
5515            }
5516        }
5517    }
5518    // an assert's inline `else error …` into a diagnostic declaration, and back
5519    let assert_member = chain.iter().find_map(|n| match n {
5520        NodeRef::Member(mm @ MemberAst::Assert { loc: Some(_), .. }) => Some(*mm),
5521        _ => None,
5522    });
5523    if let (
5524        Some(MemberAst::Assert {
5525            name: aname,
5526            tail: Some(tail),
5527            loc: Some(al),
5528            ..
5529        }),
5530        Some(tdl),
5531    ) = (assert_member, type_decl.and_then(|d| d.loc))
5532    {
5533        let asrc = src_of(&text, *al);
5534        if let Some(em) = Regex::new(r"\belse\b").unwrap().find(&asrc) {
5535            let (mut line, mut col) = (al.sl, al.sc);
5536            for u in asrc[..em.start()].encode_utf16() {
5537                if u == '\n' as u16 {
5538                    line += 1;
5539                    col = 0;
5540                } else {
5541                    col += 1;
5542                }
5543            }
5544            let tail_loc = Loc {
5545                sl: line,
5546                sc: col,
5547                el: al.el,
5548                ec: al.ec,
5549            };
5550            match tail {
5551                Tail::Inline { severity, template } => {
5552                    // the names the template reads become the parameters, typed as inferred
5553                    let mut names: Vec<String> = vec![];
5554                    let mut params: Vec<String> = vec![];
5555                    for part in template {
5556                        if let TPart::Expr(e) = part {
5557                            for n in exprs_under(&NodeRef::Expr(e)) {
5558                                if let Expr::Name(nm) = &*n {
5559                                    if !names.contains(nm) {
5560                                        names.push(nm.clone());
5561                                        let ty = t
5562                                            .types
5563                                            .get(&key_of(&n))
5564                                            .and_then(|x| x.rt.as_ref())
5565                                            .map(|rt| crate::infer::type_text(Some(rt)))
5566                                            .unwrap_or_else(|| "any".to_string());
5567                                        params.push(format!("{nm}: {ty}"));
5568                                    }
5569                                }
5570                            }
5571                        }
5572                    }
5573                    let tmpl = template
5574                        .iter()
5575                        .map(|p| match p {
5576                            TPart::Text(s) => s.clone(),
5577                            TPart::Expr(e) => format!("${{{}}}", crate::session::expr_text(e)),
5578                        })
5579                        .collect::<String>();
5580                    one(format!("declare a diagnostic for {aname}"), "refactor.extract", vec![
5581                        insert_json(Pos { line: tdl.sl, character: 0 }, &format!("diagnostic {aname}({}) {{\n    severity = {severity}\n    message = `{tmpl}`\n}}\n", params.join(", "))),
5582                        edit_json(tail_loc, &format!("else {aname}({})", names.join(", "))),
5583                    ]);
5584                }
5585                Tail::Ref { name: dname, args } => {
5586                    let dd = m.env.diags.borrow().get(dname).cloned();
5587                    let declared = m.decls.iter().any(
5588                        |d| matches!(&d.body, DeclBody::Diagnostic { name, .. } if name == dname),
5589                    );
5590                    if let (Some(dd), true) = (dd, declared) {
5591                        let arg_text: Vec<String> = args
5592                            .iter()
5593                            .map(|a| {
5594                                expr_loc(a)
5595                                    .map(|l| src_of(&text, l))
5596                                    .unwrap_or_else(|| crate::session::expr_text(a))
5597                            })
5598                            .collect();
5599                        let mut message = String::from("`");
5600                        for p in &dd.template {
5601                            match p {
5602                                TPart::Text(s) => message.push_str(s),
5603                                TPart::Expr(e) => {
5604                                    let i = match &**e {
5605                                        Expr::Name(nm) => {
5606                                            dd.params.iter().position(|q| &q.name == nm)
5607                                        }
5608                                        _ => None,
5609                                    };
5610                                    message.push_str("${");
5611                                    message.push_str(
5612                                        &i.and_then(|i| arg_text.get(i).cloned())
5613                                            .unwrap_or_else(|| crate::session::expr_text(e)),
5614                                    );
5615                                    message.push('}');
5616                                }
5617                            }
5618                        }
5619                        message.push('`');
5620                        one(
5621                            format!("inline the diagnostic {dname}"),
5622                            "refactor.inline",
5623                            vec![edit_json(
5624                                tail_loc,
5625                                &format!("else {} {message}", dd.severity),
5626                            )],
5627                        );
5628                    }
5629                }
5630            }
5631        }
5632    }
5633    // an `if` chain over a discriminant into a `match`, and back
5634    if let Some(if_expr) = chain_expr(&chain, |x| matches!(x, Expr::If { .. })) {
5635        if let Some(il) = expr_loc(&if_expr) {
5636            // conditions `subject.member == "lit"` on one subject and member, the union's arms telling which record each literal picks
5637            let mut arms: Vec<(String, Rc<Expr>)> = vec![];
5638            let mut subject: Option<Rc<Expr>> = None;
5639            let mut member: Option<String> = None;
5640            let mut tail_expr: Option<Rc<Expr>> = None;
5641            let mut ok = true;
5642            let mut cur = if_expr.clone();
5643            loop {
5644                let (c, tt, f) = match &*cur {
5645                    Expr::If { c, t, f } => (c.clone(), t.clone(), f.clone()),
5646                    _ => {
5647                        tail_expr = Some(cur.clone());
5648                        break;
5649                    }
5650                };
5651                let mut matched = false;
5652                if let Expr::Bin { op, l, r } = &*c {
5653                    if op == "==" {
5654                        if let (Expr::Member { x, name, .. }, Expr::Lit(Value::Str(lit))) =
5655                            (&**l, &**r)
5656                        {
5657                            match &subject {
5658                                None => {
5659                                    subject = Some(x.clone());
5660                                    member = Some(name.clone());
5661                                }
5662                                Some(s) => {
5663                                    if crate::session::expr_text(s) != crate::session::expr_text(x)
5664                                        || member.as_deref() != Some(name.as_str())
5665                                    {
5666                                        ok = false;
5667                                        break;
5668                                    }
5669                                }
5670                            }
5671                            arms.push((lit.clone(), tt.clone()));
5672                            matched = true;
5673                        }
5674                    }
5675                }
5676                if !matched {
5677                    ok = false;
5678                    break;
5679                }
5680                cur = f;
5681            }
5682            let srt = subject
5683                .as_ref()
5684                .and_then(|s| t.types.get(&key_of(s)))
5685                .and_then(|x| x.rt.clone());
5686            if let (true, false, Some(subj), Some(mem), Some(srt)) = (
5687                ok,
5688                arms.is_empty(),
5689                subject.as_ref(),
5690                member.as_ref(),
5691                srt.as_ref(),
5692            ) {
5693                if let RTk::Union(uarms) = &srt.k {
5694                    let arm_name = |lit: &str| -> Option<String> {
5695                        uarms.iter()
5696                            .find(|r| matches!(r.k, RTk::Rec(_)) && rec_members(r).iter().any(|mm| &mm.name == mem && matches!(mm.ty.as_ref().map(|t| &t.k), Some(RTk::Lit(Value::Str(v))) if v == lit)))
5697                            .and_then(|r| r.name.borrow().clone())
5698                    };
5699                    let names: Vec<Option<String>> =
5700                        arms.iter().map(|(lit, _)| arm_name(lit)).collect();
5701                    if names.iter().all(|n| n.is_some())
5702                        && arms.iter().all(|(_, b)| expr_loc(b).is_some())
5703                    {
5704                        let lead = leading_spaces(lines.get(il.sl).copied().unwrap_or(""));
5705                        let indent = format!("{lead}    ");
5706                        let subj_text = crate::session::expr_text(subj);
5707                        let v: String = match &**subj {
5708                            Expr::Name(n) => n
5709                                .chars()
5710                                .next()
5711                                .map(|c| c.to_string())
5712                                .unwrap_or_else(|| "v".into()),
5713                            _ => "v".into(),
5714                        };
5715                        let subj_re =
5716                            Regex::new(&format!(r"\b{}\b", regex::escape(&subj_text))).unwrap();
5717                        let mut cases: Vec<String> = arms
5718                            .iter()
5719                            .zip(names.iter())
5720                            .map(|((_, body), n)| {
5721                                format!(
5722                                    "{indent}({v}: {}) => {}",
5723                                    n.as_ref().unwrap(),
5724                                    subj_re.replace_all(
5725                                        &src_of(&text, expr_loc(body).unwrap()),
5726                                        v.as_str()
5727                                    )
5728                                )
5729                            })
5730                            .collect();
5731                        if let Some(tl) = tail_expr.as_ref().and_then(expr_loc) {
5732                            cases.push(format!("{indent}(other) => {}", src_of(&text, tl)));
5733                        }
5734                        one(
5735                            "convert to match".into(),
5736                            "refactor.rewrite",
5737                            vec![edit_json(
5738                                il,
5739                                &format!("match {subj_text} {{\n{}\n{lead}}}", cases.join("\n")),
5740                            )],
5741                        );
5742                    }
5743                }
5744            }
5745        }
5746    }
5747    if let Some(mx) = chain_expr(&chain, |x| matches!(x, Expr::Match { .. })) {
5748        if let (Expr::Match { subject, arms }, Some(ml)) = (&*mx, expr_loc(&mx)) {
5749            // arms typed by records that a literal member discriminates: `if subject.kind == "lit" then … else …`
5750            let srt = t.types.get(&key_of(subject)).and_then(|x| x.rt.clone());
5751            let recs: Vec<RT> = match srt.as_ref().map(|r| &r.k) {
5752                Some(RTk::Union(us)) => us
5753                    .iter()
5754                    .filter(|r| matches!(r.k, RTk::Rec(_)))
5755                    .cloned()
5756                    .collect(),
5757                _ => vec![],
5758            };
5759            let is_lit = |m: &crate::semantics::Member| {
5760                matches!(m.ty.as_ref().map(|t| &t.k), Some(RTk::Lit(_)))
5761            };
5762            let disc: Option<String> = recs.first().and_then(|r0| {
5763                rec_members(r0)
5764                    .into_iter()
5765                    .find(|mm| {
5766                        is_lit(mm)
5767                            && recs.iter().all(|r| {
5768                                rec_members(r)
5769                                    .iter()
5770                                    .any(|x| x.name == mm.name && is_lit(x))
5771                            })
5772                    })
5773                    .map(|mm| mm.name)
5774            });
5775            if let Some(disc) = disc {
5776                let subj_text = crate::session::expr_text(subject);
5777                let mut parts: Vec<String> = vec![];
5778                let mut fallback: Option<String> = None;
5779                let mut ok = true;
5780                for arm in arms {
5781                    let Some(bl) = expr_loc(&arm.body) else {
5782                        ok = false;
5783                        break;
5784                    };
5785                    let var_re = Regex::new(&format!(r"\b{}\b", regex::escape(&arm.v))).unwrap();
5786                    let body = var_re
5787                        .replace_all(&src_of(&text, bl), subj_text.as_str())
5788                        .to_string();
5789                    let rec = match &arm.ty {
5790                        Some(TypeAst::Named { name, .. }) => recs
5791                            .iter()
5792                            .find(|r| r.name.borrow().as_deref() == Some(name.as_str())),
5793                        _ => None,
5794                    };
5795                    let lit = rec
5796                        .and_then(|r| rec_members(r).into_iter().find(|x| x.name == disc))
5797                        .and_then(|x| x.ty)
5798                        .and_then(|ty| match &ty.k {
5799                            RTk::Lit(Value::Str(s)) => Some(s.clone()),
5800                            _ => None,
5801                        });
5802                    match lit {
5803                        Some(l) => parts.push(format!(
5804                            "if {subj_text}.{disc} == {} then {body}",
5805                            json_str(&l)
5806                        )),
5807                        None => fallback = Some(body),
5808                    }
5809                }
5810                if ok && !parts.is_empty() {
5811                    one(
5812                        "convert to if".into(),
5813                        "refactor.rewrite",
5814                        vec![edit_json(
5815                            ml,
5816                            &format!(
5817                                "{} else {}",
5818                                parts.join(" else "),
5819                                fallback.unwrap_or_else(|| "null".into())
5820                            ),
5821                        )],
5822                    );
5823                }
5824            }
5825        }
5826    }
5827    // inline a derived member into its sibling uses
5828    let derived = chain.iter().find_map(|n| match n {
5829        NodeRef::Member(
5830            mm @ MemberAst::Derived {
5831                loc: Some(_), expr, ..
5832            },
5833        ) if expr_loc(expr).is_some() => Some(*mm),
5834        _ => None,
5835    });
5836    if let (
5837        Some(
5838            dm @ MemberAst::Derived {
5839                name: dname,
5840                expr: dexpr,
5841                loc: Some(dl),
5842                ..
5843            },
5844        ),
5845        Some((members, _, _)),
5846    ) = (
5847        derived,
5848        type_decl.and_then(record_body_of).and_then(record_parts),
5849    ) {
5850        let mut uses: Vec<Loc> = vec![];
5851        for other in members {
5852            if std::ptr::eq(other, dm) {
5853                continue;
5854            }
5855            for e in exprs_under(&NodeRef::Member(other)) {
5856                if let Expr::Name(nm) = &*e {
5857                    if nm == dname {
5858                        if let Some(l) = expr_loc(&e) {
5859                            uses.push(l);
5860                        }
5861                    }
5862                }
5863            }
5864        }
5865        if !uses.is_empty() {
5866            let src = src_of(&text, expr_loc(dexpr).unwrap());
5867            let plain = matches!(
5868                &**dexpr,
5869                Expr::Name(_)
5870                    | Expr::Lit(_)
5871                    | Expr::UnitLit { .. }
5872                    | Expr::Call { .. }
5873                    | Expr::Member { .. }
5874                    | Expr::Paren(_)
5875            );
5876            let wrapped = if plain { src } else { format!("({src})") };
5877            let mut edits: Vec<J> = uses.iter().map(|l| edit_json(*l, &wrapped)).collect();
5878            edits.push(edit_json(
5879                Loc {
5880                    sl: dl.sl,
5881                    sc: 0,
5882                    el: dl.el + 1,
5883                    ec: 0,
5884                },
5885                "",
5886            ));
5887            one(format!("inline {dname}"), "refactor.inline", edits);
5888        }
5889    }
5890    // flip the operands of a comparison
5891    let cmp = chain_expr(
5892        &chain,
5893        |x| matches!(x, Expr::Bin { op, l, r } if ["<", ">", "<=", ">=", "==", "!="].contains(&op.as_str()) && expr_loc(l).is_some() && expr_loc(r).is_some()),
5894    );
5895    if let Some(c) = cmp {
5896        if let (Expr::Bin { op, l, r }, Some(cl)) = (&*c, expr_loc(&c)) {
5897            let flipped = match op.as_str() {
5898                "<" => ">",
5899                ">" => "<",
5900                "<=" => ">=",
5901                ">=" => "<=",
5902                other => other,
5903            };
5904            one(
5905                "flip the comparison".into(),
5906                "refactor.rewrite",
5907                vec![edit_json(
5908                    cl,
5909                    &format!(
5910                        "{} {} {}",
5911                        src_of(&text, expr_loc(r).unwrap()),
5912                        flipped,
5913                        src_of(&text, expr_loc(l).unwrap())
5914                    ),
5915                )],
5916            );
5917        }
5918    }
5919    // extract the selected expression: into a constant (a constant expression), or a derived member (inside a record body)
5920    let selected = if range.0.line != range.1.line || range.0.character != range.1.character {
5921        chain_expr(&chain, |_| true).and_then(|_| {
5922            chain.iter().find_map(|n| match n {
5923                NodeRef::Expr(e) => expr_loc(e)
5924                    .filter(|l| {
5925                        l.sl == range.0.line
5926                            && l.sc == range.0.character
5927                            && l.el == range.1.line
5928                            && l.ec == range.1.character
5929                    })
5930                    .map(|_| (*e).clone()),
5931                _ => None,
5932            })
5933        })
5934    } else {
5935        None
5936    };
5937    if let Some(sel) = selected.filter(|e| !matches!(&**e, Expr::Name(_))) {
5938        let src = src_of(&text, expr_loc(&sel).unwrap());
5939        let enclosing_member = chain.iter().find_map(|n| match n {
5940            NodeRef::Member(mm) if mm.loc().is_some() => Some(*mm),
5941            _ => None,
5942        });
5943        let enclosing_decl = chain.iter().find_map(|n| match n {
5944            NodeRef::Decl(d) => Some(*d),
5945            _ => None,
5946        });
5947        if let (false, Some(dl)) = (mentions_name(&sel), enclosing_decl.and_then(|d| d.loc)) {
5948            one(
5949                "extract to a constant".into(),
5950                "refactor.extract",
5951                vec![
5952                    insert_json(
5953                        Pos {
5954                            line: dl.sl,
5955                            character: 0,
5956                        },
5957                        &format!("const extracted = {src}\n"),
5958                    ),
5959                    edit_json(expr_loc(&sel).unwrap(), "extracted"),
5960                ],
5961            );
5962        }
5963        if let Some(mm) = enclosing_member.filter(|mm| !matches!(mm, MemberAst::Context { .. })) {
5964            let ml = mm.loc().unwrap();
5965            let indent = leading_spaces(lines.get(ml.sl).copied().unwrap_or(""));
5966            one(
5967                "extract to a derived member".into(),
5968                "refactor.extract",
5969                vec![
5970                    insert_json(
5971                        Pos {
5972                            line: ml.sl,
5973                            character: 0,
5974                        },
5975                        &format!("{indent}extracted = {src}\n"),
5976                    ),
5977                    edit_json(expr_loc(&sel).unwrap(), "extracted"),
5978                ],
5979            );
5980        }
5981    }
5982    J::Arr(out)
5983}
5984
5985// ---------------- local variables: linked editing, rename ----------------
5986// a comprehension variable, a lambda parameter, a match arm's variable, or
5987// a function parameter: its binding token and its uses in its scope
5988fn binds_name(n: &NodeRef, name: &str) -> bool {
5989    match n {
5990        NodeRef::Expr(e) => match &***e {
5991            Expr::Comp { clauses, .. } | Expr::MapComp { clauses, .. } => {
5992                clauses.iter().any(|c| c.v == name)
5993            }
5994            Expr::Lambda { params, .. } => params.iter().any(|p| p == name),
5995            Expr::Match { arms, .. } => arms.iter().any(|a| a.v == name),
5996            _ => false,
5997        },
5998        NodeRef::Decl(d) => {
5999            matches!(&d.body, DeclBody::Func { params, .. } if params.iter().any(|p| p.name == name))
6000        }
6001        _ => false,
6002    }
6003}
6004fn expr_binds_name(e: &Rc<Expr>, name: &str) -> bool {
6005    binds_name(&NodeRef::Expr(e), name)
6006}
6007// an offset into the scope's source text (UTF-16 units) -> a Loc of `name`
6008fn loc_in(scope: Loc, src: &str, byte: usize, name: &str) -> Loc {
6009    let before = &src[..byte];
6010    let (line, col) = match before.rfind('\n') {
6011        Some(nl) => (
6012            scope.sl + before.matches('\n').count(),
6013            u16len(&before[nl + 1..]),
6014        ),
6015        None => (scope.sl, scope.sc + u16len(before)),
6016    };
6017    Loc {
6018        sl: line,
6019        sc: col,
6020        el: line,
6021        ec: col + u16len(name),
6022    }
6023}
6024fn binding_locs(text: &str, scope: &NodeRef, scope_loc: Loc, name: &str) -> Vec<Loc> {
6025    let src = src_of(text, scope_loc);
6026    let esc = regex::escape(name);
6027    let mut out: Vec<Loc> = vec![];
6028    match scope {
6029        NodeRef::Decl(_) => {
6030            let re = Regex::new(&format!(r"\(([^)]*)(?-u:\b)({esc})(?-u:\b)")).unwrap();
6031            if let Some(m) = re.find(&src) {
6032                // the pattern ends with the name itself: the token is the tail of the match
6033                out.push(loc_in(scope_loc, &src, m.end() - name.len(), name));
6034            }
6035        }
6036        NodeRef::Expr(e) if matches!(&***e, Expr::Lambda { .. }) => {
6037            // `\b(name)\b(?=[^=]*=>)`: the first whole-word occurrence whose next `=` opens `=>`
6038            let re = Regex::new(&format!(r"(?-u:\b)({esc})(?-u:\b)")).unwrap();
6039            for m in re.find_iter(&src) {
6040                let rest = &src[m.end()..];
6041                let ok = match rest.find('=') {
6042                    Some(i) => rest[i..].starts_with("=>"),
6043                    None => false,
6044                };
6045                if ok {
6046                    out.push(loc_in(scope_loc, &src, m.start(), name));
6047                    break;
6048                }
6049            }
6050        }
6051        NodeRef::Expr(e) if matches!(&***e, Expr::Match { .. }) => {
6052            let re = Regex::new(&format!(r"\(\s*({esc})(?-u:\b)")).unwrap();
6053            for m in re.find_iter(&src) {
6054                out.push(loc_in(scope_loc, &src, m.end() - name.len(), name));
6055            }
6056        }
6057        _ => {
6058            let re = Regex::new(&format!(r"(?-u:\b)for\s+({esc})(?-u:\b)")).unwrap();
6059            for m in re.find_iter(&src) {
6060                out.push(loc_in(scope_loc, &src, m.end() - name.len(), name));
6061            }
6062        }
6063    }
6064    out
6065}
6066// the uses of a local in its scope: name expressions, shadowing scopes skipped
6067fn uses_in_expr(e: &Rc<Expr>, name: &str, scope_key: usize, out: &mut Vec<Loc>) {
6068    if key_of(e) != scope_key && expr_binds_name(e, name) {
6069        return;
6070    }
6071    match &**e {
6072        Expr::Name(n) => {
6073            if n == name {
6074                if let Some(l) = expr_loc(e) {
6075                    out.push(l);
6076                }
6077            }
6078        }
6079        Expr::Template(parts) => {
6080            for p in parts {
6081                if let TPart::Expr(x) = p {
6082                    uses_in_expr(x, name, scope_key, out);
6083                }
6084            }
6085        }
6086        Expr::Obj(entries) => {
6087            for (_, v) in entries {
6088                uses_in_expr(v, name, scope_key, out);
6089            }
6090        }
6091        Expr::Arr(items) => {
6092            for (_, v) in items {
6093                uses_in_expr(v, name, scope_key, out);
6094            }
6095        }
6096        Expr::Comp { head, clauses } => {
6097            uses_in_expr(head, name, scope_key, out);
6098            for c in clauses {
6099                uses_in_expr(&c.iter, name, scope_key, out);
6100                for f in &c.filters {
6101                    uses_in_expr(f, name, scope_key, out);
6102                }
6103            }
6104        }
6105        Expr::MapComp { key, val, clauses } => {
6106            uses_in_expr(key, name, scope_key, out);
6107            uses_in_expr(val, name, scope_key, out);
6108            for c in clauses {
6109                uses_in_expr(&c.iter, name, scope_key, out);
6110                for f in &c.filters {
6111                    uses_in_expr(f, name, scope_key, out);
6112                }
6113            }
6114        }
6115        Expr::Bin { l, r, .. } => {
6116            uses_in_expr(l, name, scope_key, out);
6117            uses_in_expr(r, name, scope_key, out);
6118        }
6119        Expr::Un { x, .. } | Expr::Paren(x) => uses_in_expr(x, name, scope_key, out),
6120        Expr::If { c, t, f } => {
6121            uses_in_expr(c, name, scope_key, out);
6122            uses_in_expr(t, name, scope_key, out);
6123            uses_in_expr(f, name, scope_key, out);
6124        }
6125        Expr::Lambda { body, .. } => uses_in_expr(body, name, scope_key, out),
6126        Expr::Call { fun, args } => {
6127            uses_in_expr(fun, name, scope_key, out);
6128            for a in args {
6129                uses_in_expr(a, name, scope_key, out);
6130            }
6131        }
6132        Expr::Member { x, .. } => uses_in_expr(x, name, scope_key, out),
6133        Expr::Index { x, i } => {
6134            uses_in_expr(x, name, scope_key, out);
6135            uses_in_expr(i, name, scope_key, out);
6136        }
6137        Expr::With { base, patch } => {
6138            uses_in_expr(base, name, scope_key, out);
6139            uses_in_expr(patch, name, scope_key, out);
6140        }
6141        Expr::Match { subject, arms } => {
6142            uses_in_expr(subject, name, scope_key, out);
6143            for a in arms {
6144                if let Some(t) = &a.ty {
6145                    uses_in_type(t, name, scope_key, out);
6146                }
6147                uses_in_expr(&a.body, name, scope_key, out);
6148            }
6149        }
6150        _ => {}
6151    }
6152}
6153fn uses_in_type(t: &TypeAst, name: &str, scope_key: usize, out: &mut Vec<Loc>) {
6154    match t {
6155        TypeAst::Record { members, .. } => {
6156            for m in members {
6157                uses_in_member(m, name, scope_key, out);
6158            }
6159        }
6160        TypeAst::Map { key, val, .. } => {
6161            uses_in_type(key, name, scope_key, out);
6162            uses_in_type(val, name, scope_key, out);
6163        }
6164        TypeAst::Array { elem, .. } => uses_in_type(elem, name, scope_key, out),
6165        TypeAst::Union { arms, .. } | TypeAst::Isect { arms, .. } => {
6166            for a in arms {
6167                uses_in_type(a, name, scope_key, out);
6168            }
6169        }
6170        TypeAst::Func { params, ret, .. } => {
6171            for a in params {
6172                uses_in_type(a, name, scope_key, out);
6173            }
6174            uses_in_type(ret, name, scope_key, out);
6175        }
6176        TypeAst::Named {
6177            args, preds, ext, ..
6178        } => {
6179            for a in args {
6180                uses_in_type(a, name, scope_key, out);
6181            }
6182            for x in preds.iter().flatten() {
6183                uses_in_expr(x, name, scope_key, out);
6184            }
6185            if let Some(x) = ext {
6186                uses_in_type(x, name, scope_key, out);
6187            }
6188        }
6189        _ => {}
6190    }
6191}
6192fn uses_in_member(m: &MemberAst, name: &str, scope_key: usize, out: &mut Vec<Loc>) {
6193    match m {
6194        MemberAst::Value { ty, dflt, .. } => {
6195            uses_in_type(ty, name, scope_key, out);
6196            if let Some(d) = dflt {
6197                uses_in_expr(d, name, scope_key, out);
6198            }
6199        }
6200        MemberAst::Derived { ty, expr, .. } => {
6201            if let Some(t) = ty {
6202                uses_in_type(t, name, scope_key, out);
6203            }
6204            uses_in_expr(expr, name, scope_key, out);
6205        }
6206        MemberAst::Context { ty, .. } => uses_in_type(ty, name, scope_key, out),
6207        MemberAst::Assert { cond, tail, .. } => {
6208            uses_in_expr(cond, name, scope_key, out);
6209            if let Some(t) = tail {
6210                uses_in_tail(t, name, scope_key, out);
6211            }
6212        }
6213        MemberAst::When { cond, body, .. } => {
6214            uses_in_expr(cond, name, scope_key, out);
6215            for b in body {
6216                uses_in_member(b, name, scope_key, out);
6217            }
6218        }
6219    }
6220}
6221fn uses_in_tail(t: &Tail, name: &str, scope_key: usize, out: &mut Vec<Loc>) {
6222    match t {
6223        Tail::Inline { template, .. } => {
6224            for p in template {
6225                if let TPart::Expr(x) = p {
6226                    uses_in_expr(x, name, scope_key, out);
6227                }
6228            }
6229        }
6230        Tail::Ref { args, .. } => {
6231            for a in args {
6232                uses_in_expr(a, name, scope_key, out);
6233            }
6234        }
6235    }
6236}
6237fn local_ranges(st: &State, uri: &str, pos: Pos) -> Option<Vec<Loc>> {
6238    let text = st.text(uri)?.clone();
6239    let parsed = parse_source(&text);
6240    if !parsed.errors.is_empty() {
6241        return None;
6242    }
6243    let hit = node_at(&parsed.decls, pos)?;
6244    let mut chain: Vec<NodeRef> = vec![hit.node.clone()];
6245    chain.extend(hit.parents.iter().rev().cloned());
6246    let (name, scope): (String, Option<NodeRef>) = match &hit.node {
6247        NodeRef::Expr(e) if matches!(&***e, Expr::Name(_)) => {
6248            let Expr::Name(n) = &***e else { unreachable!() };
6249            let scope = hit.parents.iter().rev().find(|p| binds_name(p, n)).cloned();
6250            (n.clone(), scope)
6251        }
6252        _ => {
6253            // on a binding token: the scope node itself, the name under the cursor
6254            let line = text.split('\n').nth(pos.line).unwrap_or("");
6255            let re = Regex::new(r"[A-Za-z_][A-Za-z0-9_]*").unwrap();
6256            let mut found: Option<String> = None;
6257            for m in re.find_iter(line) {
6258                let a = u16_col(line, m.start());
6259                let b = a + u16len(m.as_str());
6260                if a <= pos.character && pos.character <= b {
6261                    found = Some(m.as_str().to_string());
6262                    break;
6263                }
6264            }
6265            let n = found?;
6266            let scope = chain.iter().find(|p| binds_name(p, &n)).cloned();
6267            (n, scope)
6268        }
6269    };
6270    let scope = scope?;
6271    let scope_loc = scope.loc()?;
6272    let mut locs = binding_locs(&text, &scope, scope_loc, &name);
6273    match &scope {
6274        NodeRef::Expr(e) => {
6275            // the scope's own children, the scope itself not counted as shadowing
6276            let key = key_of(e);
6277            match &***e {
6278                Expr::Comp { head, clauses } => {
6279                    uses_in_expr(head, &name, key, &mut locs);
6280                    for c in clauses {
6281                        uses_in_expr(&c.iter, &name, key, &mut locs);
6282                        for f in &c.filters {
6283                            uses_in_expr(f, &name, key, &mut locs);
6284                        }
6285                    }
6286                }
6287                Expr::MapComp {
6288                    key: k,
6289                    val,
6290                    clauses,
6291                } => {
6292                    uses_in_expr(k, &name, key, &mut locs);
6293                    uses_in_expr(val, &name, key, &mut locs);
6294                    for c in clauses {
6295                        uses_in_expr(&c.iter, &name, key, &mut locs);
6296                        for f in &c.filters {
6297                            uses_in_expr(f, &name, key, &mut locs);
6298                        }
6299                    }
6300                }
6301                Expr::Lambda { body, .. } => uses_in_expr(body, &name, key, &mut locs),
6302                Expr::Match { subject, arms } => {
6303                    uses_in_expr(subject, &name, key, &mut locs);
6304                    for a in arms {
6305                        if let Some(t) = &a.ty {
6306                            uses_in_type(t, &name, key, &mut locs);
6307                        }
6308                        uses_in_expr(&a.body, &name, key, &mut locs);
6309                    }
6310                }
6311                _ => {}
6312            }
6313        }
6314        NodeRef::Decl(d) => {
6315            if let DeclBody::Func {
6316                params, ret, body, ..
6317            } = &d.body
6318            {
6319                for p in params {
6320                    if let Some(t) = &p.ty {
6321                        uses_in_type(t, &name, 0, &mut locs);
6322                    }
6323                }
6324                if let Some(t) = ret {
6325                    uses_in_type(t, &name, 0, &mut locs);
6326                }
6327                uses_in_expr(body, &name, 0, &mut locs);
6328            }
6329        }
6330        _ => {}
6331    }
6332    let mut seen: Vec<(usize, usize)> = vec![];
6333    let mut out: Vec<Loc> = vec![];
6334    for l in locs {
6335        if !seen.contains(&(l.sl, l.sc)) {
6336            seen.push((l.sl, l.sc));
6337            out.push(l);
6338        }
6339    }
6340    out.sort_by(|p, q| p.sl.cmp(&q.sl).then(p.sc.cmp(&q.sc)));
6341    Some(out)
6342}
6343fn linked_editing_range(st: &State, uri: &str, pos: Pos) -> J {
6344    match local_ranges(st, uri, pos) {
6345        Some(locs) if !locs.is_empty() => J::obj(vec![
6346            (
6347                "ranges",
6348                J::Arr(locs.iter().map(|l| range_json(*l)).collect()),
6349            ),
6350            ("wordPattern", J::s("[A-Za-z_][A-Za-z0-9_]*")),
6351        ]),
6352        _ => J::Null,
6353    }
6354}
6355
6356// ---------------- the syntax tree ----------------
6357// tree-sitter's own S-expression (`ts_node_string`): named nodes with
6358// their field names — what web-tree-sitter's `toString()` prints too
6359fn syntax_tree(st: &State, uri: &str) -> J {
6360    let Some(text) = st.text(uri) else {
6361        return J::Null;
6362    };
6363    let mut parser = tree_sitter::Parser::new();
6364    let lang: tree_sitter::Language = crate::parse::LANGUAGE.into();
6365    parser.set_language(&lang).expect("grammar");
6366    let tree = parser.parse(text, None).expect("parse");
6367    J::obj(vec![("tree", J::s(tree.root_node().to_sexp()))])
6368}
6369
6370// ---------------- request handling ----------------
6371/// returns the exit code when the client asked to exit
6372fn handle(st: &mut State, msg: &Value) -> Option<i32> {
6373    // a message without a method is the client's response to a request of
6374    // ours (window/workDoneProgress/create): nothing to answer
6375    get(msg, "method")?;
6376    let id = get(msg, "id");
6377    let method = as_str(get(msg, "method")).unwrap_or("");
6378    let params = get(msg, "params");
6379    let td_uri = || {
6380        as_str(
6381            params
6382                .and_then(|p| get(p, "textDocument"))
6383                .and_then(|t| get(t, "uri")),
6384        )
6385        .unwrap_or("")
6386        .to_string()
6387    };
6388    let position = || {
6389        let pos = params.and_then(|p| get(p, "position"));
6390        Pos {
6391            line: as_usize(pos.and_then(|p| get(p, "line"))).unwrap_or(0),
6392            character: as_usize(pos.and_then(|p| get(p, "character"))).unwrap_or(0),
6393        }
6394    };
6395    let reanalyze = |st: &mut State| {
6396        st.analyses.clear();
6397        let uris: Vec<String> = st.docs.iter().map(|(u, _)| u.clone()).collect();
6398        for u in uris {
6399            analyze(st, &u);
6400        }
6401    };
6402    match method {
6403        "initialize" => {
6404            st.progress_supported = as_bool(
6405                params
6406                    .and_then(|p| get(p, "capabilities"))
6407                    .and_then(|c| get(c, "window"))
6408                    .and_then(|w| get(w, "workDoneProgress")),
6409            )
6410            .unwrap_or(false);
6411            let caps = J::obj(vec![
6412                ("textDocumentSync", J::Num(1)),
6413                ("hoverProvider", J::Bool(true)),
6414                ("definitionProvider", J::Bool(true)),
6415                ("typeDefinitionProvider", J::Bool(true)),
6416                ("referencesProvider", J::Bool(true)),
6417                ("documentHighlightProvider", J::Bool(true)),
6418                ("documentSymbolProvider", J::Bool(true)),
6419                ("foldingRangeProvider", J::Bool(true)),
6420                ("documentFormattingProvider", J::Bool(true)),
6421                (
6422                    "renameProvider",
6423                    J::obj(vec![("prepareProvider", J::Bool(true))]),
6424                ),
6425                (
6426                    "completionProvider",
6427                    J::obj(vec![(
6428                        "triggerCharacters",
6429                        J::Arr(vec![J::s("."), J::s("$"), J::s(":")]),
6430                    )]),
6431                ),
6432                (
6433                    "codeLensProvider",
6434                    J::obj(vec![("resolveProvider", J::Bool(false))]),
6435                ),
6436                (
6437                    "signatureHelpProvider",
6438                    J::obj(vec![(
6439                        "triggerCharacters",
6440                        J::Arr(vec![J::s("("), J::s(",")]),
6441                    )]),
6442                ),
6443                ("workspaceSymbolProvider", J::Bool(true)),
6444                ("selectionRangeProvider", J::Bool(true)),
6445                (
6446                    "semanticTokensProvider",
6447                    J::obj(vec![
6448                        (
6449                            "legend",
6450                            J::obj(vec![
6451                                (
6452                                    "tokenTypes",
6453                                    J::Arr(TOKEN_TYPES.iter().map(|t| J::s(*t)).collect()),
6454                                ),
6455                                (
6456                                    "tokenModifiers",
6457                                    J::Arr(TOKEN_MODS.iter().map(|t| J::s(*t)).collect()),
6458                                ),
6459                            ]),
6460                        ),
6461                        ("full", J::Bool(true)),
6462                    ]),
6463                ),
6464                ("inlayHintProvider", J::Bool(true)),
6465                ("callHierarchyProvider", J::Bool(true)),
6466                ("typeHierarchyProvider", J::Bool(true)),
6467                (
6468                    "codeActionProvider",
6469                    J::obj(vec![(
6470                        "codeActionKinds",
6471                        J::Arr(vec![
6472                            J::s("quickfix"),
6473                            J::s("refactor.rewrite"),
6474                            J::s("refactor.extract"),
6475                            J::s("refactor.inline"),
6476                        ]),
6477                    )]),
6478                ),
6479                ("linkedEditingRangeProvider", J::Bool(true)),
6480                (
6481                    "documentOnTypeFormattingProvider",
6482                    J::obj(vec![
6483                        ("firstTriggerCharacter", J::s("\n")),
6484                        (
6485                            "moreTriggerCharacter",
6486                            J::Arr(vec![J::s("}"), J::s("]"), J::s(")")]),
6487                        ),
6488                    ]),
6489                ),
6490                (
6491                    "executeCommandProvider",
6492                    J::obj(vec![(
6493                        "commands",
6494                        J::Arr(
6495                            [
6496                                "decl.evaluate",
6497                                "decl.validate",
6498                                "decl.trace",
6499                                "decl.showSyntaxTree",
6500                                "decl.reloadWorkspace",
6501                            ]
6502                            .iter()
6503                            .map(|c| J::s(*c))
6504                            .collect(),
6505                        ),
6506                    )]),
6507                ),
6508            ]);
6509            reply(
6510                id,
6511                J::obj(vec![
6512                    ("capabilities", caps),
6513                    (
6514                        "serverInfo",
6515                        J::obj(vec![("name", J::s("decl-lsp")), ("version", J::s("0.3.0"))]),
6516                    ),
6517                ]),
6518            );
6519        }
6520        "initialized" => {}
6521        "workspace/didChangeConfiguration" => {
6522            let inputs = params
6523                .and_then(|p| get(p, "settings"))
6524                .and_then(|s| get(s, "decl"))
6525                .and_then(|d| get(d, "inputs"));
6526            st.inputs = match inputs {
6527                Some(Value::JObj(es)) => es
6528                    .iter()
6529                    .filter_map(|(k, v)| as_str(Some(v)).map(|f| (k.clone(), f.to_string())))
6530                    .collect(),
6531                _ => vec![],
6532            };
6533            let hints = params
6534                .and_then(|p| get(p, "settings"))
6535                .and_then(|s| get(s, "decl"))
6536                .and_then(|d| get(d, "inlayHints"));
6537            if let Some(b) = as_bool(hints.and_then(|h| get(h, "types"))) {
6538                st.hint_types = b;
6539            }
6540            if let Some(b) = as_bool(hints.and_then(|h| get(h, "parameterNames"))) {
6541                st.hint_parameter_names = b;
6542            }
6543            if let Some(b) = as_bool(hints.and_then(|h| get(h, "values"))) {
6544                st.hint_values = b;
6545            }
6546            if let Some(b) = as_bool(hints.and_then(|h| get(h, "units"))) {
6547                st.hint_units = b;
6548            }
6549            if let Some(b) = as_bool(hints.and_then(|h| get(h, "contextVariables"))) {
6550                st.hint_context_variables = b;
6551            }
6552            reanalyze(st);
6553        }
6554        "workspace/didChangeWatchedFiles" => reanalyze(st),
6555        "decl/files" => {
6556            // a browser client's workspace files (the reference's web worker): the host here is the
6557            // file system, so they become overlay texts — never written to disk
6558            if let Some(Value::JArr(files)) = params.and_then(|p| get(p, "files")) {
6559                for f in files.iter() {
6560                    if let (Some(u), Some(text)) = (as_str(get(f, "uri")), as_str(get(f, "text"))) {
6561                        st.overlay.insert(path_of(u), text.to_string());
6562                    }
6563                }
6564            }
6565            if let Some(Value::JArr(removed)) = params.and_then(|p| get(p, "remove")) {
6566                for u in removed.iter() {
6567                    if let Some(u) = as_str(Some(u)) {
6568                        st.overlay.remove(&path_of(u));
6569                    }
6570                }
6571            }
6572            reanalyze(st);
6573        }
6574        "textDocument/didOpen" => {
6575            let uri = td_uri();
6576            let text = as_str(
6577                params
6578                    .and_then(|p| get(p, "textDocument"))
6579                    .and_then(|t| get(t, "text")),
6580            )
6581            .unwrap_or("")
6582            .to_string();
6583            st.set(&uri, text);
6584            st.analyses.clear();
6585            analyze(st, &uri);
6586        }
6587        "textDocument/didChange" => {
6588            let uri = td_uri();
6589            let text = params
6590                .and_then(|p| get(p, "contentChanges"))
6591                .and_then(|c| {
6592                    if let Value::JArr(items) = c {
6593                        items.first()
6594                    } else {
6595                        None
6596                    }
6597                })
6598                .and_then(|c| as_str(get(c, "text")))
6599                .unwrap_or("")
6600                .to_string();
6601            st.set(&uri, text);
6602            st.analyses.clear();
6603            analyze(st, &uri);
6604        }
6605        "textDocument/didSave" => {}
6606        "textDocument/didClose" => {
6607            let uri = td_uri();
6608            st.docs.retain(|(u, _)| *u != uri);
6609            st.overlay.remove(&path_of(&uri));
6610            st.analyses.remove(&uri);
6611            st.last_good.remove(&uri);
6612            notify(
6613                "textDocument/publishDiagnostics",
6614                J::obj(vec![
6615                    ("uri", J::s(uri.clone())),
6616                    ("diagnostics", J::Arr(vec![])),
6617                ]),
6618            );
6619        }
6620        "textDocument/hover" => {
6621            let r = hover(st, &td_uri(), position());
6622            reply(id, r);
6623        }
6624        "textDocument/definition" => {
6625            let r = definition(st, &td_uri(), position());
6626            reply(id, r);
6627        }
6628        "textDocument/typeDefinition" => {
6629            let r = type_definition(st, &td_uri(), position());
6630            reply(id, r);
6631        }
6632        "textDocument/references" => {
6633            let incl = as_bool(
6634                params
6635                    .and_then(|p| get(p, "context"))
6636                    .and_then(|c| get(c, "includeDeclaration")),
6637            )
6638            .unwrap_or(false);
6639            let refs = references(st, &td_uri(), position(), incl);
6640            reply(
6641                id,
6642                J::Arr(refs.iter().map(|(m, l)| location(m, *l)).collect()),
6643            );
6644        }
6645        "textDocument/documentHighlight" => {
6646            let uri = td_uri();
6647            let path = path_of(&uri);
6648            let refs = references(st, &uri, position(), true);
6649            reply(
6650                id,
6651                J::Arr(
6652                    refs.iter()
6653                        .filter(|(m, _)| m.path == path)
6654                        .map(|(_, l)| J::obj(vec![("range", range_json(*l)), ("kind", J::Num(1))]))
6655                        .collect(),
6656                ),
6657            );
6658        }
6659        "textDocument/completion" => {
6660            let r = completion(st, &td_uri(), position());
6661            reply(id, r);
6662        }
6663        "textDocument/documentSymbol" => reply(id, document_symbols(st, &td_uri())),
6664        "textDocument/foldingRange" => reply(id, folding_ranges(st, &td_uri())),
6665        "textDocument/formatting" => reply(id, formatting(st, &td_uri())),
6666        "textDocument/prepareRename" => {
6667            let r = prepare_rename(st, &td_uri(), position());
6668            reply(id, r);
6669        }
6670        "textDocument/rename" => {
6671            let new_name = as_str(params.and_then(|p| get(p, "newName")))
6672                .unwrap_or("")
6673                .to_string();
6674            let r = rename(st, &td_uri(), position(), &new_name);
6675            reply(id, r);
6676        }
6677        "textDocument/codeLens" => reply(id, code_lenses(st, &td_uri())),
6678        "textDocument/signatureHelp" => {
6679            let r = signature_help(st, &td_uri(), position());
6680            reply(id, r);
6681        }
6682        "workspace/symbol" => reply(
6683            id,
6684            workspace_symbols(
6685                st,
6686                as_str(params.and_then(|p| get(p, "query"))).unwrap_or(""),
6687            ),
6688        ),
6689        "textDocument/selectionRange" => {
6690            let positions: Vec<Pos> = match params.and_then(|p| get(p, "positions")) {
6691                Some(Value::JArr(items)) => items
6692                    .iter()
6693                    .map(|p| Pos {
6694                        line: as_usize(get(p, "line")).unwrap_or(0),
6695                        character: as_usize(get(p, "character")).unwrap_or(0),
6696                    })
6697                    .collect(),
6698                _ => vec![],
6699            };
6700            reply(id, selection_ranges(st, &td_uri(), &positions));
6701        }
6702        "textDocument/semanticTokens/full" => {
6703            let r = semantic_tokens(st, &td_uri());
6704            reply(id, r);
6705        }
6706        "textDocument/inlayHint" => {
6707            let rg = params.and_then(|p| get(p, "range"));
6708            let pt = |k: &str| {
6709                let p = rg.and_then(|r| get(r, k));
6710                Pos {
6711                    line: as_usize(p.and_then(|x| get(x, "line"))).unwrap_or(0),
6712                    character: as_usize(p.and_then(|x| get(x, "character"))).unwrap_or(0),
6713                }
6714            };
6715            let range = (pt("start"), pt("end"));
6716            let r = inlay_hints(st, &td_uri(), range);
6717            reply(id, r);
6718        }
6719        "textDocument/prepareCallHierarchy" => {
6720            let r = prepare_hierarchy(st, &td_uri(), position(), "func");
6721            reply(id, r);
6722        }
6723        "callHierarchy/incomingCalls" => {
6724            reply(id, incoming_calls(st, params.and_then(|p| get(p, "item"))))
6725        }
6726        "callHierarchy/outgoingCalls" => {
6727            reply(id, outgoing_calls(st, params.and_then(|p| get(p, "item"))))
6728        }
6729        "textDocument/prepareTypeHierarchy" => {
6730            let r = prepare_hierarchy(st, &td_uri(), position(), "type");
6731            reply(id, r);
6732        }
6733        "typeHierarchy/supertypes" => {
6734            reply(id, supertypes(st, params.and_then(|p| get(p, "item"))))
6735        }
6736        "typeHierarchy/subtypes" => reply(id, subtypes(st, params.and_then(|p| get(p, "item")))),
6737        "textDocument/codeAction" => {
6738            let rg = params.and_then(|p| get(p, "range"));
6739            let pt = |k: &str| {
6740                let p = rg.and_then(|r| get(r, k));
6741                Pos {
6742                    line: as_usize(p.and_then(|x| get(x, "line"))).unwrap_or(0),
6743                    character: as_usize(p.and_then(|x| get(x, "character"))).unwrap_or(0),
6744                }
6745            };
6746            let range = (pt("start"), pt("end"));
6747            let diags: Vec<Value> = match params
6748                .and_then(|p| get(p, "context"))
6749                .and_then(|c| get(c, "diagnostics"))
6750            {
6751                Some(Value::JArr(items)) => items.iter().cloned().collect(),
6752                _ => vec![],
6753            };
6754            let r = code_actions(st, &td_uri(), range, &diags);
6755            reply(id, r);
6756        }
6757        "textDocument/linkedEditingRange" => {
6758            reply(id, linked_editing_range(st, &td_uri(), position()))
6759        }
6760        "textDocument/onTypeFormatting" => {
6761            let ch = as_str(params.and_then(|p| get(p, "ch")))
6762                .unwrap_or("")
6763                .to_string();
6764            reply(id, on_type_formatting(st, &td_uri(), position(), &ch));
6765        }
6766        "workspace/executeCommand" => {
6767            let command = as_str(params.and_then(|p| get(p, "command")))
6768                .unwrap_or("")
6769                .to_string();
6770            let r = execute_command(st, &command, params.and_then(|p| get(p, "arguments")));
6771            reply(id, r);
6772        }
6773        "shutdown" => reply(id, J::Null),
6774        "exit" => return Some(0),
6775        _ => reply(id, J::Null),
6776    }
6777    None
6778}
6779
6780pub fn main() -> i32 {
6781    std::panic::set_hook(Box::new(|_| {})); // the error reply carries the message; nothing on stderr
6782    let mut st = State::default();
6783    let mut stdin = std::io::stdin().lock();
6784    let mut buf: Vec<u8> = vec![];
6785    let mut chunk = [0u8; 65536];
6786    let cl = Regex::new(r"(?i)Content-Length: (\d+)").unwrap();
6787    loop {
6788        let n = match stdin.read(&mut chunk) {
6789            Ok(0) | Err(_) => return 0, // stdin closed: everything queued was handled in order
6790            Ok(n) => n,
6791        };
6792        buf.extend_from_slice(&chunk[..n]);
6793        while let Some(header_end) = buf.windows(4).position(|w| w == b"\r\n\r\n") {
6794            let header = String::from_utf8_lossy(&buf[..header_end]).to_string();
6795            let Some(len) = cl
6796                .captures(&header)
6797                .and_then(|c| c[1].parse::<usize>().ok())
6798            else {
6799                buf.drain(..header_end + 4);
6800                continue;
6801            };
6802            if buf.len() < header_end + 4 + len {
6803                break;
6804            }
6805            let body =
6806                String::from_utf8_lossy(&buf[header_end + 4..header_end + 4 + len]).to_string();
6807            buf.drain(..header_end + 4 + len);
6808            if let Ok(msg) = read_json(&body) {
6809                // a request whose handler panics is answered with an error, never left waiting
6810                let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
6811                    handle(&mut st, &msg)
6812                }));
6813                match outcome {
6814                    Ok(Some(code)) => return code,
6815                    Ok(None) => {}
6816                    Err(payload) => {
6817                        let message = payload
6818                            .downcast_ref::<String>()
6819                            .cloned()
6820                            .or_else(|| payload.downcast_ref::<&str>().map(|x| x.to_string()))
6821                            .unwrap_or_else(|| "internal error".into());
6822                        notify(
6823                            "window/logMessage",
6824                            J::obj(vec![
6825                                ("type", J::Num(1)),
6826                                ("message", J::s(message.clone())),
6827                            ]),
6828                        );
6829                        if let Some(id) = get(&msg, "id") {
6830                            send(&format!("{{\"jsonrpc\":\"2.0\",\"id\":{},\"error\":{{\"code\":-32603,\"message\":{}}}}}", json_of(id), json_str(&message)));
6831                        }
6832                    }
6833                }
6834            }
6835        }
6836    }
6837}