mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
Use bitflags
for tracking Context
flags (#4381)
This commit is contained in:
parent
09dbd2029c
commit
7e3ba7f32a
8 changed files with 384 additions and 170 deletions
|
@ -1,13 +1,13 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use nohash_hasher::{BuildNoHashHasher, IntMap};
|
||||
use rustpython_parser::ast::{Expr, Stmt};
|
||||
use smallvec::smallvec;
|
||||
|
||||
use ruff_python_ast::call_path::{collect_call_path, from_unqualified_name, CallPath};
|
||||
use ruff_python_ast::helpers::from_relative_import;
|
||||
use ruff_python_ast::typing::AnnotationKind;
|
||||
use ruff_python_stdlib::path::is_python_stub_file;
|
||||
use ruff_python_stdlib::typing::TYPING_EXTENSIONS;
|
||||
|
||||
|
@ -19,16 +19,6 @@ use crate::definition::{Definition, DefinitionId, Definitions, Member, Module};
|
|||
use crate::node::{NodeId, Nodes};
|
||||
use crate::scope::{Scope, ScopeId, ScopeKind, Scopes};
|
||||
|
||||
/// A snapshot of the [`Context`] at a given point in the AST traversal.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Snapshot {
|
||||
scope_id: ScopeId,
|
||||
stmt_id: Option<NodeId>,
|
||||
definition_id: DefinitionId,
|
||||
in_annotation: bool,
|
||||
in_type_checking_block: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Context<'a> {
|
||||
pub typing_modules: &'a [String],
|
||||
|
@ -54,19 +44,7 @@ pub struct Context<'a> {
|
|||
pub body: &'a [Stmt],
|
||||
pub body_index: usize,
|
||||
// Internal, derivative state.
|
||||
pub in_annotation: bool,
|
||||
pub in_type_definition: bool,
|
||||
pub in_deferred_string_type_definition: Option<AnnotationKind>,
|
||||
pub in_deferred_type_definition: bool,
|
||||
pub in_exception_handler: bool,
|
||||
pub in_f_string: bool,
|
||||
pub in_boolean_test: bool,
|
||||
pub in_literal: bool,
|
||||
pub in_subscript: bool,
|
||||
pub in_type_checking_block: bool,
|
||||
pub seen_import_boundary: bool,
|
||||
pub futures_allowed: bool,
|
||||
pub annotations_future_enabled: bool,
|
||||
pub flags: ContextFlags,
|
||||
pub handled_exceptions: Vec<Exceptions>,
|
||||
}
|
||||
|
||||
|
@ -87,19 +65,7 @@ impl<'a> Context<'a> {
|
|||
shadowed_bindings: IntMap::default(),
|
||||
body: &[],
|
||||
body_index: 0,
|
||||
in_annotation: false,
|
||||
in_type_definition: false,
|
||||
in_deferred_string_type_definition: None,
|
||||
in_deferred_type_definition: false,
|
||||
in_exception_handler: false,
|
||||
in_f_string: false,
|
||||
in_boolean_test: false,
|
||||
in_literal: false,
|
||||
in_subscript: false,
|
||||
in_type_checking_block: false,
|
||||
seen_import_boundary: false,
|
||||
futures_allowed: true,
|
||||
annotations_future_enabled: is_python_stub_file(path),
|
||||
flags: ContextFlags::new(path),
|
||||
handled_exceptions: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
@ -429,16 +395,12 @@ impl<'a> Context<'a> {
|
|||
.map_or(true, |stmt_id| self.stmts.parent_id(stmt_id).is_none())
|
||||
}
|
||||
|
||||
/// Returns `true` if the context is in an exception handler.
|
||||
pub const fn in_exception_handler(&self) -> bool {
|
||||
self.in_exception_handler
|
||||
}
|
||||
|
||||
/// Return the [`ExecutionContext`] of the current scope.
|
||||
pub const fn execution_context(&self) -> ExecutionContext {
|
||||
if self.in_type_checking_block
|
||||
|| self.in_annotation
|
||||
|| self.in_deferred_string_type_definition.is_some()
|
||||
if self.in_type_checking_block()
|
||||
|| self.in_annotation()
|
||||
|| self.in_complex_string_type_definition()
|
||||
|| self.in_simple_string_type_definition()
|
||||
{
|
||||
ExecutionContext::Typing
|
||||
} else {
|
||||
|
@ -461,8 +423,7 @@ impl<'a> Context<'a> {
|
|||
scope_id: self.scope_id,
|
||||
stmt_id: self.stmt_id,
|
||||
definition_id: self.definition_id,
|
||||
in_annotation: self.in_annotation,
|
||||
in_type_checking_block: self.in_type_checking_block,
|
||||
flags: self.flags,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,13 +433,273 @@ impl<'a> Context<'a> {
|
|||
scope_id,
|
||||
stmt_id,
|
||||
definition_id,
|
||||
in_annotation,
|
||||
in_type_checking_block,
|
||||
flags,
|
||||
} = snapshot;
|
||||
self.scope_id = scope_id;
|
||||
self.stmt_id = stmt_id;
|
||||
self.definition_id = definition_id;
|
||||
self.in_annotation = in_annotation;
|
||||
self.in_type_checking_block = in_type_checking_block;
|
||||
self.flags = flags;
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in a type annotation.
|
||||
pub const fn in_annotation(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::ANNOTATION)
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in a type definition.
|
||||
pub const fn in_type_definition(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::TYPE_DEFINITION)
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in a "simple" string type definition.
|
||||
pub const fn in_simple_string_type_definition(&self) -> bool {
|
||||
self.flags
|
||||
.contains(ContextFlags::SIMPLE_STRING_TYPE_DEFINITION)
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in a "complex" string type definition.
|
||||
pub const fn in_complex_string_type_definition(&self) -> bool {
|
||||
self.flags
|
||||
.contains(ContextFlags::COMPLEX_STRING_TYPE_DEFINITION)
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in a `__future__` type definition.
|
||||
pub const fn in_future_type_definition(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::FUTURE_TYPE_DEFINITION)
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in any kind of deferred type definition.
|
||||
pub const fn in_deferred_type_definition(&self) -> bool {
|
||||
self.in_simple_string_type_definition()
|
||||
|| self.in_complex_string_type_definition()
|
||||
|| self.in_future_type_definition()
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in an exception handler.
|
||||
pub const fn in_exception_handler(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::EXCEPTION_HANDLER)
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in an f-string.
|
||||
pub const fn in_f_string(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::F_STRING)
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in boolean test.
|
||||
pub const fn in_boolean_test(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::BOOLEAN_TEST)
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in a `typing::Literal` annotation.
|
||||
pub const fn in_literal(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::LITERAL)
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in a subscript expression.
|
||||
pub const fn in_subscript(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::SUBSCRIPT)
|
||||
}
|
||||
|
||||
/// Return `true` if the context is in a type-checking block.
|
||||
pub const fn in_type_checking_block(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::TYPE_CHECKING_BLOCK)
|
||||
}
|
||||
|
||||
/// Return `true` if the context has traversed past the "top-of-file" import boundary.
|
||||
pub const fn seen_import_boundary(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::IMPORT_BOUNDARY)
|
||||
}
|
||||
|
||||
/// Return `true` if the context has traverse past the `__future__` import boundary.
|
||||
pub const fn seen_futures_boundary(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::FUTURES_BOUNDARY)
|
||||
}
|
||||
|
||||
/// Return `true` if `__future__`-style type annotations are enabled.
|
||||
pub const fn future_annotations(&self) -> bool {
|
||||
self.flags.contains(ContextFlags::FUTURE_ANNOTATIONS)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Flags indicating the current context of the analysis.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct ContextFlags: u16 {
|
||||
/// The context is in a type annotation.
|
||||
///
|
||||
/// For example, the context could be visiting `int` in:
|
||||
/// ```python
|
||||
/// x: int = 1
|
||||
/// ```
|
||||
const ANNOTATION = 1 << 0;
|
||||
|
||||
/// The context is in a type definition.
|
||||
///
|
||||
/// For example, the context could be visiting `int` in:
|
||||
/// ```python
|
||||
/// from typing import NewType
|
||||
///
|
||||
/// UserId = NewType("UserId", int)
|
||||
/// ```
|
||||
///
|
||||
/// All type annotations are also type definitions, but the converse is not true.
|
||||
/// In our example, `int` is a type definition but not a type annotation, as it
|
||||
/// doesn't appear in a type annotation context, but rather in a type definition.
|
||||
const TYPE_DEFINITION = 1 << 1;
|
||||
|
||||
/// The context is in a (deferred) "simple" string type definition.
|
||||
///
|
||||
/// For example, the context could be visiting `list[int]` in:
|
||||
/// ```python
|
||||
/// x: "list[int]" = []
|
||||
/// ```
|
||||
///
|
||||
/// "Simple" string type definitions are those that consist of a single string literal,
|
||||
/// as opposed to an implicitly concatenated string literal.
|
||||
const SIMPLE_STRING_TYPE_DEFINITION = 1 << 2;
|
||||
|
||||
/// The context is in a (deferred) "complex" string type definition.
|
||||
///
|
||||
/// For example, the context could be visiting `list[int]` in:
|
||||
/// ```python
|
||||
/// x: ("list" "[int]") = []
|
||||
/// ```
|
||||
///
|
||||
/// "Complex" string type definitions are those that consist of a implicitly concatenated
|
||||
/// string literals. These are uncommon but valid.
|
||||
const COMPLEX_STRING_TYPE_DEFINITION = 1 << 3;
|
||||
|
||||
/// The context is in a (deferred) `__future__` type definition.
|
||||
///
|
||||
/// For example, the context could be visiting `list[int]` in:
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
///
|
||||
/// x: list[int] = []
|
||||
/// ```
|
||||
///
|
||||
/// `__future__`-style type annotations are only enabled if the `annotations` feature
|
||||
/// is enabled via `from __future__ import annotations`.
|
||||
const FUTURE_TYPE_DEFINITION = 1 << 4;
|
||||
|
||||
/// The context is in an exception handler.
|
||||
///
|
||||
/// For example, the context could be visiting `x` in:
|
||||
/// ```python
|
||||
/// try:
|
||||
/// ...
|
||||
/// except Exception:
|
||||
/// x: int = 1
|
||||
/// ```
|
||||
const EXCEPTION_HANDLER = 1 << 5;
|
||||
|
||||
/// The context is in an f-string.
|
||||
///
|
||||
/// For example, the context could be visiting `x` in:
|
||||
/// ```python
|
||||
/// f'{x}'
|
||||
/// ```
|
||||
const F_STRING = 1 << 6;
|
||||
|
||||
/// The context is in a boolean test.
|
||||
///
|
||||
/// For example, the context could be visiting `x` in:
|
||||
/// ```python
|
||||
/// if x:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// The implication is that the actual value returned by the current expression is
|
||||
/// not used, only its truthiness.
|
||||
const BOOLEAN_TEST = 1 << 7;
|
||||
|
||||
/// The context is in a `typing::Literal` annotation.
|
||||
///
|
||||
/// For example, the context could be visiting any of `"A"`, `"B"`, or `"C"` in:
|
||||
/// ```python
|
||||
/// def f(x: Literal["A", "B", "C"]):
|
||||
/// ...
|
||||
/// ```
|
||||
const LITERAL = 1 << 8;
|
||||
|
||||
/// The context is in a subscript expression.
|
||||
///
|
||||
/// For example, the context could be visiting `x["a"]` in:
|
||||
/// ```python
|
||||
/// x["a"]["b"]
|
||||
/// ```
|
||||
const SUBSCRIPT = 1 << 9;
|
||||
|
||||
/// The context is in a type-checking block.
|
||||
///
|
||||
/// For example, the context could be visiting `x` in:
|
||||
/// ```python
|
||||
/// from typing import TYPE_CHECKING
|
||||
///
|
||||
///
|
||||
/// if TYPE_CHECKING:
|
||||
/// x: int = 1
|
||||
/// ```
|
||||
const TYPE_CHECKING_BLOCK = 1 << 10;
|
||||
|
||||
|
||||
/// The context has traversed past the "top-of-file" import boundary.
|
||||
///
|
||||
/// For example, the context could be visiting `x` in:
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// def f() -> None:
|
||||
/// ...
|
||||
///
|
||||
/// x: int = 1
|
||||
/// ```
|
||||
const IMPORT_BOUNDARY = 1 << 11;
|
||||
|
||||
/// The context has traversed past the `__future__` import boundary.
|
||||
///
|
||||
/// For example, the context could be visiting `x` in:
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
///
|
||||
/// import os
|
||||
///
|
||||
/// x: int = 1
|
||||
/// ```
|
||||
///
|
||||
/// Python considers it a syntax error to import from `__future__` after
|
||||
/// any other non-`__future__`-importing statements.
|
||||
const FUTURES_BOUNDARY = 1 << 12;
|
||||
|
||||
/// `__future__`-style type annotations are enabled in this context.
|
||||
///
|
||||
/// For example, the context could be visiting `x` in:
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
///
|
||||
///
|
||||
/// def f(x: int) -> int:
|
||||
/// ...
|
||||
/// ```
|
||||
const FUTURE_ANNOTATIONS = 1 << 13;
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextFlags {
|
||||
pub fn new(path: &Path) -> Self {
|
||||
let mut flags = Self::default();
|
||||
if is_python_stub_file(path) {
|
||||
flags |= Self::FUTURE_ANNOTATIONS;
|
||||
}
|
||||
flags
|
||||
}
|
||||
}
|
||||
|
||||
/// A snapshot of the [`Context`] at a given point in the AST traversal.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Snapshot {
|
||||
scope_id: ScopeId,
|
||||
stmt_id: Option<NodeId>,
|
||||
definition_id: DefinitionId,
|
||||
flags: ContextFlags,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue