mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
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:
commit
c61c42bff7
22 changed files with 341 additions and 194 deletions
|
@ -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" }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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!();
|
||||
}
|
||||
|
|
|
@ -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!();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue