Two non-functional problem reports

This commit is contained in:
Chad Stearns 2020-03-29 14:28:06 -04:00
parent a00ed8a1ca
commit 221581432a
4 changed files with 142 additions and 7 deletions

View file

@ -431,20 +431,19 @@ pub fn canonicalize_expr<'a>(
loc_body_expr.region,
&loc_body_expr.value,
);
// Now that we've collected all the references, check to see if any of the args we defined
// went unreferenced. If any did, report them as unused arguments.
for (symbol, region) in scope.symbols() {
if !original_scope.contains_symbol(*symbol) {
if !output.references.has_lookup(*symbol) {
for (sub_symbol, region) in scope.symbols() {
if !original_scope.contains_symbol(*sub_symbol) {
if !output.references.has_lookup(*sub_symbol) {
// The body never referenced this argument we declared. It's an unused argument!
env.problem(Problem::UnusedArgument(*symbol, *region));
env.problem(Problem::UnusedArgument(symbol, *sub_symbol, *region));
}
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
// we end up with weird conclusions like the expression (\x -> x + 1)
// references the (nonexistant) local variable x!
output.references.lookups.remove(symbol);
output.references.lookups.remove(sub_symbol);
}
}

View file

@ -10,7 +10,9 @@ use roc_region::all::{Located, Region};
pub enum Problem {
UnusedDef(Symbol, Region),
UnusedImport(ModuleId, Region),
UnusedArgument(Symbol, Region),
/// First symbol is the name of the closure with that argument
/// Second symbol is the name of the argument that is unused
UnusedArgument(Symbol, Symbol, Region),
PrecedenceProblem(PrecedenceProblem),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(PatternType, Region),

View file

@ -84,6 +84,41 @@ pub fn can_problem(filename: PathBuf, problem: Problem) -> Report {
" then remove it so future readers of your code don't wonder why it is there.",
));
}
Problem::UnusedImport(module_id, region) => {
texts.push(plain_text("Nothing from "));
texts.push(Module(module_id));
texts.push(plain_text(" is used in this module."));
texts.push(newline());
texts.push(newline());
texts.push(Region(region));
texts.push(newline());
texts.push(newline());
texts.push(plain_text("Since "));
texts.push(Module(module_id));
texts.push(plain_text(" isn't used, you don't need to import it."));
}
Problem::UnusedArgument(closure_symbol, argument_symbol, region) => {
texts.push(Value(closure_symbol));
texts.push(plain_text(" doesn't use "));
texts.push(Value(argument_symbol));
texts.push(plain_text("."));
texts.push(newline());
texts.push(newline());
texts.push(Region(region));
texts.push(newline());
texts.push(newline());
texts.push(plain_text("If you don't need "));
texts.push(Value(argument_symbol));
texts.push(plain_text(
", then you can just remove it. However, if you really do need ",
));
texts.push(Value(argument_symbol));
texts.push(plain_text(" as an argument of "));
texts.push(Value(closure_symbol));
texts.push(plain_text(", prefix it with an underscore, like this: \"_"));
texts.push(Value(argument_symbol));
texts.push(plain_text("\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."));
}
_ => {
panic!("TODO implement others");
}

View file

@ -287,6 +287,105 @@ mod test_report {
);
}
#[test]
fn report_unused_argument() {
let src: &str = indoc!(
r#"
y = 9
box = \class, htmlChildren ->
div [ class ] []
div = 4
box "wizard" []
"#
);
let (_type_problems, can_problems, mut subs, home, interns) = infer_expr_help(src);
let mut buf: String = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
match can_problems.first() {
None => {}
Some(problem) => {
let report = can_problem(
filename_from_string(r"\code\proj\Main.roc"),
problem.clone(),
);
report
.text
.render_ci(&mut buf, &mut subs, home, &src_lines, &interns)
}
}
assert_eq!(
buf,
indoc!(
r#"
box doesn't use htmlChildren.
3 box = \class, htmlChildren ->
If you don't need htmlChildren, then you can just remove it. However, if you really do need htmlChildren as an argument of box, prefix it with an underscore, like this: "_htmlChildren". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."#
)
);
}
#[test]
fn report_unused_import() {
let src: &str = indoc!(
r#"
interface Report
exposes [
plainText,
emText
]
imports [
Symbol.{ Interns }
]
plainText = \str -> PlainText str
emText = \str -> EmText str
"#
);
let (_type_problems, can_problems, mut subs, home, interns) = infer_expr_help(src);
let mut buf: String = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
match can_problems.first() {
None => {}
Some(problem) => {
let report = can_problem(
filename_from_string(r"\code\proj\Main.roc"),
problem.clone(),
);
report
.text
.render_ci(&mut buf, &mut subs, home, &src_lines, &interns)
}
}
assert_eq!(
buf,
indoc!(
r#"
Nothing from Symbol is used in this module.
6 imports [
7 Symbol.{ Interns }
^^^^^^
8 ]
Since Symbol isn't used, you don't need to import it."#
)
);
}
#[test]
fn report_plain_text_color() {
report_renders_in_color(to_simple_report(plain_text("y")), "<white>y<reset>");