Skip to content

Commit

Permalink
Add semantic model flag when inside f-string replacement field (#10766)
Browse files Browse the repository at this point in the history
## Summary

This PR adds a new semantic model flag to indicate that the checker is
inside an f-string replacement field. This will be used to ignore
certain checks if the target version doesn't support a specific feature
like PEP 701.

fixes: #10761 

## Test Plan

Add a test case from the raised issue.
  • Loading branch information
dhruvmanila committed Apr 4, 2024
1 parent 6b4fa17 commit d02b106
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 9 deletions.
Expand Up @@ -5,3 +5,5 @@

# https://github.com/astral-sh/ruff/issues/10546
x: "Literal['foo', 'bar']"
# https://github.com/astral-sh/ruff/issues/10761
f"Before {f'x {x}' if y else f'foo {z}'} after"
13 changes: 11 additions & 2 deletions crates/ruff_linter/src/checkers/ast/mod.rs
Expand Up @@ -32,8 +32,8 @@ use itertools::Itertools;
use log::debug;
use ruff_python_ast::{
self as ast, all::DunderAllName, Comprehension, ElifElseClause, ExceptHandler, Expr,
ExprContext, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt,
Suite, UnaryOp,
ExprContext, FStringElement, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters,
Pattern, Stmt, Suite, UnaryOp,
};
use ruff_text_size::{Ranged, TextRange, TextSize};

Expand Down Expand Up @@ -1580,6 +1580,15 @@ impl<'a> Visitor<'a> for Checker<'a> {
.push((bound, self.semantic.snapshot()));
}
}

fn visit_f_string_element(&mut self, f_string_element: &'a FStringElement) {
let snapshot = self.semantic.flags;
if f_string_element.is_expression() {
self.semantic.flags |= SemanticModelFlags::F_STRING_REPLACEMENT_FIELD;
}
visitor::walk_f_string_element(self, f_string_element);
self.semantic.flags = snapshot;
}
}

impl<'a> Checker<'a> {
Expand Down
Expand Up @@ -449,13 +449,8 @@ pub(crate) fn check_string_quotes(checker: &mut Checker, string_like: StringLike
return;
}

// If the string is part of a f-string, ignore it.
if checker
.indexer()
.fstring_ranges()
.outermost(string_like.start())
.is_some_and(|outer| outer.start() < string_like.start() && string_like.end() < outer.end())
{
// TODO(dhruvmanila): Support checking for escaped quotes in f-strings.
if checker.semantic().in_f_string_replacement_field() {
return;
}

Expand Down
15 changes: 15 additions & 0 deletions crates/ruff_python_semantic/src/model.rs
Expand Up @@ -1525,6 +1525,12 @@ impl<'a> SemanticModel<'a> {
self.flags.intersects(SemanticModelFlags::F_STRING)
}

/// Return `true` if the model is in an f-string replacement field.
pub const fn in_f_string_replacement_field(&self) -> bool {
self.flags
.intersects(SemanticModelFlags::F_STRING_REPLACEMENT_FIELD)
}

/// Return `true` if the model is in boolean test.
pub const fn in_boolean_test(&self) -> bool {
self.flags.intersects(SemanticModelFlags::BOOLEAN_TEST)
Expand Down Expand Up @@ -1960,6 +1966,15 @@ bitflags! {
/// ```
const DUNDER_ALL_DEFINITION = 1 << 22;

/// The model is in an f-string replacement field.
///
/// For example, the model could be visiting `x` or `y` in:
///
/// ```python
/// f"first {x} second {y}"
/// ```
const F_STRING_REPLACEMENT_FIELD = 1 << 23;

/// The context is in any type annotation.
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();

Expand Down

0 comments on commit d02b106

Please sign in to comment.