mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
make file loading errors that happen late in compilation still fatal
This commit is contained in:
parent
21d063da26
commit
8f4945f286
9 changed files with 189 additions and 114 deletions
|
@ -730,6 +730,16 @@ pub fn build(
|
|||
Ok(problems.exit_code())
|
||||
}
|
||||
BuildAndRun => {
|
||||
if problems.fatally_errored {
|
||||
problems.print_to_stdout(total_time);
|
||||
println!(
|
||||
".\n\nCannot run program due to fatal error…\n\n\x1B[36m{}\x1B[39m",
|
||||
"─".repeat(80)
|
||||
);
|
||||
|
||||
// Return a nonzero exit code due to falta problem
|
||||
return Ok(problems.exit_code());
|
||||
}
|
||||
if problems.errors > 0 || problems.warnings > 0 {
|
||||
problems.print_to_stdout(total_time);
|
||||
println!(
|
||||
|
|
|
@ -735,8 +735,8 @@ pub fn canonicalize_expr<'a>(
|
|||
|
||||
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
|
||||
|
||||
ast::Expr::IngestedFile(file_path, type_ann) => {
|
||||
let mut file = File::open(file_path).expect("file should exist due to earlier check. In the rare case the file got deleted, we should create a can error here too.");
|
||||
ast::Expr::IngestedFile(file_path, type_ann) => match File::open(file_path) {
|
||||
Ok(mut file) => {
|
||||
let mut bytes = vec![];
|
||||
match file.read_to_end(&mut bytes) {
|
||||
Ok(_) => (
|
||||
|
@ -754,9 +754,35 @@ pub fn canonicalize_expr<'a>(
|
|||
),
|
||||
Output::default(),
|
||||
),
|
||||
Err(e) => todo!("failed to load file emit can error: {}", e),
|
||||
Err(e) => {
|
||||
env.problems.push(Problem::FileProblem {
|
||||
filename: file_path.to_path_buf(),
|
||||
error: e.kind(),
|
||||
});
|
||||
|
||||
// This will not manifest as a real runtime error and is just returned to have a value here.
|
||||
// The pushed FileProblem will be fatal to compilation.
|
||||
(
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::NoImplementation),
|
||||
Output::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
env.problems.push(Problem::FileProblem {
|
||||
filename: file_path.to_path_buf(),
|
||||
error: e.kind(),
|
||||
});
|
||||
|
||||
// This will not manifest as a real runtime error and is just returned to have a value here.
|
||||
// The pushed FileProblem will be fatal to compilation.
|
||||
(
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::NoImplementation),
|
||||
Output::default(),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
ast::Expr::SingleQuote(string) => {
|
||||
let mut it = string.chars().peekable();
|
||||
|
|
|
@ -51,7 +51,7 @@ use roc_parse::module::module_defs;
|
|||
use roc_parse::parser::{FileError, Parser, SourceError, SyntaxError};
|
||||
use roc_problem::Severity;
|
||||
use roc_region::all::{LineInfo, Loc, Region};
|
||||
use roc_reporting::report::{Annotation, Palette, RenderTarget};
|
||||
use roc_reporting::report::{to_file_problem_report_string, Palette, RenderTarget};
|
||||
use roc_solve::module::{extract_module_owned_implementations, Solved, SolvedModule};
|
||||
use roc_solve_problem::TypeError;
|
||||
use roc_target::TargetInfo;
|
||||
|
@ -1791,7 +1791,7 @@ fn state_thread_step<'a>(
|
|||
Ok(ControlFlow::Break(LoadResult::Monomorphized(monomorphized)))
|
||||
}
|
||||
Msg::FailedToReadFile { filename, error } => {
|
||||
let buf = to_file_problem_report(&filename, error);
|
||||
let buf = to_file_problem_report_string(&filename, error);
|
||||
Err(LoadingProblem::FormattedReport(buf))
|
||||
}
|
||||
|
||||
|
@ -1939,7 +1939,9 @@ pub fn report_loading_problem(
|
|||
)
|
||||
}
|
||||
LoadingProblem::FormattedReport(report) => report,
|
||||
LoadingProblem::FileProblem { filename, error } => to_file_problem_report(&filename, error),
|
||||
LoadingProblem::FileProblem { filename, error } => {
|
||||
to_file_problem_report_string(&filename, error)
|
||||
}
|
||||
err => todo!("Loading error: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
@ -5688,15 +5690,19 @@ fn value_def_from_imports<'a>(
|
|||
let value = match entry.value {
|
||||
Module(_, _) => None,
|
||||
Package(_, _, _) => None,
|
||||
IngestedFile(file_name, typed_ident) => {
|
||||
let file_path = if let StrLiteral::PlainLine(filename) = file_name {
|
||||
let file_path = header_path.to_path_buf().with_file_name(filename);
|
||||
IngestedFile(ingested_path, typed_ident) => {
|
||||
let file_path = if let StrLiteral::PlainLine(ingested_path) = ingested_path {
|
||||
let mut file_path = header_path.to_path_buf();
|
||||
// Remove the header file name and push the new path.
|
||||
file_path.pop();
|
||||
file_path.push(ingested_path);
|
||||
|
||||
match fs::metadata(&file_path) {
|
||||
Ok(md) => {
|
||||
if !md.is_file() {
|
||||
// TODO: is there a better loading problem to return when not a file.
|
||||
if md.is_dir() {
|
||||
return Err(LoadingProblem::FileProblem {
|
||||
filename: file_path,
|
||||
// TODO: change to IsADirectory once that is stable.
|
||||
error: io::ErrorKind::InvalidInput,
|
||||
});
|
||||
}
|
||||
|
@ -6551,87 +6557,6 @@ fn run_task<'a>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String {
|
||||
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
let src_lines: Vec<&str> = Vec::new();
|
||||
|
||||
let mut module_ids = ModuleIds::default();
|
||||
|
||||
let module_id = module_ids.get_or_insert(&"find module name somehow?".into());
|
||||
|
||||
let interns = Interns::default();
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
|
||||
|
||||
let report = match error {
|
||||
io::ErrorKind::NotFound => {
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(r"I am looking for this file, but it's not there:"),
|
||||
alloc
|
||||
.parser_suggestion(filename.to_str().unwrap())
|
||||
.indent(4),
|
||||
alloc.concat([
|
||||
alloc.reflow(r"Is the file supposed to be there? "),
|
||||
alloc.reflow("Maybe there is a typo in the file name?"),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
doc,
|
||||
title: "FILE NOT FOUND".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
}
|
||||
}
|
||||
io::ErrorKind::PermissionDenied => {
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(r"I don't have the required permissions to read this file:"),
|
||||
alloc
|
||||
.parser_suggestion(filename.to_str().unwrap())
|
||||
.indent(4),
|
||||
alloc
|
||||
.concat([alloc.reflow(r"Is it the right file? Maybe change its permissions?")]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
doc,
|
||||
title: "FILE PERMISSION DENIED".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let error = std::io::Error::from(error);
|
||||
let formatted = format!("{}", error);
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(r"I tried to read this file:"),
|
||||
alloc
|
||||
.text(filename.to_str().unwrap())
|
||||
.annotate(Annotation::Error)
|
||||
.indent(4),
|
||||
alloc.reflow(r"But ran into:"),
|
||||
alloc.text(formatted).annotate(Annotation::Error).indent(4),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
doc,
|
||||
title: "FILE PROBLEM".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut buf = String::new();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
fn to_import_cycle_report(
|
||||
module_ids: ModuleIds,
|
||||
all_ident_ids: IdentIdsByModule,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use roc_collections::all::MutSet;
|
||||
use roc_module::called_via::BinOp;
|
||||
use roc_module::ident::{Ident, Lowercase, ModuleName, TagName};
|
||||
|
@ -204,11 +207,15 @@ pub enum Problem {
|
|||
OverAppliedCrash {
|
||||
region: Region,
|
||||
},
|
||||
FileProblem {
|
||||
filename: PathBuf,
|
||||
error: io::ErrorKind,
|
||||
},
|
||||
}
|
||||
|
||||
impl Problem {
|
||||
pub fn severity(&self) -> Severity {
|
||||
use Severity::{RuntimeError, Warning};
|
||||
use Severity::{Fatal, RuntimeError, Warning};
|
||||
|
||||
match self {
|
||||
Problem::UnusedDef(_, _) => Warning,
|
||||
|
@ -269,6 +276,7 @@ impl Problem {
|
|||
Problem::UnappliedCrash { .. } => RuntimeError,
|
||||
Problem::OverAppliedCrash { .. } => RuntimeError,
|
||||
Problem::DefsOnlyUsedInRecursion(_, _) => Warning,
|
||||
Problem::FileProblem { .. } => Fatal,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,6 +422,7 @@ impl Problem {
|
|||
| Problem::RuntimeError(RuntimeError::VoidValue)
|
||||
| Problem::RuntimeError(RuntimeError::ExposedButNotDefined(_))
|
||||
| Problem::RuntimeError(RuntimeError::NoImplementationNamed { .. })
|
||||
| Problem::FileProblem { .. }
|
||||
| Problem::ExposedButNotDefined(_) => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@ pub mod can;
|
|||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Severity {
|
||||
/// This should stop compilation in all cases.
|
||||
/// Due to delayed loading of ingested files, this is wanted behaviour over a runtime error.
|
||||
Fatal,
|
||||
|
||||
/// This will cause a runtime error if some code get srun
|
||||
/// (e.g. type mismatch, naming error)
|
||||
RuntimeError,
|
||||
|
|
|
@ -136,7 +136,7 @@ pub fn compile_to_mono<'a, 'i, I: Iterator<Item = &'i str>>(
|
|||
Severity::Warning => {
|
||||
warnings.push(buf);
|
||||
}
|
||||
Severity::RuntimeError => {
|
||||
Severity::Fatal | Severity::RuntimeError => {
|
||||
errors.push(buf);
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ pub fn compile_to_mono<'a, 'i, I: Iterator<Item = &'i str>>(
|
|||
Severity::Warning => {
|
||||
warnings.push(buf);
|
||||
}
|
||||
Severity::RuntimeError => {
|
||||
Severity::Fatal | Severity::RuntimeError => {
|
||||
errors.push(buf);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use roc_solve_problem::TypeError;
|
|||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Problems {
|
||||
pub fatally_errored: bool,
|
||||
pub errors: usize,
|
||||
pub warnings: usize,
|
||||
}
|
||||
|
@ -65,6 +66,7 @@ pub fn report_problems(
|
|||
// never need to re-allocate either the warnings or the errors vec!
|
||||
let mut warnings = Vec::with_capacity(total_problems);
|
||||
let mut errors = Vec::with_capacity(total_problems);
|
||||
let mut fatally_errored = false;
|
||||
|
||||
for (home, (module_path, src)) in sources.iter() {
|
||||
let mut src_lines: Vec<&str> = Vec::new();
|
||||
|
@ -92,6 +94,10 @@ pub fn report_problems(
|
|||
RuntimeError => {
|
||||
errors.push(buf);
|
||||
}
|
||||
Fatal => {
|
||||
fatally_errored = true;
|
||||
errors.push(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,6 +117,10 @@ pub fn report_problems(
|
|||
RuntimeError => {
|
||||
errors.push(buf);
|
||||
}
|
||||
Fatal => {
|
||||
fatally_errored = true;
|
||||
errors.push(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,6 +154,7 @@ pub fn report_problems(
|
|||
}
|
||||
|
||||
Problems {
|
||||
fatally_errored,
|
||||
errors: errors.len(),
|
||||
warnings: warnings.len(),
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use roc_types::types::AliasKind;
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::error::r#type::suggest;
|
||||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||
use crate::report::{to_file_problem_report, Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
const SYNTAX_PROBLEM: &str = "SYNTAX PROBLEM";
|
||||
|
@ -1093,6 +1093,11 @@ pub fn can_problem<'b>(
|
|||
]);
|
||||
title = "OVERAPPLIED CRASH".to_string();
|
||||
}
|
||||
Problem::FileProblem { filename, error } => {
|
||||
let report = to_file_problem_report(&alloc, &filename, error);
|
||||
doc = report.doc;
|
||||
title = report.title;
|
||||
}
|
||||
};
|
||||
|
||||
Report {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::{Lowercase, ModuleName, TagName, Uppercase};
|
||||
use roc_module::symbol::{Interns, ModuleId, PQModuleName, PackageQualified, Symbol};
|
||||
use roc_module::symbol::{Interns, ModuleId, ModuleIds, PQModuleName, PackageQualified, Symbol};
|
||||
use roc_problem::Severity;
|
||||
use roc_region::all::LineColumnRegion;
|
||||
use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fmt, io};
|
||||
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated};
|
||||
|
||||
pub use crate::error::canonicalize::can_problem;
|
||||
|
@ -1077,3 +1077,88 @@ where
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_file_problem_report_string(filename: &Path, error: io::ErrorKind) -> String {
|
||||
let src_lines: Vec<&str> = Vec::new();
|
||||
|
||||
let mut module_ids = ModuleIds::default();
|
||||
|
||||
let module_id = module_ids.get_or_insert(&"find module name somehow?".into());
|
||||
|
||||
let interns = Interns::default();
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
|
||||
|
||||
let mut buf = String::new();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
let report = to_file_problem_report(&alloc, filename, error);
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn to_file_problem_report<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: &Path,
|
||||
error: io::ErrorKind,
|
||||
) -> Report<'b> {
|
||||
let filename: String = filename.to_str().unwrap().to_string();
|
||||
match error {
|
||||
io::ErrorKind::NotFound => {
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(r"I am looking for this file, but it's not there:"),
|
||||
alloc
|
||||
.string(filename)
|
||||
.annotate(Annotation::ParserSuggestion)
|
||||
.indent(4),
|
||||
alloc.concat([
|
||||
alloc.reflow(r"Is the file supposed to be there? "),
|
||||
alloc.reflow("Maybe there is a typo in the file name?"),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
doc,
|
||||
title: "FILE NOT FOUND".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
}
|
||||
}
|
||||
io::ErrorKind::PermissionDenied => {
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(r"I don't have the required permissions to read this file:"),
|
||||
alloc
|
||||
.string(filename)
|
||||
.annotate(Annotation::ParserSuggestion)
|
||||
.indent(4),
|
||||
alloc
|
||||
.concat([alloc.reflow(r"Is it the right file? Maybe change its permissions?")]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
doc,
|
||||
title: "FILE PERMISSION DENIED".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let error = std::io::Error::from(error);
|
||||
let formatted = format!("{}", error);
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(r"I tried to read this file:"),
|
||||
alloc.string(filename).annotate(Annotation::Error).indent(4),
|
||||
alloc.reflow(r"But ran into:"),
|
||||
alloc.text(formatted).annotate(Annotation::Error).indent(4),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
doc,
|
||||
title: "FILE PROBLEM".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue