mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 13:59:08 +00:00
Handle errors in generated type annotations
This commit is contained in:
parent
0ddf12e323
commit
46736ccaea
5 changed files with 182 additions and 57 deletions
|
@ -3,7 +3,7 @@ use std::io::Write;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::{collections::String as BumpString, Bump};
|
||||||
use roc_can::abilities::{IAbilitiesStore, Resolved};
|
use roc_can::abilities::{IAbilitiesStore, Resolved};
|
||||||
use roc_can::expr::{DeclarationTag, Declarations, Expr};
|
use roc_can::expr::{DeclarationTag, Declarations, Expr};
|
||||||
use roc_error_macros::{internal_error, user_error};
|
use roc_error_macros::{internal_error, user_error};
|
||||||
|
@ -19,9 +19,10 @@ use roc_parse::header::parse_module_defs;
|
||||||
use roc_parse::normalize::Normalize;
|
use roc_parse::normalize::Normalize;
|
||||||
use roc_parse::{header, parser::SyntaxError, state::State};
|
use roc_parse::{header, parser::SyntaxError, state::State};
|
||||||
use roc_problem::can::RuntimeError;
|
use roc_problem::can::RuntimeError;
|
||||||
|
use roc_region::all::{LineColumn, LineInfo};
|
||||||
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
|
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
|
||||||
use roc_target::Target;
|
use roc_target::Target;
|
||||||
use roc_types::subs::{Content, Subs, Variable};
|
use roc_types::subs::{Subs, Variable};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum FormatMode {
|
pub enum FormatMode {
|
||||||
|
@ -273,7 +274,19 @@ fn fmt_all<'a>(buf: &mut Buf<'a>, ast: &'a FullAst) {
|
||||||
buf.fmt_end_of_file();
|
buf.fmt_end_of_file();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn annotate_file(arena: &Bump, file: PathBuf) -> Result<(), LoadingProblem> {
|
#[derive(Debug)]
|
||||||
|
pub enum AnnotationProblem<'a> {
|
||||||
|
Loading(LoadingProblem<'a>),
|
||||||
|
Type(TypeProblem),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TypeProblem {
|
||||||
|
pub name: String,
|
||||||
|
pub position: LineColumn,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn annotate_file(arena: &Bump, file: PathBuf) -> Result<(), AnnotationProblem> {
|
||||||
let load_config = LoadConfig {
|
let load_config = LoadConfig {
|
||||||
target: Target::default(),
|
target: Target::default(),
|
||||||
function_kind: FunctionKind::from_env(),
|
function_kind: FunctionKind::from_env(),
|
||||||
|
@ -289,9 +302,10 @@ pub fn annotate_file(arena: &Bump, file: PathBuf) -> Result<(), LoadingProblem>
|
||||||
None,
|
None,
|
||||||
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
||||||
load_config,
|
load_config,
|
||||||
)?;
|
)
|
||||||
|
.map_err(AnnotationProblem::Loading)?;
|
||||||
|
|
||||||
let buf = annotate_module(&mut loaded);
|
let buf = annotate_module(arena, &mut loaded)?;
|
||||||
|
|
||||||
std::fs::write(&file, buf.as_str())
|
std::fs::write(&file, buf.as_str())
|
||||||
.unwrap_or_else(|e| internal_error!("failed to write annotated file to {file:?}: {e}"));
|
.unwrap_or_else(|e| internal_error!("failed to write annotated file to {file:?}: {e}"));
|
||||||
|
@ -299,7 +313,10 @@ pub fn annotate_file(arena: &Bump, file: PathBuf) -> Result<(), LoadingProblem>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn annotate_module(loaded: &mut LoadedModule) -> String {
|
fn annotate_module<'a>(
|
||||||
|
arena: &'a Bump,
|
||||||
|
loaded: &mut LoadedModule,
|
||||||
|
) -> Result<BumpString<'a>, AnnotationProblem<'a>> {
|
||||||
let (decls, subs, abilities) =
|
let (decls, subs, abilities) =
|
||||||
if let Some(decls) = loaded.declarations_by_id.get(&loaded.module_id) {
|
if let Some(decls) = loaded.declarations_by_id.get(&loaded.module_id) {
|
||||||
let subs = loaded.solved.inner_mut();
|
let subs = loaded.solved.inner_mut();
|
||||||
|
@ -329,17 +346,14 @@ fn annotate_module(loaded: &mut LoadedModule) -> String {
|
||||||
src,
|
src,
|
||||||
loaded.module_id,
|
loaded.module_id,
|
||||||
&loaded.interns,
|
&loaded.interns,
|
||||||
);
|
)
|
||||||
|
.map_err(AnnotationProblem::Type)?;
|
||||||
edits.sort_by_key(|(offset, _)| *offset);
|
edits.sort_by_key(|(offset, _)| *offset);
|
||||||
|
|
||||||
let mut buffer = String::new();
|
let mut buffer = BumpString::new_in(arena);
|
||||||
let mut file_progress = 0;
|
let mut file_progress = 0;
|
||||||
|
|
||||||
for (position, edit) in edits {
|
for (position, edit) in edits {
|
||||||
if file_progress > position {
|
|
||||||
internal_error!("Module definitions are out of order");
|
|
||||||
};
|
|
||||||
|
|
||||||
buffer.push_str(&src[file_progress..position]);
|
buffer.push_str(&src[file_progress..position]);
|
||||||
buffer.push_str(&edit);
|
buffer.push_str(&edit);
|
||||||
|
|
||||||
|
@ -347,7 +361,7 @@ fn annotate_module(loaded: &mut LoadedModule) -> String {
|
||||||
}
|
}
|
||||||
buffer.push_str(&src[file_progress..]);
|
buffer.push_str(&src[file_progress..]);
|
||||||
|
|
||||||
buffer
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn annotation_edits(
|
pub fn annotation_edits(
|
||||||
|
@ -357,10 +371,10 @@ pub fn annotation_edits(
|
||||||
src: &str,
|
src: &str,
|
||||||
module_id: ModuleId,
|
module_id: ModuleId,
|
||||||
interns: &Interns,
|
interns: &Interns,
|
||||||
) -> Vec<(usize, String)> {
|
) -> Result<Vec<(usize, String)>, TypeProblem> {
|
||||||
decls
|
let mut edits = Vec::with_capacity(decls.len());
|
||||||
.iter_bottom_up()
|
|
||||||
.flat_map(|(index, tag)| {
|
for (index, tag) in decls.iter_bottom_up() {
|
||||||
let var = decls.variables[index];
|
let var = decls.variables[index];
|
||||||
let symbol = decls.symbols[index];
|
let symbol = decls.symbols[index];
|
||||||
let expr = &decls.expressions[index].value;
|
let expr = &decls.expressions[index].value;
|
||||||
|
@ -368,28 +382,24 @@ pub fn annotation_edits(
|
||||||
if decls.annotations[index].is_some()
|
if decls.annotations[index].is_some()
|
||||||
| matches!(
|
| matches!(
|
||||||
*expr,
|
*expr,
|
||||||
Expr::RuntimeError(RuntimeError::ExposedButNotDefined(..))
|
Expr::RuntimeError(RuntimeError::ExposedButNotDefined(..)) | Expr::ImportParams(..)
|
||||||
| Expr::ImportParams(..)
|
|
||||||
)
|
)
|
||||||
| abilities.is_specialization_name(symbol.value)
|
| abilities.is_specialization_name(symbol.value)
|
||||||
| matches!(subs.get_content_without_compacting(var), Content::Error)
|
|
||||||
| matches!(tag, DeclarationTag::MutualRecursion { .. })
|
| matches!(tag, DeclarationTag::MutualRecursion { .. })
|
||||||
{
|
{
|
||||||
return None;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let byte_range = match tag {
|
let byte_range = match tag {
|
||||||
DeclarationTag::Destructure(i) => {
|
DeclarationTag::Destructure(i) => decls.destructs[i.index()].loc_pattern.byte_range(),
|
||||||
decls.destructs[i.index()].loc_pattern.byte_range()
|
|
||||||
}
|
|
||||||
_ => symbol.byte_range(),
|
_ => symbol.byte_range(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let edit = annotation_edit(src, subs, interns, module_id, var, byte_range);
|
let edit = annotation_edit(src, subs, interns, module_id, var, byte_range)?;
|
||||||
|
|
||||||
Some(edit)
|
edits.push(edit);
|
||||||
})
|
}
|
||||||
.collect()
|
Ok(edits)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn annotation_edit(
|
pub fn annotation_edit(
|
||||||
|
@ -399,16 +409,24 @@ pub fn annotation_edit(
|
||||||
module_id: ModuleId,
|
module_id: ModuleId,
|
||||||
var: Variable,
|
var: Variable,
|
||||||
symbol_range: Range<usize>,
|
symbol_range: Range<usize>,
|
||||||
) -> (usize, String) {
|
) -> Result<(usize, String), TypeProblem> {
|
||||||
|
let symbol_str = &src[symbol_range.clone()];
|
||||||
|
if subs.var_contains_error(var) {
|
||||||
|
let line_info = LineInfo::new(src);
|
||||||
|
let position = line_info.convert_offset(symbol_range.start as u32);
|
||||||
|
return Err(TypeProblem {
|
||||||
|
name: symbol_str.to_owned(),
|
||||||
|
position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let signature = roc_types::pretty_print::name_and_print_var(
|
let signature = roc_types::pretty_print::name_and_print_var(
|
||||||
var,
|
var,
|
||||||
&mut subs.clone(),
|
&mut subs.clone(),
|
||||||
module_id,
|
module_id,
|
||||||
interns,
|
interns,
|
||||||
roc_types::pretty_print::DebugPrint::NOTHING,
|
roc_types::pretty_print::DebugPrint::NOTHING,
|
||||||
)
|
);
|
||||||
// Generated names for errors start with `#`
|
|
||||||
.replace('#', "");
|
|
||||||
|
|
||||||
let line_start = src[..symbol_range.start]
|
let line_start = src[..symbol_range.start]
|
||||||
.rfind('\n')
|
.rfind('\n')
|
||||||
|
@ -417,10 +435,9 @@ pub fn annotation_edit(
|
||||||
.split_once(|c: char| !c.is_ascii_whitespace())
|
.split_once(|c: char| !c.is_ascii_whitespace())
|
||||||
.map_or("", |pair| pair.0);
|
.map_or("", |pair| pair.0);
|
||||||
|
|
||||||
let symbol_str = &src[symbol_range.clone()];
|
|
||||||
let edit = format!("{indent}{symbol_str} : {signature}\n");
|
let edit = format!("{indent}{symbol_str} : {signature}\n");
|
||||||
|
|
||||||
(line_start, edit)
|
Ok((line_start, edit))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -43,7 +43,8 @@ use tempfile::TempDir;
|
||||||
|
|
||||||
mod format;
|
mod format;
|
||||||
pub use format::{
|
pub use format::{
|
||||||
annotate_file, annotation_edit, annotation_edits, format_files, format_src, FormatMode,
|
annotate_file, annotation_edit, annotation_edits, format_files, format_src, AnnotationProblem,
|
||||||
|
FormatMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CMD_BUILD: &str = "build";
|
pub const CMD_BUILD: &str = "build";
|
||||||
|
|
|
@ -4,12 +4,12 @@ use roc_build::link::LinkType;
|
||||||
use roc_build::program::{check_file, CodeGenBackend};
|
use roc_build::program::{check_file, CodeGenBackend};
|
||||||
use roc_cli::{
|
use roc_cli::{
|
||||||
annotate_file, build_app, default_linking_strategy, format_files, format_src, test,
|
annotate_file, build_app, default_linking_strategy, format_files, format_src, test,
|
||||||
BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DEV, CMD_DOCS, CMD_FORMAT,
|
AnnotationProblem, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DEV, CMD_DOCS,
|
||||||
CMD_FORMAT_ANNOTATE, CMD_GLUE, CMD_PREPROCESS_HOST, CMD_REPL, CMD_RUN, CMD_TEST, CMD_VERSION,
|
CMD_FORMAT, CMD_FORMAT_ANNOTATE, CMD_GLUE, CMD_PREPROCESS_HOST, CMD_REPL, CMD_RUN, CMD_TEST,
|
||||||
DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_DEV, FLAG_DOCS_ROOT, FLAG_LIB, FLAG_MAIN, FLAG_MIGRATE,
|
CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_DEV, FLAG_DOCS_ROOT, FLAG_LIB, FLAG_MAIN,
|
||||||
FLAG_NO_COLOR, FLAG_NO_HEADER, FLAG_NO_LINK, FLAG_OUTPUT, FLAG_PP_DYLIB, FLAG_PP_HOST,
|
FLAG_MIGRATE, FLAG_NO_COLOR, FLAG_NO_HEADER, FLAG_NO_LINK, FLAG_OUTPUT, FLAG_PP_DYLIB,
|
||||||
FLAG_PP_PLATFORM, FLAG_STDIN, FLAG_STDOUT, FLAG_TARGET, FLAG_TIME, FLAG_VERBOSE, GLUE_DIR,
|
FLAG_PP_HOST, FLAG_PP_PLATFORM, FLAG_STDIN, FLAG_STDOUT, FLAG_TARGET, FLAG_TIME, FLAG_VERBOSE,
|
||||||
GLUE_SPEC, ROC_FILE, VERSION,
|
GLUE_DIR, GLUE_SPEC, ROC_FILE, VERSION,
|
||||||
};
|
};
|
||||||
use roc_docs::generate_docs_html;
|
use roc_docs::generate_docs_html;
|
||||||
use roc_error_macros::{internal_error, user_error};
|
use roc_error_macros::{internal_error, user_error};
|
||||||
|
@ -359,10 +359,23 @@ fn main() -> io::Result<()> {
|
||||||
|
|
||||||
let annotate_exit_code = match annotate_file(&arena, roc_file_path.to_owned()) {
|
let annotate_exit_code = match annotate_file(&arena, roc_file_path.to_owned()) {
|
||||||
Ok(()) => 0,
|
Ok(()) => 0,
|
||||||
Err(LoadingProblem::FormattedReport(report, ..)) => {
|
Err(AnnotationProblem::Loading(LoadingProblem::FormattedReport(report, ..))) => {
|
||||||
eprintln!("{report}");
|
eprintln!("{report}");
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
Err(AnnotationProblem::Type(type_problem)) => {
|
||||||
|
eprintln!(
|
||||||
|
"The type generated for `{}` on line {} contains an error",
|
||||||
|
type_problem.name, type_problem.position.line,
|
||||||
|
);
|
||||||
|
eprintln!(
|
||||||
|
"run `roc check \"{}\"` for a more detailed error",
|
||||||
|
roc_file_path.to_str().unwrap_or_else(|| internal_error!(
|
||||||
|
"File path is not a valid utf8 string"
|
||||||
|
))
|
||||||
|
);
|
||||||
|
1
|
||||||
|
}
|
||||||
Err(other) => {
|
Err(other) => {
|
||||||
internal_error!("build_file failed with error:\n{other:?}");
|
internal_error!("build_file failed with error:\n{other:?}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -2184,6 +2184,98 @@ impl Subs {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn var_contains_error(&self, var: Variable) -> bool {
|
||||||
|
match &self.get_content_without_compacting(var).clone() {
|
||||||
|
Content::Error => true,
|
||||||
|
Content::FlexVar(Some(index)) => {
|
||||||
|
// Generated names for errors start with `#`
|
||||||
|
self[*index].as_str().starts_with('#')
|
||||||
|
}
|
||||||
|
Content::FlexVar(..)
|
||||||
|
| Content::RigidVar(..)
|
||||||
|
| Content::FlexAbleVar(..)
|
||||||
|
| Content::RigidAbleVar(..)
|
||||||
|
| Content::ErasedLambda
|
||||||
|
| Content::RangedNumber(..)
|
||||||
|
| Content::Pure
|
||||||
|
| Content::Effectful
|
||||||
|
| Content::Structure(FlatType::EmptyRecord)
|
||||||
|
| Content::Structure(FlatType::EmptyTagUnion)
|
||||||
|
| Content::Structure(FlatType::EffectfulFunc) => false,
|
||||||
|
Content::RecursionVar { structure, .. } => self.var_contains_error(*structure),
|
||||||
|
Content::LambdaSet(LambdaSet {
|
||||||
|
solved,
|
||||||
|
recursion_var,
|
||||||
|
unspecialized,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
if let Some(rec_var) = recursion_var.into_variable() {
|
||||||
|
if self.var_contains_error(rec_var) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unspecialized
|
||||||
|
.into_iter()
|
||||||
|
.any(|uls_index| self.var_contains_error(self[uls_index].0))
|
||||||
|
|| solved.variables().into_iter().any(|slice_index| {
|
||||||
|
self[slice_index]
|
||||||
|
.into_iter()
|
||||||
|
.any(|var_index| self.var_contains_error(self[var_index]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Content::Alias(_symbol, args, actual, _kind) => {
|
||||||
|
self.var_contains_error(*actual)
|
||||||
|
|| args
|
||||||
|
.into_iter()
|
||||||
|
.take(args.len())
|
||||||
|
.any(|index| self.var_contains_error(self[index]))
|
||||||
|
}
|
||||||
|
Content::Structure(FlatType::Apply(_, args)) => args
|
||||||
|
.into_iter()
|
||||||
|
.any(|index| self.var_contains_error(self[index])),
|
||||||
|
Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var, fx_var)) => {
|
||||||
|
self.var_contains_error(*closure_var)
|
||||||
|
|| self.var_contains_error(*ret_var)
|
||||||
|
|| self.var_contains_error(*fx_var)
|
||||||
|
|| arg_vars
|
||||||
|
.into_iter()
|
||||||
|
.any(|index| self.var_contains_error(self[index]))
|
||||||
|
}
|
||||||
|
Content::Structure(FlatType::Record(sorted_fields, ext_var)) => {
|
||||||
|
self.var_contains_error(*ext_var)
|
||||||
|
|| sorted_fields
|
||||||
|
.iter_variables()
|
||||||
|
.any(|index| self.var_contains_error(self[index]))
|
||||||
|
}
|
||||||
|
Content::Structure(FlatType::Tuple(elems, ext_var)) => {
|
||||||
|
self.var_contains_error(*ext_var)
|
||||||
|
|| elems
|
||||||
|
.iter_variables()
|
||||||
|
.any(|index| self.var_contains_error(self[index]))
|
||||||
|
}
|
||||||
|
Content::Structure(FlatType::TagUnion(tags, ext_var)) => {
|
||||||
|
self.var_contains_error(ext_var.var())
|
||||||
|
|| tags.variables().into_iter().any(|slice_index| {
|
||||||
|
self[slice_index]
|
||||||
|
.into_iter()
|
||||||
|
.any(|var_index| self.var_contains_error(self[var_index]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Content::Structure(FlatType::FunctionOrTagUnion(_, _, ext_var)) => {
|
||||||
|
self.var_contains_error(ext_var.var())
|
||||||
|
}
|
||||||
|
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, ext_var)) => {
|
||||||
|
self.var_contains_error(ext_var.var())
|
||||||
|
|| self.var_contains_error(*rec_var)
|
||||||
|
|| tags.variables().into_iter().any(|slice_index| {
|
||||||
|
self[slice_index]
|
||||||
|
.into_iter()
|
||||||
|
.any(|var_index| self.var_contains_error(self[var_index]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|
|
@ -357,6 +357,7 @@ impl AnalyzedDocument {
|
||||||
*module_id,
|
*module_id,
|
||||||
interns,
|
interns,
|
||||||
)
|
)
|
||||||
|
.ok()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(offset, new_text)| {
|
.map(|(offset, new_text)| {
|
||||||
let pos = roc_region::all::Position::new(offset as u32);
|
let pos = roc_region::all::Position::new(offset as u32);
|
||||||
|
@ -392,7 +393,8 @@ impl AnalyzedDocument {
|
||||||
*module_id,
|
*module_id,
|
||||||
decl.var,
|
decl.var,
|
||||||
decl.range,
|
decl.range,
|
||||||
);
|
)
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
let pos = RocPosition::new(offset as u32);
|
let pos = RocPosition::new(offset as u32);
|
||||||
let range = Region::new(pos, pos).to_range(self.line_info());
|
let range = Region::new(pos, pos).to_range(self.line_info());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue