From 78f311262630b35c1630c653c5a5ae42d625933b Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 3 Nov 2024 14:18:06 +0100 Subject: [PATCH] Allow interpreting consts and statics with interpret function command --- crates/hir-def/src/data.rs | 1 + crates/hir-ty/src/consteval.rs | 23 +++++-- crates/hir-ty/src/mir/eval.rs | 10 +-- crates/hir-ty/src/mir/eval/tests.rs | 2 +- crates/hir/src/lib.rs | 31 ++++----- .../src/handlers/inline_const_as_literal.rs | 5 +- crates/ide/src/interpret.rs | 64 +++++++++++++++++++ crates/ide/src/interpret_function.rs | 47 -------------- crates/ide/src/lib.rs | 4 +- crates/intern/src/symbol/symbols.rs | 2 + crates/rust-analyzer/src/cli/run_tests.rs | 9 ++- editors/code/package.json | 2 +- 12 files changed, 114 insertions(+), 86 deletions(-) create mode 100644 crates/ide/src/interpret.rs delete mode 100644 crates/ide/src/interpret_function.rs diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs index f49018eaf3..6d07dc8f9b 100644 --- a/crates/hir-def/src/data.rs +++ b/crates/hir-def/src/data.rs @@ -37,6 +37,7 @@ pub struct FunctionData { pub name: Name, pub params: Box<[TypeRefId]>, pub ret_type: TypeRefId, + // FIXME: why are these stored here? They should be accessed via the query pub attrs: Attrs, pub visibility: RawVisibility, pub abi: Option, diff --git a/crates/hir-ty/src/consteval.rs b/crates/hir-ty/src/consteval.rs index a56056b077..142766c039 100644 --- a/crates/hir-ty/src/consteval.rs +++ b/crates/hir-ty/src/consteval.rs @@ -56,6 +56,21 @@ pub enum ConstEvalError { MirEvalError(MirEvalError), } +impl ConstEvalError { + pub fn pretty_print( + &self, + f: &mut String, + db: &dyn HirDatabase, + span_formatter: impl Fn(span::FileId, span::TextRange) -> String, + edition: span::Edition, + ) -> std::result::Result<(), std::fmt::Error> { + match self { + ConstEvalError::MirLowerError(e) => e.pretty_print(f, db, span_formatter, edition), + ConstEvalError::MirEvalError(e) => e.pretty_print(f, db, span_formatter, edition), + } + } +} + impl From for ConstEvalError { fn from(value: MirLowerError) -> Self { match value { @@ -253,7 +268,7 @@ pub(crate) fn const_eval_query( } GeneralConstId::InTypeConstId(c) => db.mir_body(c.into())?, }; - let c = interpret_mir(db, body, false, trait_env).0?; + let c = interpret_mir(db, body, false, trait_env)?.0?; Ok(c) } @@ -266,7 +281,7 @@ pub(crate) fn const_eval_static_query( Substitution::empty(Interner), db.trait_environment_for_body(def.into()), )?; - let c = interpret_mir(db, body, false, None).0?; + let c = interpret_mir(db, body, false, None)?.0?; Ok(c) } @@ -298,7 +313,7 @@ pub(crate) fn const_eval_discriminant_variant( Substitution::empty(Interner), db.trait_environment_for_body(def), )?; - let c = interpret_mir(db, mir_body, false, None).0?; + let c = interpret_mir(db, mir_body, false, None)?.0?; let c = if is_signed { try_const_isize(db, &c).unwrap() } else { @@ -339,7 +354,7 @@ pub(crate) fn eval_to_const( } } if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, ctx.body, &infer, expr) { - if let Ok(result) = interpret_mir(db, Arc::new(mir_body), true, None).0 { + if let Ok((Ok(result), _)) = interpret_mir(db, Arc::new(mir_body), true, None) { return result; } } diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index 47026995c0..d7029651fc 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -585,13 +585,9 @@ pub fn interpret_mir( // (and probably should) do better here, for example by excluding bindings outside of the target expression. assert_placeholder_ty_is_unused: bool, trait_env: Option>, -) -> (Result, MirOutput) { +) -> Result<(Result, MirOutput)> { let ty = body.locals[return_slot()].ty.clone(); - let mut evaluator = - match Evaluator::new(db, body.owner, assert_placeholder_ty_is_unused, trait_env) { - Ok(it) => it, - Err(e) => return (Err(e), MirOutput { stdout: vec![], stderr: vec![] }), - }; + let mut evaluator = Evaluator::new(db, body.owner, assert_placeholder_ty_is_unused, trait_env)?; let it: Result = (|| { if evaluator.ptr_size() != std::mem::size_of::() { not_supported!("targets with different pointer size from host"); @@ -613,7 +609,7 @@ pub fn interpret_mir( }; Ok(intern_const_scalar(ConstScalar::Bytes(bytes, memory_map), ty)) })(); - (it, MirOutput { stdout: evaluator.stdout, stderr: evaluator.stderr }) + Ok((it, MirOutput { stdout: evaluator.stdout, stderr: evaluator.stderr })) } #[cfg(test)] diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs index 595a78da10..30d1137373 100644 --- a/crates/hir-ty/src/mir/eval/tests.rs +++ b/crates/hir-ty/src/mir/eval/tests.rs @@ -32,7 +32,7 @@ fn eval_main(db: &TestDB, file_id: EditionedFileId) -> Result<(String, String), ) .map_err(|e| MirEvalError::MirLowerError(func_id, e))?; - let (result, output) = interpret_mir(db, body, false, None); + let (result, output) = interpret_mir(db, body, false, None)?; result?; Ok((output.stdout().into_owned(), output.stderr().into_owned())) } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index a4045dc5ad..ebd84fd2be 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2306,22 +2306,15 @@ impl Function { self, db: &dyn HirDatabase, span_formatter: impl Fn(FileId, TextRange) -> String, - ) -> String { + ) -> Result { let krate = HasModule::krate(&self.id, db.upcast()); let edition = db.crate_graph()[krate].edition; - let body = match db.monomorphized_mir_body( + let body = db.monomorphized_mir_body( self.id.into(), Substitution::empty(Interner), db.trait_environment(self.id.into()), - ) { - Ok(body) => body, - Err(e) => { - let mut r = String::new(); - _ = e.pretty_print(&mut r, db, &span_formatter, edition); - return r; - } - }; - let (result, output) = interpret_mir(db, body, false, None); + )?; + let (result, output) = interpret_mir(db, body, false, None)?; let mut text = match result { Ok(_) => "pass".to_owned(), Err(e) => { @@ -2340,7 +2333,7 @@ impl Function { text += "\n--------- stderr ---------\n"; text += &stderr; } - text + Ok(text) } } @@ -2563,9 +2556,9 @@ impl Const { /// Evaluate the constant and return the result as a string. /// /// This function is intended for IDE assistance, different from [`Const::render_eval`]. - pub fn eval(self, db: &dyn HirDatabase, edition: Edition) -> Result { + pub fn eval(self, db: &dyn HirDatabase) -> Result { let c = db.const_eval(self.id.into(), Substitution::empty(Interner), None)?; - Ok(format!("{}", c.display(db, edition))) + Ok(format!("{}", c.display(db, self.krate(db).edition(db)))) } /// Evaluate the constant and return the result as a string, with more detailed information. @@ -2640,7 +2633,15 @@ impl Static { Type::from_value_def(db, self.id) } - /// Evaluate the constant and return the result as a string, with more detailed information. + /// Evaluate the static and return the result as a string. + /// + /// This function is intended for IDE assistance, different from [`Static::render_eval`]. + pub fn eval(self, db: &dyn HirDatabase) -> Result { + let c = db.const_eval(self.id.into(), Substitution::empty(Interner), None)?; + Ok(format!("{}", c.display(db, self.krate(db).edition(db)))) + } + + /// Evaluate the static and return the result as a string, with more detailed information. /// /// This function is intended for user-facing display. pub fn render_eval( diff --git a/crates/ide-assists/src/handlers/inline_const_as_literal.rs b/crates/ide-assists/src/handlers/inline_const_as_literal.rs index 6b504a918b..2bd4c4da1e 100644 --- a/crates/ide-assists/src/handlers/inline_const_as_literal.rs +++ b/crates/ide-assists/src/handlers/inline_const_as_literal.rs @@ -51,10 +51,7 @@ pub(crate) fn inline_const_as_literal(acc: &mut Assists, ctx: &AssistContext<'_> | ast::Expr::MatchExpr(_) | ast::Expr::MacroExpr(_) | ast::Expr::BinExpr(_) - | ast::Expr::CallExpr(_) => { - let edition = ctx.sema.scope(variable.syntax())?.krate().edition(ctx.db()); - konst.eval(ctx.sema.db, edition).ok()? - } + | ast::Expr::CallExpr(_) => konst.eval(ctx.sema.db).ok()?, _ => return None, }; diff --git a/crates/ide/src/interpret.rs b/crates/ide/src/interpret.rs new file mode 100644 index 0000000000..5abe572a77 --- /dev/null +++ b/crates/ide/src/interpret.rs @@ -0,0 +1,64 @@ +use hir::{DefWithBody, Semantics}; +use ide_db::{base_db::SourceRootDatabase, FilePosition, LineIndexDatabase, RootDatabase}; +use std::time::{Duration, Instant}; +use stdx::format_to; +use syntax::{algo::ancestors_at_offset, ast, AstNode, TextRange}; + +// Feature: Interpret a function, static or const. +// +// |=== +// | Editor | Action Name +// +// | VS Code | **rust-analyzer: Interpret** +// |=== +pub(crate) fn interpret(db: &RootDatabase, position: FilePosition) -> String { + match find_and_interpret(db, position) { + Some((duration, mut result)) => { + result.push('\n'); + format_to!(result, "----------------------\n"); + format_to!(result, " Finished in {}s\n", duration.as_secs_f32()); + result + } + _ => "Not inside a function, const or static".to_owned(), + } +} + +fn find_and_interpret(db: &RootDatabase, position: FilePosition) -> Option<(Duration, String)> { + let sema = Semantics::new(db); + let source_file = sema.parse_guess_edition(position.file_id); + + let item = ancestors_at_offset(source_file.syntax(), position.offset) + .filter(|it| !ast::MacroCall::can_cast(it.kind())) + .find_map(ast::Item::cast)?; + let def: DefWithBody = match item { + ast::Item::Fn(it) => sema.to_def(&it)?.into(), + ast::Item::Const(it) => sema.to_def(&it)?.into(), + ast::Item::Static(it) => sema.to_def(&it)?.into(), + _ => return None, + }; + let span_formatter = |file_id, text_range: TextRange| { + let path = &db + .source_root(db.file_source_root(file_id)) + .path_for_file(&file_id) + .map(|x| x.to_string()); + let path = path.as_deref().unwrap_or(""); + match db.line_index(file_id).try_line_col(text_range.start()) { + Some(line_col) => format!("file://{path}:{}:{}", line_col.line + 1, line_col.col), + None => format!("file://{path} range {text_range:?}"), + } + }; + let start_time = Instant::now(); + let res = match def { + DefWithBody::Function(it) => it.eval(db, span_formatter), + DefWithBody::Static(it) => it.eval(db), + DefWithBody::Const(it) => it.eval(db), + _ => unreachable!(), + }; + let res = res.unwrap_or_else(|e| { + let mut r = String::new(); + _ = e.pretty_print(&mut r, db, span_formatter, def.module(db).krate().edition(db)); + r + }); + let duration = Instant::now() - start_time; + Some((duration, res)) +} diff --git a/crates/ide/src/interpret_function.rs b/crates/ide/src/interpret_function.rs deleted file mode 100644 index ff1317d135..0000000000 --- a/crates/ide/src/interpret_function.rs +++ /dev/null @@ -1,47 +0,0 @@ -use hir::Semantics; -use ide_db::{base_db::SourceRootDatabase, FilePosition, LineIndexDatabase, RootDatabase}; -use std::{fmt::Write, time::Instant}; -use syntax::{algo::ancestors_at_offset, ast, AstNode, TextRange}; - -// Feature: Interpret Function -// -// |=== -// | Editor | Action Name -// -// | VS Code | **rust-analyzer: Interpret Function** -// |=== -pub(crate) fn interpret_function(db: &RootDatabase, position: FilePosition) -> String { - let start_time = Instant::now(); - let mut result = - find_and_interpret(db, position).unwrap_or_else(|| "Not inside a function body".to_owned()); - let duration = Instant::now() - start_time; - writeln!(result).unwrap(); - writeln!(result, "----------------------").unwrap(); - writeln!(result, " Finished in {}s", duration.as_secs_f32()).unwrap(); - result -} - -fn find_and_interpret(db: &RootDatabase, position: FilePosition) -> Option { - let sema = Semantics::new(db); - let source_file = sema.parse_guess_edition(position.file_id); - - let item = ancestors_at_offset(source_file.syntax(), position.offset) - .filter(|it| !ast::MacroCall::can_cast(it.kind())) - .find_map(ast::Item::cast)?; - let def = match item { - ast::Item::Fn(it) => sema.to_def(&it)?, - _ => return None, - }; - let span_formatter = |file_id, text_range: TextRange| { - let path = &db - .source_root(db.file_source_root(file_id)) - .path_for_file(&file_id) - .map(|x| x.to_string()); - let path = path.as_deref().unwrap_or(""); - match db.line_index(file_id).try_line_col(text_range.start()) { - Some(line_col) => format!("file://{path}#{}:{}", line_col.line + 1, line_col.col), - None => format!("file://{path} range {text_range:?}"), - } - }; - Some(def.eval(db, span_formatter)) -} diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index d053c4b3c9..d4ef9570e1 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -33,7 +33,7 @@ mod goto_type_definition; mod highlight_related; mod hover; mod inlay_hints; -mod interpret_function; +mod interpret; mod join_lines; mod markdown_remove; mod matching_brace; @@ -350,7 +350,7 @@ impl Analysis { } pub fn interpret_function(&self, position: FilePosition) -> Cancellable { - self.with_db(|db| interpret_function::interpret_function(db, position)) + self.with_db(|db| interpret::interpret(db, position)) } pub fn view_item_tree(&self, file_id: FileId) -> Cancellable { diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index a62cbc7fb2..aecafb444c 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -398,6 +398,8 @@ define_symbols! { rustc_const_panic_str, rustc_deprecated_safe_2024, rustc_has_incoherent_inherent_impls, + rustc_intrinsic, + rustc_intrinsic_must_be_overridden, rustc_layout_scalar_valid_range_end, rustc_layout_scalar_valid_range_start, rustc_legacy_const_generics, diff --git a/crates/rust-analyzer/src/cli/run_tests.rs b/crates/rust-analyzer/src/cli/run_tests.rs index 11534bbeba..7398b9a9ef 100644 --- a/crates/rust-analyzer/src/cli/run_tests.rs +++ b/crates/rust-analyzer/src/cli/run_tests.rs @@ -61,12 +61,11 @@ impl flags::RunTests { } let mut sw_one = StopWatch::start(); let result = test.eval(db, span_formatter); - if result.trim() == "pass" { - pass_count += 1; - } else { - fail_count += 1; + match &result { + Ok(result) if result.trim() == "pass" => pass_count += 1, + _ => fail_count += 1, } - println!("{result}"); + println!("{result:?}"); eprintln!("{:<20} {}", format!("test {}", full_name), sw_one.elapsed()); } println!("{pass_count} passed, {fail_count} failed, {ignore_count} ignored"); diff --git a/editors/code/package.json b/editors/code/package.json index 6eebdf9f01..ccc8c0e384 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -125,7 +125,7 @@ }, { "command": "rust-analyzer.interpretFunction", - "title": "Interpret Function", + "title": "Interpret", "category": "rust-analyzer (debug command)" }, {