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 bumpalo::Bump;
use roc_collections::all::MutSet;
use roc_module::ident::Ident;
use roc_module::ident::{Lowercase, TagName, Uppercase};
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{Problem, RuntimeError};
use roc_solve::solve;
use roc_types::pretty_print::content_to_string;
use roc_types::subs::{Content, Subs};
use roc_types::types::{write_error_type, ErrorType};
use std::path::PathBuf;
use roc_module::ident::Ident;
use std::fmt;
use std::path::PathBuf;
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"#;
@ -72,7 +72,7 @@ impl Report {
let header = format!(
"-- {} {}",
self.title,
"-".repeat(80 - self.title.len() - 4)
"-".repeat(80 - (self.title.len() + 4))
);
alloc.stack(vec![alloc.text(header), self.text.pretty(alloc)])
@ -202,7 +202,10 @@ fn pretty_runtime_error<'b>(
RuntimeError::Shadowing {
original_region,
shadow,
} => alloc.stack(vec![
} => {
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."#;
alloc.stack(vec![
alloc
.text("The ")
.append(alloc.ident(shadow.value))
@ -210,13 +213,54 @@ fn pretty_runtime_error<'b>(
alloc.region(original_region),
alloc.reflow("But then it's defined a second time here:"),
alloc.region(shadow.region),
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.reflow(line),
])
}
RuntimeError::LookupNotInScope(loc_name, 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),
RuntimeError(RuntimeError),
TypeError(solve::TypeError),
ErrorTypeInline(ErrorType),
ErrorTypeBlock(Box<ReportText>),
@ -413,7 +458,7 @@ pub struct RocDocAllocator<'a> {
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>
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
A: 'a + Clone,
I: IntoIterator,
@ -467,7 +512,7 @@ impl<'a> RocDocAllocator<'a> {
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
A: 'a + Clone,
I: IntoIterator,
@ -544,6 +589,13 @@ impl<'a> RocDocAllocator<'a> {
.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> {
let max_line_number_length = (region.end_line + 1).to_string().len();
let indent = 2;
@ -1046,6 +1098,7 @@ impl ReportText {
.annotate(Annotation::Symbol),
TypeProblem(problem) => Self::type_problem_to_pretty(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 std::path::PathBuf;
use crate::report::{RocDocAllocator, RocDocBuilder};
use ven_pretty::DocAllocator;
pub fn type_problem(filename: PathBuf, problem: solve::TypeError) -> Report {
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 {
// NOTE: one-based
let remainder10 = number % 10;
@ -817,24 +845,10 @@ fn to_circular_report(
symbol: Symbol,
overall_type: ErrorType,
) -> 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 {
title: "TYPE MISMATCH".to_string(),
title: "CIRCULAR TYPE".to_string(),
filename,
text: Concat(lines),
text: ReportText::TypeError(solve::TypeError::CircularType(region, symbol, overall_type)),
}
}
@ -954,6 +968,129 @@ pub struct Diff<T> {
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 {
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 {
use super::ReportText;

View file

@ -1146,18 +1146,18 @@ mod test_reporting {
),
indoc!(
r#"
-- TYPE MISMATCH ---------------------------------------------------------------
-- CIRCULAR TYPE ---------------------------------------------------------------
I'm inferring a weird self-referential type for `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!(
r#"
-- TYPE MISMATCH ---------------------------------------------------------------
-- CIRCULAR TYPE ---------------------------------------------------------------
I'm inferring a weird self-referential type for `f`:
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."#
),
)
}
}