highlight code snippets better

This commit is contained in:
Folkert 2020-04-12 20:52:43 +02:00
parent c326b09964
commit 7632a4b484
11 changed files with 216 additions and 188 deletions

View File

@ -22,14 +22,21 @@ use std::collections::HashMap;
use std::fmt::Debug;
use ven_graph::{strongly_connected_components, topological_sort_into_groups};
#[allow(clippy::type_complexity)]
#[derive(Clone, Debug, PartialEq)]
pub struct Def {
pub loc_pattern: Located<Pattern>,
pub loc_expr: Located<Expr>,
pub expr_var: Variable,
pub pattern_vars: SendMap<Symbol, Variable>,
pub annotation: Option<(Type, IntroducedVariables, SendMap<Symbol, Alias>)>,
pub annotation: Option<Annotation>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Annotation {
pub signature: Type,
pub introduced_variables: IntroducedVariables,
pub aliases: SendMap<Symbol, Alias>,
pub region: Region,
}
#[derive(Debug)]
@ -794,11 +801,12 @@ fn canonicalize_pending_def<'a>(
value: loc_can_expr.value.clone(),
},
pattern_vars: im::HashMap::clone(&vars_by_symbol),
annotation: Some((
typ.clone(),
output.introduced_variables.clone(),
ann.aliases.clone(),
)),
annotation: Some(Annotation {
signature: typ.clone(),
introduced_variables: output.introduced_variables.clone(),
aliases: ann.aliases.clone(),
region: loc_ann.region,
}),
},
);
}
@ -996,11 +1004,12 @@ fn canonicalize_pending_def<'a>(
value: loc_can_expr.value.clone(),
},
pattern_vars: im::HashMap::clone(&vars_by_symbol),
annotation: Some((
typ.clone(),
output.introduced_variables.clone(),
ann.aliases.clone(),
)),
annotation: Some(Annotation {
signature: typ.clone(),
introduced_variables: output.introduced_variables.clone(),
aliases: ann.aliases.clone(),
region: loc_ann.region,
}),
},
);
}

View File

