mirror of
https://github.com/nushell/nushell
synced 2025-01-14 22:24:54 +00:00
5d1eb031eb
# Description Because the IR compiler was previously optional, compile errors were not treated as fatal errors, and were just logged like parse warnings are. This unfortunately meant that if a user encountered a compile error, they would see "Can't evaluate block in IR mode" as the actual error in addition to (hopefully) logging the compile error. This changes compile errors to be treated like parse errors so that they show up as the last error, helping users understand what's wrong a little bit more easily. Fixes #14333. # User-Facing Changes - Shouldn't see "Can't evaluate block in IR mode" - Should only see compile error - No evaluation should happen # Tests + Formatting Didn't add any tests specifically for this, but it might be good to have at least one that checks to ensure the compile error shows up and the "can't evaluate" error does not.
146 lines
5 KiB
Rust
146 lines
5 KiB
Rust
use crate::util::{eval_source, print_pipeline};
|
|
use log::{info, trace};
|
|
use nu_engine::{convert_env_values, eval_block};
|
|
use nu_parser::parse;
|
|
use nu_path::canonicalize_with;
|
|
use nu_protocol::{
|
|
cli_error::report_compile_error,
|
|
debugger::WithoutDebug,
|
|
engine::{EngineState, Stack, StateWorkingSet},
|
|
report_parse_error, report_parse_warning, PipelineData, ShellError, Span, Value,
|
|
};
|
|
use std::sync::Arc;
|
|
|
|
/// Entry point for evaluating a file.
|
|
///
|
|
/// If the file contains a main command, it is invoked with `args` and the pipeline data from `input`;
|
|
/// otherwise, the pipeline data is forwarded to the first command in the file, and `args` are ignored.
|
|
pub fn evaluate_file(
|
|
path: String,
|
|
args: &[String],
|
|
engine_state: &mut EngineState,
|
|
stack: &mut Stack,
|
|
input: PipelineData,
|
|
) -> Result<(), ShellError> {
|
|
// Convert environment variables from Strings to Values and store them in the engine state.
|
|
convert_env_values(engine_state, stack)?;
|
|
|
|
let cwd = engine_state.cwd_as_string(Some(stack))?;
|
|
|
|
let file_path =
|
|
canonicalize_with(&path, cwd).map_err(|err| ShellError::FileNotFoundCustom {
|
|
msg: format!("Could not access file '{path}': {err}"),
|
|
span: Span::unknown(),
|
|
})?;
|
|
|
|
let file_path_str = file_path
|
|
.to_str()
|
|
.ok_or_else(|| ShellError::NonUtf8Custom {
|
|
msg: format!(
|
|
"Input file name '{}' is not valid UTF8",
|
|
file_path.to_string_lossy()
|
|
),
|
|
span: Span::unknown(),
|
|
})?;
|
|
|
|
let file = std::fs::read(&file_path).map_err(|err| ShellError::FileNotFoundCustom {
|
|
msg: format!("Could not read file '{file_path_str}': {err}"),
|
|
span: Span::unknown(),
|
|
})?;
|
|
engine_state.file = Some(file_path.clone());
|
|
|
|
let parent = file_path
|
|
.parent()
|
|
.ok_or_else(|| ShellError::FileNotFoundCustom {
|
|
msg: format!("The file path '{file_path_str}' does not have a parent"),
|
|
span: Span::unknown(),
|
|
})?;
|
|
|
|
stack.add_env_var(
|
|
"FILE_PWD".to_string(),
|
|
Value::string(parent.to_string_lossy(), Span::unknown()),
|
|
);
|
|
stack.add_env_var(
|
|
"CURRENT_FILE".to_string(),
|
|
Value::string(file_path.to_string_lossy(), Span::unknown()),
|
|
);
|
|
stack.add_env_var(
|
|
"PROCESS_PATH".to_string(),
|
|
Value::string(path, Span::unknown()),
|
|
);
|
|
|
|
let source_filename = file_path
|
|
.file_name()
|
|
.expect("internal error: missing filename");
|
|
|
|
let mut working_set = StateWorkingSet::new(engine_state);
|
|
trace!("parsing file: {}", file_path_str);
|
|
let block = parse(&mut working_set, Some(file_path_str), &file, false);
|
|
|
|
if let Some(warning) = working_set.parse_warnings.first() {
|
|
report_parse_warning(&working_set, warning);
|
|
}
|
|
|
|
// If any parse errors were found, report the first error and exit.
|
|
if let Some(err) = working_set.parse_errors.first() {
|
|
report_parse_error(&working_set, err);
|
|
std::process::exit(1);
|
|
}
|
|
|
|
if let Some(err) = working_set.compile_errors.first() {
|
|
report_compile_error(&working_set, err);
|
|
std::process::exit(1);
|
|
}
|
|
|
|
// Look for blocks whose name starts with "main" and replace it with the filename.
|
|
for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) {
|
|
if block.signature.name == "main" {
|
|
block.signature.name = source_filename.to_string_lossy().to_string();
|
|
} else if block.signature.name.starts_with("main ") {
|
|
block.signature.name =
|
|
source_filename.to_string_lossy().to_string() + " " + &block.signature.name[5..];
|
|
}
|
|
}
|
|
|
|
// Merge the changes into the engine state.
|
|
engine_state.merge_delta(working_set.delta)?;
|
|
|
|
// Check if the file contains a main command.
|
|
let exit_code = if engine_state.find_decl(b"main", &[]).is_some() {
|
|
// Evaluate the file, but don't run main yet.
|
|
let pipeline =
|
|
match eval_block::<WithoutDebug>(engine_state, stack, &block, PipelineData::empty()) {
|
|
Ok(data) => data,
|
|
Err(ShellError::Return { .. }) => {
|
|
// Allow early return before main is run.
|
|
return Ok(());
|
|
}
|
|
Err(err) => return Err(err),
|
|
};
|
|
|
|
// Print the pipeline output of the last command of the file.
|
|
print_pipeline(engine_state, stack, pipeline, true)?;
|
|
|
|
// Invoke the main command with arguments.
|
|
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
|
|
let args = format!("main {}", args.join(" "));
|
|
eval_source(
|
|
engine_state,
|
|
stack,
|
|
args.as_bytes(),
|
|
"<commandline>",
|
|
input,
|
|
true,
|
|
)
|
|
} else {
|
|
eval_source(engine_state, stack, &file, file_path_str, input, true)
|
|
};
|
|
|
|
if exit_code != 0 {
|
|
std::process::exit(exit_code);
|
|
}
|
|
|
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
|
|
|
Ok(())
|
|
}
|