From f16e780e0ac74e6c9bd94435ce1992324b26eab2 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 14 Aug 2023 09:46:54 -0400 Subject: [PATCH] Add an implicit concatenation flag to string and bytes constants (#6512) ## Summary Per the discussion in https://github.com/astral-sh/ruff/discussions/6183, this PR adds an `implicit_concatenated` flag to the string and bytes constant variants. It's not actually _used_ anywhere as of this PR, but it is covered by the tests. Specifically, we now use a struct for the string and bytes cases, along with the `Expr::FString` node. That struct holds the value, plus the flag: ```rust #[derive(Clone, Debug, PartialEq, is_macro::Is)] pub enum Constant { Str(StringConstant), Bytes(BytesConstant), ... } #[derive(Clone, Debug, PartialEq, Eq)] pub struct StringConstant { /// The string value as resolved by the parser (i.e., without quotes, or escape sequences, or /// implicit concatenations). pub value: String, /// Whether the string contains multiple string tokens that were implicitly concatenated. pub implicit_concatenated: bool, } impl Deref for StringConstant { type Target = str; fn deref(&self) -> &Self::Target { self.value.as_str() } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct BytesConstant { /// The bytes value as resolved by the parser (i.e., without quotes, or escape sequences, or /// implicit concatenations). pub value: Vec, /// Whether the string contains multiple string tokens that were implicitly concatenated. pub implicit_concatenated: bool, } impl Deref for BytesConstant { type Target = [u8]; fn deref(&self) -> &Self::Target { self.value.as_slice() } } ``` ## Test Plan `cargo test` --- .../src/checkers/ast/analyze/expression.rs | 8 +- crates/ruff/src/checkers/ast/mod.rs | 2 +- .../rules/airflow/rules/task_variable_name.rs | 2 +- .../rules/check_positional_boolean_in_def.rs | 2 +- .../rules/getattr_with_constant.rs | 2 +- .../rules/setattr_with_constant.rs | 2 +- .../rules/unreliable_callable_check.rs | 4 +- .../rules/all_with_model_form.rs | 8 +- .../flake8_pyi/rules/unrecognized_platform.rs | 2 +- .../flake8_pytest_style/rules/helpers.rs | 6 +- .../flake8_pytest_style/rules/parametrize.rs | 36 +- .../rules/flake8_simplify/rules/ast_expr.rs | 4 +- .../src/rules/flake8_simplify/rules/ast_if.rs | 16 +- .../path_constructor_current_directory.rs | 4 +- crates/ruff/src/rules/flynt/helpers.rs | 2 +- .../flynt/rules/static_join_to_fstring.rs | 32 +- .../src/rules/pandas_vet/rules/read_table.rs | 4 +- .../ruff/src/rules/pyflakes/rules/strings.rs | 6 +- .../pylint/rules/assert_on_string_literal.rs | 2 +- .../pylint/rules/bad_string_format_type.rs | 5 +- crates/ruff/src/rules/pylint/rules/logging.rs | 2 +- .../pylint/rules/magic_value_comparison.rs | 4 +- ...convert_named_tuple_functional_to_class.rs | 5 +- .../convert_typed_dict_functional_to_class.rs | 5 +- .../rules/pyupgrade/rules/native_literals.rs | 10 +- .../rules/printf_string_formatting.rs | 5 +- .../pyupgrade/rules/redundant_open_modes.rs | 10 +- .../tryceratops/rules/raise_vanilla_args.rs | 6 +- crates/ruff_python_ast/src/all.rs | 2 +- crates/ruff_python_ast/src/comparable.rs | 26 +- crates/ruff_python_ast/src/helpers.rs | 23 +- crates/ruff_python_ast/src/node.rs | 6 +- crates/ruff_python_ast/src/nodes.rs | 80 ++- crates/ruff_python_ast/src/relocate.rs | 2 +- crates/ruff_python_ast/src/visitor.rs | 2 +- crates/ruff_python_codegen/src/generator.rs | 12 +- ...parser__parser__tests__dict_unpacking.snap | 20 +- ..._tests__generator_expression_argument.snap | 15 +- ...f_python_parser__parser__tests__match.snap | 20 +- ...on_parser__parser__tests__parse_class.snap | 5 +- ...parser__parser__tests__parse_f_string.snap | 6 +- ...n_parser__parser__tests__parse_kwargs.snap | 5 +- ..._parser__parser__tests__parse_print_2.snap | 5 +- ...ser__parser__tests__parse_print_hello.snap | 5 +- ...n_parser__parser__tests__parse_string.snap | 5 +- ...parser__tests__parse_type_declaration.snap | 5 +- ...f_python_parser__parser__tests__patma.snap | 35 +- ...uff_python_parser__parser__tests__try.snap | 12 +- ...ython_parser__parser__tests__try_star.snap | 27 +- ...arser__string__tests__backspace_alias.snap | 5 +- ...hon_parser__string__tests__bell_alias.snap | 5 +- ..._string__tests__carriage_return_alias.snap | 5 +- ...r_tabulation_with_justification_alias.snap | 5 +- ...n_parser__string__tests__delete_alias.snap | 5 +- ...er__string__tests__double_quoted_byte.snap | 519 +++++++++--------- ...n_parser__string__tests__escape_alias.snap | 5 +- ...g__tests__escape_char_in_byte_literal.snap | 27 +- ...n_parser__string__tests__escape_octet.snap | 17 +- ...arser__string__tests__form_feed_alias.snap | 5 +- ...string__tests__fstring_constant_range.snap | 16 +- ...ing__tests__fstring_escaped_character.snap | 6 +- ...tring__tests__fstring_escaped_newline.snap | 6 +- ...ing__tests__fstring_line_continuation.snap | 6 +- ...ring_parse_self_documenting_base_more.snap | 10 +- ...fstring_parse_self_documenting_format.snap | 6 +- ...ing__tests__fstring_unescaped_newline.snap | 6 +- ...thon_parser__string__tests__hts_alias.snap | 5 +- ...tring__tests__parse_f_string_concat_1.snap | 6 +- ...tring__tests__parse_f_string_concat_2.snap | 6 +- ...tring__tests__parse_f_string_concat_3.snap | 11 +- ...tring__tests__parse_f_string_concat_4.snap | 63 +++ ..._parser__string__tests__parse_fstring.snap | 5 +- ...ring_nested_concatenation_string_spec.snap | 50 ++ ...ing__tests__parse_fstring_nested_spec.snap | 1 + ...sts__parse_fstring_nested_string_spec.snap | 50 ++ ..._tests__parse_fstring_not_nested_spec.snap | 6 +- ...r__string__tests__parse_string_concat.snap | 5 +- ..._parse_string_triple_quotes_with_kind.snap | 5 +- ...ing__tests__parse_u_f_string_concat_1.snap | 6 +- ...ing__tests__parse_u_f_string_concat_2.snap | 6 +- ...tring__tests__parse_u_string_concat_1.snap | 5 +- ...tring__tests__parse_u_string_concat_2.snap | 5 +- ...er__string__tests__raw_byte_literal_1.snap | 15 +- ...er__string__tests__raw_byte_literal_2.snap | 11 +- ...on_parser__string__tests__raw_fstring.snap | 1 + ...er__string__tests__single_quoted_byte.snap | 519 +++++++++--------- ...ing__tests__triple_quoted_raw_fstring.snap | 1 + crates/ruff_python_parser/src/string.rs | 63 ++- 88 files changed, 1252 insertions(+), 761 deletions(-) create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap diff --git a/crates/ruff/src/checkers/ast/analyze/expression.rs b/crates/ruff/src/checkers/ast/analyze/expression.rs index d452a718a3..15cf51627d 100644 --- a/crates/ruff/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff/src/checkers/ast/analyze/expression.rs @@ -418,9 +418,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::BadStringFormatCharacter) { pylint::rules::bad_string_format_character::call( - checker, - val.as_str(), - location, + checker, val, location, ); } } @@ -918,7 +916,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { pylint::rules::await_outside_async(checker, expr); } } - Expr::FString(ast::ExprFString { values, range: _ }) => { + Expr::FString(ast::ExprFString { values, .. }) => { if checker.enabled(Rule::FStringMissingPlaceholders) { pyflakes::rules::f_string_missing_placeholders(expr, values, checker); } @@ -945,7 +943,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { range: _, }) => { if let Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), + value: Constant::Str(ast::StringConstant { value, .. }), .. }) = left.as_ref() { diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 0cb15891ec..4eb0589eb2 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -1275,7 +1275,7 @@ where fn visit_format_spec(&mut self, format_spec: &'b Expr) { match format_spec { - Expr::FString(ast::ExprFString { values, range: _ }) => { + Expr::FString(ast::ExprFString { values, .. }) => { for value in values { self.visit_expr(value); } diff --git a/crates/ruff/src/rules/airflow/rules/task_variable_name.rs b/crates/ruff/src/rules/airflow/rules/task_variable_name.rs index 9c73477af5..6a33944448 100644 --- a/crates/ruff/src/rules/airflow/rules/task_variable_name.rs +++ b/crates/ruff/src/rules/airflow/rules/task_variable_name.rs @@ -80,7 +80,7 @@ pub(crate) fn variable_name_task_id( // If the keyword argument is not a string, we can't do anything. let task_id = match &keyword.value { Expr::Constant(constant) => match &constant.value { - Constant::Str(value) => value, + Constant::Str(ast::StringConstant { value, .. }) => value, _ => return None, }, _ => return None, diff --git a/crates/ruff/src/rules/flake8_boolean_trap/rules/check_positional_boolean_in_def.rs b/crates/ruff/src/rules/flake8_boolean_trap/rules/check_positional_boolean_in_def.rs index a60dc6ae6a..b1197a75a7 100644 --- a/crates/ruff/src/rules/flake8_boolean_trap/rules/check_positional_boolean_in_def.rs +++ b/crates/ruff/src/rules/flake8_boolean_trap/rules/check_positional_boolean_in_def.rs @@ -112,7 +112,7 @@ pub(crate) fn check_positional_boolean_in_def( let hint = match expr.as_ref() { Expr::Name(name) => &name.id == "bool", Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), + value: Constant::Str(ast::StringConstant { value, .. }), .. }) => value == "bool", _ => false, diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/getattr_with_constant.rs b/crates/ruff/src/rules/flake8_bugbear/rules/getattr_with_constant.rs index 94de14785c..c7c5944c81 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/getattr_with_constant.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/getattr_with_constant.rs @@ -64,7 +64,7 @@ pub(crate) fn getattr_with_constant( return; }; let Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), + value: Constant::Str(ast::StringConstant { value, .. }), .. }) = arg else { diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/setattr_with_constant.rs b/crates/ruff/src/rules/flake8_bugbear/rules/setattr_with_constant.rs index ab5bc1462e..9bb1802b85 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/setattr_with_constant.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/setattr_with_constant.rs @@ -88,7 +88,7 @@ pub(crate) fn setattr_with_constant( if !is_identifier(name) { return; } - if is_mangled_private(name.as_str()) { + if is_mangled_private(name) { return; } // We can only replace a `setattr` call (which is an `Expr`) with an assignment diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs b/crates/ruff/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs index d9532a927b..eefcf16bd7 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/unreliable_callable_check.rs @@ -68,13 +68,13 @@ pub(crate) fn unreliable_callable_check( return; }; let Expr::Constant(ast::ExprConstant { - value: Constant::Str(string), + value: Constant::Str(ast::StringConstant { value, .. }), .. }) = attr else { return; }; - if string != "__call__" { + if value != "__call__" { return; } diff --git a/crates/ruff/src/rules/flake8_django/rules/all_with_model_form.rs b/crates/ruff/src/rules/flake8_django/rules/all_with_model_form.rs index 77dea2d27d..a54f7e08d2 100644 --- a/crates/ruff/src/rules/flake8_django/rules/all_with_model_form.rs +++ b/crates/ruff/src/rules/flake8_django/rules/all_with_model_form.rs @@ -83,13 +83,13 @@ pub(crate) fn all_with_model_form( continue; }; match value { - Constant::Str(s) => { - if s == "__all__" { + Constant::Str(ast::StringConstant { value, .. }) => { + if value == "__all__" { return Some(Diagnostic::new(DjangoAllWithModelForm, element.range())); } } - Constant::Bytes(b) => { - if b == "__all__".as_bytes() { + Constant::Bytes(ast::BytesConstant { value, .. }) => { + if value == "__all__".as_bytes() { return Some(Diagnostic::new(DjangoAllWithModelForm, element.range())); } } diff --git a/crates/ruff/src/rules/flake8_pyi/rules/unrecognized_platform.rs b/crates/ruff/src/rules/flake8_pyi/rules/unrecognized_platform.rs index 971bfd4b03..75d48b7326 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/unrecognized_platform.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/unrecognized_platform.rs @@ -123,7 +123,7 @@ pub(crate) fn unrecognized_platform(checker: &mut Checker, test: &Expr) { } if let Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), + value: Constant::Str(ast::StringConstant { value, .. }), .. }) = right { diff --git a/crates/ruff/src/rules/flake8_pytest_style/rules/helpers.rs b/crates/ruff/src/rules/flake8_pytest_style/rules/helpers.rs index df35d5a44f..43a183267f 100644 --- a/crates/ruff/src/rules/flake8_pytest_style/rules/helpers.rs +++ b/crates/ruff/src/rules/flake8_pytest_style/rules/helpers.rs @@ -46,11 +46,11 @@ pub(super) fn is_pytest_parametrize(decorator: &Decorator, semantic: &SemanticMo pub(super) fn keyword_is_literal(keyword: &Keyword, literal: &str) -> bool { if let Expr::Constant(ast::ExprConstant { - value: Constant::Str(string), + value: Constant::Str(ast::StringConstant { value, .. }), .. }) = &keyword.value { - string == literal + value == literal } else { false } @@ -63,7 +63,7 @@ pub(super) fn is_empty_or_null_string(expr: &Expr) -> bool { .. }) => string.is_empty(), Expr::Constant(constant) if constant.value.is_none() => true, - Expr::FString(ast::ExprFString { values, range: _ }) => { + Expr::FString(ast::ExprFString { values, .. }) => { values.iter().all(is_empty_or_null_string) } _ => false, diff --git a/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs b/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs index e42a3d9550..8c635daaf2 100644 --- a/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs +++ b/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs @@ -50,9 +50,9 @@ impl Violation for PytestParametrizeValuesWrongType { } fn elts_to_csv(elts: &[Expr], generator: Generator) -> Option { - let all_literals = elts.iter().all(|e| { + let all_literals = elts.iter().all(|expr| { matches!( - e, + expr, Expr::Constant(ast::ExprConstant { value: Constant::Str(_), .. @@ -65,19 +65,23 @@ fn elts_to_csv(elts: &[Expr], generator: Generator) -> Option { } let node = Expr::Constant(ast::ExprConstant { - value: Constant::Str(elts.iter().fold(String::new(), |mut acc, elt| { - if let Expr::Constant(ast::ExprConstant { - value: Constant::Str(ref s), - .. - }) = elt - { - if !acc.is_empty() { - acc.push(','); + value: elts + .iter() + .fold(String::new(), |mut acc, elt| { + if let Expr::Constant(ast::ExprConstant { + value: Constant::Str(ast::StringConstant { value, .. }), + .. + }) = elt + { + if !acc.is_empty() { + acc.push(','); + } + acc.push_str(value.as_str()); } - acc.push_str(s); - } - acc - })), + acc + }) + .into(), + kind: None, range: TextRange::default(), }); @@ -166,7 +170,7 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) { .iter() .map(|name| { Expr::Constant(ast::ExprConstant { - value: Constant::Str((*name).to_string()), + value: (*name).to_string().into(), kind: None, range: TextRange::default(), }) @@ -201,7 +205,7 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) { .iter() .map(|name| { Expr::Constant(ast::ExprConstant { - value: Constant::Str((*name).to_string()), + value: (*name).to_string().into(), kind: None, range: TextRange::default(), }) diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_expr.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_expr.rs index 26c8612bf0..380a71bf0d 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_expr.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_expr.rs @@ -115,7 +115,7 @@ pub(crate) fn use_capital_environment_variables(checker: &mut Checker, expr: &Ex return; }; let Expr::Constant(ast::ExprConstant { - value: Constant::Str(env_var), + value: Constant::Str(ast::StringConstant { value: env_var, .. }), .. }) = arg else { @@ -167,7 +167,7 @@ fn check_os_environ_subscript(checker: &mut Checker, expr: &Expr) { return; } let Expr::Constant(ast::ExprConstant { - value: Constant::Str(env_var), + value: Constant::Str(ast::StringConstant { value: env_var, .. }), kind, range: _, }) = slice.as_ref() diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs index cb18e590f4..be12732220 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs @@ -264,15 +264,13 @@ fn is_main_check(expr: &Expr) -> bool { { if let Expr::Name(ast::ExprName { id, .. }) = left.as_ref() { if id == "__name__" { - if comparators.len() == 1 { - if let Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), - .. - }) = &comparators[0] - { - if value == "__main__" { - return true; - } + if let [Expr::Constant(ast::ExprConstant { + value: Constant::Str(ast::StringConstant { value, .. }), + .. + })] = comparators.as_slice() + { + if value == "__main__" { + return true; } } } diff --git a/crates/ruff/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs b/crates/ruff/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs index 3b582bc214..2a814c01a6 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs +++ b/crates/ruff/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs @@ -1,4 +1,4 @@ -use ruff_python_ast::{Arguments, Constant, Expr, ExprCall, ExprConstant}; +use ruff_python_ast::{self as ast, Arguments, Constant, Expr, ExprCall, ExprConstant}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; @@ -67,7 +67,7 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &E } let [Expr::Constant(ExprConstant { - value: Constant::Str(value), + value: Constant::Str(ast::StringConstant { value, .. }), kind: _, range, })] = args.as_slice() diff --git a/crates/ruff/src/rules/flynt/helpers.rs b/crates/ruff/src/rules/flynt/helpers.rs index 31368a0fd9..35183bb020 100644 --- a/crates/ruff/src/rules/flynt/helpers.rs +++ b/crates/ruff/src/rules/flynt/helpers.rs @@ -16,7 +16,7 @@ fn to_formatted_value_expr(inner: &Expr) -> Expr { /// Convert a string to a constant string expression. pub(super) fn to_constant_string(s: &str) -> Expr { let node = ast::ExprConstant { - value: Constant::Str(s.to_owned()), + value: s.to_owned().into(), kind: None, range: TextRange::default(), }; diff --git a/crates/ruff/src/rules/flynt/rules/static_join_to_fstring.rs b/crates/ruff/src/rules/flynt/rules/static_join_to_fstring.rs index f1290ee8be..05c39b937b 100644 --- a/crates/ruff/src/rules/flynt/rules/static_join_to_fstring.rs +++ b/crates/ruff/src/rules/flynt/rules/static_join_to_fstring.rs @@ -61,22 +61,21 @@ fn build_fstring(joiner: &str, joinees: &[Expr]) -> Option { ) }) { let node = ast::ExprConstant { - value: Constant::Str( - joinees - .iter() - .filter_map(|expr| { - if let Expr::Constant(ast::ExprConstant { - value: Constant::Str(string), - .. - }) = expr - { - Some(string.as_str()) - } else { - None - } - }) - .join(joiner), - ), + value: joinees + .iter() + .filter_map(|expr| { + if let Expr::Constant(ast::ExprConstant { + value: Constant::Str(ast::StringConstant { value, .. }), + .. + }) = expr + { + Some(value.as_str()) + } else { + None + } + }) + .join(joiner) + .into(), range: TextRange::default(), kind: None, }; @@ -100,6 +99,7 @@ fn build_fstring(joiner: &str, joinees: &[Expr]) -> Option { let node = ast::ExprFString { values: fstring_elems, + implicit_concatenated: false, range: TextRange::default(), }; Some(node.into()) diff --git a/crates/ruff/src/rules/pandas_vet/rules/read_table.rs b/crates/ruff/src/rules/pandas_vet/rules/read_table.rs index 9f856ba82d..fb03d56ef2 100644 --- a/crates/ruff/src/rules/pandas_vet/rules/read_table.rs +++ b/crates/ruff/src/rules/pandas_vet/rules/read_table.rs @@ -51,14 +51,14 @@ pub(crate) fn use_of_read_table(checker: &mut Checker, call: &ast::ExprCall) { .is_some_and(|call_path| matches!(call_path.as_slice(), ["pandas", "read_table"])) { if let Some(Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), + value: Constant::Str(ast::StringConstant { value, .. }), .. })) = call .arguments .find_keyword("sep") .map(|keyword| &keyword.value) { - if value.as_str() == "," { + if value == "," { checker .diagnostics .push(Diagnostic::new(PandasUseOfDotReadTable, call.func.range())); diff --git a/crates/ruff/src/rules/pyflakes/rules/strings.rs b/crates/ruff/src/rules/pyflakes/rules/strings.rs index a57ceead4d..17d9be995b 100644 --- a/crates/ruff/src/rules/pyflakes/rules/strings.rs +++ b/crates/ruff/src/rules/pyflakes/rules/strings.rs @@ -583,10 +583,10 @@ pub(crate) fn percent_format_extra_named_arguments( .enumerate() .filter_map(|(index, key)| match key { Some(Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), + value: Constant::Str(ast::StringConstant { value, .. }), .. })) => { - if summary.keywords.contains(value) { + if summary.keywords.contains(value.as_str()) { None } else { Some((index, value.as_str())) @@ -646,7 +646,7 @@ pub(crate) fn percent_format_missing_arguments( for key in keys.iter().flatten() { match key { Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), + value: Constant::Str(ast::StringConstant { value, .. }), .. }) => { keywords.insert(value); diff --git a/crates/ruff/src/rules/pylint/rules/assert_on_string_literal.rs b/crates/ruff/src/rules/pylint/rules/assert_on_string_literal.rs index 0dcde075d1..b88987fbae 100644 --- a/crates/ruff/src/rules/pylint/rules/assert_on_string_literal.rs +++ b/crates/ruff/src/rules/pylint/rules/assert_on_string_literal.rs @@ -71,7 +71,7 @@ pub(crate) fn assert_on_string_literal(checker: &mut Checker, test: &Expr) { } _ => {} }, - Expr::FString(ast::ExprFString { values, range: _ }) => { + Expr::FString(ast::ExprFString { values, .. }) => { checker.diagnostics.push(Diagnostic::new( AssertOnStringLiteral { kind: if values.iter().all(|value| match value { diff --git a/crates/ruff/src/rules/pylint/rules/bad_string_format_type.rs b/crates/ruff/src/rules/pylint/rules/bad_string_format_type.rs index dfb7283f60..7ae0e648fe 100644 --- a/crates/ruff/src/rules/pylint/rules/bad_string_format_type.rs +++ b/crates/ruff/src/rules/pylint/rules/bad_string_format_type.rs @@ -187,7 +187,10 @@ fn is_valid_dict( return true; }; if let Expr::Constant(ast::ExprConstant { - value: Constant::Str(mapping_key), + value: + Constant::Str(ast::StringConstant { + value: mapping_key, .. + }), .. }) = key { diff --git a/crates/ruff/src/rules/pylint/rules/logging.rs b/crates/ruff/src/rules/pylint/rules/logging.rs index 5bbc56b4c2..1c05df692f 100644 --- a/crates/ruff/src/rules/pylint/rules/logging.rs +++ b/crates/ruff/src/rules/pylint/rules/logging.rs @@ -109,7 +109,7 @@ pub(crate) fn logging_call(checker: &mut Checker, call: &ast::ExprCall) { } let Some(Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), + value: Constant::Str(ast::StringConstant { value, .. }), .. })) = call.arguments.find_positional(0) else { diff --git a/crates/ruff/src/rules/pylint/rules/magic_value_comparison.rs b/crates/ruff/src/rules/pylint/rules/magic_value_comparison.rs index 0c7385c410..3f96d789c2 100644 --- a/crates/ruff/src/rules/pylint/rules/magic_value_comparison.rs +++ b/crates/ruff/src/rules/pylint/rules/magic_value_comparison.rs @@ -79,7 +79,9 @@ fn is_magic_value(constant: &Constant, allowed_types: &[ConstantType]) -> bool { Constant::Bool(_) => false, Constant::Ellipsis => false, // Otherwise, special-case some common string and integer types. - Constant::Str(value) => !matches!(value.as_str(), "" | "__main__"), + Constant::Str(ast::StringConstant { value, .. }) => { + !matches!(value.as_str(), "" | "__main__") + } Constant::Int(value) => !matches!(value.try_into(), Ok(0 | 1)), Constant::Bytes(_) => true, Constant::Float(_) => true, diff --git a/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs b/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs index a245a15e4f..927716050f 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs @@ -130,7 +130,10 @@ fn create_properties_from_fields_arg(fields: &Expr) -> Result> { bail!("Expected `elts` to have exactly two elements") }; let Expr::Constant(ast::ExprConstant { - value: Constant::Str(property), + value: + Constant::Str(ast::StringConstant { + value: property, .. + }), .. }) = &field_name else { diff --git a/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs b/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs index e59f0b50e5..6312bcaa86 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs @@ -147,7 +147,10 @@ fn properties_from_dict_literal(keys: &[Option], values: &[Expr]) -> Resul .zip(values.iter()) .map(|(key, value)| match key { Some(Expr::Constant(ast::ExprConstant { - value: Constant::Str(property), + value: + Constant::Str(ast::StringConstant { + value: property, .. + }), .. })) => { if !is_identifier(property) { diff --git a/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs b/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs index 31b918f100..61eea79e01 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs @@ -38,8 +38,14 @@ impl FromStr for LiteralType { impl From for Constant { fn from(value: LiteralType) -> Self { match value { - LiteralType::Str => Constant::Str(String::new()), - LiteralType::Bytes => Constant::Bytes(vec![]), + LiteralType::Str => Constant::Str(ast::StringConstant { + value: String::new(), + implicit_concatenated: false, + }), + LiteralType::Bytes => Constant::Bytes(ast::BytesConstant { + value: Vec::new(), + implicit_concatenated: false, + }), LiteralType::Int => Constant::Int(BigInt::from(0)), LiteralType::Float => Constant::Float(0.0), LiteralType::Bool => Constant::Bool(false), diff --git a/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs b/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs index a170e20f05..68032f5b9f 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs @@ -202,7 +202,10 @@ fn clean_params_dictionary( match key { Some(key) => { if let Expr::Constant(ast::ExprConstant { - value: Constant::Str(key_string), + value: + Constant::Str(ast::StringConstant { + value: key_string, .. + }), .. }) = key { diff --git a/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs b/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs index cddf140161..0a99e7a3f7 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs @@ -73,11 +73,15 @@ pub(crate) fn redundant_open_modes(checker: &mut Checker, call: &ast::ExprCall) if !call.arguments.is_empty() { if let Some(keyword) = call.arguments.find_keyword(MODE_KEYWORD_ARGUMENT) { if let Expr::Constant(ast::ExprConstant { - value: Constant::Str(mode_param_value), + value: + Constant::Str(ast::StringConstant { + value: mode_param_value, + .. + }), .. }) = &keyword.value { - if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) { + if let Ok(mode) = OpenMode::from_str(mode_param_value) { checker.diagnostics.push(create_check( call, &keyword.value, @@ -97,7 +101,7 @@ pub(crate) fn redundant_open_modes(checker: &mut Checker, call: &ast::ExprCall) .. }) = &mode_param { - if let Ok(mode) = OpenMode::from_str(value.as_str()) { + if let Ok(mode) = OpenMode::from_str(value) { checker.diagnostics.push(create_check( call, mode_param, diff --git a/crates/ruff/src/rules/tryceratops/rules/raise_vanilla_args.rs b/crates/ruff/src/rules/tryceratops/rules/raise_vanilla_args.rs index ad2efa5ba5..53f56c5829 100644 --- a/crates/ruff/src/rules/tryceratops/rules/raise_vanilla_args.rs +++ b/crates/ruff/src/rules/tryceratops/rules/raise_vanilla_args.rs @@ -54,7 +54,7 @@ where F: (Fn(&str) -> bool) + Copy, { match expr { - Expr::FString(ast::ExprFString { values, range: _ }) => { + Expr::FString(ast::ExprFString { values, .. }) => { for value in values { if any_string(value, predicate) { return true; @@ -62,10 +62,10 @@ where } } Expr::Constant(ast::ExprConstant { - value: Constant::Str(val), + value: Constant::Str(value), .. }) => { - if predicate(val.as_str()) { + if predicate(value) { return true; } } diff --git a/crates/ruff_python_ast/src/all.rs b/crates/ruff_python_ast/src/all.rs index d5917661d6..3f738984b5 100644 --- a/crates/ruff_python_ast/src/all.rs +++ b/crates/ruff_python_ast/src/all.rs @@ -24,7 +24,7 @@ where fn add_to_names<'a>(elts: &'a [Expr], names: &mut Vec<&'a str>, flags: &mut DunderAllFlags) { for elt in elts { if let Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), + value: Constant::Str(ast::StringConstant { value, .. }), .. }) = elt { diff --git a/crates/ruff_python_ast/src/comparable.rs b/crates/ruff_python_ast/src/comparable.rs index 00a6ef87d2..f8dbdf2732 100644 --- a/crates/ruff_python_ast/src/comparable.rs +++ b/crates/ruff_python_ast/src/comparable.rs @@ -326,8 +326,18 @@ impl<'a> From<&'a ast::Constant> for ComparableConstant<'a> { match constant { ast::Constant::None => Self::None, ast::Constant::Bool(value) => Self::Bool(value), - ast::Constant::Str(value) => Self::Str(value), - ast::Constant::Bytes(value) => Self::Bytes(value), + ast::Constant::Str(ast::StringConstant { + value, + // Compare strings based on resolved value, not representation (i.e., ignore whether + // the string was implicitly concatenated). + implicit_concatenated: _, + }) => Self::Str(value), + ast::Constant::Bytes(ast::BytesConstant { + value, + // Compare bytes based on resolved value, not representation (i.e., ignore whether + // the bytes were implicitly concatenated). + implicit_concatenated: _, + }) => Self::Bytes(value), ast::Constant::Int(value) => Self::Int(value), ast::Constant::Float(value) => Self::Float(value.to_bits()), ast::Constant::Complex { real, imag } => Self::Complex { @@ -865,11 +875,13 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { debug_text: debug_text.as_ref(), format_spec: format_spec.as_ref().map(Into::into), }), - ast::Expr::FString(ast::ExprFString { values, range: _ }) => { - Self::FString(ExprFString { - values: values.iter().map(Into::into).collect(), - }) - } + ast::Expr::FString(ast::ExprFString { + values, + implicit_concatenated: _, + range: _, + }) => Self::FString(ExprFString { + values: values.iter().map(Into::into).collect(), + }), ast::Expr::Constant(ast::ExprConstant { value, kind, diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 5af9a38dcb..2f43d3da1c 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -123,10 +123,8 @@ where return true; } match expr { - Expr::BoolOp(ast::ExprBoolOp { - values, range: _, .. - }) - | Expr::FString(ast::ExprFString { values, range: _ }) => { + Expr::BoolOp(ast::ExprBoolOp { values, .. }) + | Expr::FString(ast::ExprFString { values, .. }) => { values.iter().any(|expr| any_over_expr(expr, func)) } Expr::NamedExpr(ast::ExprNamedExpr { @@ -1087,25 +1085,26 @@ impl Truthiness { Expr::Constant(ast::ExprConstant { value, .. }) => match value { Constant::Bool(value) => Some(*value), Constant::None => Some(false), - Constant::Str(string) => Some(!string.is_empty()), + Constant::Str(ast::StringConstant { value, .. }) => Some(!value.is_empty()), Constant::Bytes(bytes) => Some(!bytes.is_empty()), Constant::Int(int) => Some(!int.is_zero()), Constant::Float(float) => Some(*float != 0.0), Constant::Complex { real, imag } => Some(*real != 0.0 || *imag != 0.0), Constant::Ellipsis => Some(true), }, - Expr::FString(ast::ExprFString { values, range: _ }) => { + Expr::FString(ast::ExprFString { values, .. }) => { if values.is_empty() { Some(false) } else if values.iter().any(|value| { - let Expr::Constant(ast::ExprConstant { - value: Constant::Str(string), + if let Expr::Constant(ast::ExprConstant { + value: Constant::Str(ast::StringConstant { value, .. }), .. }) = &value - else { - return false; - }; - !string.is_empty() + { + !value.is_empty() + } else { + false + } }) { Some(true) } else { diff --git a/crates/ruff_python_ast/src/node.rs b/crates/ruff_python_ast/src/node.rs index 4f6798d07e..d24d431833 100644 --- a/crates/ruff_python_ast/src/node.rs +++ b/crates/ruff_python_ast/src/node.rs @@ -2676,7 +2676,11 @@ impl AstNode for ast::ExprFString { where V: PreorderVisitor<'a> + ?Sized, { - let ast::ExprFString { values, range: _ } = self; + let ast::ExprFString { + values, + implicit_concatenated: _, + range: _, + } = self; for expr in values { visitor.visit_expr(expr); diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index 5011ba76d9..331968c47e 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -881,6 +881,8 @@ pub struct DebugText { pub struct ExprFString { pub range: TextRange, pub values: Vec, + /// Whether the f-string contains multiple string tokens that were implicitly concatenated. + pub implicit_concatenated: bool, } impl From for Expr { @@ -2463,8 +2465,8 @@ impl std::cmp::PartialEq for Int { pub enum Constant { None, Bool(bool), - Str(String), - Bytes(Vec), + Str(StringConstant), + Bytes(BytesConstant), Int(BigInt), Float(f64), Complex { real: f64, imag: f64 }, @@ -2472,38 +2474,68 @@ pub enum Constant { } impl Constant { - pub fn is_true(self) -> bool { - self.bool().is_some_and(|b| b) - } - pub fn is_false(self) -> bool { - self.bool().is_some_and(|b| !b) - } - pub fn complex(self) -> Option<(f64, f64)> { + /// Returns `true` if the constant is a string or bytes constant that contains multiple, + /// implicitly concatenated string tokens. + pub fn is_implicit_concatenated(&self) -> bool { match self { - Constant::Complex { real, imag } => Some((real, imag)), - _ => None, + Constant::Str(value) => value.implicit_concatenated, + Constant::Bytes(value) => value.implicit_concatenated, + _ => false, } } } -impl From for Constant { - fn from(s: String) -> Constant { - Self::Str(s) +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StringConstant { + /// The string value as resolved by the parser (i.e., without quotes, or escape sequences, or + /// implicit concatenations). + pub value: String, + /// Whether the string contains multiple string tokens that were implicitly concatenated. + pub implicit_concatenated: bool, +} + +impl Deref for StringConstant { + type Target = str; + fn deref(&self) -> &Self::Target { + self.value.as_str() } } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BytesConstant { + /// The bytes value as resolved by the parser (i.e., without quotes, or escape sequences, or + /// implicit concatenations). + pub value: Vec, + /// Whether the string contains multiple string tokens that were implicitly concatenated. + pub implicit_concatenated: bool, +} + +impl Deref for BytesConstant { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + self.value.as_slice() + } +} + impl From> for Constant { - fn from(b: Vec) -> Constant { - Self::Bytes(b) + fn from(value: Vec) -> Constant { + Self::Bytes(BytesConstant { + value, + implicit_concatenated: false, + }) + } +} +impl From for Constant { + fn from(value: String) -> Constant { + Self::Str(StringConstant { + value, + implicit_concatenated: false, + }) } } impl From for Constant { - fn from(b: bool) -> Constant { - Self::Bool(b) - } -} -impl From for Constant { - fn from(i: BigInt) -> Constant { - Self::Int(i) + fn from(value: bool) -> Constant { + Self::Bool(value) } } @@ -3056,7 +3088,7 @@ mod size_assertions { assert_eq_size!(StmtClassDef, [u8; 104]); assert_eq_size!(StmtTry, [u8; 104]); assert_eq_size!(Expr, [u8; 80]); - assert_eq_size!(Constant, [u8; 32]); + assert_eq_size!(Constant, [u8; 40]); assert_eq_size!(Pattern, [u8; 96]); assert_eq_size!(Mod, [u8; 32]); } diff --git a/crates/ruff_python_ast/src/relocate.rs b/crates/ruff_python_ast/src/relocate.rs index 292101e664..122cdbc259 100644 --- a/crates/ruff_python_ast/src/relocate.rs +++ b/crates/ruff_python_ast/src/relocate.rs @@ -140,7 +140,7 @@ pub fn relocate_expr(expr: &mut Expr, location: TextRange) { relocate_expr(expr, location); } } - Expr::FString(nodes::ExprFString { values, range }) => { + Expr::FString(nodes::ExprFString { values, range, .. }) => { *range = location; for expr in values { relocate_expr(expr, location); diff --git a/crates/ruff_python_ast/src/visitor.rs b/crates/ruff_python_ast/src/visitor.rs index ffe005e50c..d9c9eff67c 100644 --- a/crates/ruff_python_ast/src/visitor.rs +++ b/crates/ruff_python_ast/src/visitor.rs @@ -476,7 +476,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { visitor.visit_format_spec(expr); } } - Expr::FString(ast::ExprFString { values, range: _ }) => { + Expr::FString(ast::ExprFString { values, .. }) => { for expr in values { visitor.visit_expr(expr); } diff --git a/crates/ruff_python_codegen/src/generator.rs b/crates/ruff_python_codegen/src/generator.rs index 65a1280272..9af88721bd 100644 --- a/crates/ruff_python_codegen/src/generator.rs +++ b/crates/ruff_python_codegen/src/generator.rs @@ -1104,7 +1104,7 @@ impl<'a> Generator<'a> { *conversion, format_spec.as_deref(), ), - Expr::FString(ast::ExprFString { values, range: _ }) => { + Expr::FString(ast::ExprFString { values, .. }) => { self.unparse_f_string(values, false); } Expr::Constant(ast::ExprConstant { @@ -1197,8 +1197,8 @@ impl<'a> Generator<'a> { Constant::Bytes(b) => { self.p_bytes_repr(b); } - Constant::Str(s) => { - self.p_str_repr(s); + Constant::Str(ast::StringConstant { value, .. }) => { + self.p_str_repr(value); } Constant::None => self.p("None"), Constant::Bool(b) => self.p(if *b { "True" } else { "False" }), @@ -1339,13 +1339,13 @@ impl<'a> Generator<'a> { fn unparse_f_string_elem(&mut self, expr: &Expr, is_spec: bool) { match expr { Expr::Constant(ast::ExprConstant { value, .. }) => { - if let Constant::Str(s) = value { - self.unparse_f_string_literal(s); + if let Constant::Str(ast::StringConstant { value, .. }) = value { + self.unparse_f_string_literal(value); } else { unreachable!() } } - Expr::FString(ast::ExprFString { values, range: _ }) => { + Expr::FString(ast::ExprFString { values, .. }) => { self.unparse_f_string(values, is_spec); } Expr::FormattedValue(ast::ExprFormattedValue { diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__dict_unpacking.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__dict_unpacking.snap index 6d54f4db9e..ff72ab9daf 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__dict_unpacking.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__dict_unpacking.snap @@ -11,7 +11,10 @@ Dict( ExprConstant { range: 1..4, value: Str( - "a", + StringConstant { + value: "a", + implicit_concatenated: false, + }, ), kind: None, }, @@ -23,7 +26,10 @@ Dict( ExprConstant { range: 16..19, value: Str( - "d", + StringConstant { + value: "d", + implicit_concatenated: false, + }, ), kind: None, }, @@ -35,7 +41,10 @@ Dict( ExprConstant { range: 6..9, value: Str( - "b", + StringConstant { + value: "b", + implicit_concatenated: false, + }, ), kind: None, }, @@ -51,7 +60,10 @@ Dict( ExprConstant { range: 21..24, value: Str( - "e", + StringConstant { + value: "e", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__generator_expression_argument.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__generator_expression_argument.snap index 9f5db3c598..4617a2a863 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__generator_expression_argument.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__generator_expression_argument.snap @@ -12,7 +12,10 @@ Call( ExprConstant { range: 0..3, value: Str( - " ", + StringConstant { + value: " ", + implicit_concatenated: false, + }, ), kind: None, }, @@ -68,7 +71,10 @@ Call( ExprConstant { range: 43..53, value: Str( - "LIMIT %d", + StringConstant { + value: "LIMIT %d", + implicit_concatenated: false, + }, ), kind: None, }, @@ -109,7 +115,10 @@ Call( ExprConstant { range: 91..102, value: Str( - "OFFSET %d", + StringConstant { + value: "OFFSET %d", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__match.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__match.snap index 69099b2bc2..c862d1c1da 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__match.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__match.snap @@ -15,7 +15,10 @@ expression: parse_ast ExprConstant { range: 8..14, value: Str( - "test", + StringConstant { + value: "test", + implicit_concatenated: false, + }, ), kind: None, }, @@ -100,7 +103,10 @@ expression: parse_ast ExprConstant { range: 81..88, value: Str( - "label", + StringConstant { + value: "label", + implicit_concatenated: false, + }, ), kind: None, }, @@ -112,7 +118,10 @@ expression: parse_ast ExprConstant { range: 90..96, value: Str( - "test", + StringConstant { + value: "test", + implicit_concatenated: false, + }, ), kind: None, }, @@ -131,7 +140,10 @@ expression: parse_ast ExprConstant { range: 118..125, value: Str( - "label", + StringConstant { + value: "label", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_class.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_class.snap index 601df87ff8..e567e1d4e4 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_class.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_class.snap @@ -117,7 +117,10 @@ expression: "parse_suite(source, \"\").unwrap()" ExprConstant { range: 80..89, value: Str( - "default", + StringConstant { + value: "default", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_f_string.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_f_string.snap index 939230b0bd..f65e153bea 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_f_string.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_f_string.snap @@ -14,12 +14,16 @@ expression: parse_ast ExprConstant { range: 2..13, value: Str( - "Hello world", + StringConstant { + value: "Hello world", + implicit_concatenated: false, + }, ), kind: None, }, ), ], + implicit_concatenated: false, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_kwargs.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_kwargs.snap index b9ff2b631a..dbf2ae2258 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_kwargs.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_kwargs.snap @@ -23,7 +23,10 @@ expression: parse_ast ExprConstant { range: 8..20, value: Str( - "positional", + StringConstant { + value: "positional", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_print_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_print_2.snap index 57fec3fd7b..583746f9dd 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_print_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_print_2.snap @@ -23,7 +23,10 @@ expression: parse_ast ExprConstant { range: 6..19, value: Str( - "Hello world", + StringConstant { + value: "Hello world", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_print_hello.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_print_hello.snap index f78c89031c..fab7de1e6a 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_print_hello.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_print_hello.snap @@ -23,7 +23,10 @@ expression: parse_ast ExprConstant { range: 6..19, value: Str( - "Hello world", + StringConstant { + value: "Hello world", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_string.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_string.snap index 21c12fb897..301501a7d9 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_string.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_string.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..13, value: Str( - "Hello world", + StringConstant { + value: "Hello world", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_type_declaration.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_type_declaration.snap index d2763aee2e..2186cc9152 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_type_declaration.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__parse_type_declaration.snap @@ -82,7 +82,10 @@ expression: "parse_suite(source, \"\").unwrap()" ExprConstant { range: 48..61, value: Str( - "ForwardRefY", + StringConstant { + value: "ForwardRefY", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__patma.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__patma.snap index 0951751f81..a29d01b17a 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__patma.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__patma.snap @@ -522,7 +522,10 @@ expression: parse_ast ExprConstant { range: 484..489, value: Str( - "seq", + StringConstant { + value: "seq", + implicit_concatenated: false, + }, ), kind: None, }, @@ -552,7 +555,10 @@ expression: parse_ast ExprConstant { range: 518..523, value: Str( - "map", + StringConstant { + value: "map", + implicit_concatenated: false, + }, ), kind: None, }, @@ -857,7 +863,10 @@ expression: parse_ast ExprConstant { range: 664..667, value: Str( - "X", + StringConstant { + value: "X", + implicit_concatenated: false, + }, ), kind: None, }, @@ -1617,7 +1626,10 @@ expression: parse_ast ExprConstant { range: 1287..1292, value: Str( - "foo", + StringConstant { + value: "foo", + implicit_concatenated: false, + }, ), kind: None, }, @@ -2565,7 +2577,10 @@ expression: parse_ast ExprConstant { range: 2036..2038, value: Str( - "", + StringConstant { + value: "", + implicit_concatenated: false, + }, ), kind: None, }, @@ -2611,7 +2626,10 @@ expression: parse_ast ExprConstant { range: 2064..2066, value: Str( - "", + StringConstant { + value: "", + implicit_concatenated: false, + }, ), kind: None, }, @@ -3251,7 +3269,10 @@ expression: parse_ast ExprConstant { range: 2449..2452, value: Str( - "X", + StringConstant { + value: "X", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__try.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__try.snap index 7d4e1e0186..78942ef266 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__try.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__try.snap @@ -87,7 +87,10 @@ expression: parse_ast ExprConstant { range: 64..71, value: Str( - "caught ", + StringConstant { + value: "caught ", + implicit_concatenated: false, + }, ), kind: None, }, @@ -126,6 +129,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: false, }, ), ], @@ -181,7 +185,10 @@ expression: parse_ast ExprConstant { range: 116..123, value: Str( - "caught ", + StringConstant { + value: "caught ", + implicit_concatenated: false, + }, ), kind: None, }, @@ -220,6 +227,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: false, }, ), ], diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__try_star.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__try_star.snap index 089d473452..1f8294f3b2 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__try_star.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__try_star.snap @@ -28,7 +28,10 @@ expression: parse_ast ExprConstant { range: 30..34, value: Str( - "eg", + StringConstant { + value: "eg", + implicit_concatenated: false, + }, ), kind: None, }, @@ -203,7 +206,10 @@ expression: parse_ast ExprConstant { range: 135..142, value: Str( - "caught ", + StringConstant { + value: "caught ", + implicit_concatenated: false, + }, ), kind: None, }, @@ -245,7 +251,10 @@ expression: parse_ast ExprConstant { range: 151..164, value: Str( - " with nested ", + StringConstant { + value: " with nested ", + implicit_concatenated: false, + }, ), kind: None, }, @@ -276,6 +285,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: false, }, ), ], @@ -331,7 +341,10 @@ expression: parse_ast ExprConstant { range: 215..222, value: Str( - "caught ", + StringConstant { + value: "caught ", + implicit_concatenated: false, + }, ), kind: None, }, @@ -373,7 +386,10 @@ expression: parse_ast ExprConstant { range: 231..244, value: Str( - " with nested ", + StringConstant { + value: " with nested ", + implicit_concatenated: false, + }, ), kind: None, }, @@ -404,6 +420,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: false, }, ), ], diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__backspace_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__backspace_alias.snap index 11ee52f9fc..1d1aef73a6 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__backspace_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__backspace_alias.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..15, value: Str( - "\u{8}", + StringConstant { + value: "\u{8}", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__bell_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__bell_alias.snap index 3d40adcad1..5c4b9fda0f 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__bell_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__bell_alias.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..9, value: Str( - "\u{7}", + StringConstant { + value: "\u{7}", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__carriage_return_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__carriage_return_alias.snap index 6b57141008..f16ca7fce6 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__carriage_return_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__carriage_return_alias.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..21, value: Str( - "\r", + StringConstant { + value: "\r", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__character_tabulation_with_justification_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__character_tabulation_with_justification_alias.snap index b172239e13..ba1969efc4 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__character_tabulation_with_justification_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__character_tabulation_with_justification_alias.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..45, value: Str( - "\u{89}", + StringConstant { + value: "\u{89}", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__delete_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__delete_alias.snap index 9bd9b1231d..4c9ef1738a 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__delete_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__delete_alias.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..12, value: Str( - "\u{7f}", + StringConstant { + value: "\u{7f}", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__double_quoted_byte.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__double_quoted_byte.snap index 1b6f47e27a..7ec79b41ff 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__double_quoted_byte.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__double_quoted_byte.snap @@ -10,264 +10,267 @@ expression: parse_ast ExprConstant { range: 0..738, value: Bytes( - [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99, - 100, - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120, - 121, - 122, - 123, - 124, - 125, - 126, - 127, - 128, - 129, - 130, - 131, - 132, - 133, - 134, - 135, - 136, - 137, - 138, - 139, - 140, - 141, - 142, - 143, - 144, - 145, - 146, - 147, - 148, - 149, - 150, - 151, - 152, - 153, - 154, - 155, - 156, - 157, - 158, - 159, - 160, - 161, - 162, - 163, - 164, - 165, - 166, - 167, - 168, - 169, - 170, - 171, - 172, - 173, - 174, - 175, - 176, - 177, - 178, - 179, - 180, - 181, - 182, - 183, - 184, - 185, - 186, - 187, - 188, - 189, - 190, - 191, - 192, - 193, - 194, - 195, - 196, - 197, - 198, - 199, - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 209, - 210, - 211, - 212, - 213, - 214, - 215, - 216, - 217, - 218, - 219, - 220, - 221, - 222, - 223, - 224, - 225, - 226, - 227, - 228, - 229, - 230, - 231, - 232, - 233, - 234, - 235, - 236, - 237, - 238, - 239, - 240, - 241, - 242, - 243, - 244, - 245, - 246, - 247, - 248, - 249, - 250, - 251, - 252, - 253, - 254, - 255, - ], + BytesConstant { + value: [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + ], + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_alias.snap index 83ac74c136..7777636fcc 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_alias.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..12, value: Str( - "\u{1b}", + StringConstant { + value: "\u{1b}", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_char_in_byte_literal.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_char_in_byte_literal.snap index c0da7019e9..85a4d7705d 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_char_in_byte_literal.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_char_in_byte_literal.snap @@ -10,18 +10,21 @@ expression: parse_ast ExprConstant { range: 0..13, value: Bytes( - [ - 111, - 109, - 107, - 109, - 111, - 107, - 92, - 88, - 97, - 97, - ], + BytesConstant { + value: [ + 111, + 109, + 107, + 109, + 111, + 107, + 92, + 88, + 97, + 97, + ], + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_octet.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_octet.snap index 856ca44647..fc484d7a07 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_octet.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__escape_octet.snap @@ -10,13 +10,16 @@ expression: parse_ast ExprConstant { range: 0..14, value: Bytes( - [ - 35, - 97, - 4, - 83, - 52, - ], + BytesConstant { + value: [ + 35, + 97, + 4, + 83, + 52, + ], + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__form_feed_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__form_feed_alias.snap index 4773e4f2d3..2ec6f9f544 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__form_feed_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__form_feed_alias.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..15, value: Str( - "\u{c}", + StringConstant { + value: "\u{c}", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap index 942f50d6e8..6b4a1a92c8 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_constant_range.snap @@ -14,7 +14,10 @@ expression: parse_ast ExprConstant { range: 2..5, value: Str( - "aaa", + StringConstant { + value: "aaa", + implicit_concatenated: false, + }, ), kind: None, }, @@ -38,7 +41,10 @@ expression: parse_ast ExprConstant { range: 10..13, value: Str( - "ccc", + StringConstant { + value: "ccc", + implicit_concatenated: false, + }, ), kind: None, }, @@ -62,12 +68,16 @@ expression: parse_ast ExprConstant { range: 18..21, value: Str( - "eee", + StringConstant { + value: "eee", + implicit_concatenated: false, + }, ), kind: None, }, ), ], + implicit_concatenated: false, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap index fbaabcce46..d96a1cef58 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_character.snap @@ -14,7 +14,10 @@ expression: parse_ast ExprConstant { range: 2..4, value: Str( - "\\", + StringConstant { + value: "\\", + implicit_concatenated: false, + }, ), kind: None, }, @@ -35,6 +38,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: false, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap index 4264a83d77..fe3c6d028e 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_escaped_newline.snap @@ -14,7 +14,10 @@ expression: parse_ast ExprConstant { range: 2..4, value: Str( - "\n", + StringConstant { + value: "\n", + implicit_concatenated: false, + }, ), kind: None, }, @@ -35,6 +38,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: false, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap index b88ac0d2e9..35cff59a24 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_line_continuation.snap @@ -14,7 +14,10 @@ expression: parse_ast ExprConstant { range: 3..5, value: Str( - "\\\n", + StringConstant { + value: "\\\n", + implicit_concatenated: false, + }, ), kind: None, }, @@ -35,6 +38,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: false, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap index dda8ce1dce..655b3cc3d8 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_base_more.snap @@ -7,7 +7,10 @@ expression: parse_ast ExprConstant { range: 2..6, value: Str( - "mix ", + StringConstant { + value: "mix ", + implicit_concatenated: false, + }, ), kind: None, }, @@ -36,7 +39,10 @@ expression: parse_ast ExprConstant { range: 13..28, value: Str( - " with text and ", + StringConstant { + value: " with text and ", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap index 2327df5fcb..22b250a8ce 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_parse_self_documenting_format.snap @@ -29,12 +29,16 @@ expression: parse_ast ExprConstant { range: 9..12, value: Str( - ">10", + StringConstant { + value: ">10", + implicit_concatenated: false, + }, ), kind: None, }, ), ], + implicit_concatenated: false, }, ), ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap index dccc5db5fc..0b711c8cc9 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__fstring_unescaped_newline.snap @@ -14,7 +14,10 @@ expression: parse_ast ExprConstant { range: 4..5, value: Str( - "\n", + StringConstant { + value: "\n", + implicit_concatenated: false, + }, ), kind: None, }, @@ -35,6 +38,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: false, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__hts_alias.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__hts_alias.snap index 3d25aecfae..8017bb4b35 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__hts_alias.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__hts_alias.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..9, value: Str( - "\u{88}", + StringConstant { + value: "\u{88}", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap index dada3b5a60..8680272f60 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_1.snap @@ -14,12 +14,16 @@ expression: parse_ast ExprConstant { range: 1..16, value: Str( - "Hello world", + StringConstant { + value: "Hello world", + implicit_concatenated: true, + }, ), kind: None, }, ), ], + implicit_concatenated: true, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap index dada3b5a60..8680272f60 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_2.snap @@ -14,12 +14,16 @@ expression: parse_ast ExprConstant { range: 1..16, value: Str( - "Hello world", + StringConstant { + value: "Hello world", + implicit_concatenated: true, + }, ), kind: None, }, ), ], + implicit_concatenated: true, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap index a955540697..2c5e80aad5 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_3.snap @@ -14,7 +14,10 @@ expression: parse_ast ExprConstant { range: 1..16, value: Str( - "Hello world", + StringConstant { + value: "Hello world", + implicit_concatenated: true, + }, ), kind: None, }, @@ -26,7 +29,10 @@ expression: parse_ast ExprConstant { range: 17..20, value: Str( - "!", + StringConstant { + value: "!", + implicit_concatenated: false, + }, ), kind: None, }, @@ -37,6 +43,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: true, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap new file mode 100644 index 0000000000..19e8b7ae04 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_f_string_concat_4.snap @@ -0,0 +1,63 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: parse_ast +--- +[ + Expr( + StmtExpr { + range: 0..31, + value: FString( + ExprFString { + range: 0..31, + values: [ + Constant( + ExprConstant { + range: 1..16, + value: Str( + StringConstant { + value: "Hello world", + implicit_concatenated: true, + }, + ), + kind: None, + }, + ), + FormattedValue( + ExprFormattedValue { + range: 16..21, + value: Constant( + ExprConstant { + range: 17..20, + value: Str( + StringConstant { + value: "!", + implicit_concatenated: false, + }, + ), + kind: None, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Constant( + ExprConstant { + range: 24..30, + value: Str( + StringConstant { + value: "again!", + implicit_concatenated: true, + }, + ), + kind: None, + }, + ), + ], + implicit_concatenated: true, + }, + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap index 12a39ccb92..282eb79f97 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring.snap @@ -37,7 +37,10 @@ expression: parse_ast ExprConstant { range: 10..17, value: Str( - "{foo}", + StringConstant { + value: "{foo}", + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap new file mode 100644 index 0000000000..97525a5e10 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_concatenation_string_spec.snap @@ -0,0 +1,50 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: parse_ast +--- +[ + FormattedValue( + ExprFormattedValue { + range: 2..15, + value: Name( + ExprName { + range: 3..6, + id: "foo", + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + FString( + ExprFString { + range: 7..14, + values: [ + FormattedValue( + ExprFormattedValue { + range: 7..14, + value: Constant( + ExprConstant { + range: 8..13, + value: Str( + StringConstant { + value: "", + implicit_concatenated: true, + }, + ), + kind: None, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + implicit_concatenated: false, + }, + ), + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap index 7d8b5d0f97..d93be4602f 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_spec.snap @@ -36,6 +36,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: false, }, ), ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap new file mode 100644 index 0000000000..31db5e6cf8 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_nested_string_spec.snap @@ -0,0 +1,50 @@ +--- +source: crates/ruff_python_parser/src/string.rs +expression: parse_ast +--- +[ + FormattedValue( + ExprFormattedValue { + range: 2..12, + value: Name( + ExprName { + range: 3..6, + id: "foo", + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: Some( + FString( + ExprFString { + range: 7..11, + values: [ + FormattedValue( + ExprFormattedValue { + range: 7..11, + value: Constant( + ExprConstant { + range: 8..10, + value: Str( + StringConstant { + value: "", + implicit_concatenated: false, + }, + ), + kind: None, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + implicit_concatenated: false, + }, + ), + ), + }, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap index 138ba6b187..5a621fc857 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_fstring_not_nested_spec.snap @@ -24,12 +24,16 @@ expression: parse_ast ExprConstant { range: 7..11, value: Str( - "spec", + StringConstant { + value: "spec", + implicit_concatenated: false, + }, ), kind: None, }, ), ], + implicit_concatenated: false, }, ), ), diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_concat.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_concat.snap index 68f419ced0..2c5d32050d 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_concat.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_concat.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..16, value: Str( - "Hello world", + StringConstant { + value: "Hello world", + implicit_concatenated: true, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_triple_quotes_with_kind.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_triple_quotes_with_kind.snap index b6561e362d..8e2e003f91 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_triple_quotes_with_kind.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_string_triple_quotes_with_kind.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..20, value: Str( - "Hello, world!", + StringConstant { + value: "Hello, world!", + implicit_concatenated: false, + }, ), kind: Some( "u", diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap index db31dbe42c..23593fee07 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_1.snap @@ -14,7 +14,10 @@ expression: parse_ast ExprConstant { range: 2..17, value: Str( - "Hello world", + StringConstant { + value: "Hello world", + implicit_concatenated: true, + }, ), kind: Some( "u", @@ -22,6 +25,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: true, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap index f2cf90b6b6..e6a8a74995 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_f_string_concat_2.snap @@ -14,7 +14,10 @@ expression: parse_ast ExprConstant { range: 2..21, value: Str( - "Hello world!", + StringConstant { + value: "Hello world!", + implicit_concatenated: true, + }, ), kind: Some( "u", @@ -22,6 +25,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: true, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_1.snap index 629c241e8e..cb8d2848a9 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_1.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..17, value: Str( - "Hello world", + StringConstant { + value: "Hello world", + implicit_concatenated: true, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_2.snap index f89a6bd7f7..6fdde47ad2 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__parse_u_string_concat_2.snap @@ -10,7 +10,10 @@ expression: parse_ast ExprConstant { range: 0..17, value: Str( - "Hello world", + StringConstant { + value: "Hello world", + implicit_concatenated: true, + }, ), kind: Some( "u", diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_1.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_1.snap index 55f2380f02..37273bc676 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_1.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_1.snap @@ -10,12 +10,15 @@ expression: parse_ast ExprConstant { range: 0..8, value: Bytes( - [ - 92, - 120, - 49, - 122, - ], + BytesConstant { + value: [ + 92, + 120, + 49, + 122, + ], + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_2.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_2.snap index 8a44d4ee8f..558aae8c95 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_2.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_byte_literal_2.snap @@ -10,10 +10,13 @@ expression: parse_ast ExprConstant { range: 0..6, value: Bytes( - [ - 92, - 92, - ], + BytesConstant { + value: [ + 92, + 92, + ], + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap index 9c98593743..65f4daf83d 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__raw_fstring.snap @@ -26,6 +26,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: false, }, ), }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__single_quoted_byte.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__single_quoted_byte.snap index 1b6f47e27a..7ec79b41ff 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__single_quoted_byte.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__single_quoted_byte.snap @@ -10,264 +10,267 @@ expression: parse_ast ExprConstant { range: 0..738, value: Bytes( - [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99, - 100, - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120, - 121, - 122, - 123, - 124, - 125, - 126, - 127, - 128, - 129, - 130, - 131, - 132, - 133, - 134, - 135, - 136, - 137, - 138, - 139, - 140, - 141, - 142, - 143, - 144, - 145, - 146, - 147, - 148, - 149, - 150, - 151, - 152, - 153, - 154, - 155, - 156, - 157, - 158, - 159, - 160, - 161, - 162, - 163, - 164, - 165, - 166, - 167, - 168, - 169, - 170, - 171, - 172, - 173, - 174, - 175, - 176, - 177, - 178, - 179, - 180, - 181, - 182, - 183, - 184, - 185, - 186, - 187, - 188, - 189, - 190, - 191, - 192, - 193, - 194, - 195, - 196, - 197, - 198, - 199, - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 209, - 210, - 211, - 212, - 213, - 214, - 215, - 216, - 217, - 218, - 219, - 220, - 221, - 222, - 223, - 224, - 225, - 226, - 227, - 228, - 229, - 230, - 231, - 232, - 233, - 234, - 235, - 236, - 237, - 238, - 239, - 240, - 241, - 242, - 243, - 244, - 245, - 246, - 247, - 248, - 249, - 250, - 251, - 252, - 253, - 254, - 255, - ], + BytesConstant { + value: [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + ], + implicit_concatenated: false, + }, ), kind: None, }, diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap index 8f95a922aa..6793e65f73 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__string__tests__triple_quoted_raw_fstring.snap @@ -26,6 +26,7 @@ expression: parse_ast }, ), ], + implicit_concatenated: false, }, ), }, diff --git a/crates/ruff_python_parser/src/string.rs b/crates/ruff_python_parser/src/string.rs index 63b884e99c..9f154d071c 100644 --- a/crates/ruff_python_parser/src/string.rs +++ b/crates/ruff_python_parser/src/string.rs @@ -1,6 +1,4 @@ -use itertools::Itertools; - -use ruff_python_ast::{self as ast, Constant, Expr}; +use ruff_python_ast::{self as ast, BytesConstant, Constant, Expr, StringConstant}; use ruff_python_ast::{ConversionFlag, Ranged}; use ruff_text_size::{TextLen, TextRange, TextSize}; @@ -245,6 +243,7 @@ impl<'a> StringParser<'a> { spec = Some(Box::new(Expr::from(ast::ExprFString { values: parsed_spec, + implicit_concatenated: false, range: self.range(start_location), }))); } @@ -513,25 +512,25 @@ impl<'a> StringParser<'a> { } Ok(Expr::from(ast::ExprConstant { - value: Constant::Bytes(content.chars().map(|c| c as u8).collect()), + value: content.chars().map(|c| c as u8).collect::>().into(), kind: None, range: self.range(start_location), })) } fn parse_string(&mut self) -> Result { - let mut content = String::new(); + let mut value = String::new(); let start_location = self.get_pos(); while let Some(ch) = self.next_char() { match ch { '\\' if !self.kind.is_raw() => { - content.push_str(&self.parse_escaped_char()?); + value.push_str(&self.parse_escaped_char()?); } - ch => content.push(ch), + ch => value.push(ch), } } Ok(Expr::from(ast::ExprConstant { - value: Constant::Str(content), + value: value.into(), kind: self.kind.is_unicode().then(|| "u".to_string()), range: self.range(start_location), })) @@ -577,6 +576,7 @@ pub(crate) fn parse_strings( .filter(|(_, (_, kind, ..), _)| kind.is_any_bytes()) .count(); let has_bytes = num_bytes > 0; + let implicit_concatenated = values.len() > 1; if has_bytes && num_bytes < values.len() { return Err(LexicalError { @@ -593,7 +593,7 @@ pub(crate) fn parse_strings( for value in parse_string(&source, kind, triple_quoted, start)? { match value { Expr::Constant(ast::ExprConstant { - value: Constant::Bytes(value), + value: Constant::Bytes(BytesConstant { value, .. }), .. }) => content.extend(value), _ => unreachable!("Unexpected non-bytes expression."), @@ -601,7 +601,10 @@ pub(crate) fn parse_strings( } } return Ok(ast::ExprConstant { - value: Constant::Bytes(content), + value: Constant::Bytes(BytesConstant { + value: content, + implicit_concatenated, + }), kind: None, range: TextRange::new(initial_start, last_end), } @@ -614,7 +617,7 @@ pub(crate) fn parse_strings( for value in parse_string(&source, kind, triple_quoted, start)? { match value { Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), + value: Constant::Str(StringConstant { value, .. }), .. }) => content.push(value), _ => unreachable!("Unexpected non-string expression."), @@ -622,7 +625,10 @@ pub(crate) fn parse_strings( } } return Ok(ast::ExprConstant { - value: Constant::Str(content.join("")), + value: Constant::Str(StringConstant { + value: content.join(""), + implicit_concatenated, + }), kind: initial_kind, range: TextRange::new(initial_start, last_end), } @@ -637,7 +643,10 @@ pub(crate) fn parse_strings( let take_current = |current: &mut Vec, start, end| -> Expr { Expr::Constant(ast::ExprConstant { - value: Constant::Str(current.drain(..).join("")), + value: Constant::Str(StringConstant { + value: current.drain(..).collect::(), + implicit_concatenated, + }), kind: initial_kind.clone(), range: TextRange::new(start, end), }) @@ -654,14 +663,14 @@ pub(crate) fn parse_strings( deduped.push(value); } Expr::Constant(ast::ExprConstant { - value: Constant::Str(inner), + value: Constant::Str(StringConstant { value, .. }), .. }) => { if current.is_empty() { current_start = value_range.start(); } current_end = value_range.end(); - current.push(inner); + current.push(value); } _ => unreachable!("Unexpected non-string expression."), } @@ -673,6 +682,7 @@ pub(crate) fn parse_strings( Ok(Expr::FString(ast::ExprFString { values: deduped, + implicit_concatenated, range: TextRange::new(initial_start, last_end), })) } @@ -963,6 +973,13 @@ mod tests { insta::assert_debug_snapshot!(parse_ast); } + #[test] + fn test_parse_f_string_concat_4() { + let source = "'Hello ' f'world{\"!\"}' 'again!'"; + let parse_ast = parse_suite(source, "").unwrap(); + insta::assert_debug_snapshot!(parse_ast); + } + #[test] fn test_parse_u_f_string_concat_1() { let source = "u'Hello ' f'world'"; @@ -1080,6 +1097,22 @@ mod tests { insta::assert_debug_snapshot!(parse_ast); } + #[test] + fn test_parse_fstring_nested_string_spec() { + let source = "{foo:{''}}"; + let parse_ast = parse_fstring(source).unwrap(); + + insta::assert_debug_snapshot!(parse_ast); + } + + #[test] + fn test_parse_fstring_nested_concatenation_string_spec() { + let source = "{foo:{'' ''}}"; + let parse_ast = parse_fstring(source).unwrap(); + + insta::assert_debug_snapshot!(parse_ast); + } + macro_rules! test_aliases_parse { ($($name:ident: $alias:expr,)*) => { $(