Skip to content

Commit

Permalink
Merge pull request #6853 from roc-lang/no_std-error-macros
Browse files Browse the repository at this point in the history
Make error macros be no_std
  • Loading branch information
rtfeldman authored Jun 30, 2024
2 parents 8538a88 + e421ef6 commit f974ec5
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 16 deletions.
1 change: 0 additions & 1 deletion crates/compiler/gen_wasm/src/code_builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use bitvec::vec::BitVec;
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use core::panic;
use roc_wasm_module::linking::IndexRelocType;

use roc_error_macros::internal_error;
Expand Down
142 changes: 127 additions & 15 deletions crates/error_macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,145 @@
//! Provides macros for consistent reporting of errors in Roc's rust code.
#![no_std]

#[cfg(any(unix, windows, target_arch = "wasm32"))]
use core::fmt;

#[cfg(unix)]
extern "C" {
fn write(fd: i32, buf: *const u8, count: usize) -> isize;
fn exit(status: i32) -> !;
}

#[cfg(unix)]
const STDERR_FD: i32 = 2;

#[cfg(windows)]
extern "C" {
fn GetStdHandle(nStdHandle: i32) -> *mut core::ffi::c_void;
fn WriteFile(
hFile: *mut core::ffi::c_void,
lpBuffer: *const u8,
nNumberOfBytesToWrite: u32,
lpNumberOfBytesWritten: *mut u32,
lpOverlapped: *mut core::ffi::c_void,
) -> i32;
fn ExitProcess(exit_code: u32) -> !;
}

#[cfg(windows)]
const STD_ERROR_HANDLE: i32 = -12;

/// Print each of the given strings to stderr (if it's available; on wasm, nothing will
/// be printed) and then immediately exit the program with an error.
/// On wasm, this will trap, and on UNIX or Windows it will exit with a code of 1.
#[inline(never)]
#[cold]
#[cfg(any(unix, windows, target_arch = "wasm32"))]
pub fn error_and_exit(args: fmt::Arguments) -> ! {
use fmt::Write;

struct StderrWriter;

impl Write for StderrWriter {
#[cfg(unix)]
fn write_str(&mut self, s: &str) -> fmt::Result {
unsafe {
let _ = write(STDERR_FD, s.as_ptr(), s.len());
}
Ok(())
}

#[cfg(windows)]
fn write_str(&mut self, s: &str) -> fmt::Result {
unsafe {
let handle = GetStdHandle(STD_ERROR_HANDLE);
let mut written = 0;
let _ = WriteFile(
handle,
s.as_ptr(),
s.len() as u32,
&mut written,
core::ptr::null_mut(),
);
}
Ok(())
}

#[cfg(target_arch = "wasm32")]
fn write_str(&mut self, _s: &str) -> fmt::Result {
Ok(())
}
}

let _ = fmt::write(&mut StderrWriter, args);

// Write a newline at the end to make sure stderr gets flushed.
let _ = StderrWriter.write_str("\n");

#[cfg(unix)]
unsafe {
exit(1)
}

#[cfg(windows)]
unsafe {
ExitProcess(1)
}

#[cfg(target_arch = "wasm32")]
{
// We have no way to write to any stderr equivalent in wasm,
// so just trap to end the program immediately.
core::arch::wasm32::unreachable()
}
}

pub const INTERNAL_ERROR_MESSAGE: &str = concat!(
"An internal compiler expectation was broken.\n",
"This is definitely a compiler bug.\n",
"Please file an issue here: <https://github.com/roc-lang/roc/issues/new/choose>\n",
);

/// `internal_error!` should be used whenever a compiler invariant is broken.
/// It is a wrapper around panic that tells the user to file a bug.
/// It tells the user to file a bug and then exits the program with a nonzero exit code.
/// (On wasm it doesn't tell the user anything, since we don't necessarily have a way to print.)
/// This should only be used in cases where there would be a compiler bug and the user can't fix it.
/// If there is simply an unimplemented feature, please use `unimplemented!`
/// If there is a user error, please use roc_reporting to print a nice error message.
#[macro_export]
macro_rules! internal_error {
() => ({
$crate::error_and_exit(format_args!("{}", $crate::INTERNAL_ERROR_MESSAGE))
});
($($arg:tt)*) => ({
eprintln!("An internal compiler expectation was broken.");
eprintln!("This is definitely a compiler bug.");
// TODO: update this to the new bug template.
eprintln!("Please file an issue here: https://github.com/roc-lang/roc/issues/new/choose");
#[allow(clippy::panic)] {
panic!($($arg)*);
}
$crate::error_and_exit(format_args!(
"{}{}",
$crate::INTERNAL_ERROR_MESSAGE,
format_args!($($arg)*)
))
})
}

pub const USER_ERROR_MESSAGE: &str = concat!(
"We ran into an issue while compiling your code.\n",
"Sadly, we don't have a pretty error message for this case yet.\n",
"If you can't figure out the problem from the context below, please reach out at <https://roc.zulipchat.com>\n",
);

/// `user_error!` should only ever be used temporarily.
/// It is a way to document locations where we do not yet have nice error reporting.
/// All cases of `user_error!` should eventually be replaced with pretty error printing using roc_reporting.
#[macro_export]
macro_rules! user_error {
() => ({
$crate::error_and_exit(format_args!("{}", $crate::USER_ERROR_MESSAGE))
});
($($arg:tt)*) => ({
eprintln!("We ran into an issue while compiling your code.");
eprintln!("Sadly, we don't have a pretty error message for this case yet.");
eprintln!("If you can't figure out the problem from the context below, please reach out at: https://roc.zulipchat.com/");
eprintln!($($arg)*);
std::process::exit(1);
$crate::error_and_exit(format_args!(
"{}{}",
$crate::USER_ERROR_MESSAGE,
format_args!($($arg)*)
))
})
}

Expand Down Expand Up @@ -92,13 +204,13 @@ macro_rules! assert_copyable {
#[macro_export]
macro_rules! _incomplete_project {
($project_name:literal, $tracking_issue_no:literal) => {
panic!(
$crate::internal_error!(
"[{}] not yet implemented. Tracking issue: https://github.com/roc-lang/roc/issues/{}",
$project_name, $tracking_issue_no,
)
};
($project_name:literal, $tracking_issue_no:literal, $($arg:tt)+) => {
panic!(
$crate::internal_error!(
"[{}] not yet implemented. Tracking issue: https://github.com/roc-lang/roc/issues/{}.\nAdditional information: {}",
$project_name, $tracking_issue_no,
format_args!($($arg)+),
Expand Down

0 comments on commit f974ec5

Please sign in to comment.