mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Merge pull request #2393 from rtfeldman/i/2343
Generate unique symbols for shadowing identifiers
This commit is contained in:
commit
6dd769aa38
11 changed files with 89 additions and 35 deletions
|
@ -1,7 +1,7 @@
|
||||||
use const_format::concatcp;
|
use const_format::concatcp;
|
||||||
#[cfg(feature = "llvm")]
|
#[cfg(feature = "llvm")]
|
||||||
use gen::{gen_and_eval, ReplOutput};
|
use gen::{gen_and_eval, ReplOutput};
|
||||||
use roc_parse::parser::{EExpr, SyntaxError};
|
use roc_parse::parser::{EExpr, ELambda, SyntaxError};
|
||||||
use rustyline::highlight::{Highlighter, PromptInfo};
|
use rustyline::highlight::{Highlighter, PromptInfo};
|
||||||
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
||||||
use rustyline_derive::{Completer, Helper, Hinter};
|
use rustyline_derive::{Completer, Helper, Hinter};
|
||||||
|
@ -97,7 +97,8 @@ impl Validator for InputValidator {
|
||||||
match roc_parse::expr::parse_loc_expr(0, &arena, state) {
|
match roc_parse::expr::parse_loc_expr(0, &arena, state) {
|
||||||
// Special case some syntax errors to allow for multi-line inputs
|
// Special case some syntax errors to allow for multi-line inputs
|
||||||
Err((_, EExpr::DefMissingFinalExpr(_), _))
|
Err((_, EExpr::DefMissingFinalExpr(_), _))
|
||||||
| Err((_, EExpr::DefMissingFinalExpr2(_, _), _)) => {
|
| Err((_, EExpr::DefMissingFinalExpr2(_, _), _))
|
||||||
|
| Err((_, EExpr::Lambda(ELambda::Body(_, _), _), _)) => {
|
||||||
Ok(ValidationResult::Incomplete)
|
Ok(ValidationResult::Incomplete)
|
||||||
}
|
}
|
||||||
_ => Ok(ValidationResult::Valid(None)),
|
_ => Ok(ValidationResult::Valid(None)),
|
||||||
|
|
|
@ -925,4 +925,38 @@ mod repl_eval {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn issue_2343_complete_mono_with_shadowed_vars() {
|
||||||
|
expect_failure(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
b = False
|
||||||
|
f = \b ->
|
||||||
|
when b is
|
||||||
|
True -> 5
|
||||||
|
False -> 15
|
||||||
|
f b
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── DUPLICATE NAME ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
The b name is first defined here:
|
||||||
|
|
||||||
|
4│ b = False
|
||||||
|
^
|
||||||
|
|
||||||
|
But then it's defined a second time here:
|
||||||
|
|
||||||
|
5│ f = \b ->
|
||||||
|
^
|
||||||
|
|
||||||
|
Since these variables have the same name, it's easy to use the wrong
|
||||||
|
one on accident. Give one of them a new name.
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -390,7 +390,7 @@ fn can_annotation_help(
|
||||||
) {
|
) {
|
||||||
Ok(symbol) => symbol,
|
Ok(symbol) => symbol,
|
||||||
|
|
||||||
Err((original_region, shadow)) => {
|
Err((original_region, shadow, _new_symbol)) => {
|
||||||
let problem = Problem::Shadowed(original_region, shadow.clone());
|
let problem = Problem::Shadowed(original_region, shadow.clone());
|
||||||
|
|
||||||
env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
|
env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
|
||||||
|
|
|
@ -809,7 +809,7 @@ fn pattern_to_vars_by_symbol(
|
||||||
) {
|
) {
|
||||||
use Pattern::*;
|
use Pattern::*;
|
||||||
match pattern {
|
match pattern {
|
||||||
Identifier(symbol) => {
|
Identifier(symbol) | Shadowed(_, _, symbol) => {
|
||||||
vars_by_symbol.insert(*symbol, expr_var);
|
vars_by_symbol.insert(*symbol, expr_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -832,8 +832,6 @@ fn pattern_to_vars_by_symbol(
|
||||||
| Underscore
|
| Underscore
|
||||||
| MalformedPattern(_, _)
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_) => {}
|
| UnsupportedPattern(_) => {}
|
||||||
|
|
||||||
Shadowed(_, _) => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -884,7 +882,7 @@ fn canonicalize_pending_def<'a>(
|
||||||
Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed {
|
Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed {
|
||||||
def_symbol: *symbol,
|
def_symbol: *symbol,
|
||||||
},
|
},
|
||||||
Pattern::Shadowed(region, loc_ident) => RuntimeError::Shadowing {
|
Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing {
|
||||||
original_region: *region,
|
original_region: *region,
|
||||||
shadow: loc_ident.clone(),
|
shadow: loc_ident.clone(),
|
||||||
},
|
},
|
||||||
|
@ -1502,7 +1500,7 @@ fn to_pending_def<'a>(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
Err((original_region, loc_shadowed_symbol)) => {
|
Err((original_region, loc_shadowed_symbol, _new_symbol)) => {
|
||||||
env.problem(Problem::ShadowingInAnnotation {
|
env.problem(Problem::ShadowingInAnnotation {
|
||||||
original_region,
|
original_region,
|
||||||
shadow: loc_shadowed_symbol,
|
shadow: loc_shadowed_symbol,
|
||||||
|
|
|
@ -398,7 +398,7 @@ fn fix_values_captured_in_closure_pattern(
|
||||||
| FloatLiteral(_, _, _)
|
| FloatLiteral(_, _, _)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
| Underscore
|
| Underscore
|
||||||
| Shadowed(_, _)
|
| Shadowed(..)
|
||||||
| MalformedPattern(_, _)
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_) => (),
|
| UnsupportedPattern(_) => (),
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub enum Pattern {
|
||||||
Underscore,
|
Underscore,
|
||||||
|
|
||||||
// Runtime Exceptions
|
// Runtime Exceptions
|
||||||
Shadowed(Region, Loc<Ident>),
|
Shadowed(Region, Loc<Ident>, Symbol),
|
||||||
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||||
UnsupportedPattern(Region),
|
UnsupportedPattern(Region),
|
||||||
// parse error patterns
|
// parse error patterns
|
||||||
|
@ -65,7 +65,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
||||||
use Pattern::*;
|
use Pattern::*;
|
||||||
|
|
||||||
match pattern {
|
match pattern {
|
||||||
Identifier(symbol) => {
|
Identifier(symbol) | Shadowed(_, _, symbol) => {
|
||||||
symbols.push(*symbol);
|
symbols.push(*symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,8 +92,6 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
||||||
| Underscore
|
| Underscore
|
||||||
| MalformedPattern(_, _)
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_) => {}
|
| UnsupportedPattern(_) => {}
|
||||||
|
|
||||||
Shadowed(_, _) => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,13 +119,14 @@ pub fn canonicalize_pattern<'a>(
|
||||||
|
|
||||||
Pattern::Identifier(symbol)
|
Pattern::Identifier(symbol)
|
||||||
}
|
}
|
||||||
Err((original_region, shadow)) => {
|
Err((original_region, shadow, new_symbol)) => {
|
||||||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||||
original_region,
|
original_region,
|
||||||
shadow: shadow.clone(),
|
shadow: shadow.clone(),
|
||||||
}));
|
}));
|
||||||
|
output.references.bound_symbols.insert(new_symbol);
|
||||||
|
|
||||||
Pattern::Shadowed(original_region, shadow)
|
Pattern::Shadowed(original_region, shadow, new_symbol)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GlobalTag(name) => {
|
GlobalTag(name) => {
|
||||||
|
@ -268,7 +267,7 @@ pub fn canonicalize_pattern<'a>(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err((original_region, shadow)) => {
|
Err((original_region, shadow, new_symbol)) => {
|
||||||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||||
original_region,
|
original_region,
|
||||||
shadow: shadow.clone(),
|
shadow: shadow.clone(),
|
||||||
|
@ -278,7 +277,8 @@ pub fn canonicalize_pattern<'a>(
|
||||||
// are, we're definitely shadowed and will
|
// are, we're definitely shadowed and will
|
||||||
// get a runtime exception as soon as we
|
// get a runtime exception as soon as we
|
||||||
// encounter the first bad pattern.
|
// encounter the first bad pattern.
|
||||||
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow));
|
opt_erroneous =
|
||||||
|
Some(Pattern::Shadowed(original_region, shadow, new_symbol));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -339,7 +339,7 @@ pub fn canonicalize_pattern<'a>(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err((original_region, shadow)) => {
|
Err((original_region, shadow, new_symbol)) => {
|
||||||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||||
original_region,
|
original_region,
|
||||||
shadow: shadow.clone(),
|
shadow: shadow.clone(),
|
||||||
|
@ -349,7 +349,8 @@ pub fn canonicalize_pattern<'a>(
|
||||||
// are, we're definitely shadowed and will
|
// are, we're definitely shadowed and will
|
||||||
// get a runtime exception as soon as we
|
// get a runtime exception as soon as we
|
||||||
// encounter the first bad pattern.
|
// encounter the first bad pattern.
|
||||||
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow));
|
opt_erroneous =
|
||||||
|
Some(Pattern::Shadowed(original_region, shadow, new_symbol));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -452,7 +453,7 @@ fn add_bindings_from_patterns(
|
||||||
use Pattern::*;
|
use Pattern::*;
|
||||||
|
|
||||||
match pattern {
|
match pattern {
|
||||||
Identifier(symbol) => {
|
Identifier(symbol) | Shadowed(_, _, symbol) => {
|
||||||
answer.push((*symbol, *region));
|
answer.push((*symbol, *region));
|
||||||
}
|
}
|
||||||
AppliedTag {
|
AppliedTag {
|
||||||
|
@ -477,7 +478,6 @@ fn add_bindings_from_patterns(
|
||||||
| FloatLiteral(_, _, _)
|
| FloatLiteral(_, _, _)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
| Underscore
|
| Underscore
|
||||||
| Shadowed(_, _)
|
|
||||||
| MalformedPattern(_, _)
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_) => (),
|
| UnsupportedPattern(_) => (),
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,15 +110,21 @@ impl Scope {
|
||||||
exposed_ident_ids: &IdentIds,
|
exposed_ident_ids: &IdentIds,
|
||||||
all_ident_ids: &mut IdentIds,
|
all_ident_ids: &mut IdentIds,
|
||||||
region: Region,
|
region: Region,
|
||||||
) -> Result<Symbol, (Region, Loc<Ident>)> {
|
) -> Result<Symbol, (Region, Loc<Ident>, Symbol)> {
|
||||||
match self.idents.get(&ident) {
|
match self.idents.get(&ident) {
|
||||||
Some((_, original_region)) => {
|
Some(&(_, original_region)) => {
|
||||||
let shadow = Loc {
|
let shadow = Loc {
|
||||||
value: ident,
|
value: ident.clone(),
|
||||||
region,
|
region,
|
||||||
};
|
};
|
||||||
|
|
||||||
Err((*original_region, shadow))
|
let ident_id = all_ident_ids.add(ident.clone());
|
||||||
|
let symbol = Symbol::new(self.home, ident_id);
|
||||||
|
|
||||||
|
self.symbols.insert(symbol, region);
|
||||||
|
self.idents.insert(ident, (symbol, region));
|
||||||
|
|
||||||
|
Err((original_region, shadow, symbol))
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// If this IdentId was already added previously
|
// If this IdentId was already added previously
|
||||||
|
|
|
@ -368,9 +368,11 @@ mod test_can {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||||
|
|
||||||
assert_eq!(problems.len(), 1);
|
assert_eq!(problems.len(), 2);
|
||||||
assert!(problems.iter().all(|problem| match problem {
|
assert!(problems.iter().all(|problem| match problem {
|
||||||
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
|
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
|
||||||
|
// Due to one of the shadows
|
||||||
|
Problem::UnusedDef(..) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -389,9 +391,11 @@ mod test_can {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||||
|
|
||||||
assert_eq!(problems.len(), 1);
|
assert_eq!(problems.len(), 2);
|
||||||
assert!(problems.iter().all(|problem| match problem {
|
assert!(problems.iter().all(|problem| match problem {
|
||||||
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
|
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
|
||||||
|
// Due to one of the shadows
|
||||||
|
Problem::UnusedDef(..) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -410,10 +414,12 @@ mod test_can {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||||
|
|
||||||
assert_eq!(problems.len(), 1);
|
assert_eq!(problems.len(), 2);
|
||||||
println!("{:#?}", problems);
|
println!("{:#?}", problems);
|
||||||
assert!(problems.iter().all(|problem| match problem {
|
assert!(problems.iter().all(|problem| match problem {
|
||||||
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
|
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
|
||||||
|
// Due to one of the shadows
|
||||||
|
Problem::UnusedDef(..) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,12 +48,11 @@ fn headers_from_annotation_help(
|
||||||
headers: &mut SendMap<Symbol, Loc<Type>>,
|
headers: &mut SendMap<Symbol, Loc<Type>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match pattern {
|
match pattern {
|
||||||
Identifier(symbol) => {
|
Identifier(symbol) | Shadowed(_, _, symbol) => {
|
||||||
headers.insert(*symbol, annotation.clone());
|
headers.insert(*symbol, annotation.clone());
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Underscore
|
Underscore
|
||||||
| Shadowed(_, _)
|
|
||||||
| MalformedPattern(_, _)
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_)
|
| UnsupportedPattern(_)
|
||||||
| NumLiteral(_, _, _)
|
| NumLiteral(_, _, _)
|
||||||
|
@ -159,11 +158,11 @@ pub fn constrain_pattern(
|
||||||
PresenceConstraint::IsOpen,
|
PresenceConstraint::IsOpen,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => {
|
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) => {
|
||||||
// Neither the _ pattern nor erroneous ones add any constraints.
|
// Neither the _ pattern nor erroneous ones add any constraints.
|
||||||
}
|
}
|
||||||
|
|
||||||
Identifier(symbol) => {
|
Identifier(symbol) | Shadowed(_, _, symbol) => {
|
||||||
if destruct_position {
|
if destruct_position {
|
||||||
state.constraints.push(Constraint::Present(
|
state.constraints.push(Constraint::Present(
|
||||||
expected.get_type_ref().clone(),
|
expected.get_type_ref().clone(),
|
||||||
|
|
|
@ -1980,12 +1980,12 @@ fn pattern_to_when<'a>(
|
||||||
// for underscore we generate a dummy Symbol
|
// for underscore we generate a dummy Symbol
|
||||||
(env.unique_symbol(), body)
|
(env.unique_symbol(), body)
|
||||||
}
|
}
|
||||||
Shadowed(region, loc_ident) => {
|
Shadowed(region, loc_ident, new_symbol) => {
|
||||||
let error = roc_problem::can::RuntimeError::Shadowing {
|
let error = roc_problem::can::RuntimeError::Shadowing {
|
||||||
original_region: *region,
|
original_region: *region,
|
||||||
shadow: loc_ident.clone(),
|
shadow: loc_ident.clone(),
|
||||||
};
|
};
|
||||||
(env.unique_symbol(), Loc::at_zero(RuntimeError(error)))
|
(*new_symbol, Loc::at_zero(RuntimeError(error)))
|
||||||
}
|
}
|
||||||
|
|
||||||
UnsupportedPattern(region) => {
|
UnsupportedPattern(region) => {
|
||||||
|
@ -7653,7 +7653,7 @@ fn from_can_pattern_help<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
|
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
|
||||||
Shadowed(region, ident) => Err(RuntimeError::Shadowing {
|
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
|
||||||
original_region: *region,
|
original_region: *region,
|
||||||
shadow: ident.clone(),
|
shadow: ident.clone(),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -424,6 +424,16 @@ mod test_reporting {
|
||||||
|
|
||||||
`Booly` is not used anywhere in your code.
|
`Booly` is not used anywhere in your code.
|
||||||
|
|
||||||
|
3│ Booly : [ Yes, No, Maybe ]
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If you didn't intend on using `Booly` then remove it so future readers
|
||||||
|
of your code don't wonder why it is there.
|
||||||
|
|
||||||
|
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
`Booly` is not used anywhere in your code.
|
||||||
|
|
||||||
1│ Booly : [ Yes, No ]
|
1│ Booly : [ Yes, No ]
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue