circular types with pretty

This commit is contained in:
Folkert 2020-04-10 14:48:34 +02:00
parent fcb62cdf8e
commit 1672b89e19
3 changed files with 424 additions and 44 deletions

View file

@ -1,17 +1,17 @@
use crate::report::ReportText::{BinOp, Concat, Module, Region, Value}; use crate::report::ReportText::{BinOp, Concat, Module, Region, Value};
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
use roc_module::ident::Ident;
use roc_module::ident::{Lowercase, TagName, Uppercase}; use roc_module::ident::{Lowercase, TagName, Uppercase};
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_solve::solve;
use roc_types::pretty_print::content_to_string; use roc_types::pretty_print::content_to_string;
use roc_types::subs::{Content, Subs}; use roc_types::subs::{Content, Subs};
use roc_types::types::{write_error_type, ErrorType}; use roc_types::types::{write_error_type, ErrorType};
use std::path::PathBuf;
use roc_module::ident::Ident;
use std::fmt; use std::fmt;
use std::path::PathBuf;
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated}; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated};
const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#; const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#;
@ -72,7 +72,7 @@ impl Report {
let header = format!( let header = format!(
"-- {} {}", "-- {} {}",
self.title, self.title,
"-".repeat(80 - self.title.len() - 4) "-".repeat(80 - (self.title.len() + 4))
); );
alloc.stack(vec![alloc.text(header), self.text.pretty(alloc)]) alloc.stack(vec![alloc.text(header), self.text.pretty(alloc)])
@ -202,21 +202,65 @@ fn pretty_runtime_error<'b>(
RuntimeError::Shadowing { RuntimeError::Shadowing {
original_region, original_region,
shadow, shadow,
} => alloc.stack(vec![ } => {
alloc let line = r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
.text("The ")
.append(alloc.ident(shadow.value)) alloc.stack(vec![
.append(alloc.reflow(" name is first defined here:")), alloc
alloc.region(original_region), .text("The ")
alloc.reflow("But then it's defined a second time here:"), .append(alloc.ident(shadow.value))
alloc.region(shadow.region), .append(alloc.reflow(" name is first defined here:")),
alloc.reflow(r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#), alloc.region(original_region),
]), alloc.reflow("But then it's defined a second time here:"),
alloc.region(shadow.region),
alloc.reflow(line),
])
}
RuntimeError::LookupNotInScope(loc_name, options) => { RuntimeError::LookupNotInScope(loc_name, options) => {
not_found(alloc, loc_name.region, &loc_name.value, "value", options) not_found(alloc, loc_name.region, &loc_name.value, "value", options)
} }
other => todo!("TODO implement run time error reporting for {:?}", other), RuntimeError::CircularDef(idents, _regions) => match idents.first() {
Some(ident) => alloc.stack(vec![alloc
.reflow("The ")
.append(alloc.ident(ident.value.clone()))
.append(alloc.reflow(
" value is defined directly in terms of itself, causing an infinite loop.",
))]),
None => alloc.nil(),
},
other => {
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
// UnsupportedPattern(Region),
// UnrecognizedFunctionName(Located<InlinableString>),
// ValueNotExposed {
// module_name: InlinableString,
// ident: InlinableString,
// region: Region,
// },
// ModuleNotImported {
// module_name: InlinableString,
// ident: InlinableString,
// region: Region,
// },
// InvalidPrecedence(PrecedenceProblem, Region),
// MalformedIdentifier(Box<str>, Region),
// MalformedClosure(Region),
// FloatOutsideRange(Box<str>),
// IntOutsideRange(Box<str>),
// InvalidHex(std::num::ParseIntError, Box<str>),
// InvalidOctal(std::num::ParseIntError, Box<str>),
// InvalidBinary(std::num::ParseIntError, Box<str>),
// QualifiedPatternIdent(InlinableString),
// CircularDef(
// Vec<Located<Ident>>,
// Vec<(Region /* pattern */, Region /* expr */)>,
// ),
//
// /// When the author specifies a type annotation but no implementation
// NoImplementation,
todo!("TODO implement run time error reporting for {:?}", other)
}
} }
} }
@ -279,6 +323,7 @@ pub enum ReportText {
TypeProblem(crate::type_error::Problem), TypeProblem(crate::type_error::Problem),
RuntimeError(RuntimeError), RuntimeError(RuntimeError),
TypeError(solve::TypeError),
ErrorTypeInline(ErrorType), ErrorTypeInline(ErrorType),
ErrorTypeBlock(Box<ReportText>), ErrorTypeBlock(Box<ReportText>),
@ -413,7 +458,7 @@ pub struct RocDocAllocator<'a> {
interns: &'a Interns, interns: &'a Interns,
} }
type RocDocBuilder<'b> = DocBuilder<'b, RocDocAllocator<'b>, Annotation>; pub type RocDocBuilder<'b> = DocBuilder<'b, RocDocAllocator<'b>, Annotation>;
impl<'a, A> DocAllocator<'a, A> for RocDocAllocator<'a> impl<'a, A> DocAllocator<'a, A> for RocDocAllocator<'a>
where where
@ -458,7 +503,7 @@ impl<'a> RocDocAllocator<'a> {
} }
} }
fn vcat<A, I>(&'a self, docs: I) -> DocBuilder<'a, Self, A> pub fn vcat<A, I>(&'a self, docs: I) -> DocBuilder<'a, Self, A>
where where
A: 'a + Clone, A: 'a + Clone,
I: IntoIterator, I: IntoIterator,
@ -467,7 +512,7 @@ impl<'a> RocDocAllocator<'a> {
self.intersperse(docs, self.line()) self.intersperse(docs, self.line())
} }
fn stack<A, I>(&'a self, docs: I) -> DocBuilder<'a, Self, A> pub fn stack<A, I>(&'a self, docs: I) -> DocBuilder<'a, Self, A>
where where
A: 'a + Clone, A: 'a + Clone,
I: IntoIterator, I: IntoIterator,
@ -544,6 +589,13 @@ impl<'a> RocDocAllocator<'a> {
.annotate(Annotation::RecordField) .annotate(Annotation::RecordField)
} }
pub fn type_block(
&'a self,
content: DocBuilder<'a, Self, Annotation>,
) -> DocBuilder<'a, Self, Annotation> {
content.annotate(Annotation::TypeBlock).indent(4)
}
pub fn region(&'a self, region: roc_region::all::Region) -> DocBuilder<'a, Self, Annotation> { pub fn region(&'a self, region: roc_region::all::Region) -> DocBuilder<'a, Self, Annotation> {
let max_line_number_length = (region.end_line + 1).to_string().len(); let max_line_number_length = (region.end_line + 1).to_string().len();
let indent = 2; let indent = 2;
@ -1046,6 +1098,7 @@ impl ReportText {
.annotate(Annotation::Symbol), .annotate(Annotation::Symbol),
TypeProblem(problem) => Self::type_problem_to_pretty(alloc, problem), TypeProblem(problem) => Self::type_problem_to_pretty(alloc, problem),
RuntimeError(problem) => pretty_runtime_error(alloc, problem), RuntimeError(problem) => pretty_runtime_error(alloc, problem),
TypeError(problem) => crate::type_error::pretty_type_error(alloc, problem),
} }
} }

View file

@ -12,6 +12,9 @@ use roc_types::subs::{Content, Variable};
use roc_types::types::{Category, ErrorType, PatternCategory, Reason, TypeExt}; use roc_types::types::{Category, ErrorType, PatternCategory, Reason, TypeExt};
use std::path::PathBuf; use std::path::PathBuf;
use crate::report::{RocDocAllocator, RocDocBuilder};
use ven_pretty::DocAllocator;
pub fn type_problem(filename: PathBuf, problem: solve::TypeError) -> Report { pub fn type_problem(filename: PathBuf, problem: solve::TypeError) -> Report {
use solve::TypeError::*; use solve::TypeError::*;
@ -28,6 +31,31 @@ pub fn type_problem(filename: PathBuf, problem: solve::TypeError) -> Report {
} }
} }
pub fn pretty_type_error<'b>(
alloc: &'b RocDocAllocator<'b>,
problem: solve::TypeError,
) -> RocDocBuilder<'b> {
use solve::TypeError::*;
match problem {
CircularType(region, symbol, overall_type) => {
let line = r#"Here is my best effort at writing down the type. You will see ∞ for parts of the type that repeat something already printed out infinitely."#;
alloc.stack(vec![
alloc
.reflow("I'm inferring a weird self-referential type for ")
.append(alloc.symbol_unqualified(symbol))
.append(alloc.text(":")),
alloc.region(region),
alloc.stack(vec![
alloc.reflow(line),
alloc.type_block(pretty_to_doc(alloc, Parens::Unnecessary, overall_type)),
]),
])
}
_ => todo!(),
}
}
fn int_to_ordinal(number: usize) -> String { fn int_to_ordinal(number: usize) -> String {
// NOTE: one-based // NOTE: one-based
let remainder10 = number % 10; let remainder10 = number % 10;
@ -817,24 +845,10 @@ fn to_circular_report(
symbol: Symbol, symbol: Symbol,
overall_type: ErrorType, overall_type: ErrorType,
) -> Report { ) -> Report {
use ReportText::*;
let lines = vec![
plain_text("I'm inferring a weird self-referential type for "),
Value(symbol),
plain_text(":"),
Region(region),
Stack(vec![
plain_text("Here is my best effort at writing down the type. You will see ∞ for parts of the type that repeat something already printed out infinitely."),
error_type_block(to_doc(Parens::Unnecessary, &overall_type)),
/* TODO hint */
]),
];
Report { Report {
title: "TYPE MISMATCH".to_string(), title: "CIRCULAR TYPE".to_string(),
filename, filename,
text: Concat(lines), text: ReportText::TypeError(solve::TypeError::CircularType(region, symbol, overall_type)),
} }
} }
@ -954,6 +968,129 @@ pub struct Diff<T> {
status: Status, status: Status,
} }
fn pretty_ext_to_doc<'b>(
alloc: &'b RocDocAllocator<'b>,
ext: TypeExt,
) -> Option<RocDocBuilder<'b>> {
use TypeExt::*;
match ext {
Closed => None,
FlexOpen(lowercase) | RigidOpen(lowercase) => {
Some(alloc.string(lowercase.as_str().to_string()))
}
}
}
pub fn pretty_to_doc<'b>(
alloc: &'b RocDocAllocator<'b>,
parens: Parens,
tipe: ErrorType,
) -> RocDocBuilder<'b> {
use ErrorType::*;
match tipe {
Function(args, ret) => to_pretty::function(
alloc,
parens,
args.into_iter()
.map(|arg| pretty_to_doc(alloc, Parens::InFn, arg))
.collect(),
pretty_to_doc(alloc, Parens::InFn, *ret),
),
Infinite => alloc.text(""),
Error => alloc.text("?"),
FlexVar(lowercase) => alloc.string(lowercase.as_str().to_string()),
RigidVar(lowercase) => alloc.string(lowercase.as_str().to_string()),
Type(symbol, args) => to_pretty::apply(
alloc,
parens,
alloc.symbol_foreign_qualified(symbol),
args.into_iter()
.map(|arg| pretty_to_doc(alloc, Parens::InTypeParam, arg))
.collect(),
),
Alias(symbol, args, _) => to_pretty::apply(
alloc,
parens,
alloc.symbol_foreign_qualified(symbol),
args.into_iter()
.map(|(_, arg)| pretty_to_doc(alloc, Parens::InTypeParam, arg))
.collect(),
),
Record(fields_map, ext) => {
let mut fields = fields_map.into_iter().collect::<Vec<_>>();
fields.sort_by(|(a, _), (b, _)| a.cmp(&b));
to_pretty::record(
alloc,
fields
.into_iter()
.map(|(k, v)| {
(
alloc.string(k.as_str().to_string()),
pretty_to_doc(alloc, Parens::Unnecessary, v),
)
})
.collect(),
pretty_ext_to_doc(alloc, ext),
)
}
TagUnion(tags_map, ext) => {
let mut tags = tags_map
.into_iter()
.map(|(name, args)| {
(
name,
args.into_iter()
.map(|arg| pretty_to_doc(alloc, Parens::InTypeParam, arg))
.collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>();
tags.sort_by(|(a, _), (b, _)| a.cmp(&b));
to_pretty::tag_union(
alloc,
tags.into_iter()
.map(|(k, v)| (alloc.tag_name(k.clone()), v))
.collect(),
pretty_ext_to_doc(alloc, ext),
)
}
RecursiveTagUnion(rec_var, tags_map, ext) => {
let mut tags = tags_map
.into_iter()
.map(|(name, args)| {
(
name,
args.into_iter()
.map(|arg| pretty_to_doc(alloc, Parens::InTypeParam, arg))
.collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>();
tags.sort_by(|(a, _), (b, _)| a.cmp(&b));
to_pretty::recursive_tag_union(
alloc,
pretty_to_doc(alloc, Parens::Unnecessary, *rec_var),
tags.into_iter()
.map(|(k, v)| (alloc.tag_name(k.clone()), v))
.collect(),
pretty_ext_to_doc(alloc, ext),
)
}
Boolean(b) => alloc.string(format!("{:?}", b)),
}
}
pub fn to_doc(parens: Parens, tipe: &ErrorType) -> ReportText { pub fn to_doc(parens: Parens, tipe: &ErrorType) -> ReportText {
use ErrorType::*; use ErrorType::*;
@ -1489,6 +1626,173 @@ fn ext_to_status(ext1: &TypeExt, ext2: &TypeExt) -> Status {
} }
} }
mod to_pretty {
use crate::report::{RocDocAllocator, RocDocBuilder};
use roc_types::pretty_print::Parens;
use ven_pretty::DocAllocator;
fn with_parens<'b>(
alloc: &'b RocDocAllocator<'b>,
text: RocDocBuilder<'b>,
) -> RocDocBuilder<'b> {
alloc.text("(").append(text).append(alloc.text(")"))
}
pub fn function<'b>(
alloc: &'b RocDocAllocator<'b>,
parens: Parens,
args: Vec<RocDocBuilder<'b>>,
ret: RocDocBuilder<'b>,
) -> RocDocBuilder<'b> {
let function_text = alloc.concat(vec![
alloc.intersperse(args, alloc.reflow(", ")),
alloc.reflow(" -> "),
ret,
]);
match parens {
Parens::Unnecessary => function_text,
_ => with_parens(alloc, function_text),
}
}
pub fn apply<'b>(
alloc: &'b RocDocAllocator<'b>,
parens: Parens,
name: RocDocBuilder<'b>,
args: Vec<RocDocBuilder<'b>>,
) -> RocDocBuilder<'b> {
if args.is_empty() {
name
} else {
let apply_text = alloc.concat(vec![
name,
alloc.space(),
alloc.intersperse(args, alloc.space()),
]);
match parens {
Parens::Unnecessary | Parens::InFn => apply_text,
Parens::InTypeParam => with_parens(alloc, apply_text),
}
}
}
pub fn record<'b>(
alloc: &'b RocDocAllocator<'b>,
entries: Vec<(RocDocBuilder<'b>, RocDocBuilder<'b>)>,
opt_ext: Option<RocDocBuilder<'b>>,
) -> RocDocBuilder<'b> {
let ext_text = if let Some(t) = opt_ext {
t
} else {
alloc.nil()
};
if entries.is_empty() {
ext_text
} else {
let entry_to_text =
|(field_name, field_type): (RocDocBuilder<'b>, RocDocBuilder<'b>)| {
field_name.append(alloc.text(" : ")).append(field_type)
};
let starts =
std::iter::once(alloc.reflow("{ ")).chain(std::iter::repeat(alloc.reflow(", ")));
let entries_doc = alloc.concat(
entries
.into_iter()
.zip(starts)
.map(|(entry, start)| start.append(entry_to_text(entry))),
);
entries_doc.append(alloc.reflow(" }")).append(ext_text)
}
}
pub fn tag_union<'b>(
alloc: &'b RocDocAllocator<'b>,
entries: Vec<(RocDocBuilder<'b>, Vec<RocDocBuilder<'b>>)>,
opt_ext: Option<RocDocBuilder<'b>>,
) -> RocDocBuilder<'b> {
let ext_text = if let Some(t) = opt_ext {
t
} else {
alloc.nil()
};
if entries.is_empty() {
alloc.text("[]")
} else {
let entry_to_text = |(tag_name, arguments): (RocDocBuilder<'b>, Vec<_>)| {
if arguments.is_empty() {
tag_name
} else {
tag_name
.append(alloc.space())
.append(alloc.intersperse(arguments, alloc.space()))
}
};
let starts =
std::iter::once(alloc.reflow("[ ")).chain(std::iter::repeat(alloc.reflow(", ")));
let entries_doc = alloc.concat(
entries
.into_iter()
.zip(starts)
.map(|(entry, start)| start.append(entry_to_text(entry))),
);
entries_doc.append(alloc.reflow(" ]")).append(ext_text)
}
}
pub fn recursive_tag_union<'b>(
alloc: &'b RocDocAllocator<'b>,
rec_var: RocDocBuilder<'b>,
entries: Vec<(RocDocBuilder<'b>, Vec<RocDocBuilder<'b>>)>,
opt_ext: Option<RocDocBuilder<'b>>,
) -> RocDocBuilder<'b> {
let ext_text = if let Some(t) = opt_ext {
t
} else {
alloc.nil()
};
if entries.is_empty() {
alloc.text("[]")
} else {
let entry_to_text = |(tag_name, arguments): (RocDocBuilder<'b>, Vec<_>)| {
if arguments.is_empty() {
tag_name
} else {
tag_name
.append(alloc.space())
.append(alloc.intersperse(arguments, alloc.space()))
}
};
let starts =
std::iter::once(alloc.reflow("[ ")).chain(std::iter::repeat(alloc.reflow(", ")));
let entries_doc = alloc.concat(
entries
.into_iter()
.zip(starts)
.map(|(entry, start)| start.append(entry_to_text(entry))),
);
entries_doc
.append(alloc.reflow(" ]"))
.append(ext_text)
.append(alloc.text(" as "))
.append(rec_var)
}
}
}
mod report_text { mod report_text {
use super::ReportText; use super::ReportText;

View file

@ -1146,18 +1146,18 @@ mod test_reporting {
), ),
indoc!( indoc!(
r#" r#"
-- TYPE MISMATCH --------------------------------------------------------------- -- CIRCULAR TYPE ---------------------------------------------------------------
I'm inferring a weird self-referential type for `g`: I'm inferring a weird self-referential type for `g`:
1 f = \g -> g g 1 f = \g -> g g
^ ^
Here is my best effort at writing down the type. You will see for parts of the type that repeat something already printed out infinitely. Here is my best effort at writing down the type. You will see for
parts of the type that repeat something already printed out
infinitely.
-> a -> a"#
"#
), ),
) )
} }
@ -1174,18 +1174,18 @@ mod test_reporting {
), ),
indoc!( indoc!(
r#" r#"
-- TYPE MISMATCH --------------------------------------------------------------- -- CIRCULAR TYPE ---------------------------------------------------------------
I'm inferring a weird self-referential type for `f`: I'm inferring a weird self-referential type for `f`:
1 f = \x -> f [x] 1 f = \x -> f [x]
^ ^
Here is my best effort at writing down the type. You will see for parts of the type that repeat something already printed out infinitely. Here is my best effort at writing down the type. You will see for
parts of the type that repeat something already printed out
infinitely.
List -> a List -> a"#
"#
), ),
) )
} }
@ -1974,4 +1974,27 @@ mod test_reporting {
), ),
) )
} }
#[test]
fn circular_definition() {
// these error messages seem pretty helpful
report_problem_as(
indoc!(
r#"
f = g
g = f
f
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
The `f` value is defined directly in terms of itself, causing an
infinite loop."#
),
)
}
} }