Rename semantic model methods to use current_* prefix (#6347)

## Summary

This PR attempts to draw a clearer divide between "methods that take
(e.g.) an expression or statement as input" and "methods that rely on
the _current_ expression or statement" in the semantic model, by
renaming methods like `stmt()` to `current_statement()`.

This had led to confusion in the past. For example, prior to this PR, we
had `scope()` (which returns the current scope), and `parent_scope`,
which returns the parent _of a scope that's passed in_. Now, the API is
clearer: `current_scope` returns the current scope, and `parent_scope`
takes a scope as argument and returns its parent.

Per above, I also changed `stmt` to `statement` and `expr` to
`expression`.
This commit is contained in:
Charlie Marsh 2023-08-07 10:44:49 -04:00 committed by GitHub
parent b763973357
commit bae87fa016
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 331 additions and 290 deletions

View file

@ -185,7 +185,7 @@ impl<'a> Binding<'a> {
/// Returns the range of the binding's parent.
pub fn parent_range(&self, semantic: &SemanticModel) -> Option<TextRange> {
self.source
.map(|node_id| semantic.stmts[node_id])
.map(|node_id| semantic.statements[node_id])
.and_then(|parent| {
if parent.is_import_from_stmt() {
Some(parent.range())

View file

@ -31,16 +31,16 @@ pub struct SemanticModel<'a> {
module_path: Option<&'a [String]>,
/// Stack of all visited statements.
pub stmts: Nodes<'a, Stmt>,
pub statements: Nodes<'a, Stmt>,
/// The identifier of the current statement.
stmt_id: Option<NodeId>,
statement_id: Option<NodeId>,
/// Stack of all visited expressions.
exprs: Nodes<'a, Expr>,
expressions: Nodes<'a, Expr>,
/// The identifier of the current expression.
expr_id: Option<NodeId>,
expression_id: Option<NodeId>,
/// Stack of all scopes, along with the identifier of the current scope.
pub scopes: Scopes<'a>,
@ -132,10 +132,10 @@ impl<'a> SemanticModel<'a> {
Self {
typing_modules,
module_path: module.path(),
stmts: Nodes::<Stmt>::default(),
stmt_id: None,
exprs: Nodes::<Expr>::default(),
expr_id: None,
statements: Nodes::<Stmt>::default(),
statement_id: None,
expressions: Nodes::<Expr>::default(),
expression_id: None,
scopes: Scopes::default(),
scope_id: ScopeId::global(),
definitions: Definitions::for_module(module),
@ -225,7 +225,7 @@ impl<'a> SemanticModel<'a> {
flags,
references: Vec::new(),
scope: self.scope_id,
source: self.stmt_id,
source: self.statement_id,
context: self.execution_context(),
exceptions: self.exceptions(),
})
@ -233,7 +233,7 @@ impl<'a> SemanticModel<'a> {
/// Return the current [`Binding`] for a given `name`.
pub fn find_binding(&self, member: &str) -> Option<&Binding> {
self.scopes()
self.current_scopes()
.find_map(|scope| scope.get(member))
.map(|binding_id| &self.bindings[binding_id])
}
@ -686,102 +686,110 @@ impl<'a> SemanticModel<'a> {
) -> Option<ImportedName> {
// TODO(charlie): Pass in a slice.
let module_path: Vec<&str> = module.split('.').collect();
self.scopes().enumerate().find_map(|(scope_index, scope)| {
scope.bindings().find_map(|(name, binding_id)| {
let binding = &self.bindings[binding_id];
match &binding.kind {
// Ex) Given `module="sys"` and `object="exit"`:
// `import sys` -> `sys.exit`
// `import sys as sys2` -> `sys2.exit`
BindingKind::Import(Import { call_path }) => {
if call_path.as_ref() == module_path.as_slice() {
if let Some(source) = binding.source {
// Verify that `sys` isn't bound in an inner scope.
if self
.scopes()
.take(scope_index)
.all(|scope| !scope.has(name))
{
return Some(ImportedName {
name: format!("{name}.{member}"),
range: self.stmts[source].range(),
context: binding.context,
});
}
}
}
}
// Ex) Given `module="os.path"` and `object="join"`:
// `from os.path import join` -> `join`
// `from os.path import join as join2` -> `join2`
BindingKind::FromImport(FromImport { call_path }) => {
if let Some((target_member, target_module)) = call_path.split_last() {
if target_module == module_path.as_slice() && target_member == &member {
self.current_scopes()
.enumerate()
.find_map(|(scope_index, scope)| {
scope.bindings().find_map(|(name, binding_id)| {
let binding = &self.bindings[binding_id];
match &binding.kind {
// Ex) Given `module="sys"` and `object="exit"`:
// `import sys` -> `sys.exit`
// `import sys as sys2` -> `sys2.exit`
BindingKind::Import(Import { call_path }) => {
if call_path.as_ref() == module_path.as_slice() {
if let Some(source) = binding.source {
// Verify that `join` isn't bound in an inner scope.
// Verify that `sys` isn't bound in an inner scope.
if self
.scopes()
.current_scopes()
.take(scope_index)
.all(|scope| !scope.has(name))
{
return Some(ImportedName {
name: (*name).to_string(),
range: self.stmts[source].range(),
name: format!("{name}.{member}"),
range: self.statements[source].range(),
context: binding.context,
});
}
}
}
}
}
// Ex) Given `module="os"` and `object="name"`:
// `import os.path ` -> `os.name`
BindingKind::SubmoduleImport(SubmoduleImport { .. }) => {
if name == module {
if let Some(source) = binding.source {
// Verify that `os` isn't bound in an inner scope.
if self
.scopes()
.take(scope_index)
.all(|scope| !scope.has(name))
// Ex) Given `module="os.path"` and `object="join"`:
// `from os.path import join` -> `join`
// `from os.path import join as join2` -> `join2`
BindingKind::FromImport(FromImport { call_path }) => {
if let Some((target_member, target_module)) = call_path.split_last() {
if target_module == module_path.as_slice()
&& target_member == &member
{
return Some(ImportedName {
name: format!("{name}.{member}"),
range: self.stmts[source].range(),
context: binding.context,
});
if let Some(source) = binding.source {
// Verify that `join` isn't bound in an inner scope.
if self
.current_scopes()
.take(scope_index)
.all(|scope| !scope.has(name))
{
return Some(ImportedName {
name: (*name).to_string(),
range: self.statements[source].range(),
context: binding.context,
});
}
}
}
}
}
// Ex) Given `module="os"` and `object="name"`:
// `import os.path ` -> `os.name`
BindingKind::SubmoduleImport(SubmoduleImport { .. }) => {
if name == module {
if let Some(source) = binding.source {
// Verify that `os` isn't bound in an inner scope.
if self
.current_scopes()
.take(scope_index)
.all(|scope| !scope.has(name))
{
return Some(ImportedName {
name: format!("{name}.{member}"),
range: self.statements[source].range(),
context: binding.context,
});
}
}
}
}
// Non-imports.
_ => {}
}
// Non-imports.
_ => {}
}
None
None
})
})
})
}
/// Push a [`Stmt`] onto the stack.
pub fn push_stmt(&mut self, stmt: &'a Stmt) {
self.stmt_id = Some(self.stmts.insert(stmt, self.stmt_id));
pub fn push_statement(&mut self, stmt: &'a Stmt) {
self.statement_id = Some(self.statements.insert(stmt, self.statement_id));
}
/// Pop the current [`Stmt`] off the stack.
pub fn pop_stmt(&mut self) {
let node_id = self.stmt_id.expect("Attempted to pop without statement");
self.stmt_id = self.stmts.parent_id(node_id);
pub fn pop_statement(&mut self) {
let node_id = self
.statement_id
.expect("Attempted to pop without statement");
self.statement_id = self.statements.parent_id(node_id);
}
/// Push a [`Expr`] onto the stack.
pub fn push_expr(&mut self, expr: &'a Expr) {
self.expr_id = Some(self.exprs.insert(expr, self.expr_id));
pub fn push_expression(&mut self, expr: &'a Expr) {
self.expression_id = Some(self.expressions.insert(expr, self.expression_id));
}
/// Pop the current [`Expr`] off the stack.
pub fn pop_expr(&mut self) {
let node_id = self.expr_id.expect("Attempted to pop without expression");
self.expr_id = self.exprs.parent_id(node_id);
pub fn pop_expression(&mut self) {
let node_id = self
.expression_id
.expect("Attempted to pop without expression");
self.expression_id = self.expressions.parent_id(node_id);
}
/// Push a [`Scope`] with the given [`ScopeKind`] onto the stack.
@ -811,69 +819,94 @@ impl<'a> SemanticModel<'a> {
}
/// Return the current `Stmt`.
pub fn stmt(&self) -> &'a Stmt {
let node_id = self.stmt_id.expect("No current statement");
self.stmts[node_id]
pub fn current_statement(&self) -> &'a Stmt {
let node_id = self.statement_id.expect("No current statement");
self.statements[node_id]
}
/// Return the parent `Stmt` of the current `Stmt`, if any.
pub fn stmt_parent(&self) -> Option<&'a Stmt> {
let node_id = self.stmt_id.expect("No current statement");
let parent_id = self.stmts.parent_id(node_id)?;
Some(self.stmts[parent_id])
}
/// Return the current `Expr`.
pub fn expr(&self) -> Option<&'a Expr> {
let node_id = self.expr_id?;
Some(self.exprs[node_id])
}
/// Return the parent `Expr` of the current `Expr`, if any.
pub fn expr_parent(&self) -> Option<&'a Expr> {
self.expr_ancestors().next()
}
/// Return the grandparent `Expr` of the current `Expr`, if any.
pub fn expr_grandparent(&self) -> Option<&'a Expr> {
self.expr_ancestors().nth(1)
}
/// Return an [`Iterator`] over the current `Expr` parents.
pub fn expr_ancestors(&self) -> impl Iterator<Item = &'a Expr> + '_ {
self.expr_id
/// Returns an [`Iterator`] over the current statement hierarchy, from the current [`Stmt`]
/// through to any parents.
pub fn current_statements(&self) -> impl Iterator<Item = &'a Stmt> + '_ {
self.statement_id
.iter()
.copied()
.flat_map(|id| {
self.exprs
.ancestor_ids(id)
.skip(1)
.map(|id| &self.exprs[id])
self.statements
.ancestor_ids(*id)
.map(|id| &self.statements[id])
})
.copied()
}
/// Returns a reference to the global scope
/// Return the parent `Stmt` of the current `Stmt`, if any.
pub fn current_statement_parent(&self) -> Option<&'a Stmt> {
self.current_statements().nth(1)
}
/// Return the grandparent `Stmt` of the current `Stmt`, if any.
pub fn current_statement_grandparent(&self) -> Option<&'a Stmt> {
self.current_statements().nth(2)
}
/// Return the current `Expr`.
pub fn current_expression(&self) -> Option<&'a Expr> {
let node_id = self.expression_id?;
Some(self.expressions[node_id])
}
/// Returns an [`Iterator`] over the current statement hierarchy, from the current [`Expr`]
/// through to any parents.
pub fn current_expressions(&self) -> impl Iterator<Item = &'a Expr> + '_ {
self.expression_id
.iter()
.flat_map(|id| {
self.expressions
.ancestor_ids(*id)
.map(|id| &self.expressions[id])
})
.copied()
}
/// Return the parent [`Expr`] of the current [`Expr`], if any.
pub fn current_expression_parent(&self) -> Option<&'a Expr> {
self.current_expressions().nth(1)
}
/// Return the grandparent [`Expr`] of the current [`Expr`], if any.
pub fn current_expression_grandparent(&self) -> Option<&'a Expr> {
self.current_expressions().nth(2)
}
/// Returns a reference to the global [`Scope`].
pub fn global_scope(&self) -> &Scope<'a> {
self.scopes.global()
}
/// Returns a mutable reference to the global scope
/// Returns a mutable reference to the global [`Scope`].
pub fn global_scope_mut(&mut self) -> &mut Scope<'a> {
self.scopes.global_mut()
}
/// Returns the current top most scope.
pub fn scope(&self) -> &Scope<'a> {
/// Returns the current top-most [`Scope`].
pub fn current_scope(&self) -> &Scope<'a> {
&self.scopes[self.scope_id]
}
/// Returns the parent of the given scope, if any.
/// Returns a mutable reference to the current top-most [`Scope`].
pub fn current_scope_mut(&mut self) -> &mut Scope<'a> {
&mut self.scopes[self.scope_id]
}
/// Returns an iterator over all scopes, starting from the current [`Scope`].
pub fn current_scopes(&self) -> impl Iterator<Item = &Scope> {
self.scopes.ancestors(self.scope_id)
}
/// Returns the parent of the given [`Scope`], if any.
pub fn parent_scope(&self, scope: &Scope) -> Option<&Scope<'a>> {
scope.parent.map(|scope_id| &self.scopes[scope_id])
}
/// Returns the first parent of the given scope that is not a [`ScopeKind::Type`] scope, if any.
/// Returns the first parent of the given [`Scope`] that is not of [`ScopeKind::Type`], if any.
pub fn first_non_type_parent_scope(&self, scope: &Scope) -> Option<&Scope<'a>> {
let mut current_scope = scope;
while let Some(parent) = self.parent_scope(current_scope) {
@ -886,22 +919,6 @@ impl<'a> SemanticModel<'a> {
None
}
/// Returns a mutable reference to the current top most scope.
pub fn scope_mut(&mut self) -> &mut Scope<'a> {
&mut self.scopes[self.scope_id]
}
/// Returns an iterator over all scopes, starting from the current scope.
pub fn scopes(&self) -> impl Iterator<Item = &Scope> {
self.scopes.ancestors(self.scope_id)
}
/// Returns an iterator over all parent statements.
pub fn parents(&self) -> impl Iterator<Item = &Stmt> + '_ {
let node_id = self.stmt_id.expect("No current statement");
self.stmts.ancestor_ids(node_id).map(|id| self.stmts[id])
}
/// Set the [`Globals`] for the current [`Scope`].
pub fn set_globals(&mut self, globals: Globals<'a>) {
// If any global bindings don't already exist in the global scope, add them.
@ -916,7 +933,7 @@ impl<'a> SemanticModel<'a> {
range: *range,
references: Vec::new(),
scope: self.scope_id,
source: self.stmt_id,
source: self.statement_id,
context: self.execution_context(),
exceptions: self.exceptions(),
flags: BindingFlags::empty(),
@ -964,13 +981,13 @@ impl<'a> SemanticModel<'a> {
pub fn at_top_level(&self) -> bool {
self.scope_id.is_global()
&& self
.stmt_id
.map_or(true, |stmt_id| self.stmts.parent_id(stmt_id).is_none())
.statement_id
.map_or(true, |stmt_id| self.statements.parent_id(stmt_id).is_none())
}
/// Return `true` if the model is in an async context.
pub fn in_async_context(&self) -> bool {
for scope in self.scopes() {
for scope in self.current_scopes() {
if scope.kind.is_async_function() {
return true;
}
@ -1048,8 +1065,8 @@ impl<'a> SemanticModel<'a> {
pub fn snapshot(&self) -> Snapshot {
Snapshot {
scope_id: self.scope_id,
stmt_id: self.stmt_id,
expr_id: self.expr_id,
stmt_id: self.statement_id,
expr_id: self.expression_id,
definition_id: self.definition_id,
flags: self.flags,
}
@ -1065,8 +1082,8 @@ impl<'a> SemanticModel<'a> {
flags,
} = snapshot;
self.scope_id = scope_id;
self.stmt_id = stmt_id;
self.expr_id = expr_id;
self.statement_id = stmt_id;
self.expression_id = expr_id;
self.definition_id = definition_id;
self.flags = flags;
}