mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
Report errors for identifiers not bound in all patterns of a branch
This commit is contained in:
parent
ce8b50caea
commit
78dc82867a
6 changed files with 154 additions and 20 deletions
|
@ -1270,6 +1270,59 @@ fn canonicalize_closure_body<'a>(
|
|||
(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)]
|
||||
fn canonicalize_when_branch<'a>(
|
||||
env: &mut Env<'a>,
|
||||
|
@ -1280,6 +1333,7 @@ fn canonicalize_when_branch<'a>(
|
|||
output: &mut Output,
|
||||
) -> (WhenBranch, References) {
|
||||
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
|
||||
for loc_pattern in branch.patterns.iter() {
|
||||
|
@ -1294,9 +1348,17 @@ fn canonicalize_when_branch<'a>(
|
|||
PermitShadows(true),
|
||||
);
|
||||
|
||||
multi_pattern_variables.add_pattern(&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(
|
||||
env,
|
||||
var_store,
|
||||
|
|
|
@ -1843,18 +1843,9 @@ fn constrain_when_branch_help(
|
|||
if i == 0 {
|
||||
state.headers.extend(partial_state.headers);
|
||||
} 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.
|
||||
for (sym, typ1) in state.headers.iter() {
|
||||
let typ2 = partial_state
|
||||
.headers
|
||||
.get(sym)
|
||||
.expect("bound variable in branch not bound in pattern!");
|
||||
|
||||
if let Some(typ2) = partial_state.headers.get(sym) {
|
||||
state.constraints.push(constraints.equal_types(
|
||||
typ1.value.clone(),
|
||||
Expected::NoExpectation(typ2.value.clone()),
|
||||
|
@ -1862,6 +1853,19 @@ fn constrain_when_branch_help(
|
|||
typ2.region,
|
||||
));
|
||||
}
|
||||
|
||||
// If the pattern doesn't bind all symbols introduced in the branch we'll have
|
||||
// reported a canonicalization error, but still might reach here; that's okay.
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -165,6 +165,10 @@ pub enum Problem {
|
|||
ability: Symbol,
|
||||
not_implemented: Vec<Symbol>,
|
||||
},
|
||||
NotBoundInAllPatterns {
|
||||
unbound_symbol: Symbol,
|
||||
region: Region,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
|
|
@ -902,6 +902,27 @@ pub fn can_problem<'b>(
|
|||
title = INCOMPLETE_ABILITY_IMPLEMENTATION.to_string();
|
||||
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 {
|
||||
|
|
|
@ -9832,4 +9832,47 @@ All branches in an `if` must have the same type!
|
|||
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.
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue