use crate::expression::Expression;
use crate::member::{
Member, MemberExpr, MemberExprBuilder, MemberExprRef, MemberTable, MemberTableBuilder,
ScopedMemberId,
};
use crate::predicate::PatternPredicate;
use crate::symbol::{ScopedSymbolId, Symbol, SymbolTable, SymbolTableBuilder};
use crate::{Db, PossiblyNarrowedPlaces};
use ruff_db::parsed::ParsedModuleRef;
use ruff_index::IndexVec;
use ruff_python_ast as ast;
use smallvec::SmallVec;
use std::hash::Hash;
use std::iter::FusedIterator;
pub(crate) fn match_subject_place_expressions(subject: &ast::Expr) -> SmallVec<[&ast::Expr; 2]> {
let mut expressions: SmallVec<[&ast::Expr; 2]> = SmallVec::new();
expressions.push(subject);
match subject {
ast::Expr::Subscript(subscript) => expressions.push(&subscript.value),
ast::Expr::Attribute(attribute) => expressions.push(&attribute.value),
_ => {}
}
expressions
}
#[derive(Eq, PartialEq, Debug, get_size2::GetSize)]
pub enum PlaceExpr {
Symbol(Symbol),
Member(Member),
}
impl PlaceExpr {
pub fn from_expr_name(name: &ast::ExprName) -> Self {
PlaceExpr::Symbol(Symbol::new(name.id.clone()))
}
pub fn try_from_expr<'e>(expr: impl Into<ast::ExprRef<'e>>) -> Option<Self> {
let expr = expr.into();
let expr = match expr {
ast::ExprRef::Named(named) if named.target.is_name_expr() => {
named.target.as_ref().into()
}
ast::ExprRef::Named(_) => return None,
_ => expr,
};
if let ast::ExprRef::Name(name) = expr {
return Some(PlaceExpr::Symbol(Symbol::new(name.id.clone())));
}
MemberExprBuilder::visit_expr(expr).and_then(Self::try_from_member_expr)
}
pub(super) fn try_from_member_expr(builder: MemberExprBuilder) -> Option<Self> {
let member_expression = MemberExpr::try_from_builder(builder)?;
Some(Self::Member(Member::new(member_expression)))
}
}
impl std::fmt::Display for PlaceExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Symbol(symbol) => std::fmt::Display::fmt(symbol, f),
Self::Member(member) => std::fmt::Display::fmt(member, f),
}
}
}
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum PlaceExprRef<'a> {
Symbol(&'a Symbol),
Member(&'a Member),
}
impl<'a> PlaceExprRef<'a> {
pub const fn as_symbol(self) -> Option<&'a Symbol> {
if let PlaceExprRef::Symbol(symbol) = self {
Some(symbol)
} else {
None
}
}
pub const fn is_symbol(self) -> bool {
matches!(self, PlaceExprRef::Symbol(_))
}
pub fn is_declared(self) -> bool {
match self {
Self::Symbol(symbol) => symbol.is_declared(),
Self::Member(member) => member.is_declared(),
}
}
pub const fn is_bound(self) -> bool {
match self {
PlaceExprRef::Symbol(symbol) => symbol.is_bound(),
PlaceExprRef::Member(member) => member.is_bound(),
}
}
pub fn num_member_segments(self) -> usize {
match self {
PlaceExprRef::Symbol(_) => 0,
PlaceExprRef::Member(member) => member.expression().num_segments(),
}
}
}
impl<'a> From<&'a Symbol> for PlaceExprRef<'a> {
fn from(value: &'a Symbol) -> Self {
Self::Symbol(value)
}
}
impl<'a> From<&'a Member> for PlaceExprRef<'a> {
fn from(value: &'a Member) -> Self {
Self::Member(value)
}
}
impl<'a> From<&'a PlaceExpr> for PlaceExprRef<'a> {
fn from(value: &'a PlaceExpr) -> Self {
match value {
PlaceExpr::Symbol(symbol) => PlaceExprRef::Symbol(symbol),
PlaceExpr::Member(member) => PlaceExprRef::Member(member),
}
}
}
impl std::fmt::Display for PlaceExprRef<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Symbol(symbol) => std::fmt::Display::fmt(symbol, f),
Self::Member(member) => std::fmt::Display::fmt(member, f),
}
}
}
#[derive(
Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, get_size2::GetSize, salsa::SalsaValue,
)]
pub enum ScopedPlaceId {
Symbol(ScopedSymbolId),
Member(ScopedMemberId),
}
#[derive(Debug, Eq, PartialEq, get_size2::GetSize)]
pub struct PlaceTable {
symbols: SymbolTable,
members: MemberTable,
}
impl PlaceTable {
pub fn parents<'a>(&'a self, place_expr: impl Into<PlaceExprRef<'a>>) -> ParentPlaceIter<'a> {
match place_expr.into() {
PlaceExprRef::Symbol(_) => ParentPlaceIter::for_symbol(),
PlaceExprRef::Member(member) => {
ParentPlaceIter::for_member(member.expression(), &self.symbols, &self.members)
}
}
}
pub fn symbols(&self) -> std::slice::Iter<'_, Symbol> {
self.symbols.iter()
}
pub fn members(&self) -> std::slice::Iter<'_, Member> {
self.members.iter()
}
#[track_caller]
pub fn symbol(&self, id: ScopedSymbolId) -> &Symbol {
self.symbols.symbol(id)
}
pub fn symbol_by_name(&self, name: &str) -> Option<&Symbol> {
self.symbols.symbol_id(name).map(|id| self.symbol(id))
}
#[track_caller]
pub fn member(&self, id: ScopedMemberId) -> &Member {
self.members.member(id)
}
pub fn symbol_id(&self, name: &str) -> Option<ScopedSymbolId> {
self.symbols.symbol_id(name)
}
pub fn place_id<'e>(&self, place_expr: impl Into<PlaceExprRef<'e>>) -> Option<ScopedPlaceId> {
let place_expr = place_expr.into();
match place_expr {
PlaceExprRef::Symbol(symbol) => self.symbols.symbol_id(symbol.name()).map(Into::into),
PlaceExprRef::Member(member) => {
self.members.member_id(member.expression()).map(Into::into)
}
}
}
#[track_caller]
pub fn place(&self, place_id: impl Into<ScopedPlaceId>) -> PlaceExprRef<'_> {
match place_id.into() {
ScopedPlaceId::Symbol(symbol) => self.symbol(symbol).into(),
ScopedPlaceId::Member(member) => self.member(member).into(),
}
}
pub fn member_id_by_instance_attribute_name(&self, name: &str) -> Option<ScopedMemberId> {
self.members.place_id_by_instance_attribute_name(name)
}
}
#[derive(Default)]
pub struct PlaceTableBuilder {
symbols: SymbolTableBuilder,
member: MemberTableBuilder,
associated_symbol_members: IndexVec<ScopedSymbolId, SmallVec<[ScopedMemberId; 4]>>,
associated_sub_members: IndexVec<ScopedMemberId, SmallVec<[ScopedMemberId; 4]>>,
}
impl PlaceTableBuilder {
pub(crate) fn place_id(&self, expression: PlaceExprRef) -> Option<ScopedPlaceId> {
match expression {
PlaceExprRef::Symbol(symbol) => self.symbols.symbol_id(symbol.name()).map(Into::into),
PlaceExprRef::Member(member) => {
self.member.member_id(member.expression()).map(Into::into)
}
}
}
#[track_caller]
pub(super) fn symbol(&self, id: ScopedSymbolId) -> &Symbol {
self.symbols.symbol(id)
}
pub(super) fn member(&self, id: ScopedMemberId) -> &Member {
self.member.member(id)
}
pub(super) fn symbol_id(&self, name: &str) -> Option<ScopedSymbolId> {
self.symbols.symbol_id(name)
}
#[track_caller]
pub(super) fn symbol_mut(&mut self, id: ScopedSymbolId) -> &mut Symbol {
self.symbols.symbol_mut(id)
}
#[track_caller]
fn member_mut(&mut self, id: ScopedMemberId) -> &mut Member {
self.member.member_mut(id)
}
#[track_caller]
pub(crate) fn place(&self, place_id: impl Into<ScopedPlaceId>) -> PlaceExprRef<'_> {
match place_id.into() {
ScopedPlaceId::Symbol(id) => PlaceExprRef::Symbol(self.symbols.symbol(id)),
ScopedPlaceId::Member(id) => PlaceExprRef::Member(self.member.member(id)),
}
}
pub(crate) fn associated_place_ids(&self, place: ScopedPlaceId) -> &[ScopedMemberId] {
match place {
ScopedPlaceId::Symbol(symbol) => &self.associated_symbol_members[symbol],
ScopedPlaceId::Member(member) => &self.associated_sub_members[member],
}
}
pub(crate) fn iter(&self) -> impl Iterator<Item = PlaceExprRef<'_>> {
self.symbols
.iter()
.map(Into::into)
.chain(self.member.iter().map(PlaceExprRef::Member))
}
pub(crate) fn symbols(&self) -> impl Iterator<Item = &Symbol> {
self.symbols.iter()
}
pub(crate) fn add_symbol(&mut self, symbol: Symbol) -> (ScopedSymbolId, bool) {
let (id, is_new) = self.symbols.add(symbol);
if is_new {
let new_id = self.associated_symbol_members.push(SmallVec::new_const());
debug_assert_eq!(new_id, id);
}
(id, is_new)
}
fn add_member(&mut self, member: Member) -> (ScopedMemberId, bool) {
let (id, is_new) = self.member.add(member);
if is_new {
let new_id = self.associated_sub_members.push(SmallVec::new_const());
debug_assert_eq!(new_id, id);
let member = self.member.member(id);
for parent_id in
ParentPlaceIter::for_member(member.expression(), &self.symbols, &self.member)
{
match parent_id {
ScopedPlaceId::Symbol(scoped_symbol_id) => {
self.associated_symbol_members[scoped_symbol_id].push(id);
}
ScopedPlaceId::Member(scoped_member_id) => {
self.associated_sub_members[scoped_member_id].push(id);
}
}
}
}
(id, is_new)
}
pub(crate) fn add_place(&mut self, place: PlaceExpr) -> (ScopedPlaceId, bool) {
match place {
PlaceExpr::Symbol(symbol) => {
let (id, is_new) = self.add_symbol(symbol);
(ScopedPlaceId::Symbol(id), is_new)
}
PlaceExpr::Member(member) => {
let (id, is_new) = self.add_member(member);
(ScopedPlaceId::Member(id), is_new)
}
}
}
#[track_caller]
pub(super) fn mark_bound(&mut self, id: ScopedPlaceId) {
match id {
ScopedPlaceId::Symbol(symbol_id) => {
self.symbol_mut(symbol_id).mark_bound();
}
ScopedPlaceId::Member(member_id) => {
self.member_mut(member_id).mark_bound();
}
}
}
#[track_caller]
pub(super) fn mark_declared(&mut self, id: ScopedPlaceId) {
match id {
ScopedPlaceId::Symbol(symbol_id) => {
self.symbol_mut(symbol_id).mark_declared();
}
ScopedPlaceId::Member(member_id) => {
self.member_mut(member_id).mark_declared();
}
}
}
pub(crate) fn finish(self) -> PlaceTable {
PlaceTable {
symbols: self.symbols.build(),
members: self.member.build(),
}
}
}
impl ScopedPlaceId {
pub const fn is_symbol(self) -> bool {
matches!(self, ScopedPlaceId::Symbol(_))
}
pub const fn is_member(self) -> bool {
matches!(self, ScopedPlaceId::Member(_))
}
pub const fn as_symbol(self) -> Option<ScopedSymbolId> {
if let ScopedPlaceId::Symbol(id) = self {
Some(id)
} else {
None
}
}
pub const fn expect_symbol(self) -> ScopedSymbolId {
match self {
ScopedPlaceId::Symbol(symbol) => symbol,
ScopedPlaceId::Member(_) => {
panic!("Expected ScopedPlaceId::Symbol, found ScopedPlaceId::Member")
}
}
}
}
impl<T> std::ops::Index<ScopedPlaceId> for Vec<T> {
type Output = T;
fn index(&self, index: ScopedPlaceId) -> &Self::Output {
match index {
ScopedPlaceId::Symbol(id) => &self[id.index()],
ScopedPlaceId::Member(id) => &self[id.index()],
}
}
}
impl From<ScopedMemberId> for ScopedPlaceId {
fn from(value: ScopedMemberId) -> Self {
Self::Member(value)
}
}
impl From<ScopedSymbolId> for ScopedPlaceId {
fn from(value: ScopedSymbolId) -> Self {
Self::Symbol(value)
}
}
pub struct ParentPlaceIter<'a> {
state: Option<ParentPlaceIterState<'a>>,
}
enum ParentPlaceIterState<'a> {
Symbol {
symbol_name: &'a str,
symbols: &'a SymbolTable,
},
Member {
symbols: &'a SymbolTable,
members: &'a MemberTable,
next_member: MemberExprRef<'a>,
},
}
impl<'a> ParentPlaceIterState<'a> {
fn parent_state(
expression: &MemberExprRef<'a>,
symbols: &'a SymbolTable,
members: &'a MemberTable,
) -> Self {
match expression.parent() {
Some(parent) => Self::Member {
next_member: parent,
symbols,
members,
},
None => Self::Symbol {
symbol_name: expression.symbol_name(),
symbols,
},
}
}
}
impl<'a> ParentPlaceIter<'a> {
fn for_symbol() -> Self {
ParentPlaceIter { state: None }
}
fn for_member(
expression: &'a MemberExpr,
symbol_table: &'a SymbolTable,
member_table: &'a MemberTable,
) -> Self {
let expr_ref = expression.as_ref();
ParentPlaceIter {
state: Some(ParentPlaceIterState::parent_state(
&expr_ref,
symbol_table,
member_table,
)),
}
}
}
impl Iterator for ParentPlaceIter<'_> {
type Item = ScopedPlaceId;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.state.take()? {
ParentPlaceIterState::Symbol {
symbol_name,
symbols,
} => {
let id = symbols.symbol_id(symbol_name)?;
break Some(id.into());
}
ParentPlaceIterState::Member {
symbols,
members,
next_member,
} => {
self.state = Some(ParentPlaceIterState::parent_state(
&next_member,
symbols,
members,
));
if let Some(id) = members.member_id(next_member) {
break Some(id.into());
}
}
}
}
}
}
impl FusedIterator for ParentPlaceIter<'_> {}
pub(crate) struct PossiblyNarrowedPlacesBuilder<'db, 'a> {
db: &'db dyn Db,
places: &'a PlaceTableBuilder,
}
impl<'db, 'a> PossiblyNarrowedPlacesBuilder<'db, 'a> {
pub(crate) fn new(db: &'db dyn Db, places: &'a PlaceTableBuilder) -> Self {
Self { db, places }
}
pub(crate) fn expression(self, expr: &ast::Expr) -> PossiblyNarrowedPlaces {
self.expression_node(expr)
}
pub(crate) fn pattern(
self,
pattern: PatternPredicate<'db>,
module: &ParsedModuleRef,
) -> PossiblyNarrowedPlaces {
self.pattern_kind(pattern.subject(self.db), module)
}
fn expression_node(&self, expr: &ast::Expr) -> PossiblyNarrowedPlaces {
match expr {
ast::Expr::Name(_) => self.simple_expr(expr),
ast::Expr::Attribute(attribute) => {
let mut places = self.simple_expr(expr);
places.extend(self.simple_expr(&attribute.value));
places
}
ast::Expr::Subscript(subscript) => {
let mut places = self.simple_expr(expr);
places.extend(self.simple_expr(&subscript.value));
places
}
ast::Expr::Compare(expr_compare) => self.expr_compare(expr_compare),
ast::Expr::Call(expr_call) => self.expr_call(expr_call),
ast::Expr::UnaryOp(unary_op) if unary_op.op == ast::UnaryOp::Not => {
self.expression_node(&unary_op.operand)
}
ast::Expr::BoolOp(bool_op) => self.expr_bool_op(bool_op),
ast::Expr::If(expr_if) => self.expr_if(expr_if),
ast::Expr::Named(expr_named) => {
let mut places = self.simple_expr(&expr_named.target);
places.extend(self.expression_node(&expr_named.value));
places
}
_ => PossiblyNarrowedPlaces::default(),
}
}
fn simple_expr(&self, expr: &ast::Expr) -> PossiblyNarrowedPlaces {
let mut places = PossiblyNarrowedPlaces::default();
if let Some(place_expr) = PlaceExpr::try_from_expr(expr) {
if let Some(place) = self.places.place_id((&place_expr).into()) {
places.insert(place);
}
}
places
}
fn expr_compare(&self, expr_compare: &ast::ExprCompare) -> PossiblyNarrowedPlaces {
let mut places = PossiblyNarrowedPlaces::default();
self.add_narrowing_target(&expr_compare.left, &mut places);
for comparator in &expr_compare.comparators {
self.add_narrowing_target(comparator, &mut places);
}
let can_narrow_tagged_union_base = matches!(
&*expr_compare.ops,
[ast::CmpOp::Eq | ast::CmpOp::NotEq | ast::CmpOp::Is | ast::CmpOp::IsNot]
);
for expr in std::iter::once(&*expr_compare.left).chain(&expr_compare.comparators) {
if can_narrow_tagged_union_base
&& let ast::Expr::Subscript(subscript) = expr.expression_value()
&& let Some(place_expr) = PlaceExpr::try_from_expr(&subscript.value)
&& let Some(place) = self.places.place_id((&place_expr).into())
{
places.insert(place);
}
if can_narrow_tagged_union_base
&& let ast::Expr::Attribute(attribute) = expr
&& let Some(place_expr) = PlaceExpr::try_from_expr(&attribute.value)
&& let Some(place) = self.places.place_id((&place_expr).into())
{
places.insert(place);
}
}
places
}
fn expr_call(&self, expr_call: &ast::ExprCall) -> PossiblyNarrowedPlaces {
let mut places = PossiblyNarrowedPlaces::default();
for argument in expr_call.arguments.args.iter().take(2).chain(
expr_call
.arguments
.keywords
.iter()
.filter(|keyword| keyword.arg.is_some())
.map(|keyword| &keyword.value),
) {
if let Some(place_expr) = PlaceExpr::try_from_expr(argument) {
if let Some(place) = self.places.place_id((&place_expr).into()) {
places.insert(place);
}
}
}
if let Some(first_arg) = expr_call.arguments.args.first() {
if expr_call.arguments.args.len() == 1 && expr_call.arguments.keywords.is_empty() {
places.extend(self.expression_node(first_arg));
}
}
places
}
fn expr_bool_op(&self, bool_op: &ast::ExprBoolOp) -> PossiblyNarrowedPlaces {
let mut places = PossiblyNarrowedPlaces::default();
for value in &bool_op.values {
places.extend(self.expression_node(value));
}
places
}
fn expr_if(&self, expr_if: &ast::ExprIf) -> PossiblyNarrowedPlaces {
let mut places = self.expression_node(&expr_if.test);
places.extend(self.expression_node(&expr_if.body));
places.extend(self.expression_node(&expr_if.orelse));
places
}
fn add_narrowing_target(&self, expr: &ast::Expr, places: &mut PossiblyNarrowedPlaces) {
if let Some(place_expr) = PlaceExpr::try_from_expr(expr)
&& let Some(place) = self.places.place_id((&place_expr).into())
{
places.insert(place);
}
match expr.expression_value() {
ast::Expr::Call(call) if call.arguments.args.len() == 1 => {
if let Some(first_arg) = call.arguments.args.first()
&& let Some(place_expr) = PlaceExpr::try_from_expr(first_arg)
&& let Some(place) = self.places.place_id((&place_expr).into())
{
places.insert(place);
}
}
ast::Expr::Attribute(attribute) if attribute.attr.as_str() == "__class__" => {
if let Some(place_expr) = PlaceExpr::try_from_expr(&attribute.value)
&& let Some(place) = self.places.place_id((&place_expr).into())
{
places.insert(place);
}
}
_ => {}
}
}
fn pattern_kind(
&self,
subject: Expression<'db>,
module: &ParsedModuleRef,
) -> PossiblyNarrowedPlaces {
let mut places = PossiblyNarrowedPlaces::default();
let subject_node = subject.node_ref(self.db).node(module);
for expression in match_subject_place_expressions(subject_node) {
if let Some(place) = PlaceExpr::try_from_expr(expression)
.and_then(|place| self.places.place_id((&place).into()))
{
places.insert(place);
}
}
places
}
}