Merge pull request #4661 from roc-lang/roc-test-dev

`test` has `dev` behavior, and `dev` does not exit if there are warnings
This commit is contained in:
Ayaz 2022-12-03 12:26:00 -06:00 committed by GitHub
commit c61c42bff7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 341 additions and 194 deletions

View file

@ -11,3 +11,4 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_error_macros = { path = "../../error_macros" }
roc_problem = { path = "../problem" }

View file

@ -7,6 +7,7 @@ use roc_module::{
ident::{Lowercase, TagIdIntType, TagName},
symbol::Symbol,
};
use roc_problem::Severity;
use roc_region::all::Region;
use self::Pattern::*;
@ -149,6 +150,17 @@ pub enum Error {
},
}
impl Error {
pub fn severity(&self) -> Severity {
use Severity::*;
match self {
Error::Incomplete(..) => RuntimeError,
Error::Redundant { .. } => Warning,
Error::Unmatchable { .. } => Warning,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Context {
BadArg,

View file

@ -101,14 +101,14 @@ pub fn load_and_monomorphize_from_str<'a>(
exposed_types: ExposedByModule,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
) -> Result<MonomorphizedModule<'a>, LoadMonomorphizedError<'a>> {
use LoadResult::*;
let load_start = LoadStart::from_str(arena, filename, src, roc_cache_dir, src_dir)?;
match load(arena, load_start, exposed_types, roc_cache_dir, load_config)? {
Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""),
TypeChecked(module) => Err(LoadMonomorphizedError::ErrorModule(module)),
}
}

View file

@ -45,6 +45,7 @@ use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName};
use roc_parse::ident::UppercaseIdent;
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_solve::module::{extract_module_owned_implementations, Solved, SolvedModule};
@ -97,20 +98,28 @@ pub struct LoadConfig {
#[derive(Debug, Clone, Copy)]
pub enum ExecutionMode {
Test,
Check,
Executable,
/// Like [`ExecutionMode::Executable`], but stops in the presence of type errors.
ExecutableIfCheck,
/// Test is like [`ExecutionMode::ExecutableIfCheck`], but rather than producing a proper
/// executable, run tests.
Test,
}
impl ExecutionMode {
fn goal_phase(&self) -> Phase {
match self {
ExecutionMode::Test | ExecutionMode::Executable => Phase::MakeSpecializations,
ExecutionMode::Check | ExecutionMode::ExecutableIfCheck => Phase::SolveTypes,
ExecutionMode::Executable => Phase::MakeSpecializations,
ExecutionMode::Check | ExecutionMode::ExecutableIfCheck | ExecutionMode::Test => {
Phase::SolveTypes
}
}
}
fn build_if_checks(&self) -> bool {
matches!(self, Self::ExecutableIfCheck | Self::Test)
}
}
/// Struct storing various intermediate stages by their ModuleId
@ -141,18 +150,22 @@ struct ModuleCache<'a> {
}
impl<'a> ModuleCache<'a> {
pub fn total_problems(&self) -> usize {
let mut total = 0;
fn has_can_errors(&self) -> bool {
self.can_problems
.values()
.flatten()
.any(|problem| problem.severity() == Severity::RuntimeError)
}
for problems in self.can_problems.values() {
total += problems.len();
}
fn has_type_errors(&self) -> bool {
self.type_problems
.values()
.flatten()
.any(|problem| problem.severity() == Severity::RuntimeError)
}
for problems in self.type_problems.values() {
total += problems.len();
}
total
pub fn has_errors(&self) -> bool {
self.has_can_errors() || self.has_type_errors()
}
}
@ -2615,7 +2628,7 @@ fn update<'a>(
let finish_type_checking = is_host_exposed &&
(state.goal_phase() == Phase::SolveTypes)
// If we're running in check-and-then-build mode, only exit now there are errors.
&& (!matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck) || state.module_cache.total_problems() > 0);
&& (!state.exec_mode.build_if_checks() || state.module_cache.has_errors());
if finish_type_checking {
debug_assert!(work.is_empty());
@ -2623,7 +2636,7 @@ fn update<'a>(
state.timings.insert(module_id, module_timing);
if matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck) {
if state.exec_mode.build_if_checks() {
// We there may outstanding modules in the typecheked cache whose ident IDs
// aren't registered; transfer all of their idents over to the state, since
// we're now done and ready to report errors.
@ -2677,9 +2690,7 @@ fn update<'a>(
},
);
if state.goal_phase() > Phase::SolveTypes
|| matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck)
{
if state.goal_phase() > Phase::SolveTypes || state.exec_mode.build_if_checks() {
let layout_cache = state.layout_caches.pop().unwrap_or_else(|| {
LayoutCache::new(state.layout_interner.fork(), state.target_info)
});
@ -2703,17 +2714,12 @@ fn update<'a>(
state.timings.insert(module_id, module_timing);
}
let work = if is_host_exposed
&& matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck)
{
let work = if is_host_exposed && state.exec_mode.build_if_checks() {
debug_assert!(
work.is_empty(),
"work left over after host exposed is checked"
);
// Update the goal phase to target full codegen.
state.exec_mode = ExecutionMode::Executable;
// Load the find + make specializations portion of the dependency graph.
state
.dependencies
@ -2785,7 +2791,10 @@ fn update<'a>(
layout_cache,
..
} => {
debug_assert!(state.goal_phase() == Phase::MakeSpecializations);
debug_assert!(
state.goal_phase() == Phase::MakeSpecializations
|| state.exec_mode.build_if_checks()
);
log!("made specializations for {:?}", module_id);
@ -5987,7 +5996,7 @@ fn run_task<'a>(
}
fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String {
use roc_reporting::report::{Report, RocDocAllocator, Severity, DEFAULT_PALETTE};
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
let src_lines: Vec<&str> = Vec::new();
@ -6074,7 +6083,7 @@ fn to_import_cycle_report(
filename: PathBuf,
render: RenderTarget,
) -> String {
use roc_reporting::report::{Report, RocDocAllocator, Severity, DEFAULT_PALETTE};
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
// import_cycle looks like CycleModule, Import1, ..., ImportN, CycleModule
@ -6134,7 +6143,7 @@ fn to_incorrect_module_name_report<'a>(
src: &'a [u8],
render: RenderTarget,
) -> String {
use roc_reporting::report::{Report, RocDocAllocator, Severity, DEFAULT_PALETTE};
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
let IncorrectModuleName {
@ -6220,7 +6229,7 @@ fn to_parse_problem_report<'a>(
}
fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> String {
use roc_reporting::report::{Report, RocDocAllocator, Severity, DEFAULT_PALETTE};
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
use PlatformPath::*;

View file

@ -7,6 +7,8 @@ use roc_parse::pattern::PatternType;
use roc_region::all::{Loc, Region};
use roc_types::types::AliasKind;
use crate::Severity;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CycleEntry {
pub symbol: Symbol,
@ -205,6 +207,71 @@ pub enum Problem {
}
impl Problem {
pub fn severity(&self) -> Severity {
use Severity::{RuntimeError, Warning};
match self {
Problem::UnusedDef(_, _) => Warning,
Problem::UnusedImport(_, _) => Warning,
Problem::UnusedModuleImport(_, _) => Warning,
Problem::ExposedButNotDefined(_) => RuntimeError,
Problem::UnknownGeneratesWith(_) => RuntimeError,
Problem::UnusedArgument(_, _, _, _) => Warning,
Problem::UnusedBranchDef(_, _) => Warning,
Problem::PrecedenceProblem(_) => RuntimeError,
Problem::UnsupportedPattern(_, _) => RuntimeError,
Problem::Shadowing { .. } => RuntimeError,
Problem::CyclicAlias(..) => RuntimeError,
Problem::BadRecursion(_) => RuntimeError,
Problem::PhantomTypeArgument { .. } => Warning,
Problem::UnboundTypeVariable { .. } => RuntimeError,
Problem::DuplicateRecordFieldValue { .. } => Warning,
Problem::DuplicateRecordFieldType { .. } => RuntimeError,
Problem::InvalidOptionalValue { .. } => RuntimeError,
Problem::DuplicateTag { .. } => RuntimeError,
Problem::RuntimeError(_) => RuntimeError,
Problem::SignatureDefMismatch { .. } => RuntimeError,
Problem::InvalidAliasRigid { .. } => RuntimeError,
Problem::InvalidInterpolation(_) => RuntimeError,
Problem::InvalidHexadecimal(_) => RuntimeError,
Problem::InvalidUnicodeCodePt(_) => RuntimeError,
Problem::NestedDatatype { .. } => RuntimeError,
Problem::InvalidExtensionType { .. } => RuntimeError,
Problem::AbilityHasTypeVariables { .. } => RuntimeError,
Problem::HasClauseIsNotAbility { .. } => RuntimeError,
Problem::IllegalHasClause { .. } => RuntimeError,
Problem::DuplicateHasAbility { .. } => Warning,
Problem::AbilityMemberMissingHasClause { .. } => RuntimeError,
Problem::AbilityMemberMultipleBoundVars { .. } => RuntimeError,
Problem::AbilityNotOnToplevel { .. } => RuntimeError, // Ideally, could be compiled
Problem::AbilityUsedAsType(_, _, _) => RuntimeError,
Problem::NestedSpecialization(_, _) => RuntimeError, // Ideally, could be compiled
Problem::IllegalDerivedAbility(_) => RuntimeError,
Problem::ImplementationNotFound { .. } => RuntimeError,
Problem::NotAnAbilityMember { .. } => RuntimeError,
Problem::OptionalAbilityImpl { .. } => RuntimeError,
Problem::QualifiedAbilityImpl { .. } => RuntimeError,
Problem::AbilityImplNotIdent { .. } => RuntimeError,
Problem::DuplicateImpl { .. } => Warning, // First impl is used at runtime
Problem::NotAnAbility(_) => Warning,
Problem::ImplementsNonRequired { .. } => Warning,
Problem::DoesNotImplementAbility { .. } => RuntimeError,
Problem::NotBoundInAllPatterns { .. } => RuntimeError,
Problem::NoIdentifiersIntroduced(_) => Warning,
Problem::OverloadedSpecialization { .. } => Warning, // Ideally, will compile
Problem::UnnecessaryOutputWildcard { .. } => Warning,
// TODO: sometimes this can just be a warning, e.g. if you have [1, .., .., 2] but we
// don't catch that yet.
Problem::MultipleListRestPattern { .. } => RuntimeError,
Problem::BadTypeArguments { .. } => RuntimeError,
// TODO: this can be a warning instead if we recover the program by
// injecting a crash message
Problem::UnappliedCrash { .. } => RuntimeError,
Problem::OverAppliedCrash { .. } => RuntimeError,
Problem::DefsOnlyUsedInRecursion(_, _) => Warning,
}
}
/// Returns a Region value from the Problem, if possible.
/// Some problems have more than one region; in those cases,
/// this tries to pick the one that's closest to the original

View file

@ -3,3 +3,15 @@
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod can;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Severity {
/// This will cause a runtime error if some code get srun
/// (e.g. type mismatch, naming error)
RuntimeError,
/// This will never cause the code to misbehave,
/// but should be cleaned up
/// (e.g. unused def, unused import)
Warning,
}

View file

@ -1,7 +1,7 @@
//! Provides types to describe problems that can occur during solving.
use roc_can::expected::{Expected, PExpected};
use roc_module::{ident::Lowercase, symbol::Symbol};
use roc_problem::can::CycleEntry;
use roc_problem::{can::CycleEntry, Severity};
use roc_region::all::Region;
use roc_types::types::{Category, ErrorType, PatternCategory};
@ -31,6 +31,27 @@ pub enum TypeError {
},
}
impl TypeError {
pub fn severity(&self) -> Severity {
use Severity::*;
match self {
TypeError::BadExpr(..) => RuntimeError,
TypeError::BadPattern(..) => RuntimeError,
TypeError::CircularType(..) => RuntimeError,
TypeError::CircularDef(_) => RuntimeError,
TypeError::UnexposedLookup(_) => RuntimeError,
TypeError::UnfulfilledAbility(_) => RuntimeError,
TypeError::BadExprMissingAbility(_, _, _, _) => RuntimeError,
TypeError::BadPatternMissingAbility(_, _, _, _) => RuntimeError,
// NB: if bidirectional exhaustiveness checking is implemented, the other direction
// is also not a runtime error.
TypeError::Exhaustive(exhtv) => exhtv.severity(),
TypeError::StructuralSpecialization { .. } => RuntimeError,
TypeError::WrongSpecialization { .. } => RuntimeError,
}
}
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Unfulfilled {
/// No claimed implementation of an ability for an opaque type.

View file

@ -7,7 +7,7 @@ use roc_build::link::llvm_module_to_dylib;
use roc_collections::all::MutSet;
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_gen_llvm::{llvm::build::LlvmBackendMode, run_roc::RocCallResult};
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading};
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, LoadMonomorphizedError, Threading};
use roc_mono::ir::{CrashTag, OptLevel};
use roc_packaging::cache::RocCacheDir;
use roc_region::all::LineInfo;
@ -87,7 +87,9 @@ fn create_llvm_module<'a>(
let mut loaded = match loaded {
Ok(x) => x,
Err(roc_load::LoadingProblem::FormattedReport(report)) => {
Err(LoadMonomorphizedError::LoadingProblem(roc_load::LoadingProblem::FormattedReport(
report,
))) => {
println!("{}", report);
panic!();
}

View file

@ -16,6 +16,7 @@ const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024;
use roc_collections::all::MutMap;
use roc_load::ExecutionMode;
use roc_load::LoadConfig;
use roc_load::LoadMonomorphizedError;
use roc_load::Threading;
use roc_module::symbol::Symbol;
use roc_mono::ir::Proc;
@ -113,7 +114,9 @@ fn compiles_to_ir(test_name: &str, src: &str) {
let mut loaded = match loaded {
Ok(x) => x,
Err(roc_load::LoadingProblem::FormattedReport(report)) => {
Err(LoadMonomorphizedError::LoadingProblem(roc_load::LoadingProblem::FormattedReport(
report,
))) => {
println!("{}", report);
panic!();
}