mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
parent
10dd57d45d
commit
2e5fef5231
14 changed files with 289 additions and 46 deletions
|
@ -124,18 +124,19 @@ impl<'a> Env<'a> {
|
|||
Ok(symbol)
|
||||
}
|
||||
None => {
|
||||
let error = RuntimeError::LookupNotInScope(
|
||||
Loc {
|
||||
let error = RuntimeError::LookupNotInScope {
|
||||
loc_name: Loc {
|
||||
value: Ident::from(ident),
|
||||
region,
|
||||
},
|
||||
scope
|
||||
suggestion_options: scope
|
||||
.locals
|
||||
.ident_ids
|
||||
.ident_strs()
|
||||
.map(|(_, string)| string.into())
|
||||
.collect(),
|
||||
);
|
||||
underscored_suggestion_region: None,
|
||||
};
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1019,9 +1019,18 @@ pub fn canonicalize_expr<'a>(
|
|||
}
|
||||
ast::Expr::Underscore(name) => {
|
||||
// we parse underscores, but they are not valid expression syntax
|
||||
|
||||
let problem = roc_problem::can::RuntimeError::MalformedIdentifier(
|
||||
(*name).into(),
|
||||
roc_parse::ident::BadIdent::Underscore(region.start()),
|
||||
if name.is_empty() {
|
||||
roc_parse::ident::BadIdent::UnderscoreAlone(region.start())
|
||||
} else {
|
||||
roc_parse::ident::BadIdent::UnderscoreAtStart {
|
||||
position: region.start(),
|
||||
// Check if there's an ignored identifier with this name in scope (for better error messages)
|
||||
declaration_region: scope.lookup_ignored_local(name),
|
||||
}
|
||||
},
|
||||
region,
|
||||
);
|
||||
|
||||
|
|
|
@ -379,6 +379,12 @@ pub fn canonicalize_pattern<'a>(
|
|||
Err(pattern) => pattern,
|
||||
}
|
||||
}
|
||||
Underscore(name) => {
|
||||
// An underscored identifier can't be used, but we'll still add it to the scope
|
||||
// for better error messages if someone tries to use it.
|
||||
scope.introduce_ignored_local(name, region);
|
||||
Pattern::Underscore
|
||||
}
|
||||
Tag(name) => {
|
||||
// Canonicalize the tag's name.
|
||||
Pattern::AppliedTag {
|
||||
|
@ -479,8 +485,6 @@ pub fn canonicalize_pattern<'a>(
|
|||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
Underscore(_) => Pattern::Underscore,
|
||||
|
||||
&NumLiteral(str) => match pattern_type {
|
||||
WhenBranch => match finish_parsing_num(str) {
|
||||
Err(_error) => {
|
||||
|
|
|
@ -41,6 +41,10 @@ pub struct Scope {
|
|||
|
||||
/// Identifiers that are in scope, and defined in the current module
|
||||
pub locals: ScopedIdentIds,
|
||||
|
||||
/// Ignored variables (variables that start with an underscore).
|
||||
/// We won't intern them because they're only used during canonicalization for error reporting.
|
||||
ignored_locals: VecMap<String, Region>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
|
@ -65,6 +69,7 @@ impl Scope {
|
|||
abilities_store: starting_abilities_store,
|
||||
shadows: VecMap::default(),
|
||||
imports: default_imports,
|
||||
ignored_locals: VecMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,13 +94,17 @@ impl Scope {
|
|||
match self.scope_contains_ident(ident) {
|
||||
InScope(symbol, _) => Ok(symbol),
|
||||
NotInScope(_) | NotPresent => {
|
||||
let error = RuntimeError::LookupNotInScope(
|
||||
Loc {
|
||||
// identifier not found
|
||||
|
||||
let error = RuntimeError::LookupNotInScope {
|
||||
loc_name: Loc {
|
||||
region,
|
||||
value: Ident::from(ident),
|
||||
},
|
||||
self.idents_in_scope().map(|v| v.as_ref().into()).collect(),
|
||||
);
|
||||
suggestion_options: self.idents_in_scope().map(|v| v.as_ref().into()).collect(),
|
||||
// Check if the user just forgot to remove an underscore from an ignored identifier
|
||||
underscored_suggestion_region: self.lookup_ignored_local(ident),
|
||||
};
|
||||
|
||||
Err(error)
|
||||
}
|
||||
|
@ -418,11 +427,13 @@ impl Scope {
|
|||
// - exposed_ident_count: unchanged
|
||||
// - home: unchanged
|
||||
let aliases_count = self.aliases.len();
|
||||
let ignored_locals_count = self.ignored_locals.len();
|
||||
let locals_snapshot = self.locals.in_scope.len();
|
||||
|
||||
let result = f(self);
|
||||
|
||||
self.aliases.truncate(aliases_count);
|
||||
self.ignored_locals.truncate(ignored_locals_count);
|
||||
|
||||
// anything added in the inner scope is no longer in scope now
|
||||
for i in locals_snapshot..self.locals.in_scope.len() {
|
||||
|
@ -444,6 +455,19 @@ impl Scope {
|
|||
pub fn gen_unique_symbol(&mut self) -> Symbol {
|
||||
Symbol::new(self.home, self.locals.gen_unique())
|
||||
}
|
||||
|
||||
/// Introduce a new ignored variable (variable starting with an underscore).
|
||||
/// The underscore itself should not be included in `ident`.
|
||||
pub fn introduce_ignored_local(&mut self, ident: &str, region: Region) {
|
||||
self.ignored_locals.insert(ident.to_owned(), region);
|
||||
}
|
||||
|
||||
/// Lookup an ignored variable (variable starting with an underscore).
|
||||
/// The underscore itself should not be included in `ident`.
|
||||
/// Returns the source code region of the ignored variable if it's found.
|
||||
pub fn lookup_ignored_local(&self, ident: &str) -> Option<Region> {
|
||||
self.ignored_locals.get(&ident.to_owned()).copied()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_alias(
|
||||
|
|
|
@ -332,7 +332,7 @@ mod test_can {
|
|||
matches!(
|
||||
problem,
|
||||
Problem::SignatureDefMismatch { .. }
|
||||
| Problem::RuntimeError(RuntimeError::LookupNotInScope(_, _))
|
||||
| Problem::RuntimeError(RuntimeError::LookupNotInScope { .. })
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
@ -360,7 +360,7 @@ mod test_can {
|
|||
matches!(
|
||||
problem,
|
||||
Problem::SignatureDefMismatch { .. }
|
||||
| Problem::RuntimeError(RuntimeError::LookupNotInScope(_, _))
|
||||
| Problem::RuntimeError(RuntimeError::LookupNotInScope { .. })
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -761,7 +761,15 @@ fn remove_spaces_bad_ident(ident: BadIdent) -> BadIdent {
|
|||
match ident {
|
||||
BadIdent::Start(_) => BadIdent::Start(Position::zero()),
|
||||
BadIdent::Space(e, _) => BadIdent::Space(e, Position::zero()),
|
||||
BadIdent::Underscore(_) => BadIdent::Underscore(Position::zero()),
|
||||
BadIdent::UnderscoreAlone(_) => BadIdent::UnderscoreAlone(Position::zero()),
|
||||
BadIdent::UnderscoreInMiddle(_) => BadIdent::UnderscoreInMiddle(Position::zero()),
|
||||
BadIdent::UnderscoreAtStart {
|
||||
position: _,
|
||||
declaration_region,
|
||||
} => BadIdent::UnderscoreAtStart {
|
||||
position: Position::zero(),
|
||||
declaration_region,
|
||||
},
|
||||
BadIdent::QualifiedTag(_) => BadIdent::QualifiedTag(Position::zero()),
|
||||
BadIdent::WeirdAccessor(_) => BadIdent::WeirdAccessor(Position::zero()),
|
||||
BadIdent::WeirdDotAccess(_) => BadIdent::WeirdDotAccess(Position::zero()),
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::parser::{BadInputError, EExpr, ParseResult, Parser};
|
|||
use crate::state::State;
|
||||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_region::all::Position;
|
||||
use roc_region::all::{Position, Region};
|
||||
|
||||
/// A tag, for example. Must start with an uppercase letter
|
||||
/// and then contain only letters and numbers afterwards - no dots allowed!
|
||||
|
@ -254,7 +254,14 @@ pub enum BadIdent {
|
|||
Start(Position),
|
||||
Space(BadInputError, Position),
|
||||
|
||||
Underscore(Position),
|
||||
UnderscoreAlone(Position),
|
||||
UnderscoreInMiddle(Position),
|
||||
UnderscoreAtStart {
|
||||
position: Position,
|
||||
/// If this variable was already declared in a pattern (e.g. \_x -> _x),
|
||||
/// then this is where it was declared.
|
||||
declaration_region: Option<Region>,
|
||||
},
|
||||
QualifiedTag(Position),
|
||||
WeirdAccessor(Position),
|
||||
WeirdDotAccess(Position),
|
||||
|
@ -529,7 +536,7 @@ fn chomp_identifier_chain<'a>(
|
|||
// to give good error messages for this case
|
||||
Err((
|
||||
chomped as u32 + 1,
|
||||
BadIdent::Underscore(pos.bump_column(chomped as u32 + 1)),
|
||||
BadIdent::UnderscoreInMiddle(pos.bump_column(chomped as u32 + 1)),
|
||||
))
|
||||
} else if first_is_uppercase {
|
||||
// just one segment, starting with an uppercase letter; that's a tag
|
||||
|
|
|
@ -335,7 +335,11 @@ impl Problem {
|
|||
})
|
||||
| Problem::RuntimeError(RuntimeError::UnsupportedPattern(region))
|
||||
| Problem::RuntimeError(RuntimeError::MalformedPattern(_, region))
|
||||
| Problem::RuntimeError(RuntimeError::LookupNotInScope(Loc { region, .. }, _))
|
||||
| Problem::RuntimeError(RuntimeError::LookupNotInScope {
|
||||
loc_name: Loc { region, .. },
|
||||
suggestion_options: _,
|
||||
underscored_suggestion_region: _,
|
||||
})
|
||||
| Problem::RuntimeError(RuntimeError::OpaqueNotDefined {
|
||||
usage: Loc { region, .. },
|
||||
..
|
||||
|
@ -505,7 +509,14 @@ pub enum RuntimeError {
|
|||
UnresolvedTypeVar,
|
||||
ErroneousType,
|
||||
|
||||
LookupNotInScope(Loc<Ident>, MutSet<Box<str>>),
|
||||
LookupNotInScope {
|
||||
loc_name: Loc<Ident>,
|
||||
/// All of the names in scope (for the error message)
|
||||
suggestion_options: MutSet<Box<str>>,
|
||||
/// If the unfound variable is `name` and there's an ignored variable called `_name`,
|
||||
/// this is the region where `_name` is defined (for the error message)
|
||||
underscored_suggestion_region: Option<Region>,
|
||||
},
|
||||
OpaqueNotDefined {
|
||||
usage: Loc<Ident>,
|
||||
opaques_in_scope: MutSet<Box<str>>,
|
||||
|
|
|
@ -2,7 +2,7 @@ Closure(
|
|||
[
|
||||
@1-11 MalformedIdent(
|
||||
"the_answer",
|
||||
Underscore(
|
||||
UnderscoreInMiddle(
|
||||
@5,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -31,7 +31,7 @@ Defs(
|
|||
@5-8 SpaceBefore(
|
||||
MalformedIdent(
|
||||
"n_p",
|
||||
Underscore(
|
||||
UnderscoreInMiddle(
|
||||
@7,
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue