Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add semantic model flag when inside f-string replacement field #10766

Merged
merged 1 commit into from Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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