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)
|
(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,
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
"###
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue