make file loading errors that happen late in compilation still fatal

This commit is contained in:
Brendan Hansknecht 2023-04-04 20:16:19 -07:00
parent 21d063da26
commit 8f4945f286
No known key found for this signature in database
GPG key ID: 0EA784685083E75B
9 changed files with 189 additions and 114 deletions

View file

@ -730,6 +730,16 @@ pub fn build(
Ok(problems.exit_code()) Ok(problems.exit_code())
} }
BuildAndRun => { 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 { if problems.errors > 0 || problems.warnings > 0 {
problems.print_to_stdout(total_time); problems.print_to_stdout(total_time);
println!( println!(

View file

@ -735,28 +735,54 @@ pub fn canonicalize_expr<'a>(
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal), ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
ast::Expr::IngestedFile(file_path, type_ann) => { ast::Expr::IngestedFile(file_path, type_ann) => match File::open(file_path) {
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."); Ok(mut file) => {
let mut bytes = vec![]; let mut bytes = vec![];
match file.read_to_end(&mut bytes) { match file.read_to_end(&mut bytes) {
Ok(_) => ( Ok(_) => (
Expr::IngestedFile( Expr::IngestedFile(
bytes, bytes,
annotation::canonicalize_annotation( annotation::canonicalize_annotation(
env, env,
scope, scope,
&type_ann.value, &type_ann.value,
region, region,
var_store, var_store,
&VecMap::default(), &VecMap::default(),
annotation::AnnotationFor::Value, annotation::AnnotationFor::Value,
),
), ),
Output::default(),
), ),
Output::default(), Err(e) => {
), env.problems.push(Problem::FileProblem {
Err(e) => todo!("failed to load file emit can error: {}", e), 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) => { ast::Expr::SingleQuote(string) => {
let mut it = string.chars().peekable(); let mut it = string.chars().peekable();

View file

@ -51,7 +51,7 @@ use roc_parse::module::module_defs;
use roc_parse::parser::{FileError, Parser, SourceError, SyntaxError}; use roc_parse::parser::{FileError, Parser, SourceError, SyntaxError};
use roc_problem::Severity; use roc_problem::Severity;
use roc_region::all::{LineInfo, Loc, Region}; 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::module::{extract_module_owned_implementations, Solved, SolvedModule};
use roc_solve_problem::TypeError; use roc_solve_problem::TypeError;
use roc_target::TargetInfo; use roc_target::TargetInfo;
@ -1791,7 +1791,7 @@ fn state_thread_step<'a>(
Ok(ControlFlow::Break(LoadResult::Monomorphized(monomorphized))) Ok(ControlFlow::Break(LoadResult::Monomorphized(monomorphized)))
} }
Msg::FailedToReadFile { filename, error } => { 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)) Err(LoadingProblem::FormattedReport(buf))
} }
@ -1939,7 +1939,9 @@ pub fn report_loading_problem(
) )
} }
LoadingProblem::FormattedReport(report) => report, 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), err => todo!("Loading error: {:?}", err),
} }
} }
@ -5688,15 +5690,19 @@ fn value_def_from_imports<'a>(
let value = match entry.value { let value = match entry.value {
Module(_, _) => None, Module(_, _) => None,
Package(_, _, _) => None, Package(_, _, _) => None,
IngestedFile(file_name, typed_ident) => { IngestedFile(ingested_path, typed_ident) => {
let file_path = if let StrLiteral::PlainLine(filename) = file_name { let file_path = if let StrLiteral::PlainLine(ingested_path) = ingested_path {
let file_path = header_path.to_path_buf().with_file_name(filename); 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) { match fs::metadata(&file_path) {
Ok(md) => { Ok(md) => {
if !md.is_file() { if md.is_dir() {
// TODO: is there a better loading problem to return when not a file.
return Err(LoadingProblem::FileProblem { return Err(LoadingProblem::FileProblem {
filename: file_path, filename: file_path,
// TODO: change to IsADirectory once that is stable.
error: io::ErrorKind::InvalidInput, error: io::ErrorKind::InvalidInput,
}); });
} }
@ -6551,87 +6557,6 @@ fn run_task<'a>(
Ok(()) 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( fn to_import_cycle_report(
module_ids: ModuleIds, module_ids: ModuleIds,
all_ident_ids: IdentIdsByModule, all_ident_ids: IdentIdsByModule,

View file

@ -1,3 +1,6 @@
use std::io;
use std::path::PathBuf;
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
use roc_module::called_via::BinOp; use roc_module::called_via::BinOp;
use roc_module::ident::{Ident, Lowercase, ModuleName, TagName}; use roc_module::ident::{Ident, Lowercase, ModuleName, TagName};
@ -204,11 +207,15 @@ pub enum Problem {
OverAppliedCrash { OverAppliedCrash {
region: Region, region: Region,
}, },
FileProblem {
filename: PathBuf,
error: io::ErrorKind,
},
} }
impl Problem { impl Problem {
pub fn severity(&self) -> Severity { pub fn severity(&self) -> Severity {
use Severity::{RuntimeError, Warning}; use Severity::{Fatal, RuntimeError, Warning};
match self { match self {
Problem::UnusedDef(_, _) => Warning, Problem::UnusedDef(_, _) => Warning,
@ -269,6 +276,7 @@ impl Problem {
Problem::UnappliedCrash { .. } => RuntimeError, Problem::UnappliedCrash { .. } => RuntimeError,
Problem::OverAppliedCrash { .. } => RuntimeError, Problem::OverAppliedCrash { .. } => RuntimeError,
Problem::DefsOnlyUsedInRecursion(_, _) => Warning, Problem::DefsOnlyUsedInRecursion(_, _) => Warning,
Problem::FileProblem { .. } => Fatal,
} }
} }
@ -414,6 +422,7 @@ impl Problem {
| Problem::RuntimeError(RuntimeError::VoidValue) | Problem::RuntimeError(RuntimeError::VoidValue)
| Problem::RuntimeError(RuntimeError::ExposedButNotDefined(_)) | Problem::RuntimeError(RuntimeError::ExposedButNotDefined(_))
| Problem::RuntimeError(RuntimeError::NoImplementationNamed { .. }) | Problem::RuntimeError(RuntimeError::NoImplementationNamed { .. })
| Problem::FileProblem { .. }
| Problem::ExposedButNotDefined(_) => None, | Problem::ExposedButNotDefined(_) => None,
} }
} }

View file

@ -6,6 +6,10 @@ pub mod can;
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Severity { 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 /// This will cause a runtime error if some code get srun
/// (e.g. type mismatch, naming error) /// (e.g. type mismatch, naming error)
RuntimeError, RuntimeError,

View file

@ -136,7 +136,7 @@ pub fn compile_to_mono<'a, 'i, I: Iterator<Item = &'i str>>(
Severity::Warning => { Severity::Warning => {
warnings.push(buf); warnings.push(buf);
} }
Severity::RuntimeError => { Severity::Fatal | Severity::RuntimeError => {
errors.push(buf); errors.push(buf);
} }
} }
@ -154,7 +154,7 @@ pub fn compile_to_mono<'a, 'i, I: Iterator<Item = &'i str>>(
Severity::Warning => { Severity::Warning => {
warnings.push(buf); warnings.push(buf);
} }
Severity::RuntimeError => { Severity::Fatal | Severity::RuntimeError => {
errors.push(buf); errors.push(buf);
} }
} }

View file

@ -7,6 +7,7 @@ use roc_solve_problem::TypeError;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Problems { pub struct Problems {
pub fatally_errored: bool,
pub errors: usize, pub errors: usize,
pub warnings: usize, pub warnings: usize,
} }
@ -65,6 +66,7 @@ pub fn report_problems(
// never need to re-allocate either the warnings or the errors vec! // never need to re-allocate either the warnings or the errors vec!
let mut warnings = Vec::with_capacity(total_problems); let mut warnings = Vec::with_capacity(total_problems);
let mut errors = 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() { for (home, (module_path, src)) in sources.iter() {
let mut src_lines: Vec<&str> = Vec::new(); let mut src_lines: Vec<&str> = Vec::new();
@ -92,6 +94,10 @@ pub fn report_problems(
RuntimeError => { RuntimeError => {
errors.push(buf); errors.push(buf);
} }
Fatal => {
fatally_errored = true;
errors.push(buf);
}
} }
} }
@ -111,6 +117,10 @@ pub fn report_problems(
RuntimeError => { RuntimeError => {
errors.push(buf); errors.push(buf);
} }
Fatal => {
fatally_errored = true;
errors.push(buf);
}
} }
} }
} }
@ -144,6 +154,7 @@ pub fn report_problems(
} }
Problems { Problems {
fatally_errored,
errors: errors.len(), errors: errors.len(),
warnings: warnings.len(), warnings: warnings.len(),
} }

View file

@ -12,7 +12,7 @@ use roc_types::types::AliasKind;
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::r#type::suggest; 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; use ven_pretty::DocAllocator;
const SYNTAX_PROBLEM: &str = "SYNTAX PROBLEM"; const SYNTAX_PROBLEM: &str = "SYNTAX PROBLEM";
@ -1093,6 +1093,11 @@ pub fn can_problem<'b>(
]); ]);
title = "OVERAPPLIED CRASH".to_string(); 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 { Report {

View file

@ -1,10 +1,10 @@
use roc_module::ident::Ident; use roc_module::ident::Ident;
use roc_module::ident::{Lowercase, ModuleName, TagName, Uppercase}; 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_problem::Severity;
use roc_region::all::LineColumnRegion; use roc_region::all::LineColumnRegion;
use std::fmt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{fmt, io};
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated}; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated};
pub use crate::error::canonicalize::can_problem; pub use crate::error::canonicalize::can_problem;
@ -1077,3 +1077,88 @@ where
Ok(()) 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,
}
}
}
}