mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
circular types with pretty
This commit is contained in:
parent
fcb62cdf8e
commit
1672b89e19
3 changed files with 424 additions and 44 deletions
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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."#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue