diff --git a/crates/ruff_python_parser/resources/inline/err/template_strings_py313.py b/crates/ruff_python_parser/resources/inline/err/template_strings_py313.py new file mode 100644 index 0000000000..2c7fde825a --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/template_strings_py313.py @@ -0,0 +1,6 @@ +# parse_options: {"target-version": "3.13"} +t"{hey}" +t'{there}' +t"""what's +happening?""" +"implicitly"t"concatenated" diff --git a/crates/ruff_python_parser/resources/inline/ok/template_strings_py314.py b/crates/ruff_python_parser/resources/inline/ok/template_strings_py314.py new file mode 100644 index 0000000000..541f49917a --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/template_strings_py314.py @@ -0,0 +1,6 @@ +# parse_options: {"target-version": "3.14"} +t"{hey}" +t'{there}' +t"""what's +happening?""" +"implicitly"t"concatenated" diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index ffe6a56d8a..9eff91aadb 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -1250,10 +1250,30 @@ impl<'src> Parser<'src> { .into(), )); } else if self.at(TokenKind::TStringStart) { - strings.push(StringType::TString( + // test_ok template_strings_py314 + // # parse_options: {"target-version": "3.14"} + // t"{hey}" + // t'{there}' + // t"""what's + // happening?""" + // "implicitly"t"concatenated" + + // test_err template_strings_py313 + // # parse_options: {"target-version": "3.13"} + // t"{hey}" + // t'{there}' + // t"""what's + // happening?""" + // "implicitly"t"concatenated" + let string_type = StringType::TString( self.parse_interpolated_string(InterpolatedStringKind::TString) .into(), - )); + ); + self.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::TemplateStrings, + string_type.range(), + ); + strings.push(string_type); } } diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@template_strings_py313.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@template_strings_py313.py.snap new file mode 100644 index 0000000000..87586ee1bc --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@template_strings_py313.py.snap @@ -0,0 +1,231 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/template_strings_py313.py +--- +## AST + +``` +Module( + ModModule { + node_index: AtomicNodeIndex(..), + range: 0..117, + body: [ + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 44..52, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 44..52, + value: TStringValue { + inner: Single( + TString( + TString { + range: 44..52, + node_index: AtomicNodeIndex(..), + elements: [ + Interpolation( + InterpolatedElement { + range: 46..51, + node_index: AtomicNodeIndex(..), + expression: Name( + ExprName { + node_index: AtomicNodeIndex(..), + range: 47..50, + id: Name("hey"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 53..63, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 53..63, + value: TStringValue { + inner: Single( + TString( + TString { + range: 53..63, + node_index: AtomicNodeIndex(..), + elements: [ + Interpolation( + InterpolatedElement { + range: 55..62, + node_index: AtomicNodeIndex(..), + expression: Name( + ExprName { + node_index: AtomicNodeIndex(..), + range: 56..61, + id: Name("there"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 64..88, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 64..88, + value: TStringValue { + inner: Single( + TString( + TString { + range: 64..88, + node_index: AtomicNodeIndex(..), + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 68..85, + node_index: AtomicNodeIndex(..), + value: "what's\nhappening?", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: true, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 89..116, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 89..116, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 89..101, + node_index: AtomicNodeIndex(..), + value: "implicitly", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 101..116, + node_index: AtomicNodeIndex(..), + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 103..115, + node_index: AtomicNodeIndex(..), + value: "concatenated", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + ], + }, +) +``` +## Unsupported Syntax Errors + + | +1 | # parse_options: {"target-version": "3.13"} +2 | t"{hey}" + | ^^^^^^^^ Syntax Error: Cannot use t-strings on Python 3.13 (syntax was added in Python 3.14) +3 | t'{there}' +4 | t"""what's + | + + + | +1 | # parse_options: {"target-version": "3.13"} +2 | t"{hey}" +3 | t'{there}' + | ^^^^^^^^^^ Syntax Error: Cannot use t-strings on Python 3.13 (syntax was added in Python 3.14) +4 | t"""what's +5 | happening?""" + | + + + | +2 | t"{hey}" +3 | t'{there}' +4 | / t"""what's +5 | | happening?""" + | |_____________^ Syntax Error: Cannot use t-strings on Python 3.13 (syntax was added in Python 3.14) +6 | "implicitly"t"concatenated" + | + + + | +4 | t"""what's +5 | happening?""" +6 | "implicitly"t"concatenated" + | ^^^^^^^^^^^^^^^ Syntax Error: Cannot use t-strings on Python 3.13 (syntax was added in Python 3.14) + | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@template_strings_py314.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@template_strings_py314.py.snap new file mode 100644 index 0000000000..f314487ff6 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@template_strings_py314.py.snap @@ -0,0 +1,194 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/template_strings_py314.py +--- +## AST + +``` +Module( + ModModule { + node_index: AtomicNodeIndex(..), + range: 0..117, + body: [ + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 44..52, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 44..52, + value: TStringValue { + inner: Single( + TString( + TString { + range: 44..52, + node_index: AtomicNodeIndex(..), + elements: [ + Interpolation( + InterpolatedElement { + range: 46..51, + node_index: AtomicNodeIndex(..), + expression: Name( + ExprName { + node_index: AtomicNodeIndex(..), + range: 47..50, + id: Name("hey"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 53..63, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 53..63, + value: TStringValue { + inner: Single( + TString( + TString { + range: 53..63, + node_index: AtomicNodeIndex(..), + elements: [ + Interpolation( + InterpolatedElement { + range: 55..62, + node_index: AtomicNodeIndex(..), + expression: Name( + ExprName { + node_index: AtomicNodeIndex(..), + range: 56..61, + id: Name("there"), + ctx: Load, + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: TStringFlags { + quote_style: Single, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 64..88, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 64..88, + value: TStringValue { + inner: Single( + TString( + TString { + range: 64..88, + node_index: AtomicNodeIndex(..), + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 68..85, + node_index: AtomicNodeIndex(..), + value: "what's\nhappening?", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: true, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: AtomicNodeIndex(..), + range: 89..116, + value: TString( + ExprTString { + node_index: AtomicNodeIndex(..), + range: 89..116, + value: TStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 89..101, + node_index: AtomicNodeIndex(..), + value: "implicitly", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + TString( + TString { + range: 101..116, + node_index: AtomicNodeIndex(..), + elements: [ + Literal( + InterpolatedStringLiteralElement { + range: 103..115, + node_index: AtomicNodeIndex(..), + value: "concatenated", + }, + ), + ], + flags: TStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, + }, + ), + ], + ), + }, + }, + ), + }, + ), + ], + }, +) +```