@ -67,6 +67,7 @@ pub enum Expr {
When {
cond_var: Variable,
expr_var: Variable,
region: Region,
loc_cond: Box<Located<Expr>>,
branches: Vec<WhenBranch>,
},
@ -464,7 +465,7 @@ pub fn canonicalize_expr<'a>(
// Infer the condition expression's type.
let cond_var = var_store.fresh();
let (can_cond, mut output) =
canonicalize_expr(env, var_store, scope, region, &loc_cond.value);
canonicalize_expr(env, var_store, scope, loc_cond.region, &loc_cond.value);
// the condition can never be a tail-call
output.tail_call = None;
@ -491,6 +492,7 @@ pub fn canonicalize_expr<'a>(
let expr = When {
expr_var: var_store.fresh(),
cond_var,
region,
loc_cond: Box::new(can_cond),
branches: can_branches,
};

View File

@ -149,8 +149,13 @@ pub fn constrain_expr(
let mut vars = Vec::with_capacity(updates.len() + 2);
let mut cons = Vec::with_capacity(updates.len() + 1);
for (field_name, Field { var, loc_expr, .. }) in updates.clone() {
let (var, tipe, con) =
constrain_field_update(env, var, region, field_name.clone(), &loc_expr);
let (var, tipe, con) = constrain_field_update(
env,
var,
loc_expr.region,
field_name.clone(),
&loc_expr,
);
fields.insert(field_name, tipe);
vars.push(var);
cons.push(con);
@ -498,6 +503,7 @@ pub fn constrain_expr(
expr_var,
loc_cond,
branches,
..
} => {
// Infer the condition expression's type.
let cond_var = *cond_var;
@ -935,16 +941,16 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
let mut new_rigids = Vec::new();
let expr_con = match &def.annotation {
Some((annotation, introduced_vars, ann_def_aliases)) => {
def_aliases = ann_def_aliases.clone();
Some(annotation) => {
def_aliases = annotation.aliases.clone();
let arity = annotation.arity();
let arity = annotation.signature.arity();
let rigids = &env.rigids;
let mut ftv = rigids.clone();
let annotation = instantiate_rigids(
annotation,
&introduced_vars,
let signature = instantiate_rigids(
&annotation.signature,
&annotation.introduced_variables,
&mut new_rigids,
&mut ftv,
&def.loc_pattern,
@ -954,8 +960,10 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
let annotation_expected = FromAnnotation(
def.loc_pattern.clone(),
arity,
AnnotationSource::TypedBody,
annotation,
AnnotationSource::TypedBody {
region: annotation.region,
},
signature,
);
pattern_state.constraints.push(Eq(
@ -1106,17 +1114,17 @@ pub fn rec_defs_help(
flex_info.def_types.extend(pattern_state.headers);
}
Some((annotation, introduced_vars, ann_def_aliases)) => {
for (symbol, alias) in ann_def_aliases.clone() {
Some(annotation) => {
for (symbol, alias) in annotation.aliases.clone() {
def_aliases.insert(symbol, alias);
}
let arity = annotation.arity();
let arity = annotation.signature.arity();
let mut ftv = env.rigids.clone();
let annotation = instantiate_rigids(
annotation,
&introduced_vars,
let signature = instantiate_rigids(
&annotation.signature,
&annotation.introduced_variables,
&mut new_rigids,
&mut ftv,
&def.loc_pattern,
@ -1126,8 +1134,10 @@ pub fn rec_defs_help(
let annotation_expected = FromAnnotation(
def.loc_pattern.clone(),
arity,
AnnotationSource::TypedBody,
annotation.clone(),
AnnotationSource::TypedBody {
region: annotation.region,
},
signature.clone(),
);
let expr_con = constrain_expr(
&Env {

View File

@ -1059,6 +1059,7 @@ pub fn constrain_expr(
expr_var,
loc_cond,
branches,
..
} => {
let cond_var = *cond_var;
let cond_type = Variable(cond_var);
@ -1897,15 +1898,15 @@ fn constrain_def(
let mut new_rigids = Vec::new();
let expr_con = match &def.annotation {
Some((annotation, introduced_vars, ann_def_aliases)) => {
def_aliases = ann_def_aliases.clone();
let arity = annotation.arity();
Some(annotation) => {
def_aliases = annotation.aliases.clone();
let arity = annotation.signature.arity();
let mut ftv = env.rigids.clone();
let annotation = instantiate_rigids(
let signature = instantiate_rigids(
var_store,
annotation,
&introduced_vars,
&annotation.signature,
&annotation.introduced_variables,
&mut new_rigids,
&mut ftv,
&def.loc_pattern,
@ -1915,8 +1916,10 @@ fn constrain_def(
let annotation_expected = Expected::FromAnnotation(
def.loc_pattern.clone(),
arity,
AnnotationSource::TypedBody,
annotation,
AnnotationSource::TypedBody {
region: annotation.region,
},
signature,
);
pattern_state.constraints.push(Eq(
@ -2119,16 +2122,16 @@ pub fn rec_defs_help(
flex_info.def_types.extend(pattern_state.headers);
}
Some((annotation, introduced_vars, ann_def_aliases)) => {
for (symbol, alias) in ann_def_aliases.clone() {
Some(annotation) => {
for (symbol, alias) in annotation.aliases.clone() {
def_aliases.insert(symbol, alias);
}
let arity = annotation.arity();
let arity = annotation.signature.arity();
let mut ftv = env.rigids.clone();
let annotation = instantiate_rigids(
let signature = instantiate_rigids(
var_store,
annotation,
&introduced_vars,
&annotation.signature,
&annotation.introduced_variables,
&mut new_rigids,
&mut ftv,
&def.loc_pattern,
@ -2137,8 +2140,10 @@ pub fn rec_defs_help(
let annotation_expected = Expected::FromAnnotation(
def.loc_pattern.clone(),
arity,
AnnotationSource::TypedBody,
annotation.clone(),
AnnotationSource::TypedBody {
region: annotation.region,
},
signature.clone(),
);
let expr_con = constrain_expr(
&Env {

View File

@ -393,6 +393,7 @@ fn pattern_to_when<'a>(
let wrapped_body = When {
cond_var: pattern_var,
expr_var: body_var,
region: Region::zero(),
loc_cond: Box::new(Located::at_zero(Var(symbol))),
branches: vec![WhenBranch {
patterns: vec![pattern],
@ -613,9 +614,10 @@ fn from_can<'a>(
When {
cond_var,
expr_var,
region,
loc_cond,
branches,
} => from_can_when(env, cond_var, expr_var, *loc_cond, branches, procs),
} => from_can_when(env, cond_var, expr_var, region, *loc_cond, branches, procs),
If {
cond_var,
@ -1077,6 +1079,7 @@ fn from_can_when<'a>(
env: &mut Env<'a, '_>,
cond_var: Variable,
expr_var: Variable,
region: Region,
loc_cond: Located<roc_can::expr::Expr>,
mut branches: std::vec::Vec<roc_can::expr::WhenBranch>,
procs: &mut Procs<'a>,
@ -1106,7 +1109,7 @@ fn from_can_when<'a>(
let context = crate::pattern::Context::BadCase;
match crate::pattern::check(
loc_when_pattern.region,
region,
&[(
Located::at(loc_when_pattern.region, mono_pattern.clone()),
guard,
@ -1225,7 +1228,7 @@ fn from_can_when<'a>(
}
let context = crate::pattern::Context::BadCase;
match crate::pattern::check(loc_cond.region, &loc_branches, context) {
match crate::pattern::check(region, &loc_branches, context) {
Ok(_) => {}
Err(errors) => {
use crate::pattern::Error::*;

View File

@ -30,6 +30,23 @@ impl Region {
}
}
pub fn contains(&self, other: &Self) -> bool {
use std::cmp::Ordering::*;
match self.start_line.cmp(&other.start_line) {
Greater => false,
Equal => match self.end_line.cmp(&other.end_line) {
Less => false,
Equal => self.start_col <= other.start_col && self.end_col >= other.end_col,
Greater => self.start_col >= other.start_col,
},
Less => match self.end_line.cmp(&other.end_line) {
Less => false,
Equal => self.end_col >= other.end_col,
Greater => true,
},
}
}
pub fn span_across(start: &Region, end: &Region) -> Self {
Region {
start_line: start.start_line,

View File

@ -233,7 +233,7 @@ pub fn mono_problem<'b>(
alloc.string(index.ordinal()),
alloc.reflow(" pattern is redundant:"),
]),
alloc.region(branch_region),
alloc.region_with_subregion(overall_region, branch_region),
alloc.reflow(
"Any value of this shape will be handled by \
a previous pattern, so this one should be removed.",
@ -253,8 +253,6 @@ pub fn unhandled_patterns_to_doc_block<'b>(
alloc: &'b RocDocAllocator<'b>,
patterns: Vec<roc_mono::pattern::Pattern>,
) -> RocDocBuilder<'b> {
use roc_mono::pattern::Pattern::*;
alloc
.vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v)))
.indent(4)
@ -726,33 +724,68 @@ impl<'a> RocDocAllocator<'a> {
.annotate(Annotation::Hint)
}
pub fn region(&'a self, region: roc_region::all::Region) -> DocBuilder<'a, Self, Annotation> {
pub fn region_with_subregion(
&'a self,
region: roc_region::all::Region,
sub_region: roc_region::all::Region,
) -> DocBuilder<'a, Self, Annotation> {
debug_assert!(region.contains(&sub_region));
// if true, the final line of the snippet will be some ^^^ that point to the region where
// the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
// where the problem is.
let error_highlight_line = sub_region.start_line == region.end_line;
let max_line_number_length = (region.end_line + 1).to_string().len();
let indent = 2;
if region.start_line == region.end_line {
let i = region.start_line;
let mut result = self.nil();
for i in region.start_line..=region.end_line {
let line_number_string = (i + 1).to_string();
let line_number = line_number_string;
let this_line_number_length = line_number.len();
let line = self.src_lines[i as usize];
let rest_of_line = if line.trim().is_empty() {
self.nil()
let rest_of_line = if !line.trim().is_empty() {
self.text(line)
.annotate(Annotation::CodeBlock)
.indent(indent)
} else {
self.nil()
.append(self.text(line).indent(2))
.annotate(Annotation::CodeBlock)
};
let source_line = self
.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text("").annotate(Annotation::GutterBar))
.append(rest_of_line);
let source_line = if !error_highlight_line
&& i >= sub_region.start_line
&& i <= sub_region.end_line
{
self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text("").annotate(Annotation::GutterBar))
.append(self.text(">").annotate(Annotation::Error))
.append(rest_of_line)
} else if error_highlight_line {
self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text("").annotate(Annotation::GutterBar))
.append(rest_of_line)
} else {
self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text("").annotate(Annotation::GutterBar))
.append(self.text(" "))
.append(rest_of_line)
};
let highlight_text = "^".repeat((region.end_col - region.start_col) as usize);
result = result.append(source_line);
if i != region.end_line {
result = result.append(self.line())
}
}
if error_highlight_line {
let highlight_text = "^".repeat((sub_region.end_col - sub_region.start_col) as usize);
let highlight_line = self
.line()
.append(self.text(" ".repeat(max_line_number_length)))
@ -760,44 +793,19 @@ impl<'a> RocDocAllocator<'a> {
.append(if highlight_text.is_empty() {
self.nil()
} else {
self.text(" ".repeat(region.start_col as usize))
self.text(" ".repeat(sub_region.start_col as usize))
.indent(indent)
.append(self.text(highlight_text).annotate(Annotation::Error))
});
source_line.append(highlight_line)
} else {
let mut result = self.nil();
for i in region.start_line..=region.end_line {
let line_number_string = (i + 1).to_string();
let line_number = line_number_string;
let this_line_number_length = line_number.len();
let line = self.src_lines[i as usize];
let rest_of_line = if !line.trim().is_empty() {
self.text(line)
.annotate(Annotation::CodeBlock)
.indent(indent)
} else {
self.nil()
};
let source_line = self
.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text("").annotate(Annotation::GutterBar))
.append(self.text(">").annotate(Annotation::Error))
.append(rest_of_line);
result = result.append(source_line);
if i != region.end_line {
result = result.append(self.line())
}
}
result
result = result.append(highlight_line);
}
result
}
pub fn region(&'a self, region: roc_region::all::Region) -> DocBuilder<'a, Self, Annotation> {
self.region_with_subregion(region, region)
}
pub fn ident(&'a self, ident: Ident) -> DocBuilder<'a, Self, Annotation> {

View File

@ -40,15 +40,20 @@ fn report_mismatch<'b>(
found: ErrorType,
expected_type: ErrorType,
region: roc_region::all::Region,
_opt_highlight: Option<roc_region::all::Region>,
opt_highlight: Option<roc_region::all::Region>,
problem: RocDocBuilder<'b>,
this_is: RocDocBuilder<'b>,
instead_of: RocDocBuilder<'b>,
further_details: Option<RocDocBuilder<'b>>,
) -> Report<'b> {
let snippet = if let Some(highlight) = opt_highlight {
alloc.region_with_subregion(highlight, region)
} else {
alloc.region(region)
};
let lines = vec![
problem,
alloc.region(region),
snippet,
type_comparison(
alloc,
found,
@ -74,14 +79,19 @@ fn report_bad_type<'b>(
found: ErrorType,
expected_type: ErrorType,
region: roc_region::all::Region,
_opt_highlight: Option<roc_region::all::Region>,
opt_highlight: Option<roc_region::all::Region>,
problem: RocDocBuilder<'b>,
this_is: RocDocBuilder<'b>,
further_details: RocDocBuilder<'b>,
) -> Report<'b> {
let snippet = if let Some(highlight) = opt_highlight {
alloc.region_with_subregion(highlight, region)
} else {
alloc.region(region)
};
let lines = vec![
problem,
alloc.region(region),
snippet,
lone_type(
alloc,
found,
@ -150,6 +160,8 @@ fn to_expr_report<'b>(
None => (alloc.text("this"), alloc.nil()),
};
let mut region = None;
let thing = match annotation_source {
TypedIfBranch {
index,
@ -176,17 +188,20 @@ fn to_expr_report<'b>(
alloc.keyword("when"),
alloc.text(" expression:"),
]),
TypedBody => alloc.concat(vec![
alloc.text("body of "),
the_name_text,
alloc.text(" definition:"),
]),
TypedBody { region: ann_region } => {
region = Some(ann_region);
alloc.concat(vec![
alloc.text("body of "),
the_name_text,
alloc.text(" definition:"),
])
}
};
let it_is = match annotation_source {
TypedIfBranch { index, .. } => format!("The {} branch is", index.ordinal()),
TypedWhenBranch { index, .. } => format!("The {} branch is", index.ordinal()),
TypedBody => "The body is".into(),
TypedBody { .. } => "The body is".into(),
};
let comparison = type_comparison(
@ -207,7 +222,14 @@ fn to_expr_report<'b>(
filename,
doc: alloc.stack(vec![
alloc.text("Something is off with the ").append(thing),
alloc.region(expr_region),
match region {
None => alloc.region(expr_region),
Some(ann_region) => {
let joined =
roc_region::all::Region::span_across(&ann_region, &expr_region);
alloc.region_with_subregion(joined, expr_region)
}
},
comparison,
]),
}

View File

@ -234,7 +234,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
&mut env,
&var_store,
&mut scope,
Region::zero(),
loc_expr.region,
&loc_expr.value,
);

View File

@ -23,10 +23,8 @@ mod test_reporting {
use std::path::PathBuf;
// use roc_region::all;
use crate::helpers::{can_expr, infer_expr, CanExprOut};
use roc_reporting::report;
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
use roc_solve::solve;
use std::fmt::Write;
fn filename_from_string(str: &str) -> PathBuf {
let mut filename = PathBuf::new();
@ -89,7 +87,7 @@ mod test_reporting {
let pointer_size = std::mem::size_of::<u64>() as u32;
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
let mono_expr = Expr::new(
let _mono_expr = Expr::new(
&arena,
&mut subs,
&mut mono_problems,
@ -556,7 +554,7 @@ mod test_reporting {
I cannot find a `theAdmin` value
<cyan>3<reset><magenta> <reset><white> theAdmin<reset>
<cyan>3<reset><magenta> <reset> <white>theAdmin<reset>
<magenta> <reset> <red>^^^^^^^^<reset>
these names seem close though:
@ -660,8 +658,9 @@ mod test_reporting {
This `if` guard condition needs to be a Bool:
2 2 if 1 -> 0x0
^
1 when 1 is
2 > 2 if 1 -> 0x0
3 _ -> 0x1
Right now its a number of type:
@ -751,6 +750,8 @@ mod test_reporting {
The 2nd branch of this `when` does not match all the previous branches:
1 when 1 is
2 2 -> "foo"
3 3 -> {}
^^
@ -817,7 +818,7 @@ mod test_reporting {
I cannot update the `.foo` field like this:
4 { x & foo: "bar" }
^^^^^^^^^^^^^^^^^^
^^^^^
You are trying to update `.foo` to be a string of type:
@ -834,67 +835,6 @@ mod test_reporting {
)
}
// needs a bit more infrastructure re. diffing records
// #[test]
// fn record_update_keys() {
// report_problem_as(
// indoc!(
// r#"
// x : { foo : {} }
// x = { foo: {} }
//
// { x & baz: "bar" }
// "#
// ),
// indoc!(
// r#"
// The `x` record does not have a `baz` field:
//
// 4 ┆ { x & baz: "bar" }
// ┆ ^^^
//
// This is usually a typo. Here are the `x` fields that are most similar:
//
// { foo : {}
// }
//
// So maybe `baz` should be `foo`?
// "#
// ),
// )
// }
// #[test]
// fn num_literal() {
// report_problem_as(
// indoc!(
// r#"
// x : Str
// x = 4
//
// x
// "#
// ),
// indoc!(
// r#"
// Something is off with the body of the `x` definition:
//
// 4 ┆ x = 4
// ┆ ^
//
// The body is a number of type:
//
// Num a
//
// But the type annotation on `x` says that it should be:
//
// Str
//
// "#
// ),
// )
// }
#[test]
fn circular_type() {
report_problem_as(
@ -1156,6 +1096,7 @@ mod test_reporting {
Something is off with the body of the `x` definition:
1 x : Int -> Int
2 x = \_ -> 3.14
^^^^^^^^^^
@ -1488,6 +1429,7 @@ mod test_reporting {
Something is off with the body of this definition:
1 { x } : { x : Int }
2 { x } = { x: 4.0 }
^^^^^^^^^^
@ -1522,6 +1464,7 @@ mod test_reporting {
Something is off with the body of the `x` definition:
1 x : { a : Int, b : Float, c : Bool }
2 x = { b: 4.0 }
^^^^^^^^^^
@ -1556,6 +1499,7 @@ mod test_reporting {
Something is off with the body of the `f` definition:
1 f : a, b -> a
2 f = \x, y -> if True then x else y
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -1593,6 +1537,7 @@ mod test_reporting {
Something is off with the body of the `f` definition:
1 f : Bool -> msg
2 f = \_ -> Foo
^^^^^^^^^
@ -1631,6 +1576,7 @@ mod test_reporting {
Something is off with the body of the `f` definition:
1 f : msg
2 f = 0x3
^^^
@ -1715,6 +1661,7 @@ mod test_reporting {
Something is off with the body of the `f` definition:
1 f : Bool -> Int
2 > f = \_ ->
3 > ok = 3
4 >
@ -1865,6 +1812,7 @@ mod test_reporting {
Something is off with the body of the `f` definition:
1 f : { fo: Int }ext -> Int
2 > f = \r ->
3 > r2 = { r & foo: r.fo }
4 >
@ -2001,6 +1949,7 @@ mod test_reporting {
Something is off with the body of the `f` definition:
1 f : [ A ] -> [ A, B ]
2 f = \a -> a
^^^^^^^
@ -2038,6 +1987,7 @@ mod test_reporting {
Something is off with the body of the `f` definition:
1 f : [ A ] -> [ A, B, C ]
2 f = \a -> a
^^^^^^^
@ -2162,8 +2112,8 @@ mod test_reporting {
This `when` does not cover all the possibilities
2 2 -> 0x3
^
1 > when 0x1 is
2 > 2 -> 0x3
Other possibilities include:
@ -2181,9 +2131,9 @@ mod test_reporting {
indoc!(
r#"
when 0x1 is
2 -> 0x3
2 -> 0x4
_ -> 0x5
2 -> 3
2 -> 4
_ -> 5
"#
),
// should not give errors
@ -2193,8 +2143,10 @@ mod test_reporting {
The 2nd pattern is redundant:
3 2 -> 0x4
^
1 when 0x1 is
2 2 -> 3
3 > 2 -> 4
4 _ -> 5
Any value of this shape will be handled by a previous pattern, so this
one should be removed.

View File

@ -606,7 +606,7 @@ pub enum PReason {
pub enum AnnotationSource {
TypedIfBranch { index: Index, num_branches: usize },
TypedWhenBranch { index: Index },
TypedBody,
TypedBody { region: Region },
}
#[derive(Debug, Clone, PartialEq, Eq)]