From 601137dd16f19f35887797c914b5b59b15ce07ed Mon Sep 17 00:00:00 2001 From: YetAnotherMinion Date: Thu, 16 Sep 2021 06:59:57 +0100 Subject: [PATCH] feat: start to report errors instead of panicing --- Cargo.toml | 1 + src/main.rs | 67 +++++++++++++++++++++--------------------------- src/reporting.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 38 deletions(-) create mode 100644 src/reporting.rs diff --git a/Cargo.toml b/Cargo.toml index 2af5904..cb790f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ uuid = { version = "0.8", features = [ "v4" ] } ahash = "0.7" serde = { version = "1.0", features = [ "derive" ] } serde_json = { version ="1.0", features = [] } +naive-wadler-prettier= { path = "../../../infra/redwood-lang/compiler/naive-wadler-prettier" } # All of these are required for deno's javascript runtime. We need to keep the # same versions as other projects in our cargo workspace. Multiple different diff --git a/src/main.rs b/src/main.rs index 01bfb0b..9ae656e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +extern crate naive_wadler_prettier as pretty; +use crate::reporting::{Problem, SetupError, TypeError}; use elmi::DataBinary; use rusty_v8 as v8; use serde::{Deserialize, Serialize}; @@ -16,6 +18,8 @@ use structopt::StructOpt; use tokio; use uuid::Uuid; +mod reporting; + fn main() { let args = Arguments::from_args(); @@ -67,9 +71,11 @@ fn main() { } // TODO remove this unwrap + // TODO CompilerError:: let data = std::fs::read(&file).unwrap(); let mut hasher = PortableHash::new(); + // TODO CompilerError::ElmJsonChecksumFailed(io::Error, PathBuf), hasher.write_all(&data).unwrap(); // also include the function name in the checksum so users can run multiple functions // from the same file but still get caching if the file does not change. @@ -497,7 +503,7 @@ enum OutputType { Value, } -fn resolve_function_type(tipe: &elmi::Type) -> Result<(Option, OutputType), Problem> { +fn resolve_function_type(tipe: &elmi::Type) -> Result<(Option, OutputType), TypeError> { match tipe { elmi::Type::TLambda(a, b) => { // We want to check the output types first because this is where we will figure out if @@ -507,7 +513,7 @@ fn resolve_function_type(tipe: &elmi::Type) -> Result<(Option, Output Ok((Some(input_type), output_type)) } - elmi::Type::TVar(_) => Err(Problem::CantEvalGeneric), + elmi::Type::TVar(_) => Err(TypeError::CantEvalGeneric), elmi::Type::TType(module_name, name, args) if args.is_empty() => { // If our function returns a primitive type if module_name == "elm/core/String" && name == "String" { @@ -517,12 +523,12 @@ fn resolve_function_type(tipe: &elmi::Type) -> Result<(Option, Output return Ok((None, OutputType::String)); } - Err(Problem::CantEvalType(tipe.clone())) + Err(TypeError::CantEvalType(tipe.clone())) } - elmi::Type::TType(module_name, name, args) => Err(Problem::CantEvalCustomType), - elmi::Type::TRecord(_, _) => Err(Problem::CantEvalRecord), - elmi::Type::TUnit => Err(Problem::CantEvalUnit), - elmi::Type::TTuple(_, _, _) => Err(Problem::CantEvalTuple), + elmi::Type::TType(module_name, name, args) => Err(TypeError::CantEvalCustomType), + elmi::Type::TRecord(_, _) => Err(TypeError::CantEvalRecord), + elmi::Type::TUnit => Err(TypeError::CantEvalUnit), + elmi::Type::TTuple(_, _, _) => Err(TypeError::CantEvalTuple), elmi::Type::TAlias(_, _, _, ref alias) => { match &**alias { elmi::AliasType::Filled(tipe) => { @@ -539,15 +545,15 @@ fn resolve_function_type(tipe: &elmi::Type) -> Result<(Option, Output // ``` resolve_function_type(tipe) } - elmi::AliasType::Holey(_) => return Err(Problem::CantEvalHoleyAlias), + elmi::AliasType::Holey(_) => return Err(TypeError::CantEvalHoleyAlias), } } } } -fn resolve_input_type(tipe: &elmi::Type) -> Result { +fn resolve_input_type(tipe: &elmi::Type) -> Result { match tipe { - elmi::Type::TLambda(_, _) => Err(Problem::EvalRequiresSingleArgument(tipe.clone())), + elmi::Type::TLambda(_, _) => Err(TypeError::EvalRequiresSingleArgument(tipe.clone())), elmi::Type::TType(module_name, name, args) if args.is_empty() => { if module_name == "elm/core/String" && name == "String" { Ok(InputType::String) @@ -556,18 +562,18 @@ fn resolve_input_type(tipe: &elmi::Type) -> Result { } else if module_name == "elm/json/Json.Encode" && name == "Value" { Ok(InputType::Value) } else { - Err(Problem::InputTypeNotSupported(tipe.clone())) + Err(TypeError::InputTypeNotSupported(tipe.clone())) } } elmi::Type::TAlias(_, _, _, ref alias) => match &**alias { elmi::AliasType::Filled(tipe) => resolve_input_type(tipe), - elmi::AliasType::Holey(_) => Err(Problem::CantEvalHoleyAlias), + elmi::AliasType::Holey(_) => Err(TypeError::CantEvalHoleyAlias), }, - _ => Err(Problem::OutputTypeNotSupported(tipe.clone())), + _ => Err(TypeError::OutputTypeNotSupported(tipe.clone())), } } -fn resolve_output_type(tipe: &elmi::Type) -> Result { +fn resolve_output_type(tipe: &elmi::Type) -> Result { match tipe { elmi::Type::TType(module_name, name, args) => { if module_name == "elm/core/String" && name == "String" { @@ -579,33 +585,17 @@ fn resolve_output_type(tipe: &elmi::Type) -> Result { } else if module_name == "elm/virtual-dom/VirtualDom" && name == "Node" { Ok(OutputType::Html) } else { - Err(Problem::OutputTypeNotSupported(tipe.clone())) + Err(TypeError::OutputTypeNotSupported(tipe.clone())) } } elmi::Type::TAlias(_, _, _, ref alias) => match &**alias { elmi::AliasType::Filled(tipe) => resolve_output_type(tipe), - elmi::AliasType::Holey(_) => Err(Problem::CantEvalHoleyAlias), + elmi::AliasType::Holey(_) => Err(TypeError::CantEvalHoleyAlias), }, - _ => Err(Problem::OutputTypeNotSupported(tipe.clone())), + _ => Err(TypeError::OutputTypeNotSupported(tipe.clone())), } } -#[derive(Debug, Clone)] -enum Problem { - CantEvalRecord, - CantEvalUnit, - CantEvalCustomType, - CantEvalHoleyAlias, - CantEvalTuple, - CantEvalGeneric, - CantEvalType(elmi::Type), - InputTypeNotSupported(elmi::Type), - OutputTypeNotSupported(elmi::Type), - EvalRequiresSingleArgument(elmi::Type), - - InstallDependencyFailed, -} - fn setup_generator_project(elm_project_dir: PathBuf) -> Result { // I added a couple of random bytes to the directory name to reduce the risk of // collisions with other programs that also use elm-stuff for their scratch space @@ -707,11 +697,11 @@ fn elm_install, S: AsRef>(our_temp_dir: P, package: S) -> Re match child.wait() { Ok(exit_status) => { if !exit_status.success() { - return Err(Problem::InstallDependencyFailed); + return Err(SetupError::InstallDependencyFailed.into()); } } Err(_) => { - return Err(Problem::InstallDependencyFailed); + return Err(SetupError::InstallDependencyFailed.into()); } } @@ -725,9 +715,10 @@ fn generate_fixture>( input: Option, output: OutputType, ) -> (String, String) { - // TODO update this size with a better estimate once I know how large the completed file is - // likely to be. - let mut buffer = String::with_capacity(4096); + // The generated fixture Elm files are around 3007 bytes which includes the import of the + // target module and function + let mut buffer = + String::with_capacity(3000 + target_module.as_ref().len() + target_function.as_ref().len()); let module_name = format!("Gen{:020}", source_checksum); diff --git a/src/reporting.rs b/src/reporting.rs new file mode 100644 index 0000000..c63d640 --- /dev/null +++ b/src/reporting.rs @@ -0,0 +1,65 @@ +use deno_core::error::{type_error, AnyError}; +use elmi; +use pretty::{self, hang, hardline, hcat, hsep, sep, space_join, Doc}; +use rusty_v8; +use std::io; +use std::path::PathBuf; + +#[derive(Debug)] +pub enum Problem { + BadTypes(TypeError), + BadCompiler(CompilerError), + BadSetup(SetupError), + BadRuntime(InterpreterError), +} + +impl From for Problem { + fn from(error: TypeError) -> Self { + Self::BadTypes(error) + } +} + +impl From for Problem { + fn from(error: CompilerError) -> Self { + Self::BadCompiler(error) + } +} + +impl From for Problem { + fn from(error: SetupError) -> Self { + Self::BadSetup(error) + } +} + +#[derive(Debug)] +pub enum TypeError { + CantEvalRecord, + CantEvalUnit, + CantEvalCustomType, + CantEvalHoleyAlias, + CantEvalTuple, + CantEvalGeneric, + CantEvalType(elmi::Type), + InputTypeNotSupported(elmi::Type), + OutputTypeNotSupported(elmi::Type), + EvalRequiresSingleArgument(elmi::Type), +} + +#[derive(Debug)] +pub enum SetupError { + InstallDependencyFailed, +} + +#[derive(Debug)] +pub enum CompilerError { + ElmJsonChecksumFailed(io::Error, PathBuf), + ReadInputFailed(io::Error, PathBuf), + WriteOutputFailed(io::Error, PathBuf), + FailedElmiParse(String), +} + +#[derive(Debug)] +pub enum InterpreterError { + Setup(rusty_v8::DataError), + EventLoop(AnyError), +}