Merge pull request #2008 from rtfeldman/improve-typo-suggestions

Minor improvements to "did you mean?" suggestions provided for missing identifiers
This commit is contained in:
Folkert de Vries 2021-11-19 10:24:52 +01:00 committed by GitHub
commit a58c999d3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 152 additions and 56 deletions

View file

@ -1,7 +1,7 @@
use crate::mem_pool::pool::{NodeId, Pool};
use bumpalo::{collections::Vec as BumpVec, Bump};
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, ModuleName};
use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
@ -134,11 +134,8 @@ impl<'a> Env<'a> {
)),
}
} else {
match self
.dep_idents
.get(&module_id)
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
{
match self.dep_idents.get(&module_id) {
Some(exposed_ids) => match exposed_ids.get_id(&ident) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
@ -146,11 +143,28 @@ impl<'a> Env<'a> {
Ok(symbol)
}
None => Err(RuntimeError::ValueNotExposed {
None => {
let exposed_values = exposed_ids
.idents()
.filter(|(_, ident)| {
ident.as_ref().starts_with(|c: char| c.is_lowercase())
})
.map(|(_, ident)| Lowercase::from(ident.as_ref()))
.collect();
Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
}),
exposed_values,
})
}
},
None => {
panic!(
"Module {} exists, but is not recorded in dep_idents",
module_name
)
}
}
}
}

View file

@ -1,6 +1,6 @@
use crate::procedure::References;
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, ModuleName};
use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
@ -99,11 +99,8 @@ impl<'a> Env<'a> {
)),
}
} else {
match self
.dep_idents
.get(&module_id)
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
{
match self.dep_idents.get(&module_id) {
Some(exposed_ids) => match exposed_ids.get_id(&ident) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
@ -111,11 +108,28 @@ impl<'a> Env<'a> {
Ok(symbol)
}
None => Err(RuntimeError::ValueNotExposed {
None => {
let exposed_values = exposed_ids
.idents()
.filter(|(_, ident)| {
ident.as_ref().starts_with(|c: char| c.is_lowercase())
})
.map(|(_, ident)| Lowercase::from(ident.as_ref()))
.collect();
Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
}),
exposed_values,
})
}
},
None => {
panic!(
"Module {} exists, but is not recorded in dep_idents",
module_name
)
}
}
}
}

View file

