Use bitflags for tracking Context flags (#4381)

This commit is contained in:
Charlie Marsh 2023-05-12 12:07:26 -04:00 committed by GitHub
parent 09dbd2029c
commit 7e3ba7f32a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 384 additions and 170 deletions

View file

@ -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,
}