don't add guarded record fields into scope

This commit is contained in:
Folkert 2020-04-09 01:06:26 +02:00
parent 85e2cf4465
commit fd7ca5bcc9
8 changed files with 196 additions and 79 deletions

View file

@ -267,45 +267,26 @@ pub fn canonicalize_pattern<'a>(
}; };
} }
RecordField(label, loc_guard) => { RecordField(label, loc_guard) => {
match scope.introduce( // a guard does not introduce the label into scope!
label.into(), let symbol = scope.ignore(label.into(), &mut env.ident_ids);
&env.exposed_ident_ids, let can_guard = canonicalize_pattern(
&mut env.ident_ids, env,
region, var_store,
) { scope,
Ok(symbol) => { pattern_type,
let can_guard = canonicalize_pattern( &loc_guard.value,
env, loc_guard.region,
var_store, );
scope,
pattern_type,
&loc_guard.value,
loc_guard.region,
);
destructs.push(Located { destructs.push(Located {
region: loc_pattern.region, region: loc_pattern.region,
value: RecordDestruct { value: RecordDestruct {
var: var_store.fresh(), var: var_store.fresh(),
label: Lowercase::from(label), label: Lowercase::from(label),
symbol, symbol,
guard: Some((var_store.fresh(), can_guard)), guard: Some((var_store.fresh(), can_guard)),
}, },
}); });
}
Err((original_region, shadow)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
}));
// No matter what the other patterns
// are, we're definitely shadowed and will
// get a runtime exception as soon as we
// encounter the first bad pattern.
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow));
}
};
} }
_ => panic!("invalid pattern in record"), _ => panic!("invalid pattern in record"),
} }

View file

@ -57,10 +57,13 @@ impl Scope {
pub fn lookup(&mut self, ident: &Ident, region: Region) -> Result<Symbol, RuntimeError> { pub fn lookup(&mut self, ident: &Ident, region: Region) -> Result<Symbol, RuntimeError> {
match self.idents.get(ident) { match self.idents.get(ident) {
Some((symbol, _)) => Ok(*symbol), Some((symbol, _)) => Ok(*symbol),
None => Err(RuntimeError::LookupNotInScope(Located { None => Err(RuntimeError::LookupNotInScope(
region, Located {
value: ident.clone().into(), region,
})), value: ident.clone().into(),
},
self.idents.keys().map(|v| v.as_ref().into()).collect(),
)),
} }
} }
@ -107,6 +110,14 @@ impl Scope {
} }
} }
/// Ignore an identifier.
///
/// Used for record guards like { x: Just _ }
pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol {
let ident_id = all_ident_ids.add(ident.into());
Symbol::new(self.home, ident_id)
}
/// Import a Symbol from another module into this module's top-level scope. /// Import a Symbol from another module into this module's top-level scope.
/// ///
/// Returns Err if this would shadow an existing ident, including the /// Returns Err if this would shadow an existing ident, including the

View file

@ -1,4 +1,5 @@
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_collections::all::MutSet;
use roc_module::ident::Ident; use roc_module::ident::Ident;
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_parse::operator::BinOp; use roc_parse::operator::BinOp;
@ -37,7 +38,7 @@ pub enum RuntimeError {
// 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),
UnrecognizedFunctionName(Located<InlinableString>), UnrecognizedFunctionName(Located<InlinableString>),
LookupNotInScope(Located<InlinableString>), LookupNotInScope(Located<InlinableString>, MutSet<Box<str>>),
ValueNotExposed { ValueNotExposed {
module_name: InlinableString, module_name: InlinableString,
ident: InlinableString, ident: InlinableString,

View file

@ -1,5 +1,6 @@
use crate::report::ReportText::{BinOp, Concat, Module, Region, Value}; use crate::report::ReportText::{BinOp, Concat, Module, Region, Value};
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::MutSet;
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::PrecedenceProblem::BothNonAssociative;
@ -130,8 +131,16 @@ pub fn can_problem(filename: PathBuf, problem: Problem) -> Report {
shadowing_report(&mut texts, original_region, shadow); shadowing_report(&mut texts, original_region, shadow);
} }
_ => { RuntimeError::LookupNotInScope(loc_name, options) => {
panic!("TODO implement run time error reporting"); texts.push(not_found(
loc_name.region,
&loc_name.value,
"value",
options,
));
}
other => {
todo!("TODO implement run time error reporting for {:?}", other);
} }
}, },
}; };
@ -156,6 +165,53 @@ fn shadowing_report(
texts.push(plain_text("Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name.")); texts.push(plain_text("Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."));
} }
fn not_found(
region: roc_region::all::Region,
name: &str,
thing: &str,
options: MutSet<Box<str>>,
) -> ReportText {
use crate::type_error::suggest;
let mut suggestions = suggest::sort(name, options.iter().map(|v| v.as_ref()).collect());
suggestions.truncate(4);
let to_details = |no_suggestion_details, yes_suggestion_details| {
if suggestions.is_empty() {
no_suggestion_details
} else {
ReportText::Stack(vec![
yes_suggestion_details,
ReportText::Indent(
4,
Box::new(ReportText::Stack(
suggestions
.into_iter()
.map(|v: &str| plain_text(v))
.collect(),
)),
),
])
}
};
let default_no = ReportText::Concat(vec![
plain_text("Is there an "),
keyword_text("import"),
plain_text(" or "),
keyword_text("exposing"),
plain_text(" missing up-top?"),
]);
let default_yes = plain_text("these names seem close though:");
ReportText::Stack(vec![
plain_text(&format!("I cannot find a `{}` {}", name, thing)),
ReportText::Region(region),
to_details(default_no, default_yes),
])
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ReportText { pub enum ReportText {
/// A value. Render it qualified unless it was defined in the current module. /// A value. Render it qualified unless it was defined in the current module.

View file

@ -882,6 +882,12 @@ pub mod suggest {
} }
} }
impl ToStr for &str {
fn to_str(&self) -> &str {
self
}
}
pub fn sort<'a, T>(typo: &'a str, mut options: Vec<T>) -> Vec<T> pub fn sort<'a, T>(typo: &'a str, mut options: Vec<T>) -> Vec<T>
where where
T: ToStr, T: ToStr,

View file

@ -1500,36 +1500,98 @@ mod test_reporting {
) )
} }
// Currently hits a bug where `x` is marked as unused #[test]
// https://github.com/rtfeldman/roc/issues/304 fn pattern_guard_mismatch() {
// #[test] report_problem_as(
// fn pattern_guard_mismatch() { indoc!(
// report_problem_as( r#"
// indoc!( when { foo: 1 } is
// r#" { foo: True } -> 42
// when { foo: 1 } is "#
// { x: True } -> 42 ),
// "# indoc!(
// ), r#"
// indoc!( The 1st pattern in this `when` is causing a mismatch:
// r#"
// The 2nd pattern in this `when` does not match the previous ones: 2 { foo: True } -> 42
// ^^^^^^^^^^^^^
// 3 ┆ {} -> 42
// ┆ ^^ The first pattern is trying to match record values of type:
//
// The 2nd pattern is trying to match record values of type: { foo : [ True ]a }
//
// {}a But the expression between `when` and `is` has the type:
//
// But all the previous branches match: { foo : Num a }
//
// Num a "#
// ),
// "# )
// ), }
// )
// } #[test]
fn pattern_guard_does_not_bind_label() {
// needs some improvement, but the principle works
report_problem_as(
indoc!(
r#"
when { foo: 1 } is
{ foo: 2 } -> foo
"#
),
indoc!(
r#"
I cannot find a `foo` value
2 { foo: 2 } -> foo
^^^
these names seem close though:
Bool
Int
Num
Map
"#
),
)
}
#[test]
fn pattern_guard_can_be_shadowed_above() {
report_problem_as(
indoc!(
r#"
foo = 3
when { foo: 1 } is
{ foo: 2 } -> foo
"#
),
// should give no error
"",
)
}
#[test]
fn pattern_guard_can_be_shadowed_below() {
report_problem_as(
indoc!(
r#"
when { foo: 1 } is
{ foo: 2 } ->
foo = 3
foo
"#
),
// should give no error
"",
)
}
#[test] #[test]
fn pattern_or_pattern_mismatch() { fn pattern_or_pattern_mismatch() {

View file

@ -1004,7 +1004,7 @@ mod test_solve {
xEmpty = if thunk {} == 42 then { x: {} } else { x: {} } xEmpty = if thunk {} == 42 then { x: {} } else { x: {} }
when xEmpty is when xEmpty is
{ x: {} } -> x { x: {} } -> {}
"# "#
), ),
"{}", "{}",
@ -1138,7 +1138,7 @@ mod test_solve {
indoc!( indoc!(
r#" r#"
when { x: 5 } is when { x: 5 } is
{ x: 4 } -> x { x: 4 } -> 4
"# "#
), ),
"Num *", "Num *",

View file

@ -908,7 +908,7 @@ mod test_uniq_solve {
xEmpty = if thunk {} == 42 then { x: {} } else { x: {} } xEmpty = if thunk {} == 42 then { x: {} } else { x: {} }
when xEmpty is when xEmpty is
{ x: {} } -> x { x: {} } -> {}
"# "#
), ),
"Attr * {}", "Attr * {}",
@ -1046,7 +1046,7 @@ mod test_uniq_solve {
indoc!( indoc!(
r#" r#"
when { x: 5 } is when { x: 5 } is
{ x: 4 } -> x { x: 4 } -> 4
"# "#
), ),
"Attr * (Num (Attr * *))", "Attr * (Num (Attr * *))",