ide/
interpret.rs

1use hir::{ConstEvalError, DefWithBody, DisplayTarget, Semantics};
2use ide_db::{FilePosition, LineIndexDatabase, RootDatabase, base_db::SourceDatabase};
3use std::time::{Duration, Instant};
4use stdx::format_to;
5use syntax::{AstNode, TextRange, algo::ancestors_at_offset, ast};
6
7// Feature: Interpret A Function, Static Or Const.
8//
9// | Editor  | Action Name |
10// |---------|-------------|
11// | VS Code | **rust-analyzer: Interpret** |
12pub(crate) fn interpret(db: &RootDatabase, position: FilePosition) -> String {
13    match find_and_interpret(db, position) {
14        Some((duration, mut result)) => {
15            result.push('\n');
16            format_to!(result, "----------------------\n");
17            format_to!(result, "  Finished in {}s\n", duration.as_secs_f32());
18            result
19        }
20        _ => "Not inside a function, const or static".to_owned(),
21    }
22}
23
24fn find_and_interpret(db: &RootDatabase, position: FilePosition) -> Option<(Duration, String)> {
25    let sema = Semantics::new(db);
26    let source_file = sema.parse_guess_edition(position.file_id);
27
28    let item = ancestors_at_offset(source_file.syntax(), position.offset)
29        .filter(|it| !ast::MacroCall::can_cast(it.kind()))
30        .find_map(ast::Item::cast)?;
31    let def: DefWithBody = match item {
32        ast::Item::Fn(it) => sema.to_def(&it)?.into(),
33        ast::Item::Const(it) => sema.to_def(&it)?.into(),
34        ast::Item::Static(it) => sema.to_def(&it)?.into(),
35        _ => return None,
36    };
37    let span_formatter = |file_id, text_range: TextRange| {
38        let source_root = db.file_source_root(file_id).source_root_id(db);
39        let source_root = db.source_root(source_root).source_root(db);
40
41        let path = source_root.path_for_file(&file_id).map(|x| x.to_string());
42        let path = path.as_deref().unwrap_or("<unknown file>");
43        match db.line_index(file_id).try_line_col(text_range.start()) {
44            Some(line_col) => format!("file://{path}:{}:{}", line_col.line + 1, line_col.col),
45            None => format!("file://{path} range {text_range:?}"),
46        }
47    };
48    let display_target = def.module(db).krate(db).to_display_target(db);
49    let start_time = Instant::now();
50    let res = match def {
51        DefWithBody::Function(it) => it.eval(db, span_formatter),
52        DefWithBody::Static(it) => it.eval(db).map(|it| it.render(db, display_target)),
53        DefWithBody::Const(it) => it.eval(db).map(|it| it.render(db, display_target)),
54        _ => unreachable!(),
55    };
56    let res = res.unwrap_or_else(|e| render_const_eval_error(db, e, display_target));
57    let duration = Instant::now() - start_time;
58    Some((duration, res))
59}
60
61pub(crate) fn render_const_eval_error(
62    db: &RootDatabase,
63    e: ConstEvalError<'_>,
64    display_target: DisplayTarget,
65) -> String {
66    let span_formatter = |file_id, text_range: TextRange| {
67        let source_root = db.file_source_root(file_id).source_root_id(db);
68        let source_root = db.source_root(source_root).source_root(db);
69        let path = source_root.path_for_file(&file_id).map(|x| x.to_string());
70        let path = path.as_deref().unwrap_or("<unknown file>");
71        match db.line_index(file_id).try_line_col(text_range.start()) {
72            Some(line_col) => format!("file://{path}:{}:{}", line_col.line + 1, line_col.col),
73            None => format!("file://{path} range {text_range:?}"),
74        }
75    };
76    let mut r = String::new();
77    _ = e.pretty_print(&mut r, db, span_formatter, display_target);
78    r
79}