mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
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:
commit
a58c999d3e
6 changed files with 152 additions and 56 deletions
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -139,6 +139,7 @@ pub enum RuntimeError {
|
|||
module_name: ModuleName,
|
||||
ident: Ident,
|
||||
region: Region,
|
||||
exposed_values: Vec<Lowercase>,
|
||||
},
|
||||
ModuleNotImported {
|
||||
module_name: ModuleName,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue