Skip to content

Commit

Permalink
Merge branch 'feat/tc008-typing-execution-context' into bugfix/tc008-…
Browse files Browse the repository at this point in the history
…union-syntax-pre-py310
  • Loading branch information
Daverball committed Jan 8, 2025
2 parents 4486fd3 + 9e8642f commit c34a855
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 16 deletions.
1 change: 1 addition & 0 deletions crates/ruff_linter/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2324,6 +2324,7 @@ impl<'a> Checker<'a> {
let parsed_expr = parsed_annotation.expression();
self.visit_expr(parsed_expr);
if self.semantic.in_type_alias_value() {
// stub files are covered by PYI020
if !self.source_type.is_stub() && self.enabled(Rule::QuotedTypeAlias) {
flake8_type_checking::rules::quoted_type_alias(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, FixAvailab
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast as ast;
use ruff_python_ast::{Expr, Stmt};
use ruff_python_semantic::{Binding, SemanticModel};
use ruff_python_semantic::{Binding, SemanticModel, TypingOnlyBindingsStatus};
use ruff_python_stdlib::typing::{is_pep_593_generic_type, is_standard_library_literal};
use ruff_text_size::Ranged;

Expand Down Expand Up @@ -70,14 +70,20 @@ impl Violation for UnquotedTypeAlias {
///
/// ## Why is this bad?
/// Unnecessary string forward references can lead to additional overhead
/// in runtime libraries making use of type hints, as well as lead to bad
/// in runtime libraries making use of type hints. They can also have bad
/// interactions with other runtime uses like [PEP 604] type unions.
///
/// For explicit type aliases the quotes are only considered redundant
/// if the type expression contains no subscripts or attribute accesses
/// this is because of stubs packages. Some types will only be subscriptable
/// at type checking time, similarly there may be some module-level
/// attributes like type aliases that are only available in the stubs.
/// PEP-613 type aliases are only flagged by the rule if Ruff can have high
/// confidence that the quotes are unnecessary. Specifically, any PEP-613
/// type alias where the type expression on the right-hand side contains
/// subscripts or attribute accesses will not be flagged. This is because
/// type aliases can reference types that are, for example, generic in stub
/// files but not at runtime. That can mean that a type checker expects the
/// referenced type to be subscripted with type arguments despite the fact
/// that doing so would fail at runtime if the type alias value was not
/// quoted. Similarly, a type alias might need to reference a module-level
/// attribute that exists in a stub file but not at runtime, meaning that
/// the type alias value would need to be quoted to avoid a runtime error.
///
/// ## Example
/// Given:
Expand All @@ -103,6 +109,15 @@ impl Violation for UnquotedTypeAlias {
/// ## Fix safety
/// This rule's fix is marked as safe, unless the type annotation contains comments.
///
/// ## See also
/// This rule only applies to type aliases in non-stub files. For removing quotes in other
/// contexts or in stub files, see:
///
/// - [`quoted-annotation-in-stub`](quoted-annotation-in-stub.md): A rule that
/// removes all quoted annotations from stub files
/// - [`quoted-annotation`](quoted-annotation.md): A rule that removes unnecessary quotes
/// from *annotations* in runtime files.
///
/// ## References
/// - [PEP 613 – Explicit Type Aliases](https://peps.python.org/pep-0613/)
/// - [PEP 695: Generic Type Alias](https://peps.python.org/pep-0695/#generic-type-alias)
Expand Down Expand Up @@ -223,7 +238,7 @@ fn collect_typing_references<'a>(
};
if checker
.semantic()
.simulate_runtime_load(name, false)
.simulate_runtime_load(name, TypingOnlyBindingsStatus::Disallowed)
.is_some()
{
return;
Expand Down Expand Up @@ -357,7 +372,7 @@ fn quotes_are_unremovable(
Expr::Name(name) => {
semantic.resolve_name(name).is_some()
&& semantic
.simulate_runtime_load(name, semantic.in_type_checking_block())
.simulate_runtime_load(name, semantic.in_type_checking_block().into())
.is_none()
}
_ => false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ use crate::checkers::ast::Checker;
/// bar: Bar
/// ```
///
/// ## See also
/// - [`quoted-annotation-in-stub`](quoted-annotation-in-stub.md): A rule that
/// removes all quoted annotations from stub files
/// - [`quoted-type-alias`](quoted-type-alias.md): A rule that removes unnecessary quotes
/// from type aliases.
///
/// ## References
/// - [PEP 563 – Postponed Evaluation of Annotations](https://peps.python.org/pep-0563/)
/// - [Python documentation: `__future__`](https://docs.python.org/3/library/__future__.html#module-__future__)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use ruff_python_ast as ast;
use ruff_python_literal::format::FormatSpec;
use ruff_python_parser::parse_expression;
use ruff_python_semantic::analyze::logging::is_logger_candidate;
use ruff_python_semantic::{Modules, SemanticModel};
use ruff_python_semantic::{Modules, SemanticModel, TypingOnlyBindingsStatus};
use ruff_text_size::{Ranged, TextRange};

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -216,7 +216,7 @@ fn should_be_fstring(
id,
literal.range(),
semantic.scope_id,
false,
TypingOnlyBindingsStatus::Disallowed,
)
.map_or(true, |id| semantic.binding(id).kind.is_builtin())
{
Expand Down
38 changes: 33 additions & 5 deletions crates/ruff_python_semantic/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,13 +713,13 @@ impl<'a> SemanticModel<'a> {
pub fn simulate_runtime_load(
&self,
name: &ast::ExprName,
allow_typing_only_bindings: bool,
typing_only_bindings_status: TypingOnlyBindingsStatus,
) -> Option<BindingId> {
self.simulate_runtime_load_at_location_in_scope(
name.id.as_str(),
name.range,
self.scope_id,
allow_typing_only_bindings,
typing_only_bindings_status,
)
}

Expand Down Expand Up @@ -752,7 +752,7 @@ impl<'a> SemanticModel<'a> {
symbol: &str,
symbol_range: TextRange,
scope_id: ScopeId,
allow_typing_only_bindings: bool,
typing_only_bindings_status: TypingOnlyBindingsStatus,
) -> Option<BindingId> {
let mut seen_function = false;
let mut class_variables_visible = true;
Expand Down Expand Up @@ -795,7 +795,9 @@ impl<'a> SemanticModel<'a> {
// runtime binding with a source-order inaccurate one
for shadowed_id in scope.shadowed_bindings(binding_id) {
let binding = &self.bindings[shadowed_id];
if !allow_typing_only_bindings && binding.context.is_typing() {
if typing_only_bindings_status.is_disallowed()
&& binding.context.is_typing()
{
continue;
}
if let BindingKind::Annotation
Expand Down Expand Up @@ -830,7 +832,7 @@ impl<'a> SemanticModel<'a> {
_ => binding_id,
};

if !allow_typing_only_bindings
if typing_only_bindings_status.is_disallowed()
&& self.bindings[candidate_id].context.is_typing()
{
continue;
Expand Down Expand Up @@ -2065,6 +2067,32 @@ impl ShadowedBinding {
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypingOnlyBindingsStatus {
Allowed,
Disallowed,
}

impl TypingOnlyBindingsStatus {
pub const fn is_allowed(self) -> bool {
matches!(self, TypingOnlyBindingsStatus::Allowed)
}

pub const fn is_disallowed(self) -> bool {
matches!(self, TypingOnlyBindingsStatus::Disallowed)
}
}

impl From<bool> for TypingOnlyBindingsStatus {
fn from(value: bool) -> Self {
if value {
TypingOnlyBindingsStatus::Allowed
} else {
TypingOnlyBindingsStatus::Disallowed
}
}
}

bitflags! {
/// A select list of Python modules that the semantic model can explicitly track.
#[derive(Debug)]
Expand Down

0 comments on commit c34a855

Please sign in to comment.