@ -992,12 +992,16 @@ define_builtins! {
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
1 BOOL_AND: "and"
2 BOOL_OR: "or"
3 BOOL_NOT: "not"
4 BOOL_XOR: "xor"
5 BOOL_EQ: "isEq"
6 BOOL_NEQ: "isNotEq"
1 BOOL_FALSE: "False" imported // Bool.Bool = [ False, True ]
// NB: not strictly needed; used for finding global tag names in error suggestions
2 BOOL_TRUE: "True" imported // Bool.Bool = [ False, True ]
// NB: not strictly needed; used for finding global tag names in error suggestions
3 BOOL_AND: "and"
4 BOOL_OR: "or"
5 BOOL_NOT: "not"
6 BOOL_XOR: "xor"
7 BOOL_EQ: "isEq"
8 BOOL_NEQ: "isNotEq"
}
3 STR: "Str" => {
0 STR_STR: "Str" imported // the Str.Str type alias
@ -1082,12 +1086,16 @@ define_builtins! {
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
1 RESULT_MAP: "map"
2 RESULT_MAP_ERR: "mapErr"
3 RESULT_WITH_DEFAULT: "withDefault"
4 RESULT_AFTER: "after"
5 RESULT_IS_OK: "isOk"
6 RESULT_IS_ERR: "isErr"
1 RESULT_OK: "Ok" imported // Result.Result a e = [ Ok a, Err e ]
// NB: not strictly needed; used for finding global tag names in error suggestions
2 RESULT_ERR: "Err" imported // Result.Result a e = [ Ok a, Err e ]
// NB: not strictly needed; used for finding global tag names in error suggestions
3 RESULT_MAP: "map"
4 RESULT_MAP_ERR: "mapErr"
5 RESULT_WITH_DEFAULT: "withDefault"
6 RESULT_AFTER: "after"
7 RESULT_IS_OK: "isOk"
8 RESULT_IS_ERR: "isErr"
}
6 DICT: "Dict" => {
0 DICT_DICT: "Dict" imported // the Dict.Dict type alias

View file

@ -139,6 +139,7 @@ pub enum RuntimeError {
module_name: ModuleName,
ident: Ident,
region: Region,
exposed_values: Vec<Lowercase>,
},
ModuleNotImported {
module_name: ModuleName,

View file

@ -6,6 +6,7 @@ use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, Runtim
use roc_region::all::{Located, Region};
use std::path::PathBuf;
use crate::error::r#type::suggest;
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
use ven_pretty::DocAllocator;
@ -874,16 +875,36 @@ fn pretty_runtime_error<'b>(
module_name,
ident,
region,
exposed_values,
} => {
let mut suggestions = suggest::sort(ident.as_ref(), exposed_values);
suggestions.truncate(4);
let did_you_mean = if suggestions.is_empty() {
alloc.concat(vec![
alloc.reflow("In fact, it looks like "),
alloc.module_name(module_name.clone()),
alloc.reflow(" doesn't expose any values!"),
])
} else {
let qualified_suggestions = suggestions
.into_iter()
.map(|v| alloc.string(module_name.to_string() + "." + v.as_str()));
alloc.stack(vec![
alloc.reflow("Did you mean one of these?"),
alloc.vcat(qualified_suggestions).indent(4),
])
};
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The "),
alloc.module_name(module_name),
alloc.reflow(" module does not expose a "),
alloc.reflow(" module does not expose `"),
alloc.string(ident.to_string()),
alloc.reflow(" value:"),
alloc.reflow("`:"),
]),
alloc.region(region),
did_you_mean,
]);
title = VALUE_NOT_EXPOSED;
@ -1176,8 +1197,6 @@ fn not_found<'b>(
thing: &'b str,
options: MutSet<Box<str>>,
) -> RocDocBuilder<'b> {
use crate::error::r#type::suggest;
let mut suggestions = suggest::sort(
name.as_inline_str().as_str(),
options.iter().map(|v| v.as_ref()).collect(),
@ -1225,8 +1244,6 @@ fn module_not_found<'b>(
name: &ModuleName,
options: MutSet<Box<str>>,
) -> RocDocBuilder<'b> {
use crate::error::r#type::suggest;
let mut suggestions =
suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect());
suggestions.truncate(4);

View file

@ -298,17 +298,24 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
List.foobar 1 2
List.isempty 1 2
"#
),
indoc!(
r#"
NOT EXPOSED
The List module does not expose a foobar value:
The List module does not expose `isempty`:
1 List.foobar 1 2
^^^^^^^^^^^
1 List.isempty 1 2
^^^^^^^^^^^^
Did you mean one of these?
List.isEmpty
List.set
List.get
List.keepIf
"#
),
)
@ -547,7 +554,35 @@ mod test_reporting {
baz
Nat
Str
U8
Err
"#
),
)
}
#[test]
fn lowercase_primitive_tag_bool() {
report_problem_as(
indoc!(
r#"
if true then 1 else 2
"#
),
indoc!(
r#"
UNRECOGNIZED NAME
I cannot find a `true` value
1 if true then 1 else 2
^^^^
Did you mean one of these?
True
Str
Num
Err
"#
),
)
@ -1950,10 +1985,10 @@ mod test_reporting {
Did you mean one of these?
Ok
U8
f
I8
F64
"#
),
)
@ -5596,10 +5631,17 @@ mod test_reporting {
r#"
NOT EXPOSED
The Num module does not expose a if value:
The Num module does not expose `if`:
1 Num.if
^^^^^^
Did you mean one of these?
Num.sin
Num.div
Num.abs
Num.neg
"#
),
)
@ -5802,8 +5844,8 @@ mod test_reporting {
Nat
Str
Err
U8
F64
"#
),
)