mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-21 05:25:00 +00:00
829 lines
27 KiB
Rust
829 lines
27 KiB
Rust
use roc_collections::{VecMap, VecSet};
|
|
use roc_module::ident::Ident;
|
|
use roc_module::symbol::{IdentId, IdentIds, ModuleId, Symbol};
|
|
use roc_problem::can::RuntimeError;
|
|
use roc_region::all::{Loc, Region};
|
|
use roc_types::subs::Variable;
|
|
use roc_types::types::{Alias, AliasKind, AliasVar, Type};
|
|
|
|
use crate::abilities::PendingAbilitiesStore;
|
|
|
|
use bitvec::vec::BitVec;
|
|
|
|
// ability -> member names
|
|
pub(crate) type PendingAbilitiesInScope = VecMap<Symbol, VecSet<Symbol>>;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Scope {
|
|
/// The type aliases currently in scope
|
|
pub aliases: VecMap<Symbol, Alias>,
|
|
|
|
/// The abilities currently in scope, and their implementors.
|
|
pub abilities_store: PendingAbilitiesStore,
|
|
|
|
/// The current module being processed. This will be used to turn
|
|
/// unqualified idents into Symbols.
|
|
home: ModuleId,
|
|
|
|
/// The first `exposed_ident_count` identifiers are exposed
|
|
exposed_ident_count: usize,
|
|
|
|
/// Identifiers that are imported (and introduced in the header)
|
|
imports: Vec<(Ident, Symbol, Region)>,
|
|
|
|
/// Shadows of an ability member, for example a local specialization of `eq` for the ability
|
|
/// member `Eq has eq : a, a -> Bool | a has Eq` gets a shadow symbol it can use for its
|
|
/// implementation.
|
|
///
|
|
/// Only one shadow of an ability member is permitted per scope.
|
|
shadows: VecMap<Symbol, Loc<Symbol>>,
|
|
|
|
/// Identifiers that are in scope, and defined in the current module
|
|
pub locals: ScopedIdentIds,
|
|
}
|
|
|
|
impl Scope {
|
|
pub fn new(
|
|
home: ModuleId,
|
|
initial_ident_ids: IdentIds,
|
|
starting_abilities_store: PendingAbilitiesStore,
|
|
) -> Scope {
|
|
let default_imports =
|
|
// Add all `Apply` types.
|
|
(Symbol::apply_types_in_scope().into_iter())
|
|
// Add all tag names we might want to suggest as hints in error messages.
|
|
.chain(Symbol::symbols_in_scope_for_hints());
|
|
|
|
let default_imports = default_imports.map(|(a, (b, c))| (a, b, c)).collect();
|
|
|
|
Scope {
|
|
home,
|
|
exposed_ident_count: initial_ident_ids.len(),
|
|
locals: ScopedIdentIds::from_ident_ids(home, initial_ident_ids),
|
|
aliases: VecMap::default(),
|
|
abilities_store: starting_abilities_store,
|
|
shadows: VecMap::default(),
|
|
imports: default_imports,
|
|
}
|
|
}
|
|
|
|
pub fn lookup(&self, ident: &Ident, region: Region) -> Result<Symbol, RuntimeError> {
|
|
self.lookup_str(ident.as_str(), region)
|
|
}
|
|
|
|
pub fn lookup_ability_member_shadow(&self, member: Symbol) -> Option<Symbol> {
|
|
self.shadows.get(&member).map(|loc_shadow| loc_shadow.value)
|
|
}
|
|
|
|
pub fn add_docs_imports(&mut self) {
|
|
self.imports
|
|
.push(("Dict".into(), Symbol::DICT_DICT, Region::zero()));
|
|
self.imports
|
|
.push(("Set".into(), Symbol::SET_SET, Region::zero()));
|
|
}
|
|
|
|
pub fn lookup_str(&self, ident: &str, region: Region) -> Result<Symbol, RuntimeError> {
|
|
use ContainsIdent::*;
|
|
|
|
match self.scope_contains_ident(ident) {
|
|
InScope(symbol, _) => Ok(symbol),
|
|
NotInScope(_) | NotPresent => {
|
|
let error = RuntimeError::LookupNotInScope(
|
|
Loc {
|
|
region,
|
|
value: Ident::from(ident),
|
|
},
|
|
self.idents_in_scope().map(|v| v.as_ref().into()).collect(),
|
|
);
|
|
|
|
Err(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn idents_in_scope(&self) -> impl Iterator<Item = Ident> + '_ {
|
|
let it1 = self.locals.idents_in_scope();
|
|
let it2 = self.imports.iter().map(|t| t.0.clone());
|
|
|
|
it2.chain(it1)
|
|
}
|
|
|
|
/// Check if there is an opaque type alias referenced by `opaque_ref` referenced in the
|
|
/// current scope. E.g. `@Age` must reference an opaque `Age` declared in this module, not any
|
|
/// other!
|
|
pub fn lookup_opaque_ref(
|
|
&self,
|
|
opaque_ref: &str,
|
|
lookup_region: Region,
|
|
) -> Result<(Symbol, &Alias), RuntimeError> {
|
|
debug_assert!(opaque_ref.starts_with('@'));
|
|
let opaque_str = &opaque_ref[1..];
|
|
let opaque = opaque_str.into();
|
|
|
|
match self.locals.has_in_scope(&opaque) {
|
|
Some((symbol, _)) => match self.lookup_opaque_alias(symbol) {
|
|
Ok(alias) => Ok((symbol, alias)),
|
|
Err(opt_alias_def_region) => {
|
|
Err(self.opaque_not_defined_error(opaque, lookup_region, opt_alias_def_region))
|
|
}
|
|
},
|
|
None => {
|
|
// opaque types can only be wrapped/unwrapped in the scope they are defined in (and below)
|
|
let error = if let Some((_, decl_region)) = self.has_imported(opaque_str) {
|
|
// specific error for when the opaque is imported, which definitely does not work
|
|
RuntimeError::OpaqueOutsideScope {
|
|
opaque,
|
|
referenced_region: lookup_region,
|
|
imported_region: decl_region,
|
|
}
|
|
} else {
|
|
self.opaque_not_defined_error(opaque, lookup_region, None)
|
|
};
|
|
|
|
Err(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lookup_opaque_alias(&self, symbol: Symbol) -> Result<&Alias, Option<Region>> {
|
|
match self.aliases.get(&symbol) {
|
|
None => Err(None),
|
|
|
|
Some(alias) => match alias.kind {
|
|
AliasKind::Opaque => Ok(alias),
|
|
AliasKind::Structural => Err(Some(alias.header_region())),
|
|
},
|
|
}
|
|
}
|
|
|
|
fn is_opaque(&self, ident_id: IdentId, string: &str) -> Option<Box<str>> {
|
|
if string.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let symbol = Symbol::new(self.home, ident_id);
|
|
|
|
if let Some(AliasKind::Opaque) = self.aliases.get(&symbol).map(|alias| alias.kind) {
|
|
Some(string.into())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn opaque_not_defined_error(
|
|
&self,
|
|
opaque: Ident,
|
|
lookup_region: Region,
|
|
opt_defined_alias: Option<Region>,
|
|
) -> RuntimeError {
|
|
// for opaques, we only look at the locals because opaques can only be matched
|
|
// on in the module that defines them.
|
|
let opaques_in_scope = self
|
|
.locals
|
|
.ident_ids
|
|
.ident_strs()
|
|
.filter_map(|(ident_id, string)| self.is_opaque(ident_id, string))
|
|
.collect();
|
|
|
|
RuntimeError::OpaqueNotDefined {
|
|
usage: Loc::at(lookup_region, opaque),
|
|
opaques_in_scope,
|
|
opt_defined_alias,
|
|
}
|
|
}
|
|
|
|
fn has_imported(&self, ident: &str) -> Option<(Symbol, Region)> {
|
|
for (import, shadow, original_region) in self.imports.iter() {
|
|
if ident == import.as_str() {
|
|
return Some((*shadow, *original_region));
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Is an identifier in scope, either in the locals or imports
|
|
fn scope_contains_ident(&self, ident: &str) -> ContainsIdent {
|
|
// exposed imports are likely to be small
|
|
match self.has_imported(ident) {
|
|
Some((symbol, region)) => ContainsIdent::InScope(symbol, region),
|
|
None => self.locals.contains_ident(ident),
|
|
}
|
|
}
|
|
|
|
fn introduce_help(&mut self, ident: &str, region: Region) -> Result<Symbol, (Symbol, Region)> {
|
|
match self.scope_contains_ident(ident) {
|
|
ContainsIdent::InScope(original_symbol, original_region) => {
|
|
// the ident is already in scope; up to the caller how to handle that
|
|
// (usually it's shadowing, but it is valid to shadow ability members)
|
|
Err((original_symbol, original_region))
|
|
}
|
|
ContainsIdent::NotPresent => {
|
|
// We know nothing about this ident yet; introduce it to the scope
|
|
let ident_id = self.locals.introduce_into_scope(ident, region);
|
|
Ok(Symbol::new(self.home, ident_id))
|
|
}
|
|
ContainsIdent::NotInScope(existing) => {
|
|
// The ident is not in scope, but its name is already in the string interner
|
|
if existing.index() < self.exposed_ident_count {
|
|
// if the identifier is exposed, use the IdentId we already have for it
|
|
// other modules depend on the symbol having that IdentId
|
|
let symbol = Symbol::new(self.home, existing);
|
|
|
|
self.locals.in_scope.set(existing.index(), true);
|
|
self.locals.regions[existing.index()] = region;
|
|
|
|
Ok(symbol)
|
|
} else {
|
|
// create a new IdentId that under the hood uses the same string bytes as an existing one
|
|
let ident_id = self.locals.introduce_into_scope_duplicate(existing, region);
|
|
|
|
Ok(Symbol::new(self.home, ident_id))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Introduce a new ident to scope.
|
|
///
|
|
/// Returns Err if this would shadow an existing ident, including the
|
|
/// Symbol and Region of the ident we already had in scope under that name.
|
|
///
|
|
/// If this ident shadows an existing one, a new ident is allocated for the shadow. This is
|
|
/// done so that all identifiers have unique symbols, which is important in particular when
|
|
/// we generate code for value identifiers.
|
|
/// If this behavior is undesirable, use [`Self::introduce_without_shadow_symbol`].
|
|
pub fn introduce(
|
|
&mut self,
|
|
ident: Ident,
|
|
region: Region,
|
|
) -> Result<Symbol, (Loc<Symbol>, Loc<Ident>, Symbol)> {
|
|
self.introduce_str(ident.as_str(), region)
|
|
}
|
|
|
|
pub fn introduce_str(
|
|
&mut self,
|
|
ident: &str,
|
|
region: Region,
|
|
) -> Result<Symbol, (Loc<Symbol>, Loc<Ident>, Symbol)> {
|
|
match self.introduce_help(ident, region) {
|
|
Ok(symbol) => Ok(symbol),
|
|
Err((shadowed_symbol, original_region)) => {
|
|
let shadow = Loc {
|
|
value: Ident::from(ident),
|
|
region,
|
|
};
|
|
let symbol = self.locals.scopeless_symbol(ident, region);
|
|
|
|
Err((Loc::at(original_region, shadowed_symbol), shadow, symbol))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Like [Self::introduce], but does not introduce a new symbol for the shadowing symbol.
|
|
pub fn introduce_without_shadow_symbol(
|
|
&mut self,
|
|
ident: &Ident,
|
|
region: Region,
|
|
) -> Result<Symbol, (Symbol, Region, Loc<Ident>)> {
|
|
match self.introduce_help(ident.as_str(), region) {
|
|
Err((symbol, original_region)) => {
|
|
let shadow = Loc {
|
|
value: ident.clone(),
|
|
region,
|
|
};
|
|
Err((symbol, original_region, shadow))
|
|
}
|
|
Ok(symbol) => Ok(symbol),
|
|
}
|
|
}
|
|
|
|
/// Like [Self::introduce], but handles the case of when an ident matches an ability member
|
|
/// name. In such cases a new symbol is created for the ident (since it's expected to be a
|
|
/// specialization of the ability member), but the ident is not added to the ident->symbol map.
|
|
///
|
|
/// If the ident does not match an ability name, the behavior of this function is exactly that
|
|
/// of `introduce`.
|
|
#[allow(clippy::type_complexity)]
|
|
pub fn introduce_or_shadow_ability_member(
|
|
&mut self,
|
|
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
|
ident: Ident,
|
|
region: Region,
|
|
) -> Result<(Symbol, Option<Symbol>), (Region, Loc<Ident>, Symbol)> {
|
|
let ident = &ident;
|
|
|
|
match self.introduce_help(ident.as_str(), region) {
|
|
Err((original_symbol, original_region)) => {
|
|
let shadow_symbol = self.scopeless_symbol(ident, region);
|
|
|
|
if self.abilities_store.is_ability_member_name(original_symbol)
|
|
|| pending_abilities_in_scope
|
|
.iter()
|
|
.any(|(_, members)| members.iter().any(|m| *m == original_symbol))
|
|
{
|
|
match self.shadows.get(&original_symbol) {
|
|
Some(loc_original_shadow) => {
|
|
// Duplicate shadow of an ability members; that's illegal.
|
|
let shadow = Loc {
|
|
value: ident.clone(),
|
|
region,
|
|
};
|
|
Err((loc_original_shadow.region, shadow, shadow_symbol))
|
|
}
|
|
None => {
|
|
self.shadows
|
|
.insert(original_symbol, Loc::at(region, shadow_symbol));
|
|
|
|
Ok((shadow_symbol, Some(original_symbol)))
|
|
}
|
|
}
|
|
} else {
|
|
// This is an illegal shadow.
|
|
let shadow = Loc {
|
|
value: ident.clone(),
|
|
region,
|
|
};
|
|
|
|
Err((original_region, shadow, shadow_symbol))
|
|
}
|
|
}
|
|
Ok(symbol) => Ok((symbol, None)),
|
|
}
|
|
}
|
|
|
|
pub fn get_member_shadow(&self, ability_member: Symbol) -> Option<&Loc<Symbol>> {
|
|
self.shadows.get(&ability_member)
|
|
}
|
|
|
|
/// Create a new symbol, but don't add it to the scope (yet)
|
|
///
|
|
/// Used for record guards like { x: Just _ } where the `x` is not added to the scope,
|
|
/// but also in other places where we need to create a symbol and we don't have the right
|
|
/// scope information yet. An identifier can be introduced later, and will use the same IdentId
|
|
pub fn scopeless_symbol(&mut self, ident: &Ident, region: Region) -> Symbol {
|
|
self.locals.scopeless_symbol(ident.as_str(), region)
|
|
}
|
|
|
|
/// Import a Symbol from another module into this module's top-level scope.
|
|
///
|
|
/// Returns Err if this would shadow an existing ident, including the
|
|
/// Symbol and Region of the ident we already had in scope under that name.
|
|
pub fn import(
|
|
&mut self,
|
|
ident: Ident,
|
|
symbol: Symbol,
|
|
region: Region,
|
|
) -> Result<(), (Symbol, Region)> {
|
|
if let Some((s, r)) = self.has_imported(ident.as_str()) {
|
|
return Err((s, r));
|
|
}
|
|
|
|
self.imports.push((ident, symbol, region));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn add_alias(
|
|
&mut self,
|
|
name: Symbol,
|
|
region: Region,
|
|
vars: Vec<Loc<AliasVar>>,
|
|
infer_ext_in_output_variables: Vec<Variable>,
|
|
typ: Type,
|
|
kind: AliasKind,
|
|
) {
|
|
let alias = create_alias(name, region, vars, infer_ext_in_output_variables, typ, kind);
|
|
self.aliases.insert(name, alias);
|
|
}
|
|
|
|
pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> {
|
|
self.aliases.get(&symbol)
|
|
}
|
|
|
|
pub fn contains_alias(&mut self, name: Symbol) -> bool {
|
|
self.aliases.contains_key(&name)
|
|
}
|
|
|
|
pub fn inner_scope<F, T>(&mut self, f: F) -> T
|
|
where
|
|
F: FnOnce(&mut Scope) -> T,
|
|
{
|
|
// store enough information to roll back to the original outer scope
|
|
//
|
|
// - abilities_store: ability definitions not allowed in inner scopes
|
|
// - locals: everything introduced in the inner scope is marked as not in scope in the rollback
|
|
// - aliases: stored in a VecMap, we just discard anything added in an inner scope
|
|
// - exposed_ident_count: unchanged
|
|
// - home: unchanged
|
|
let aliases_count = self.aliases.len();
|
|
let locals_snapshot = self.locals.in_scope.len();
|
|
|
|
let result = f(self);
|
|
|
|
self.aliases.truncate(aliases_count);
|
|
|
|
// anything added in the inner scope is no longer in scope now
|
|
for i in locals_snapshot..self.locals.in_scope.len() {
|
|
self.locals.in_scope.set(i, false);
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
pub fn register_debug_idents(&self) {
|
|
self.home.register_debug_idents(&self.locals.ident_ids)
|
|
}
|
|
|
|
/// Generates a unique, new symbol like "$1" or "$5",
|
|
/// using the home module as the module_id.
|
|
///
|
|
/// This is used, for example, during canonicalization of an Expr::Closure
|
|
/// to generate a unique symbol to refer to that closure.
|
|
pub fn gen_unique_symbol(&mut self) -> Symbol {
|
|
Symbol::new(self.home, self.locals.gen_unique())
|
|
}
|
|
}
|
|
|
|
pub fn create_alias(
|
|
name: Symbol,
|
|
region: Region,
|
|
vars: Vec<Loc<AliasVar>>,
|
|
infer_ext_in_output_variables: Vec<Variable>,
|
|
typ: Type,
|
|
kind: AliasKind,
|
|
) -> Alias {
|
|
let roc_types::types::VariableDetail {
|
|
type_variables,
|
|
lambda_set_variables,
|
|
recursion_variables,
|
|
} = typ.variables_detail();
|
|
|
|
debug_assert!({
|
|
let mut hidden = type_variables;
|
|
|
|
for var in (vars.iter().map(|lv| lv.value.var))
|
|
.chain(infer_ext_in_output_variables.iter().copied())
|
|
{
|
|
hidden.remove(&var);
|
|
}
|
|
|
|
if !hidden.is_empty() {
|
|
panic!(
|
|
"Found unbound type variables {:?} \n in type alias {:?} {:?} {:?} : {:?}",
|
|
hidden, name, &vars, &infer_ext_in_output_variables, &typ
|
|
)
|
|
}
|
|
|
|
true
|
|
});
|
|
|
|
let lambda_set_variables: Vec<_> = lambda_set_variables
|
|
.into_iter()
|
|
.map(|v| roc_types::types::LambdaSet(Type::Variable(v)))
|
|
.collect();
|
|
|
|
Alias {
|
|
region,
|
|
type_variables: vars,
|
|
lambda_set_variables,
|
|
infer_ext_in_output_variables,
|
|
recursion_variables,
|
|
typ,
|
|
kind,
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum ContainsIdent {
|
|
InScope(Symbol, Region),
|
|
NotInScope(IdentId),
|
|
NotPresent,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct ScopedIdentIds {
|
|
pub ident_ids: IdentIds,
|
|
in_scope: BitVec,
|
|
regions: Vec<Region>,
|
|
home: ModuleId,
|
|
}
|
|
|
|
impl ScopedIdentIds {
|
|
fn from_ident_ids(home: ModuleId, ident_ids: IdentIds) -> Self {
|
|
let capacity = ident_ids.len();
|
|
|
|
Self {
|
|
in_scope: BitVec::repeat(false, capacity),
|
|
ident_ids,
|
|
regions: vec![Region::zero(); capacity],
|
|
home,
|
|
}
|
|
}
|
|
|
|
fn has_in_scope(&self, ident: &Ident) -> Option<(Symbol, Region)> {
|
|
match self.contains_ident(ident.as_str()) {
|
|
ContainsIdent::InScope(symbol, region) => Some((symbol, region)),
|
|
ContainsIdent::NotInScope(_) | ContainsIdent::NotPresent => None,
|
|
}
|
|
}
|
|
|
|
fn contains_ident(&self, ident: &str) -> ContainsIdent {
|
|
use ContainsIdent::*;
|
|
|
|
let mut result = NotPresent;
|
|
|
|
for ident_id in self.ident_ids.get_id_many(ident) {
|
|
let index = ident_id.index();
|
|
if self.in_scope[index] {
|
|
return InScope(Symbol::new(self.home, ident_id), self.regions[index]);
|
|
} else {
|
|
result = NotInScope(ident_id)
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
fn idents_in_scope(&self) -> impl Iterator<Item = Ident> + '_ {
|
|
self.ident_ids
|
|
.ident_strs()
|
|
.zip(self.in_scope.iter())
|
|
.filter_map(|((_, string), keep)| {
|
|
if *keep {
|
|
Some(Ident::from(string))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
fn introduce_into_scope(&mut self, ident_name: &str, region: Region) -> IdentId {
|
|
let id = self.ident_ids.add_str(ident_name);
|
|
|
|
debug_assert_eq!(id.index(), self.in_scope.len());
|
|
debug_assert_eq!(id.index(), self.regions.len());
|
|
|
|
self.in_scope.push(true);
|
|
self.regions.push(region);
|
|
|
|
id
|
|
}
|
|
|
|
fn introduce_into_scope_duplicate(&mut self, existing: IdentId, region: Region) -> IdentId {
|
|
let id = self.ident_ids.duplicate_ident(existing);
|
|
|
|
debug_assert_eq!(id.index(), self.in_scope.len());
|
|
debug_assert_eq!(id.index(), self.regions.len());
|
|
|
|
self.in_scope.push(true);
|
|
self.regions.push(region);
|
|
|
|
id
|
|
}
|
|
|
|
/// Adds an IdentId, but does not introduce it to the scope
|
|
fn scopeless_symbol(&mut self, ident_name: &str, region: Region) -> Symbol {
|
|
let id = self.ident_ids.add_str(ident_name);
|
|
|
|
debug_assert_eq!(id.index(), self.in_scope.len());
|
|
debug_assert_eq!(id.index(), self.regions.len());
|
|
|
|
self.in_scope.push(false);
|
|
self.regions.push(region);
|
|
|
|
Symbol::new(self.home, id)
|
|
}
|
|
|
|
fn gen_unique(&mut self) -> IdentId {
|
|
let id = self.ident_ids.gen_unique();
|
|
|
|
debug_assert_eq!(id.index(), self.in_scope.len());
|
|
debug_assert_eq!(id.index(), self.regions.len());
|
|
|
|
self.in_scope.push(false);
|
|
self.regions.push(Region::zero());
|
|
|
|
id
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use roc_module::symbol::ModuleIds;
|
|
use roc_region::all::Position;
|
|
|
|
use pretty_assertions::{assert_eq, assert_ne};
|
|
|
|
#[test]
|
|
fn scope_contains_introduced() {
|
|
let _register_module_debug_names = ModuleIds::default();
|
|
let mut scope = Scope::new(
|
|
ModuleId::ATTR,
|
|
IdentIds::default(),
|
|
PendingAbilitiesStore::default(),
|
|
);
|
|
|
|
let region = Region::zero();
|
|
let ident = Ident::from("mezolit");
|
|
|
|
assert!(scope.lookup(&ident, region).is_err());
|
|
|
|
assert!(scope.introduce(ident.clone(), region).is_ok());
|
|
|
|
assert!(scope.lookup(&ident, region).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn second_introduce_shadows() {
|
|
let _register_module_debug_names = ModuleIds::default();
|
|
let mut scope = Scope::new(
|
|
ModuleId::ATTR,
|
|
IdentIds::default(),
|
|
PendingAbilitiesStore::default(),
|
|
);
|
|
|
|
let region1 = Region::from_pos(Position { offset: 10 });
|
|
let region2 = Region::from_pos(Position { offset: 20 });
|
|
let ident = Ident::from("mezolit");
|
|
|
|
assert!(scope.lookup(&ident, Region::zero()).is_err());
|
|
|
|
let first = scope.introduce(ident.clone(), region1).unwrap();
|
|
let (original, _ident, shadow_symbol) =
|
|
scope.introduce(ident.clone(), region2).unwrap_err();
|
|
|
|
scope.register_debug_idents();
|
|
|
|
assert_ne!(first, shadow_symbol);
|
|
assert_eq!(original.region, region1);
|
|
|
|
let lookup = scope.lookup(&ident, Region::zero()).unwrap();
|
|
|
|
assert_eq!(first, lookup);
|
|
}
|
|
|
|
#[test]
|
|
fn inner_scope_does_not_influence_outer() {
|
|
let _register_module_debug_names = ModuleIds::default();
|
|
let mut scope = Scope::new(
|
|
ModuleId::ATTR,
|
|
IdentIds::default(),
|
|
PendingAbilitiesStore::default(),
|
|
);
|
|
|
|
let region = Region::zero();
|
|
let ident = Ident::from("uránia");
|
|
|
|
assert!(scope.lookup(&ident, region).is_err());
|
|
|
|
scope.inner_scope(|inner| {
|
|
assert!(inner.introduce(ident.clone(), region).is_ok());
|
|
});
|
|
|
|
assert!(scope.lookup(&ident, region).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn default_idents_in_scope() {
|
|
let _register_module_debug_names = ModuleIds::default();
|
|
let scope = Scope::new(
|
|
ModuleId::ATTR,
|
|
IdentIds::default(),
|
|
PendingAbilitiesStore::default(),
|
|
);
|
|
|
|
let idents: Vec<_> = scope.idents_in_scope().collect();
|
|
|
|
assert_eq!(
|
|
&idents,
|
|
&[
|
|
Ident::from("Str"),
|
|
Ident::from("List"),
|
|
Ident::from("Box"),
|
|
Ident::from("Ok"),
|
|
Ident::from("Err"),
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn idents_with_inner_scope() {
|
|
let _register_module_debug_names = ModuleIds::default();
|
|
let mut scope = Scope::new(
|
|
ModuleId::ATTR,
|
|
IdentIds::default(),
|
|
PendingAbilitiesStore::default(),
|
|
);
|
|
|
|
let idents: Vec<_> = scope.idents_in_scope().collect();
|
|
|
|
assert_eq!(
|
|
&idents,
|
|
&[
|
|
Ident::from("Str"),
|
|
Ident::from("List"),
|
|
Ident::from("Box"),
|
|
Ident::from("Ok"),
|
|
Ident::from("Err"),
|
|
]
|
|
);
|
|
|
|
let builtin_count = idents.len();
|
|
|
|
let region = Region::zero();
|
|
|
|
let ident1 = Ident::from("uránia");
|
|
let ident2 = Ident::from("malmok");
|
|
let ident3 = Ident::from("Járnak");
|
|
|
|
scope.introduce(ident1.clone(), region).unwrap();
|
|
scope.introduce(ident2.clone(), region).unwrap();
|
|
scope.introduce(ident3.clone(), region).unwrap();
|
|
|
|
let idents: Vec<_> = scope.idents_in_scope().collect();
|
|
|
|
assert_eq!(
|
|
&idents[builtin_count..],
|
|
&[ident1.clone(), ident2.clone(), ident3.clone(),]
|
|
);
|
|
|
|
scope.inner_scope(|inner| {
|
|
let ident4 = Ident::from("Ångström");
|
|
let ident5 = Ident::from("Sirály");
|
|
|
|
inner.introduce(ident4.clone(), region).unwrap();
|
|
inner.introduce(ident5.clone(), region).unwrap();
|
|
|
|
let idents: Vec<_> = inner.idents_in_scope().collect();
|
|
|
|
assert_eq!(
|
|
&idents[builtin_count..],
|
|
&[
|
|
ident1.clone(),
|
|
ident2.clone(),
|
|
ident3.clone(),
|
|
ident4,
|
|
ident5
|
|
]
|
|
);
|
|
});
|
|
|
|
let idents: Vec<_> = scope.idents_in_scope().collect();
|
|
|
|
assert_eq!(&idents[builtin_count..], &[ident1, ident2, ident3,]);
|
|
}
|
|
|
|
#[test]
|
|
fn import_is_in_scope() {
|
|
let _register_module_debug_names = ModuleIds::default();
|
|
let mut scope = Scope::new(
|
|
ModuleId::ATTR,
|
|
IdentIds::default(),
|
|
PendingAbilitiesStore::default(),
|
|
);
|
|
|
|
let ident = Ident::from("product");
|
|
let symbol = Symbol::LIST_PRODUCT;
|
|
let region = Region::zero();
|
|
|
|
assert!(scope.lookup(&ident, region).is_err());
|
|
|
|
assert!(scope.import(ident.clone(), symbol, region).is_ok());
|
|
|
|
assert!(scope.lookup(&ident, region).is_ok());
|
|
|
|
assert!(scope.idents_in_scope().any(|x| x == ident));
|
|
}
|
|
|
|
#[test]
|
|
fn shadow_of_import() {
|
|
let _register_module_debug_names = ModuleIds::default();
|
|
let mut scope = Scope::new(
|
|
ModuleId::ATTR,
|
|
IdentIds::default(),
|
|
PendingAbilitiesStore::default(),
|
|
);
|
|
|
|
let ident = Ident::from("product");
|
|
let symbol = Symbol::LIST_PRODUCT;
|
|
|
|
let region1 = Region::from_pos(Position { offset: 10 });
|
|
let region2 = Region::from_pos(Position { offset: 20 });
|
|
|
|
scope.import(ident.clone(), symbol, region1).unwrap();
|
|
|
|
let (original, _ident, shadow_symbol) =
|
|
scope.introduce(ident.clone(), region2).unwrap_err();
|
|
|
|
scope.register_debug_idents();
|
|
|
|
assert_ne!(symbol, shadow_symbol);
|
|
assert_eq!(original.region, region1);
|
|
|
|
let lookup = scope.lookup(&ident, Region::zero()).unwrap();
|
|
|
|
assert_eq!(symbol, lookup);
|
|
}
|
|
}
|