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 crate::mem_pool::pool::{NodeId, Pool};
|
||||||
use bumpalo::{collections::Vec as BumpVec, Bump};
|
use bumpalo::{collections::Vec as BumpVec, Bump};
|
||||||
use roc_collections::all::{MutMap, MutSet};
|
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_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
|
||||||
use roc_problem::can::{Problem, RuntimeError};
|
use roc_problem::can::{Problem, RuntimeError};
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
|
@ -134,23 +134,37 @@ impl<'a> Env<'a> {
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match self
|
match self.dep_idents.get(&module_id) {
|
||||||
.dep_idents
|
Some(exposed_ids) => match exposed_ids.get_id(&ident) {
|
||||||
.get(&module_id)
|
Some(ident_id) => {
|
||||||
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
|
let symbol = Symbol::new(module_id, *ident_id);
|
||||||
{
|
|
||||||
Some(ident_id) => {
|
|
||||||
let symbol = Symbol::new(module_id, *ident_id);
|
|
||||||
|
|
||||||
self.qualified_lookups.insert(symbol);
|
self.qualified_lookups.insert(symbol);
|
||||||
|
|
||||||
Ok(symbol)
|
Ok(symbol)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
None => Err(RuntimeError::ValueNotExposed {
|
|
||||||
module_name,
|
|
||||||
ident,
|
|
||||||
region,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::procedure::References;
|
use crate::procedure::References;
|
||||||
use roc_collections::all::{MutMap, MutSet};
|
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_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
|
||||||
use roc_problem::can::{Problem, RuntimeError};
|
use roc_problem::can::{Problem, RuntimeError};
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
|
@ -99,23 +99,37 @@ impl<'a> Env<'a> {
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match self
|
match self.dep_idents.get(&module_id) {
|
||||||
.dep_idents
|
Some(exposed_ids) => match exposed_ids.get_id(&ident) {
|
||||||
.get(&module_id)
|
Some(ident_id) => {
|
||||||
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
|
let symbol = Symbol::new(module_id, *ident_id);
|
||||||
{
|
|
||||||
Some(ident_id) => {
|
|
||||||
let symbol = Symbol::new(module_id, *ident_id);
|
|
||||||
|
|
||||||
self.qualified_lookups.insert(symbol);
|
self.qualified_lookups.insert(symbol);
|
||||||
|
|
||||||
Ok(symbol)
|
Ok(symbol)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
None => Err(RuntimeError::ValueNotExposed {
|
|
||||||
module_name,
|
|
||||||
ident,
|
|
||||||
region,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -992,12 +992,16 @@ define_builtins! {
|
||||||
}
|
}
|
||||||
2 BOOL: "Bool" => {
|
2 BOOL: "Bool" => {
|
||||||
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
|
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
|
||||||
1 BOOL_AND: "and"
|
1 BOOL_FALSE: "False" imported // Bool.Bool = [ False, True ]
|
||||||
2 BOOL_OR: "or"
|
// NB: not strictly needed; used for finding global tag names in error suggestions
|
||||||
3 BOOL_NOT: "not"
|
2 BOOL_TRUE: "True" imported // Bool.Bool = [ False, True ]
|
||||||
4 BOOL_XOR: "xor"
|
// NB: not strictly needed; used for finding global tag names in error suggestions
|
||||||
5 BOOL_EQ: "isEq"
|
3 BOOL_AND: "and"
|
||||||
6 BOOL_NEQ: "isNotEq"
|
4 BOOL_OR: "or"
|
||||||
|
5 BOOL_NOT: "not"
|
||||||
|
6 BOOL_XOR: "xor"
|
||||||
|
7 BOOL_EQ: "isEq"
|
||||||
|
8 BOOL_NEQ: "isNotEq"
|
||||||
}
|
}
|
||||||
3 STR: "Str" => {
|
3 STR: "Str" => {
|
||||||
0 STR_STR: "Str" imported // the Str.Str type alias
|
0 STR_STR: "Str" imported // the Str.Str type alias
|
||||||
|
@ -1082,12 +1086,16 @@ define_builtins! {
|
||||||
}
|
}
|
||||||
5 RESULT: "Result" => {
|
5 RESULT: "Result" => {
|
||||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||||
1 RESULT_MAP: "map"
|
1 RESULT_OK: "Ok" imported // Result.Result a e = [ Ok a, Err e ]
|
||||||
2 RESULT_MAP_ERR: "mapErr"
|
// NB: not strictly needed; used for finding global tag names in error suggestions
|
||||||
3 RESULT_WITH_DEFAULT: "withDefault"
|
2 RESULT_ERR: "Err" imported // Result.Result a e = [ Ok a, Err e ]
|
||||||
4 RESULT_AFTER: "after"
|
// NB: not strictly needed; used for finding global tag names in error suggestions
|
||||||
5 RESULT_IS_OK: "isOk"
|
3 RESULT_MAP: "map"
|
||||||
6 RESULT_IS_ERR: "isErr"
|
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" => {
|
6 DICT: "Dict" => {
|
||||||
0 DICT_DICT: "Dict" imported // the Dict.Dict type alias
|
0 DICT_DICT: "Dict" imported // the Dict.Dict type alias
|
||||||
|
|
|
@ -139,6 +139,7 @@ pub enum RuntimeError {
|
||||||
module_name: ModuleName,
|
module_name: ModuleName,
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
region: Region,
|
region: Region,
|
||||||
|
exposed_values: Vec<Lowercase>,
|
||||||
},
|
},
|
||||||
ModuleNotImported {
|
ModuleNotImported {
|
||||||
module_name: ModuleName,
|
module_name: ModuleName,
|
||||||
|
|
|
@ -6,6 +6,7 @@ use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, Runtim
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::error::r#type::suggest;
|
||||||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
|
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
|
||||||
use ven_pretty::DocAllocator;
|
use ven_pretty::DocAllocator;
|
||||||
|
|
||||||
|
@ -874,16 +875,36 @@ fn pretty_runtime_error<'b>(
|
||||||
module_name,
|
module_name,
|
||||||
ident,
|
ident,
|
||||||
region,
|
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![
|
doc = alloc.stack(vec![
|
||||||
alloc.concat(vec![
|
alloc.concat(vec![
|
||||||
alloc.reflow("The "),
|
alloc.reflow("The "),
|
||||||
alloc.module_name(module_name),
|
alloc.module_name(module_name),
|
||||||
alloc.reflow(" module does not expose a "),
|
alloc.reflow(" module does not expose `"),
|
||||||
alloc.string(ident.to_string()),
|
alloc.string(ident.to_string()),
|
||||||
alloc.reflow(" value:"),
|
alloc.reflow("`:"),
|
||||||
]),
|
]),
|
||||||
alloc.region(region),
|
alloc.region(region),
|
||||||
|
did_you_mean,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
title = VALUE_NOT_EXPOSED;
|
title = VALUE_NOT_EXPOSED;
|
||||||
|
@ -1176,8 +1197,6 @@ fn not_found<'b>(
|
||||||
thing: &'b str,
|
thing: &'b str,
|
||||||
options: MutSet<Box<str>>,
|
options: MutSet<Box<str>>,
|
||||||
) -> RocDocBuilder<'b> {
|
) -> RocDocBuilder<'b> {
|
||||||
use crate::error::r#type::suggest;
|
|
||||||
|
|
||||||
let mut suggestions = suggest::sort(
|
let mut suggestions = suggest::sort(
|
||||||
name.as_inline_str().as_str(),
|
name.as_inline_str().as_str(),
|
||||||
options.iter().map(|v| v.as_ref()).collect(),
|
options.iter().map(|v| v.as_ref()).collect(),
|
||||||
|
@ -1225,8 +1244,6 @@ fn module_not_found<'b>(
|
||||||
name: &ModuleName,
|
name: &ModuleName,
|
||||||
options: MutSet<Box<str>>,
|
options: MutSet<Box<str>>,
|
||||||
) -> RocDocBuilder<'b> {
|
) -> RocDocBuilder<'b> {
|
||||||
use crate::error::r#type::suggest;
|
|
||||||
|
|
||||||
let mut suggestions =
|
let mut suggestions =
|
||||||
suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect());
|
suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect());
|
||||||
suggestions.truncate(4);
|
suggestions.truncate(4);
|
||||||
|
|
|
@ -298,17 +298,24 @@ mod test_reporting {
|
||||||
report_problem_as(
|
report_problem_as(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
List.foobar 1 2
|
List.isempty 1 2
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
── NOT EXPOSED ─────────────────────────────────────────────────────────────────
|
── 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
|
baz
|
||||||
Nat
|
Nat
|
||||||
Str
|
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?
|
Did you mean one of these?
|
||||||
|
|
||||||
|
Ok
|
||||||
U8
|
U8
|
||||||
f
|
f
|
||||||
I8
|
I8
|
||||||
F64
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -5596,10 +5631,17 @@ mod test_reporting {
|
||||||
r#"
|
r#"
|
||||||
── NOT EXPOSED ─────────────────────────────────────────────────────────────────
|
── NOT EXPOSED ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
The Num module does not expose a if value:
|
The Num module does not expose `if`:
|
||||||
|
|
||||||
1│ Num.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
|
Nat
|
||||||
Str
|
Str
|
||||||
|
Err
|
||||||
U8
|
U8
|
||||||
F64
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue