mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Allow interpreting consts and statics with interpret function command
This commit is contained in:
parent
20ab9708b4
commit
78f3112626
12 changed files with 114 additions and 86 deletions
|
@ -37,6 +37,7 @@ pub struct FunctionData {
|
||||||
pub name: Name,
|
pub name: Name,
|
||||||
pub params: Box<[TypeRefId]>,
|
pub params: Box<[TypeRefId]>,
|
||||||
pub ret_type: TypeRefId,
|
pub ret_type: TypeRefId,
|
||||||
|
// FIXME: why are these stored here? They should be accessed via the query
|
||||||
pub attrs: Attrs,
|
pub attrs: Attrs,
|
||||||
pub visibility: RawVisibility,
|
pub visibility: RawVisibility,
|
||||||
pub abi: Option<Symbol>,
|
pub abi: Option<Symbol>,
|
||||||
|
|
|
@ -56,6 +56,21 @@ pub enum ConstEvalError {
|
||||||
MirEvalError(MirEvalError),
|
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<MirLowerError> for ConstEvalError {
|
impl From<MirLowerError> for ConstEvalError {
|
||||||
fn from(value: MirLowerError) -> Self {
|
fn from(value: MirLowerError) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
@ -253,7 +268,7 @@ pub(crate) fn const_eval_query(
|
||||||
}
|
}
|
||||||
GeneralConstId::InTypeConstId(c) => db.mir_body(c.into())?,
|
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)
|
Ok(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +281,7 @@ pub(crate) fn const_eval_static_query(
|
||||||
Substitution::empty(Interner),
|
Substitution::empty(Interner),
|
||||||
db.trait_environment_for_body(def.into()),
|
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)
|
Ok(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,7 +313,7 @@ pub(crate) fn const_eval_discriminant_variant(
|
||||||
Substitution::empty(Interner),
|
Substitution::empty(Interner),
|
||||||
db.trait_environment_for_body(def),
|
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 {
|
let c = if is_signed {
|
||||||
try_const_isize(db, &c).unwrap()
|
try_const_isize(db, &c).unwrap()
|
||||||
} else {
|
} 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(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;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -585,13 +585,9 @@ pub fn interpret_mir(
|
||||||
// (and probably should) do better here, for example by excluding bindings outside of the target expression.
|
// (and probably should) do better here, for example by excluding bindings outside of the target expression.
|
||||||
assert_placeholder_ty_is_unused: bool,
|
assert_placeholder_ty_is_unused: bool,
|
||||||
trait_env: Option<Arc<TraitEnvironment>>,
|
trait_env: Option<Arc<TraitEnvironment>>,
|
||||||
) -> (Result<Const>, MirOutput) {
|
) -> Result<(Result<Const>, MirOutput)> {
|
||||||
let ty = body.locals[return_slot()].ty.clone();
|
let ty = body.locals[return_slot()].ty.clone();
|
||||||
let mut evaluator =
|
let mut evaluator = Evaluator::new(db, body.owner, assert_placeholder_ty_is_unused, trait_env)?;
|
||||||
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 it: Result<Const> = (|| {
|
let it: Result<Const> = (|| {
|
||||||
if evaluator.ptr_size() != std::mem::size_of::<usize>() {
|
if evaluator.ptr_size() != std::mem::size_of::<usize>() {
|
||||||
not_supported!("targets with different pointer size from host");
|
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))
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -32,7 +32,7 @@ fn eval_main(db: &TestDB, file_id: EditionedFileId) -> Result<(String, String),
|
||||||
)
|
)
|
||||||
.map_err(|e| MirEvalError::MirLowerError(func_id, e))?;
|
.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?;
|
result?;
|
||||||
Ok((output.stdout().into_owned(), output.stderr().into_owned()))
|
Ok((output.stdout().into_owned(), output.stderr().into_owned()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2306,22 +2306,15 @@ impl Function {
|
||||||
self,
|
self,
|
||||||
db: &dyn HirDatabase,
|
db: &dyn HirDatabase,
|
||||||
span_formatter: impl Fn(FileId, TextRange) -> String,
|
span_formatter: impl Fn(FileId, TextRange) -> String,
|
||||||
) -> String {
|
) -> Result<String, ConstEvalError> {
|
||||||
let krate = HasModule::krate(&self.id, db.upcast());
|
let krate = HasModule::krate(&self.id, db.upcast());
|
||||||
let edition = db.crate_graph()[krate].edition;
|
let edition = db.crate_graph()[krate].edition;
|
||||||
let body = match db.monomorphized_mir_body(
|
let body = db.monomorphized_mir_body(
|
||||||
self.id.into(),
|
self.id.into(),
|
||||||
Substitution::empty(Interner),
|
Substitution::empty(Interner),
|
||||||
db.trait_environment(self.id.into()),
|
db.trait_environment(self.id.into()),
|
||||||
) {
|
)?;
|
||||||
Ok(body) => body,
|
let (result, output) = interpret_mir(db, body, false, None)?;
|
||||||
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 mut text = match result {
|
let mut text = match result {
|
||||||
Ok(_) => "pass".to_owned(),
|
Ok(_) => "pass".to_owned(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -2340,7 +2333,7 @@ impl Function {
|
||||||
text += "\n--------- stderr ---------\n";
|
text += "\n--------- stderr ---------\n";
|
||||||
text += &stderr;
|
text += &stderr;
|
||||||
}
|
}
|
||||||
text
|
Ok(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2563,9 +2556,9 @@ impl Const {
|
||||||
/// Evaluate the constant and return the result as a string.
|
/// Evaluate the constant and return the result as a string.
|
||||||
///
|
///
|
||||||
/// This function is intended for IDE assistance, different from [`Const::render_eval`].
|
/// This function is intended for IDE assistance, different from [`Const::render_eval`].
|
||||||
pub fn eval(self, db: &dyn HirDatabase, edition: Edition) -> Result<String, ConstEvalError> {
|
pub fn eval(self, db: &dyn HirDatabase) -> Result<String, ConstEvalError> {
|
||||||
let c = db.const_eval(self.id.into(), Substitution::empty(Interner), None)?;
|
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.
|
/// 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)
|
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<String, ConstEvalError> {
|
||||||
|
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.
|
/// This function is intended for user-facing display.
|
||||||
pub fn render_eval(
|
pub fn render_eval(
|
||||||
|
|
|
@ -51,10 +51,7 @@ pub(crate) fn inline_const_as_literal(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||||
| ast::Expr::MatchExpr(_)
|
| ast::Expr::MatchExpr(_)
|
||||||
| ast::Expr::MacroExpr(_)
|
| ast::Expr::MacroExpr(_)
|
||||||
| ast::Expr::BinExpr(_)
|
| ast::Expr::BinExpr(_)
|
||||||
| ast::Expr::CallExpr(_) => {
|
| ast::Expr::CallExpr(_) => konst.eval(ctx.sema.db).ok()?,
|
||||||
let edition = ctx.sema.scope(variable.syntax())?.krate().edition(ctx.db());
|
|
||||||
konst.eval(ctx.sema.db, edition).ok()?
|
|
||||||
}
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
64
crates/ide/src/interpret.rs
Normal file
64
crates/ide/src/interpret.rs
Normal file
|
@ -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("<unknown file>");
|
||||||
|
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))
|
||||||
|
}
|
|
@ -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<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 = 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("<unknown file>");
|
|
||||||
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))
|
|
||||||
}
|
|
|
@ -33,7 +33,7 @@ mod goto_type_definition;
|
||||||
mod highlight_related;
|
mod highlight_related;
|
||||||
mod hover;
|
mod hover;
|
||||||
mod inlay_hints;
|
mod inlay_hints;
|
||||||
mod interpret_function;
|
mod interpret;
|
||||||
mod join_lines;
|
mod join_lines;
|
||||||
mod markdown_remove;
|
mod markdown_remove;
|
||||||
mod matching_brace;
|
mod matching_brace;
|
||||||
|
@ -350,7 +350,7 @@ impl Analysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interpret_function(&self, position: FilePosition) -> Cancellable<String> {
|
pub fn interpret_function(&self, position: FilePosition) -> Cancellable<String> {
|
||||||
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<String> {
|
pub fn view_item_tree(&self, file_id: FileId) -> Cancellable<String> {
|
||||||
|
|
|
@ -398,6 +398,8 @@ define_symbols! {
|
||||||
rustc_const_panic_str,
|
rustc_const_panic_str,
|
||||||
rustc_deprecated_safe_2024,
|
rustc_deprecated_safe_2024,
|
||||||
rustc_has_incoherent_inherent_impls,
|
rustc_has_incoherent_inherent_impls,
|
||||||
|
rustc_intrinsic,
|
||||||
|
rustc_intrinsic_must_be_overridden,
|
||||||
rustc_layout_scalar_valid_range_end,
|
rustc_layout_scalar_valid_range_end,
|
||||||
rustc_layout_scalar_valid_range_start,
|
rustc_layout_scalar_valid_range_start,
|
||||||
rustc_legacy_const_generics,
|
rustc_legacy_const_generics,
|
||||||
|
|
|
@ -61,12 +61,11 @@ impl flags::RunTests {
|
||||||
}
|
}
|
||||||
let mut sw_one = StopWatch::start();
|
let mut sw_one = StopWatch::start();
|
||||||
let result = test.eval(db, span_formatter);
|
let result = test.eval(db, span_formatter);
|
||||||
if result.trim() == "pass" {
|
match &result {
|
||||||
pass_count += 1;
|
Ok(result) if result.trim() == "pass" => pass_count += 1,
|
||||||
} else {
|
_ => fail_count += 1,
|
||||||
fail_count += 1;
|
|
||||||
}
|
}
|
||||||
println!("{result}");
|
println!("{result:?}");
|
||||||
eprintln!("{:<20} {}", format!("test {}", full_name), sw_one.elapsed());
|
eprintln!("{:<20} {}", format!("test {}", full_name), sw_one.elapsed());
|
||||||
}
|
}
|
||||||
println!("{pass_count} passed, {fail_count} failed, {ignore_count} ignored");
|
println!("{pass_count} passed, {fail_count} failed, {ignore_count} ignored");
|
||||||
|
|
|
@ -125,7 +125,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "rust-analyzer.interpretFunction",
|
"command": "rust-analyzer.interpretFunction",
|
||||||
"title": "Interpret Function",
|
"title": "Interpret",
|
||||||
"category": "rust-analyzer (debug command)"
|
"category": "rust-analyzer (debug command)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue