Skip to main content

php_lsp/analysis/
inlay_hints.rs

1use std::collections::HashMap;
2
3use php_ast::{
4    ClassMemberKind, EnumMemberKind, Expr, ExprKind, NamespaceBody, Param, Stmt, StmtKind,
5};
6use serde_json::json;
7use tower_lsp_server::ls_types::{InlayHint, InlayHintKind, InlayHintLabel, Position, Range};
8
9use crate::document::ast::{ParsedDoc, SourceView, format_type_hint};
10use crate::text::fqn_short_name;
11
12/// Resolve a foreach value/key variable's class (short name) for its type hint.
13///
14/// mir-primary: queries the recorded `ResolvedSymbol` at the variable's byte
15/// offset — works whenever mir knows the array element type (e.g. typed arrays,
16/// `@var list<T>` annotations, `enum::cases()`, and `array_map`/`array_filter`
17/// results, whose element type mir infers from the callback return type).
18fn foreach_var_class(
19    analysis: Option<&mir_analyzer::FileAnalysis>,
20    var_offset: u32,
21) -> Option<String> {
22    analysis
23        .and_then(|a| crate::types::type_query::type_at_offset(a, var_offset))
24        .and_then(crate::types::type_query::primary_class_name)
25        .map(|fqcn| fqn_short_name(&fqcn).to_string())
26}
27
28/// Returns parameter-name inlay hints AND return-type hints for all
29/// function/method declarations and calls in `doc`.
30///
31pub fn inlay_hints(
32    _source: &str,
33    doc: &ParsedDoc,
34    analysis: Option<&mir_analyzer::FileAnalysis>,
35    db: Option<&mir_analyzer::db::MirDbStorage>,
36    range: Range,
37) -> Vec<InlayHint> {
38    let sv = doc.view();
39    let defs = collect_defs(&doc.program().stmts);
40    let mut hints = Vec::new();
41    let ctx = HintCtx {
42        sv,
43        defs: &defs,
44        analysis,
45        db,
46        range,
47    };
48    hints_in_stmts(&ctx, &doc.program().stmts, &mut hints);
49    hints
50}
51
52#[derive(Clone)]
53struct CallableSignature {
54    params: Vec<String>,
55    variadic_last: bool,
56    return_type: Option<String>,
57    tooltip: Option<TooltipSymbol>,
58}
59
60#[derive(Clone)]
61struct TooltipSymbol {
62    kind: &'static str,
63    name: String,
64    class: Option<String>,
65}
66
67struct HintCtx<'a> {
68    sv: SourceView<'a>,
69    defs: &'a HashMap<String, CallableSignature>,
70    analysis: Option<&'a mir_analyzer::FileAnalysis>,
71    db: Option<&'a mir_analyzer::db::MirDbStorage>,
72    range: Range,
73}
74
75// === Definition collection ===
76
77fn collect_defs(stmts: &[Stmt<'_, '_>]) -> HashMap<String, CallableSignature> {
78    let mut map = HashMap::new();
79    collect_defs_stmts(stmts, &mut map);
80    map
81}
82
83/// Extract param names and whether the last param is variadic from a param list.
84fn params_from_list(params: &[Param<'_, '_>]) -> (Vec<String>, bool) {
85    let names = params
86        .iter()
87        .map(|p| p.name.to_string().trim_start_matches('$').to_string())
88        .collect();
89    let variadic_last = params.last().map(|p| p.variadic).unwrap_or(false);
90    (names, variadic_last)
91}
92
93fn collect_defs_stmts(stmts: &[Stmt<'_, '_>], map: &mut HashMap<String, CallableSignature>) {
94    for stmt in stmts {
95        match &stmt.kind {
96            StmtKind::Function(f) => {
97                let (params, variadic_last) = params_from_list(&f.params);
98                let return_type = f.return_type.as_ref().map(|t| format_type_hint(t));
99                map.insert(
100                    f.name.to_string(),
101                    CallableSignature {
102                        params,
103                        variadic_last,
104                        return_type,
105                        tooltip: Some(TooltipSymbol {
106                            kind: "function",
107                            name: f.name.to_string(),
108                            class: None,
109                        }),
110                    },
111                );
112            }
113            StmtKind::Class(c) => {
114                for member in c.body.members.iter() {
115                    if let ClassMemberKind::Method(m) = &member.kind {
116                        let (params, variadic_last) = params_from_list(&m.params);
117                        let return_type = m.return_type.as_ref().map(|t| format_type_hint(t));
118                        let func_def = CallableSignature {
119                            params: params.clone(),
120                            variadic_last,
121                            return_type: return_type.clone(),
122                            tooltip: c.name.map(|cn| TooltipSymbol {
123                                kind: "method",
124                                name: m.name.to_string(),
125                                class: Some(cn.to_string()),
126                            }),
127                        };
128                        // Register with qualified key "ClassName::methodName" for unambiguous lookup
129                        if let Some(cn) = c.name {
130                            let qualified = format!("{}::{}", cn, m.name);
131                            map.insert(qualified, func_def.clone());
132                        }
133                        // Register __construct under the class name so `new ClassName(...)` gets hints.
134                        if m.name == "__construct"
135                            && let Some(class_name) = c.name
136                        {
137                            map.insert(
138                                class_name.to_string(),
139                                CallableSignature {
140                                    params: params.clone(),
141                                    variadic_last,
142                                    return_type: None,
143                                    tooltip: Some(TooltipSymbol {
144                                        kind: "class",
145                                        name: class_name.to_string(),
146                                        class: None,
147                                    }),
148                                },
149                            );
150                        }
151                        map.insert(m.name.to_string(), func_def);
152                    }
153                }
154            }
155            StmtKind::Trait(t) => {
156                for member in t.body.members.iter() {
157                    if let ClassMemberKind::Method(m) = &member.kind {
158                        let (params, variadic_last) = params_from_list(&m.params);
159                        let return_type = m.return_type.as_ref().map(|t| format_type_hint(t));
160                        let func_def = CallableSignature {
161                            params,
162                            variadic_last,
163                            return_type,
164                            tooltip: Some(TooltipSymbol {
165                                kind: "method",
166                                name: m.name.to_string(),
167                                class: Some(t.name.to_string()),
168                            }),
169                        };
170                        // Register with qualified key for unambiguous lookup
171                        let qualified = format!("{}::{}", t.name, m.name);
172                        map.insert(qualified, func_def.clone());
173                        map.insert(m.name.to_string(), func_def);
174                    }
175                }
176            }
177            StmtKind::Enum(e) => {
178                for member in e.body.members.iter() {
179                    if let EnumMemberKind::Method(m) = &member.kind {
180                        let (params, variadic_last) = params_from_list(&m.params);
181                        let return_type = m.return_type.as_ref().map(|t| format_type_hint(t));
182                        let func_def = CallableSignature {
183                            params,
184                            variadic_last,
185                            return_type,
186                            tooltip: Some(TooltipSymbol {
187                                kind: "method",
188                                name: m.name.to_string(),
189                                class: Some(e.name.to_string()),
190                            }),
191                        };
192                        // Register with qualified key for unambiguous lookup
193                        let qualified = format!("{}::{}", e.name, m.name);
194                        map.insert(qualified, func_def.clone());
195                        map.insert(m.name.to_string(), func_def);
196                    }
197                }
198            }
199            StmtKind::Namespace(ns) => {
200                if let NamespaceBody::Braced(inner) = &ns.body {
201                    collect_defs_stmts(&inner.stmts, map);
202                }
203            }
204            // Register closure/arrow-function variables so `$fn(...)` call sites get hints.
205            StmtKind::Expression(e) => {
206                if let ExprKind::Assign(assign) = &e.kind
207                    && let ExprKind::Variable(var_name) = &assign.target.kind
208                {
209                    let key = format!("${}", var_name.as_str());
210                    match &assign.value.kind {
211                        ExprKind::Closure(c) => {
212                            let (params, variadic_last) = params_from_list(&c.params);
213                            let return_type = c.return_type.as_ref().map(|t| format_type_hint(t));
214                            map.insert(
215                                key,
216                                CallableSignature {
217                                    params,
218                                    variadic_last,
219                                    return_type,
220                                    tooltip: None,
221                                },
222                            );
223                        }
224                        ExprKind::ArrowFunction(a) => {
225                            let (params, variadic_last) = params_from_list(&a.params);
226                            let return_type = a.return_type.as_ref().map(|t| format_type_hint(t));
227                            map.insert(
228                                key,
229                                CallableSignature {
230                                    params,
231                                    variadic_last,
232                                    return_type,
233                                    tooltip: None,
234                                },
235                            );
236                        }
237                        _ => {}
238                    }
239                }
240            }
241            _ => {}
242        }
243    }
244}
245
246// === AST walking ===
247
248fn hints_in_stmts(ctx: &HintCtx<'_>, stmts: &[Stmt<'_, '_>], out: &mut Vec<InlayHint>) {
249    for stmt in stmts {
250        hints_in_stmt(ctx, stmt, out);
251    }
252}
253
254fn hints_in_stmt(ctx: &HintCtx<'_>, stmt: &Stmt<'_, '_>, out: &mut Vec<InlayHint>) {
255    match &stmt.kind {
256        StmtKind::Expression(e) => hints_in_expr(ctx, e, out),
257        StmtKind::Return(Some(v)) => hints_in_expr(ctx, v, out),
258        StmtKind::Echo(exprs) => {
259            for expr in exprs.iter() {
260                hints_in_expr(ctx, expr, out);
261            }
262        }
263        StmtKind::Function(f) => hints_in_stmts(ctx, &f.body.stmts, out),
264        StmtKind::Class(c) => {
265            for member in c.body.members.iter() {
266                if let ClassMemberKind::Method(m) = &member.kind
267                    && let Some(body) = &m.body
268                {
269                    hints_in_stmts(ctx, &body.stmts, out);
270                }
271            }
272        }
273        StmtKind::Trait(t) => {
274            for member in t.body.members.iter() {
275                if let ClassMemberKind::Method(m) = &member.kind
276                    && let Some(body) = &m.body
277                {
278                    hints_in_stmts(ctx, &body.stmts, out);
279                }
280            }
281        }
282        StmtKind::Enum(e) => {
283            for member in e.body.members.iter() {
284                if let EnumMemberKind::Method(m) = &member.kind
285                    && let Some(body) = &m.body
286                {
287                    hints_in_stmts(ctx, &body.stmts, out);
288                }
289            }
290        }
291        StmtKind::Namespace(ns) => {
292            if let NamespaceBody::Braced(inner) = &ns.body {
293                hints_in_stmts(ctx, &inner.stmts, out);
294            }
295        }
296        StmtKind::If(i) => {
297            hints_in_expr(ctx, &i.condition, out);
298            hints_in_stmt(ctx, i.then_branch, out);
299            for ei in i.elseif_branches.iter() {
300                hints_in_expr(ctx, &ei.condition, out);
301                hints_in_stmt(ctx, &ei.body, out);
302            }
303            if let Some(e) = &i.else_branch {
304                hints_in_stmt(ctx, e, out);
305            }
306        }
307        StmtKind::While(w) => {
308            hints_in_expr(ctx, &w.condition, out);
309            hints_in_stmt(ctx, w.body, out);
310        }
311        StmtKind::For(f) => {
312            for e in f.init.iter() {
313                hints_in_expr(ctx, e, out);
314            }
315            for cond in f.condition.iter() {
316                hints_in_expr(ctx, cond, out);
317            }
318            for e in f.update.iter() {
319                hints_in_expr(ctx, e, out);
320            }
321            hints_in_stmt(ctx, f.body, out);
322        }
323        StmtKind::Foreach(f) => {
324            hints_in_expr(ctx, &f.expr, out);
325            // Emit type hint after the value variable, e.g. `foreach ($arr as $item /* : Foo */)`.
326            if let ExprKind::Variable(_) = &f.value.kind
327                && let Some(ty) = foreach_var_class(ctx.analysis, f.value.span.start)
328            {
329                let pos = ctx.sv.position_of(f.value.span.end);
330                if pos_in_range(pos, ctx.range) {
331                    out.push(make_foreach_type_hint(pos, &ty));
332                }
333            }
334            // Emit type hint after the key variable if present, e.g. `foreach ($map as $key => $value)`.
335            if let Some(key_expr) = &f.key
336                && let ExprKind::Variable(_) = &key_expr.kind
337                && let Some(ty) = foreach_var_class(ctx.analysis, key_expr.span.start)
338            {
339                let pos = ctx.sv.position_of(key_expr.span.end);
340                if pos_in_range(pos, ctx.range) {
341                    out.push(make_foreach_type_hint(pos, &ty));
342                }
343            }
344            hints_in_stmt(ctx, f.body, out);
345        }
346        StmtKind::TryCatch(t) => {
347            hints_in_stmts(ctx, &t.body.stmts, out);
348            for catch in t.catches.iter() {
349                hints_in_stmts(ctx, &catch.body.stmts, out);
350            }
351            if let Some(finally) = &t.finally {
352                hints_in_stmts(ctx, &finally.stmts, out);
353            }
354        }
355        StmtKind::Block(stmts) => hints_in_stmts(ctx, &stmts.stmts, out),
356        StmtKind::DoWhile(d) => {
357            hints_in_stmt(ctx, d.body, out);
358            hints_in_expr(ctx, &d.condition, out);
359        }
360        StmtKind::Switch(s) => {
361            hints_in_expr(ctx, &s.expr, out);
362            for case in s.body.cases.iter() {
363                if let Some(v) = &case.value {
364                    hints_in_expr(ctx, v, out);
365                }
366                hints_in_stmts(ctx, &case.body, out);
367            }
368        }
369        _ => {}
370    }
371}
372
373fn hints_in_expr(ctx: &HintCtx<'_>, expr: &Expr<'_, '_>, out: &mut Vec<InlayHint>) {
374    match &expr.kind {
375        ExprKind::FunctionCall(f) => {
376            let def = ident_name(f.name)
377                .and_then(|_| callable_from_symbol(ctx, f.name.span.start))
378                .or_else(|| callable_from_local_function(ctx, f.name));
379            if let Some(def) = def {
380                emit_param_hints(ctx, &f.args, &def, out);
381            }
382            hints_in_expr(ctx, f.name, out);
383            for arg in f.args.iter() {
384                if let Some(value) = &arg.value {
385                    hints_in_expr(ctx, value, out);
386                }
387            }
388        }
389        ExprKind::MethodCall(m) | ExprKind::NullsafeMethodCall(m) => {
390            if let Some(def) = callable_from_symbol(ctx, m.method.span.start) {
391                emit_param_hints(ctx, &m.args, &def, out);
392            }
393            hints_in_expr(ctx, m.object, out);
394            for arg in m.args.iter() {
395                if let Some(value) = &arg.value {
396                    hints_in_expr(ctx, value, out);
397                }
398            }
399        }
400        ExprKind::StaticMethodCall(m) => {
401            if let Some(def) = callable_from_symbol(ctx, m.method.span.start) {
402                emit_param_hints(ctx, &m.args, &def, out);
403            }
404            hints_in_expr(ctx, m.class, out);
405            for arg in m.args.iter() {
406                if let Some(value) = &arg.value {
407                    hints_in_expr(ctx, value, out);
408                }
409            }
410        }
411        ExprKind::New(n) => {
412            let def = callable_from_symbol(ctx, n.class.span.start).or_else(|| {
413                ident_name(n.class).and_then(|class_name| ctx.defs.get(class_name).cloned())
414            });
415            if let Some(def) = def {
416                emit_param_hints(ctx, &n.args, &def, out);
417            }
418            for arg in n.args.iter() {
419                if let Some(value) = &arg.value {
420                    hints_in_expr(ctx, value, out);
421                }
422            }
423        }
424        ExprKind::Assign(a) => {
425            // Emit return-type hint after a function call on the RHS
426            emit_return_type_hint(ctx, a.value, out);
427            hints_in_expr(ctx, a.target, out);
428            hints_in_expr(ctx, a.value, out);
429        }
430        // Walk into closure bodies so nested function calls get hints.
431        ExprKind::Closure(c) => hints_in_stmts(ctx, &c.body.stmts, out),
432        // Walk into arrow function bodies so nested calls get hints.
433        // No return-type hint: the annotation is already visible in the source,
434        // and php-lsp has no type inference to supply hints for unannotated fns.
435        ExprKind::ArrowFunction(a) => hints_in_expr(ctx, a.body, out),
436        ExprKind::Parenthesized(e) => hints_in_expr(ctx, e, out),
437        ExprKind::Ternary(t) => {
438            hints_in_expr(ctx, t.condition, out);
439            if let Some(then_expr) = t.then_expr {
440                hints_in_expr(ctx, then_expr, out);
441            }
442            hints_in_expr(ctx, t.else_expr, out);
443        }
444        ExprKind::NullCoalesce(n) => {
445            hints_in_expr(ctx, n.left, out);
446            hints_in_expr(ctx, n.right, out);
447        }
448        ExprKind::Binary(b) => {
449            hints_in_expr(ctx, b.left, out);
450            hints_in_expr(ctx, b.right, out);
451        }
452        ExprKind::CloneWith(target, withs) => {
453            hints_in_expr(ctx, target, out);
454            hints_in_expr(ctx, withs, out);
455        }
456        ExprKind::Match(m) => {
457            hints_in_expr(ctx, m.subject, out);
458            for arm in m.arms.iter() {
459                if let Some(conds) = &arm.conditions {
460                    for c in conds.iter() {
461                        hints_in_expr(ctx, c, out);
462                    }
463                }
464                hints_in_expr(ctx, &arm.body, out);
465            }
466        }
467        _ => {}
468    }
469}
470
471fn emit_param_hints(
472    ctx: &HintCtx<'_>,
473    args: &[php_ast::Arg<'_, '_>],
474    def: &CallableSignature,
475    out: &mut Vec<InlayHint>,
476) {
477    for (i, arg) in args.iter().enumerate() {
478        // An unpacked/spread argument (`...$args`) consumes an unknown number
479        // of positional parameters, so raw arg-list index no longer lines up
480        // with the real parameter index for it *or anything after it* —
481        // suppress hints for the rest of the call, not just the unpack itself.
482        if arg.unpack {
483            break;
484        }
485        // Skip named arguments (they already have the label in sv.source()).
486        if arg.name.is_some() {
487            continue;
488        }
489        // For a variadic last param, repeat its name for every excess argument.
490        let param = if let Some(p) = def.params.get(i) {
491            p
492        } else if def.variadic_last {
493            match def.params.last() {
494                Some(p) => p,
495                None => continue,
496            }
497        } else {
498            continue;
499        };
500        let pos = ctx.sv.position_of(arg.span.start);
501        if pos_in_range(pos, ctx.range) {
502            out.push(make_param_hint(pos, param, def.tooltip.as_ref()));
503        }
504    }
505}
506
507fn emit_return_type_hint(ctx: &HintCtx<'_>, expr: &Expr<'_, '_>, out: &mut Vec<InlayHint>) {
508    let def = match &expr.kind {
509        ExprKind::FunctionCall(f) => {
510            ident_name(f.name).and_then(|_| callable_from_symbol(ctx, f.name.span.start))
511        }
512        ExprKind::MethodCall(m) | ExprKind::NullsafeMethodCall(m) => {
513            callable_from_symbol(ctx, m.method.span.start)
514        }
515        ExprKind::StaticMethodCall(m) => callable_from_symbol(ctx, m.method.span.start),
516        _ => None,
517    };
518    if let Some(def) = def
519        && let Some(ret_type) = &def.return_type
520    {
521        if ret_type == "void" {
522            return;
523        }
524        let pos = ctx.sv.position_of(expr.span.end);
525        if pos_in_range(pos, ctx.range) {
526            out.push(make_return_hint(pos, ret_type, def.tooltip.as_ref()));
527        }
528    }
529}
530
531fn callable_from_local_function(
532    ctx: &HintCtx<'_>,
533    expr: &Expr<'_, '_>,
534) -> Option<CallableSignature> {
535    ident_name(expr)
536        .map(str::to_string)
537        .or_else(|| {
538            if let ExprKind::Variable(n) = &expr.kind {
539                Some(format!("${}", n.as_str()))
540            } else {
541                None
542            }
543        })
544        .and_then(|key| ctx.defs.get(&key).cloned())
545}
546
547fn callable_from_symbol(ctx: &HintCtx<'_>, offset: u32) -> Option<CallableSignature> {
548    let symbol = ctx.analysis?.symbol_at(offset)?.to_symbol()?;
549    callable_signature_from_name(ctx.db?, &symbol)
550}
551
552fn callable_signature_from_name(
553    db: &mir_analyzer::db::MirDbStorage,
554    symbol: &mir_analyzer::Name,
555) -> Option<CallableSignature> {
556    match symbol {
557        mir_analyzer::Name::Function(fqn) => {
558            let f = mir_analyzer::db::find_function(
559                db,
560                mir_analyzer::db::Fqcn::from_str(db, fqn.as_ref()),
561            )?;
562            if mir_analyzer::is_builtin_function(&f.short_name) {
563                return None;
564            }
565            Some(CallableSignature {
566                params: declared_param_names(&f.params),
567                variadic_last: f.params.last().is_some_and(|p| p.is_variadic),
568                return_type: f.effective_return_type().map(ToString::to_string),
569                tooltip: Some(TooltipSymbol {
570                    kind: "function",
571                    name: f.short_name.to_string(),
572                    class: None,
573                }),
574            })
575        }
576        mir_analyzer::Name::Method { class, name } => {
577            let (_, m) = mir_analyzer::db::find_method_in_chain(
578                db,
579                mir_analyzer::db::Fqcn::from_str(db, class.as_ref()),
580                name.as_ref(),
581            )?;
582            Some(CallableSignature {
583                params: declared_param_names(&m.params),
584                variadic_last: m.params.last().is_some_and(|p| p.is_variadic),
585                return_type: m
586                    .return_type
587                    .as_deref()
588                    .or(m.inferred_return_type.as_deref())
589                    .map(ToString::to_string),
590                tooltip: Some(TooltipSymbol {
591                    kind: "method",
592                    name: m.name.to_string(),
593                    class: Some(class.to_string()),
594                }),
595            })
596        }
597        mir_analyzer::Name::Class(fqcn) => {
598            let (_, m) = mir_analyzer::db::find_method_in_chain(
599                db,
600                mir_analyzer::db::Fqcn::from_str(db, fqcn.as_ref()),
601                "__construct",
602            )?;
603            Some(CallableSignature {
604                params: declared_param_names(&m.params),
605                variadic_last: m.params.last().is_some_and(|p| p.is_variadic),
606                return_type: None,
607                tooltip: Some(TooltipSymbol {
608                    kind: "class",
609                    name: fqcn.to_string(),
610                    class: None,
611                }),
612            })
613        }
614        _ => None,
615    }
616}
617
618fn declared_param_names(params: &[mir_analyzer::DeclaredParam]) -> Vec<String> {
619    params
620        .iter()
621        .map(|p| p.name.as_str().trim_start_matches('$').to_string())
622        .collect()
623}
624
625fn ident_name<'a>(expr: &'a Expr<'_, '_>) -> Option<&'a str> {
626    if let ExprKind::Identifier(name) = &expr.kind {
627        Some(name)
628    } else {
629        None
630    }
631}
632
633fn make_param_hint(
634    position: Position,
635    param_name: &str,
636    symbol: Option<&TooltipSymbol>,
637) -> InlayHint {
638    InlayHint {
639        position,
640        label: InlayHintLabel::String(format!("{}:", param_name)),
641        kind: Some(InlayHintKind::PARAMETER),
642        text_edits: None,
643        tooltip: None,
644        padding_left: None,
645        padding_right: Some(true),
646        data: hint_data(symbol),
647    }
648}
649
650fn make_return_hint(
651    position: Position,
652    ret_type: &str,
653    symbol: Option<&TooltipSymbol>,
654) -> InlayHint {
655    InlayHint {
656        position,
657        label: InlayHintLabel::String(format!(": {ret_type}")),
658        kind: Some(InlayHintKind::TYPE),
659        text_edits: None,
660        tooltip: None,
661        padding_left: Some(true),
662        padding_right: None,
663        data: hint_data(symbol),
664    }
665}
666
667fn hint_data(symbol: Option<&TooltipSymbol>) -> Option<serde_json::Value> {
668    let symbol = symbol?;
669    let mut value = json!({
670        "php_lsp_fn": symbol.name,
671        "php_lsp_symbol_kind": symbol.kind,
672    });
673    if let Some(class) = &symbol.class {
674        value["php_lsp_class"] = json!(class);
675    }
676    Some(value)
677}
678
679fn make_foreach_type_hint(position: Position, ty: &str) -> InlayHint {
680    InlayHint {
681        position,
682        label: InlayHintLabel::String(format!(": {ty}")),
683        kind: Some(InlayHintKind::TYPE),
684        text_edits: None,
685        tooltip: None,
686        padding_left: Some(true),
687        padding_right: None,
688        data: None,
689    }
690}
691
692fn pos_in_range(pos: Position, range: Range) -> bool {
693    if pos.line < range.start.line || pos.line > range.end.line {
694        return false;
695    }
696    if pos.line == range.start.line && pos.character < range.start.character {
697        return false;
698    }
699    if pos.line == range.end.line && pos.character >= range.end.character {
700        return false;
701    }
702    true
703}