mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 00:24:34 +00:00
convert type problems into pretty doc
This commit is contained in:
parent
a587e31897
commit
5e226ee5f4
5 changed files with 186 additions and 8 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -243,6 +243,12 @@ version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "distance"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d9d8664cf849d7d0f3114a3a387d2f5e4303176d746d5a951aaddc66dfe9240"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.5.3"
|
version = "1.5.3"
|
||||||
|
@ -1104,6 +1110,7 @@ name = "roc_reporting"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
|
"distance",
|
||||||
"im",
|
"im",
|
||||||
"im-rc",
|
"im-rc",
|
||||||
"indoc",
|
"indoc",
|
||||||
|
|
|
@ -18,6 +18,7 @@ ven_pretty = { path = "../../vendor/pretty" }
|
||||||
inlinable_string = "0.1.0"
|
inlinable_string = "0.1.0"
|
||||||
im = "14" # im and im-rc should always have the same version!
|
im = "14" # im and im-rc should always have the same version!
|
||||||
im-rc = "14" # im and im-rc should always have the same version!
|
im-rc = "14" # im and im-rc should always have the same version!
|
||||||
|
distance = "0.4.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
roc_constrain = { path = "../constrain" }
|
roc_constrain = { path = "../constrain" }
|
||||||
|
|
|
@ -13,6 +13,8 @@ use roc_region::all::Located;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
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"#;
|
||||||
|
|
||||||
/// A textual report.
|
/// A textual report.
|
||||||
pub struct Report {
|
pub struct Report {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
@ -32,6 +34,8 @@ pub struct Palette<'a> {
|
||||||
pub gutter_bar: &'a str,
|
pub gutter_bar: &'a str,
|
||||||
pub module_name: &'a str,
|
pub module_name: &'a str,
|
||||||
pub binop: &'a str,
|
pub binop: &'a str,
|
||||||
|
pub typo: &'a str,
|
||||||
|
pub typo_suggestion: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DEFAULT_PALETTE: Palette = Palette {
|
pub const DEFAULT_PALETTE: Palette = Palette {
|
||||||
|
@ -46,6 +50,8 @@ pub const DEFAULT_PALETTE: Palette = Palette {
|
||||||
gutter_bar: MAGENTA_CODE,
|
gutter_bar: MAGENTA_CODE,
|
||||||
module_name: GREEN_CODE,
|
module_name: GREEN_CODE,
|
||||||
binop: GREEN_CODE,
|
binop: GREEN_CODE,
|
||||||
|
typo: YELLOW_CODE,
|
||||||
|
typo_suggestion: GREEN_CODE,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn can_problem(filename: PathBuf, problem: Problem) -> Report {
|
pub fn can_problem(filename: PathBuf, problem: Problem) -> Report {
|
||||||
|
@ -164,6 +170,8 @@ pub enum ReportText {
|
||||||
/// do something fancier later.
|
/// do something fancier later.
|
||||||
Type(Content),
|
Type(Content),
|
||||||
|
|
||||||
|
TypeProblem(crate::type_error::Problem),
|
||||||
|
|
||||||
ErrorTypeInline(ErrorType),
|
ErrorTypeInline(ErrorType),
|
||||||
ErrorTypeBlock(Box<ReportText>),
|
ErrorTypeBlock(Box<ReportText>),
|
||||||
|
|
||||||
|
@ -307,6 +315,9 @@ pub enum Annotation {
|
||||||
CodeBlock,
|
CodeBlock,
|
||||||
TypeBlock,
|
TypeBlock,
|
||||||
Module,
|
Module,
|
||||||
|
Typo,
|
||||||
|
TypoSuggestion,
|
||||||
|
Hint,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render with minimal formatting
|
/// Render with minimal formatting
|
||||||
|
@ -374,7 +385,9 @@ where
|
||||||
Url => {
|
Url => {
|
||||||
self.write_str("<")?;
|
self.write_str("<")?;
|
||||||
}
|
}
|
||||||
GlobalTag | PrivateTag | Keyword | RecordField | Symbol if !self.in_type_block => {
|
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
|
||||||
|
if !self.in_type_block =>
|
||||||
|
{
|
||||||
self.write_str("`")?;
|
self.write_str("`")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,7 +412,9 @@ where
|
||||||
Url => {
|
Url => {
|
||||||
self.write_str(">")?;
|
self.write_str(">")?;
|
||||||
}
|
}
|
||||||
GlobalTag | PrivateTag | Keyword | RecordField | Symbol if !self.in_type_block => {
|
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
|
||||||
|
if !self.in_type_block =>
|
||||||
|
{
|
||||||
self.write_str("`")?;
|
self.write_str("`")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,7 +450,7 @@ where
|
||||||
Emphasized => {
|
Emphasized => {
|
||||||
self.write_str(BOLD_CODE)?;
|
self.write_str(BOLD_CODE)?;
|
||||||
}
|
}
|
||||||
Url => {
|
Url | Hint => {
|
||||||
self.write_str(UNDERLINE_CODE)?;
|
self.write_str(UNDERLINE_CODE)?;
|
||||||
}
|
}
|
||||||
PlainText => {
|
PlainText => {
|
||||||
|
@ -471,6 +486,12 @@ where
|
||||||
Module => {
|
Module => {
|
||||||
self.write_str(self.palette.module_name)?;
|
self.write_str(self.palette.module_name)?;
|
||||||
}
|
}
|
||||||
|
Typo => {
|
||||||
|
self.write_str(self.palette.typo)?;
|
||||||
|
}
|
||||||
|
TypoSuggestion => {
|
||||||
|
self.write_str(self.palette.typo_suggestion)?;
|
||||||
|
}
|
||||||
TypeBlock | GlobalTag | PrivateTag | RecordField | Keyword => { /* nothing yet */ }
|
TypeBlock | GlobalTag | PrivateTag | RecordField | Keyword => { /* nothing yet */ }
|
||||||
}
|
}
|
||||||
self.style_stack.push(*annotation);
|
self.style_stack.push(*annotation);
|
||||||
|
@ -484,7 +505,8 @@ where
|
||||||
None => {}
|
None => {}
|
||||||
Some(annotation) => match annotation {
|
Some(annotation) => match annotation {
|
||||||
Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | Error | GutterBar
|
Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | Error | GutterBar
|
||||||
| Structure | CodeBlock | PlainText | LineNumber | Module => {
|
| Typo | TypoSuggestion | Structure | CodeBlock | PlainText | LineNumber | Hint
|
||||||
|
| Module => {
|
||||||
self.write_str(RESET_CODE)?;
|
self.write_str(RESET_CODE)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -758,6 +780,98 @@ impl ReportText {
|
||||||
Name(ident) => alloc
|
Name(ident) => alloc
|
||||||
.text(format!("{}", ident.as_inline_str()))
|
.text(format!("{}", ident.as_inline_str()))
|
||||||
.annotate(Annotation::Symbol),
|
.annotate(Annotation::Symbol),
|
||||||
|
TypeProblem(problem) => Self::type_problem_to_pretty(alloc, home, interns, problem),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn type_problem_to_pretty<'b, D>(
|
||||||
|
alloc: &'b D,
|
||||||
|
home: ModuleId,
|
||||||
|
interns: &Interns,
|
||||||
|
problem: crate::type_error::Problem,
|
||||||
|
) -> DocBuilder<'b, D, Annotation>
|
||||||
|
where
|
||||||
|
D: DocAllocator<'b, Annotation>,
|
||||||
|
D::Doc: Clone,
|
||||||
|
{
|
||||||
|
use crate::type_error::suggest;
|
||||||
|
use crate::type_error::Problem::*;
|
||||||
|
|
||||||
|
match problem {
|
||||||
|
FieldTypo(typo, possibilities) => {
|
||||||
|
let suggestions = suggest::sort(typo.as_str(), possibilities);
|
||||||
|
|
||||||
|
match suggestions.get(0) {
|
||||||
|
None => alloc.nil(),
|
||||||
|
Some(nearest) => {
|
||||||
|
let typo_str = format!("{}", typo);
|
||||||
|
let nearest_str = format!("{}", nearest);
|
||||||
|
|
||||||
|
let found = alloc.text(typo_str).annotate(Annotation::Typo);
|
||||||
|
let suggestion =
|
||||||
|
alloc.text(nearest_str).annotate(Annotation::TypoSuggestion);
|
||||||
|
|
||||||
|
let hint1 = Self::hint(alloc)
|
||||||
|
.append(alloc.reflow("Seems like a record field typo. Maybe "))
|
||||||
|
.append(found)
|
||||||
|
.append(alloc.reflow(" should be "))
|
||||||
|
.append(suggestion)
|
||||||
|
.append(alloc.text("?"));
|
||||||
|
|
||||||
|
let hint2 = Self::hint(alloc).append(alloc.reflow(ADD_ANNOTATIONS));
|
||||||
|
|
||||||
|
hint1
|
||||||
|
.append(alloc.line())
|
||||||
|
.append(alloc.line())
|
||||||
|
.append(hint2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TagTypo(typo, possibilities_tn) => {
|
||||||
|
let possibilities = possibilities_tn
|
||||||
|
.into_iter()
|
||||||
|
.map(|tag_name| tag_name.into_string(interns, home))
|
||||||
|
.collect();
|
||||||
|
let typo_str = format!("{}", typo.into_string(interns, home));
|
||||||
|
let suggestions = suggest::sort(&typo_str, possibilities);
|
||||||
|
|
||||||
|
match suggestions.get(0) {
|
||||||
|
None => alloc.nil(),
|
||||||
|
Some(nearest) => {
|
||||||
|
let nearest_str = format!("{}", nearest);
|
||||||
|
|
||||||
|
let found = alloc.text(typo_str).annotate(Annotation::Typo);
|
||||||
|
let suggestion =
|
||||||
|
alloc.text(nearest_str).annotate(Annotation::TypoSuggestion);
|
||||||
|
|
||||||
|
let hint1 = Self::hint(alloc)
|
||||||
|
.append(alloc.reflow("Seems like a tag typo. Maybe "))
|
||||||
|
.append(found)
|
||||||
|
.append(" should be ")
|
||||||
|
.append(suggestion)
|
||||||
|
.append(alloc.text("?"));
|
||||||
|
|
||||||
|
let hint2 = Self::hint(alloc).append(alloc.reflow(ADD_ANNOTATIONS));
|
||||||
|
|
||||||
|
hint1
|
||||||
|
.append(alloc.line())
|
||||||
|
.append(alloc.line())
|
||||||
|
.append(hint2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hint<'b, D>(alloc: &'b D) -> DocBuilder<'b, D, Annotation>
|
||||||
|
where
|
||||||
|
D: DocAllocator<'b, Annotation>,
|
||||||
|
D::Doc: Clone,
|
||||||
|
{
|
||||||
|
alloc
|
||||||
|
.text("Hint:")
|
||||||
|
.append(alloc.softline())
|
||||||
|
.annotate(Annotation::Hint)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -838,7 +838,7 @@ fn to_circular_report(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Problem {
|
pub enum Problem {
|
||||||
IntFloat,
|
IntFloat,
|
||||||
ArityMismatch(usize, usize),
|
ArityMismatch(usize, usize),
|
||||||
|
@ -851,9 +851,50 @@ pub enum Problem {
|
||||||
BadRigidVar(Lowercase, ErrorType),
|
BadRigidVar(Lowercase, ErrorType),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn problems_to_hint(_problems: Vec<Problem>) -> Vec<ReportText> {
|
fn problems_to_hint(mut problems: Vec<Problem>) -> Option<ReportText> {
|
||||||
// TODO
|
if problems.is_empty() {
|
||||||
vec![]
|
None
|
||||||
|
} else {
|
||||||
|
let problem = problems.remove(problems.len() - 1);
|
||||||
|
Some(ReportText::TypeProblem(problem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod suggest {
|
||||||
|
use core::convert::AsRef;
|
||||||
|
use distance;
|
||||||
|
use inlinable_string::InlinableString;
|
||||||
|
use roc_module::ident::Lowercase;
|
||||||
|
|
||||||
|
pub trait ToStr {
|
||||||
|
fn to_str(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStr for Lowercase {
|
||||||
|
fn to_str(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStr for InlinableString {
|
||||||
|
fn to_str(&self) -> &str {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sort<'a, T>(typo: &'a str, mut options: Vec<T>) -> Vec<T>
|
||||||
|
where
|
||||||
|
T: ToStr,
|
||||||
|
{
|
||||||
|
options.sort_by(|a, b| {
|
||||||
|
let l = distance::damerau_levenshtein(typo, a.to_str());
|
||||||
|
let r = distance::damerau_levenshtein(typo, b.to_str());
|
||||||
|
|
||||||
|
l.cmp(&r)
|
||||||
|
});
|
||||||
|
|
||||||
|
options
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Comparison {
|
pub struct Comparison {
|
||||||
|
|
|
@ -1190,6 +1190,11 @@ mod test_reporting {
|
||||||
{ foo : Int }
|
{ foo : Int }
|
||||||
|
|
||||||
|
|
||||||
|
Hint: Seems like a record field typo. Maybe `bar` should be `foo`?
|
||||||
|
|
||||||
|
Hint: 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
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1222,6 +1227,11 @@ mod test_reporting {
|
||||||
[ Green, Red ]
|
[ Green, Red ]
|
||||||
|
|
||||||
|
|
||||||
|
Hint: Seems like a tag typo. Maybe `Blue` should be `Red`?
|
||||||
|
|
||||||
|
Hint: 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
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1254,6 +1264,11 @@ mod test_reporting {
|
||||||
[ Green Bool, Red Int ]
|
[ Green Bool, Red Int ]
|
||||||
|
|
||||||
|
|
||||||
|
Hint: Seems like a tag typo. Maybe `Blue` should be `Red`?
|
||||||
|
|
||||||
|
Hint: 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
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue