Report errors for identifiers not bound in all patterns of a branch

This commit is contained in:
Ayaz Hafiz 2022-07-21 12:15:05 -04:00
parent ce8b50caea
commit 78dc82867a
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
6 changed files with 154 additions and 20 deletions

View file

@ -1270,6 +1270,59 @@ fn canonicalize_closure_body<'a>(
(closure_data, output) (closure_data, output)
} }
enum MultiPatternVariables {
OnePattern,
MultiPattern {
bound_occurences: VecMap<Symbol, (Region, u8)>,
},
}
impl MultiPatternVariables {
#[inline(always)]
fn new(num_patterns: usize) -> Self {
if num_patterns > 1 {
Self::MultiPattern {
bound_occurences: VecMap::with_capacity(2),
}
} else {
Self::OnePattern
}
}
#[inline(always)]
fn add_pattern(&mut self, pattern: &Loc<Pattern>) {
match self {
MultiPatternVariables::OnePattern => {}
MultiPatternVariables::MultiPattern { bound_occurences } => {
for (sym, region) in BindingsFromPattern::new(pattern) {
if !bound_occurences.contains_key(&sym) {
bound_occurences.insert(sym, (region, 0));
}
bound_occurences.get_mut(&sym).unwrap().1 += 1;
}
}
}
}
#[inline(always)]
fn get_unbound(self) -> impl Iterator<Item = (Symbol, Region)> {
let bound_occurences = match self {
MultiPatternVariables::OnePattern => Default::default(),
MultiPatternVariables::MultiPattern { bound_occurences } => bound_occurences,
};
bound_occurences
.into_iter()
.filter_map(|(sym, (region, occurs))| {
if occurs == 1 {
Some((sym, region))
} else {
None
}
})
}
}
#[inline(always)] #[inline(always)]
fn canonicalize_when_branch<'a>( fn canonicalize_when_branch<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
@ -1280,6 +1333,7 @@ fn canonicalize_when_branch<'a>(
output: &mut Output, output: &mut Output,
) -> (WhenBranch, References) { ) -> (WhenBranch, References) {
let mut patterns = Vec::with_capacity(branch.patterns.len()); let mut patterns = Vec::with_capacity(branch.patterns.len());
let mut multi_pattern_variables = MultiPatternVariables::new(branch.patterns.len());
// TODO report symbols not bound in all patterns // TODO report symbols not bound in all patterns
for loc_pattern in branch.patterns.iter() { for loc_pattern in branch.patterns.iter() {
@ -1294,9 +1348,17 @@ fn canonicalize_when_branch<'a>(
PermitShadows(true), PermitShadows(true),
); );
multi_pattern_variables.add_pattern(&can_pattern);
patterns.push(can_pattern); patterns.push(can_pattern);
} }
for (unbound_symbol, region) in multi_pattern_variables.get_unbound() {
env.problem(Problem::NotBoundInAllPatterns {
unbound_symbol,
region,
})
}
let (value, mut branch_output) = canonicalize_expr( let (value, mut branch_output) = canonicalize_expr(
env, env,
var_store, var_store,

View file

@ -1843,24 +1843,28 @@ fn constrain_when_branch_help(
if i == 0 { if i == 0 {
state.headers.extend(partial_state.headers); state.headers.extend(partial_state.headers);
} else { } else {
debug_assert!(
state.headers.keys().all(|sym| partial_state.headers.contains_key(sym)) &&
partial_state.headers.keys().all(|sym| state.headers.contains_key(sym)),
"State and partial state headers differ in bound symbols, should have been caught in canonicalization");
// Make sure the bound variables in the patterns on the same branch agree in their types. // Make sure the bound variables in the patterns on the same branch agree in their types.
for (sym, typ1) in state.headers.iter() { for (sym, typ1) in state.headers.iter() {
let typ2 = partial_state if let Some(typ2) = partial_state.headers.get(sym) {
.headers state.constraints.push(constraints.equal_types(
.get(sym) typ1.value.clone(),
.expect("bound variable in branch not bound in pattern!"); Expected::NoExpectation(typ2.value.clone()),
Category::When,
typ2.region,
));
}
state.constraints.push(constraints.equal_types( // If the pattern doesn't bind all symbols introduced in the branch we'll have
typ1.value.clone(), // reported a canonicalization error, but still might reach here; that's okay.
Expected::NoExpectation(typ2.value.clone()), }
Category::When,
typ2.region, // Add any variables this pattern binds that the other patterns don't bind.
)); // This will already have been reported as an error, but we still might be able to
// solve their types.
for (sym, ty) in partial_state.headers {
if !state.headers.contains_key(&sym) {
state.headers.insert(sym, ty);
}
} }
} }
} }

View file

@ -165,6 +165,10 @@ pub enum Problem {
ability: Symbol, ability: Symbol,
not_implemented: Vec<Symbol>, not_implemented: Vec<Symbol>,
}, },
NotBoundInAllPatterns {
unbound_symbol: Symbol,
region: Region,
},
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]

View file

@ -7370,11 +7370,11 @@ mod solve_expr {
"# "#
), ),
@r###" @r###"
A "" : [A Str, B Str] A "" : [A Str, B Str]
x : Str x : Str
x : Str x : Str
x : Str x : Str
"### "###
); );
} }
} }

View file

@ -902,6 +902,27 @@ pub fn can_problem<'b>(
title = INCOMPLETE_ABILITY_IMPLEMENTATION.to_string(); title = INCOMPLETE_ABILITY_IMPLEMENTATION.to_string();
severity = Severity::RuntimeError; severity = Severity::RuntimeError;
} }
Problem::NotBoundInAllPatterns {
unbound_symbol,
region,
} => {
doc = alloc.stack([
alloc.concat([
alloc.symbol_unqualified(unbound_symbol),
alloc.reflow(" is not bound in all patterns of this "),
alloc.keyword("when"),
alloc.reflow(" branch"),
]),
alloc.region(lines.convert_region(region)),
alloc.concat([
alloc.reflow("Identifiers introduced in a "),
alloc.keyword("when"),
alloc.reflow(" branch must be bound in all patterns of the branch. Otherwise, the program would crash when it tries to use an identifier that wasn't bound!"),
]),
]);
title = "NAME NOT BOUND IN ALL PATTERNS".to_string();
severity = Severity::RuntimeError;
}
}; };
Report { Report {

View file

@ -9832,4 +9832,47 @@ All branches in an `if` must have the same type!
U16 -> A U16 -> A
"### "###
); );
test_report!(
symbols_not_bound_in_all_patterns,
indoc!(
r#"
when A "" is
A x | B y -> x
"#
),
@r###"
NAME NOT BOUND IN ALL PATTERNS /code/proj/Main.roc
`x` is not bound in all patterns of this `when` branch
5 A x | B y -> x
^
Identifiers introduced in a `when` branch must be bound in all patterns
of the branch. Otherwise, the program would crash when it tries to use
an identifier that wasn't bound!
NAME NOT BOUND IN ALL PATTERNS /code/proj/Main.roc
`y` is not bound in all patterns of this `when` branch
5 A x | B y -> x
^
Identifiers introduced in a `when` branch must be bound in all patterns
of the branch. Otherwise, the program would crash when it tries to use
an identifier that wasn't bound!
UNUSED DEFINITION /code/proj/Main.roc
`y` is not used anywhere in your code.
5 A x | B y -> x
^
If you didn't intend on using `y` then remove it so future readers of
your code don't wonder why it is there.
"###
);
} }