use crate::types::{Range, RefEquality}; use rustc_hash::FxHashMap; use rustpython_parser::ast::{Arguments, Expr, Keyword, Stmt}; use std::num::TryFromIntError; use std::ops::{Deref, Index, IndexMut}; #[derive(Debug)] pub struct Scope<'a> { pub id: ScopeId, pub kind: ScopeKind<'a>, pub import_starred: bool, pub uses_locals: bool, /// A map from bound name to binding index, for live bindings. bindings: FxHashMap<&'a str, BindingId>, /// A map from bound name to binding index, for bindings that were created /// in the scope but rebound (and thus overridden) later on in the same /// scope. pub rebounds: FxHashMap<&'a str, Vec>, } impl<'a> Scope<'a> { pub fn global() -> Self { Scope::local(ScopeId::global(), ScopeKind::Module) } pub fn local(id: ScopeId, kind: ScopeKind<'a>) -> Self { Scope { id, kind, import_starred: false, uses_locals: false, bindings: FxHashMap::default(), rebounds: FxHashMap::default(), } } /// Returns the [id](BindingId) of the binding with the given name. pub fn get(&self, name: &str) -> Option<&BindingId> { self.bindings.get(name) } /// Adds a new binding with the given name to this scope. pub fn add(&mut self, name: &'a str, id: BindingId) -> Option { self.bindings.insert(name, id) } /// Returns `true` if this scope defines a binding with the given name. pub fn defines(&self, name: &str) -> bool { self.bindings.contains_key(name) } /// Removes the binding with the given name pub fn remove(&mut self, name: &str) -> Option { self.bindings.remove(name) } /// Returns the ids of all bindings defined in this scope. pub fn binding_ids(&self) -> std::collections::hash_map::Values<&str, BindingId> { self.bindings.values() } pub fn bindings(&self) -> std::collections::hash_map::Iter<&'a str, BindingId> { self.bindings.iter() } } /// Id uniquely identifying a scope in a program. /// /// Using a `u32` is sufficient because Ruff only supports parsing documents with a size of max `u32::max` /// and it is impossible to have more scopes than characters in the file (because defining a function or class /// requires more than one character). #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct ScopeId(u32); impl ScopeId { /// Returns the ID for the global scope #[inline] pub const fn global() -> Self { ScopeId(0) } /// Returns `true` if this is the id of the global scope pub const fn is_global(&self) -> bool { self.0 == 0 } } impl TryFrom for ScopeId { type Error = TryFromIntError; fn try_from(value: usize) -> Result { Ok(Self(u32::try_from(value)?)) } } impl From for usize { fn from(value: ScopeId) -> Self { value.0 as usize } } #[derive(Debug)] pub enum ScopeKind<'a> { Class(ClassDef<'a>), Function(FunctionDef<'a>), Generator, Module, Lambda(Lambda<'a>), } #[derive(Debug)] pub struct FunctionDef<'a> { // Properties derived from StmtKind::FunctionDef. pub name: &'a str, pub args: &'a Arguments, pub body: &'a [Stmt], pub decorator_list: &'a [Expr], // pub returns: Option<&'a Expr>, // pub type_comment: Option<&'a str>, // Scope-specific properties. // TODO(charlie): Create AsyncFunctionDef to mirror the AST. pub async_: bool, pub globals: FxHashMap<&'a str, &'a Stmt>, } #[derive(Debug)] pub struct ClassDef<'a> { // Properties derived from StmtKind::ClassDef. pub name: &'a str, pub bases: &'a [Expr], pub keywords: &'a [Keyword], // pub body: &'a [Stmt], pub decorator_list: &'a [Expr], // Scope-specific properties. pub globals: FxHashMap<&'a str, &'a Stmt>, } #[derive(Debug)] pub struct Lambda<'a> { pub args: &'a Arguments, pub body: &'a Expr, } /// The scopes of a program indexed by [`ScopeId`] #[derive(Debug)] pub struct Scopes<'a>(Vec>); impl<'a> Scopes<'a> { /// Returns a reference to the global scope pub fn global(&self) -> &Scope<'a> { &self[ScopeId::global()] } /// Returns a mutable reference to the global scope pub fn global_mut(&mut self) -> &mut Scope<'a> { &mut self[ScopeId::global()] } /// Pushes a new scope and returns its unique id pub(crate) fn push_scope(&mut self, kind: ScopeKind<'a>) -> ScopeId { let next_id = ScopeId::try_from(self.0.len()).unwrap(); self.0.push(Scope::local(next_id, kind)); next_id } } impl Default for Scopes<'_> { fn default() -> Self { Self(vec![Scope::global()]) } } impl<'a> Index for Scopes<'a> { type Output = Scope<'a>; fn index(&self, index: ScopeId) -> &Self::Output { &self.0[usize::from(index)] } } impl<'a> IndexMut for Scopes<'a> { fn index_mut(&mut self, index: ScopeId) -> &mut Self::Output { &mut self.0[usize::from(index)] } } impl<'a> Deref for Scopes<'a> { type Target = [Scope<'a>]; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Debug, Clone)] pub struct ScopeStack(Vec); impl ScopeStack { /// Pushes a new scope on the stack pub fn push(&mut self, id: ScopeId) { self.0.push(id); } /// Pops the top most scope pub fn pop(&mut self) -> Option { self.0.pop() } /// Returns the id of the top-most pub fn top(&self) -> Option { self.0.last().copied() } /// Returns an iterator from the current scope to the top scope (reverse iterator) pub fn iter(&self) -> std::iter::Rev> { self.0.iter().rev() } } impl Default for ScopeStack { fn default() -> Self { Self(vec![ScopeId::global()]) } } #[derive(Debug, Clone)] pub struct Binding<'a> { pub kind: BindingKind<'a>, pub range: Range, /// The context in which the binding was created. pub context: ExecutionContext, /// The statement in which the [`Binding`] was defined. pub source: Option>, /// Tuple of (scope index, range) indicating the scope and range at which /// the binding was last used in a runtime context. pub runtime_usage: Option<(ScopeId, Range)>, /// Tuple of (scope index, range) indicating the scope and range at which /// the binding was last used in a typing-time context. pub typing_usage: Option<(ScopeId, Range)>, /// Tuple of (scope index, range) indicating the scope and range at which /// the binding was last used in a synthetic context. This is used for /// (e.g.) `__future__` imports, explicit re-exports, and other bindings /// that should be considered used even if they're never referenced. pub synthetic_usage: Option<(ScopeId, Range)>, } impl<'a> Binding<'a> { pub fn mark_used(&mut self, scope: ScopeId, range: Range, context: ExecutionContext) { match context { ExecutionContext::Runtime => self.runtime_usage = Some((scope, range)), ExecutionContext::Typing => self.typing_usage = Some((scope, range)), } } pub const fn used(&self) -> bool { self.runtime_usage.is_some() || self.synthetic_usage.is_some() || self.typing_usage.is_some() } pub const fn is_definition(&self) -> bool { matches!( self.kind, BindingKind::ClassDefinition | BindingKind::FunctionDefinition | BindingKind::Builtin | BindingKind::FutureImportation | BindingKind::StarImportation(..) | BindingKind::Importation(..) | BindingKind::FromImportation(..) | BindingKind::SubmoduleImportation(..) ) } pub fn redefines(&self, existing: &'a Binding) -> bool { match &self.kind { BindingKind::Importation(.., full_name) => { if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind { return full_name == existing; } } BindingKind::FromImportation(.., full_name) => { if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind { return full_name == existing; } } BindingKind::SubmoduleImportation(.., full_name) => match &existing.kind { BindingKind::Importation(.., existing) | BindingKind::SubmoduleImportation(.., existing) => { return full_name == existing; } BindingKind::FromImportation(.., existing) => { return full_name == existing; } _ => {} }, BindingKind::Annotation => { return false; } BindingKind::FutureImportation => { return false; } BindingKind::StarImportation(..) => { return false; } _ => {} } existing.is_definition() } } #[derive(Copy, Debug, Clone)] pub enum ExecutionContext { Runtime, Typing, } /// ID uniquely identifying a [Binding] in a program. /// /// Using a `u32` to identify [Binding]s should is sufficient because Ruff only supports documents with a /// size smaller than or equal to `u32::max`. A document with the size of `u32::max` must have fewer than `u32::max` /// bindings because bindings must be separated by whitespace (and have an assignment). #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct BindingId(u32); impl From for usize { fn from(value: BindingId) -> Self { value.0 as usize } } impl TryFrom for BindingId { type Error = TryFromIntError; fn try_from(value: usize) -> Result { Ok(Self(u32::try_from(value)?)) } } impl nohash_hasher::IsEnabled for BindingId {} // Pyflakes defines the following binding hierarchy (via inheritance): // Binding // ExportBinding // Annotation // Argument // Assignment // NamedExprAssignment // Definition // FunctionDefinition // ClassDefinition // Builtin // Importation // SubmoduleImportation // ImportationFrom // StarImportation // FutureImportation #[derive(Clone, Debug, is_macro::Is)] pub enum BindingKind<'a> { Annotation, Argument, Assignment, Binding, LoopVar, Global, Nonlocal, Builtin, ClassDefinition, FunctionDefinition, Export(Vec), FutureImportation, StarImportation(Option, Option), Importation(&'a str, &'a str), FromImportation(&'a str, String), SubmoduleImportation(&'a str, &'a str), } /// The bindings in a program. /// /// Bindings are indexed by [`BindingId`] #[derive(Debug, Clone, Default)] pub struct Bindings<'a>(Vec>); impl<'a> Bindings<'a> { /// Pushes a new binding and returns its id pub fn push(&mut self, binding: Binding<'a>) -> BindingId { let id = self.next_id(); self.0.push(binding); id } /// Returns the id that will be assigned when pushing the next binding pub fn next_id(&self) -> BindingId { BindingId::try_from(self.0.len()).unwrap() } } impl<'a> Index for Bindings<'a> { type Output = Binding<'a>; fn index(&self, index: BindingId) -> &Self::Output { &self.0[usize::from(index)] } } impl<'a> IndexMut for Bindings<'a> { fn index_mut(&mut self, index: BindingId) -> &mut Self::Output { &mut self.0[usize::from(index)] } } impl<'a> Deref for Bindings<'a> { type Target = [Binding<'a>]; fn deref(&self) -> &Self::Target { &self.0 } } impl<'a> FromIterator> for Bindings<'a> { fn from_iter>>(iter: T) -> Self { Self(Vec::from_iter(iter)) } }