mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 00:01:16 +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 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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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."#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue