Skip to content

Commit 33fbc51

Browse files
committed
PEP 750 tstring
1 parent 746e71a commit 33fbc51

File tree

12 files changed

+1050
-24
lines changed

12 files changed

+1050
-24
lines changed

crates/codegen/src/compile.rs

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ use num_traits::{Num, ToPrimitive};
2424
use ruff_python_ast::{
2525
Alias, Arguments, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText, Decorator, DictItem,
2626
ExceptHandler, ExceptHandlerExceptHandler, Expr, ExprAttribute, ExprBoolOp, ExprContext,
27-
ExprFString, ExprList, ExprName, ExprSlice, ExprStarred, ExprSubscript, ExprTuple, ExprUnaryOp,
28-
FString, FStringFlags, FStringPart, Identifier, Int, InterpolatedStringElement,
27+
ExprFString, ExprList, ExprName, ExprSlice, ExprStarred, ExprSubscript, ExprTString, ExprTuple,
28+
ExprUnaryOp, FString, FStringFlags, FStringPart, Identifier, Int, InterpolatedStringElement,
2929
InterpolatedStringElements, Keyword, MatchCase, ModExpression, ModModule, Operator, Parameters,
3030
Pattern, PatternMatchAs, PatternMatchClass, PatternMatchMapping, PatternMatchOr,
3131
PatternMatchSequence, PatternMatchSingleton, PatternMatchStar, PatternMatchValue, Singleton,
32-
Stmt, StmtExpr, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
33-
TypeParams, UnaryOp, WithItem,
32+
Stmt, StmtExpr, TString, TypeParam, TypeParamParamSpec, TypeParamTypeVar,
33+
TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem,
3434
visitor::{Visitor, walk_expr},
3535
};
3636
use ruff_text_size::{Ranged, TextRange};
@@ -6282,8 +6282,8 @@ impl Compiler {
62826282
Expr::FString(fstring) => {
62836283
self.compile_expr_fstring(fstring)?;
62846284
}
6285-
Expr::TString(_) => {
6286-
return Err(self.error(CodegenErrorType::NotImplementedYet));
6285+
Expr::TString(tstring) => {
6286+
self.compile_expr_tstring(tstring)?;
62876287
}
62886288
Expr::StringLiteral(string) => {
62896289
let value = string.value.to_str();
@@ -7466,6 +7466,114 @@ impl Compiler {
74667466

74677467
Ok(())
74687468
}
7469+
7470+
fn compile_expr_tstring(&mut self, expr_tstring: &ExprTString) -> CompileResult<()> {
7471+
// TStringValue can contain multiple TString parts (implicit concatenation)
7472+
// Each TString part should be compiled and the results merged into a single Template
7473+
let tstring_value = &expr_tstring.value;
7474+
7475+
// Collect all strings and compile all interpolations
7476+
let mut all_strings: Vec<Wtf8Buf> = Vec::new();
7477+
let mut current_string = Wtf8Buf::new();
7478+
let mut interp_count: u32 = 0;
7479+
7480+
for tstring in tstring_value.iter() {
7481+
self.compile_tstring_into(
7482+
tstring,
7483+
&mut all_strings,
7484+
&mut current_string,
7485+
&mut interp_count,
7486+
)?;
7487+
}
7488+
7489+
// Add trailing string
7490+
all_strings.push(std::mem::take(&mut current_string));
7491+
7492+
// Now build the Template:
7493+
// Stack currently has all interpolations from compile_tstring_into calls
7494+
7495+
// 1. Build interpolations tuple from the interpolations on the stack
7496+
emit!(self, Instruction::BuildTuple { size: interp_count });
7497+
7498+
// 2. Load all string parts
7499+
let string_count: u32 = all_strings
7500+
.len()
7501+
.try_into()
7502+
.expect("t-string string count overflowed");
7503+
for s in &all_strings {
7504+
self.emit_load_const(ConstantData::Str { value: s.clone() });
7505+
}
7506+
7507+
// 3. Build strings tuple
7508+
emit!(self, Instruction::BuildTuple { size: string_count });
7509+
7510+
// 4. Swap so strings is below interpolations: [interps, strings] -> [strings, interps]
7511+
emit!(self, Instruction::Swap { index: 2 });
7512+
7513+
// 5. Build the Template
7514+
emit!(self, Instruction::BuildTemplate);
7515+
7516+
Ok(())
7517+
}
7518+
7519+
fn compile_tstring_into(
7520+
&mut self,
7521+
tstring: &TString,
7522+
strings: &mut Vec<Wtf8Buf>,
7523+
current_string: &mut Wtf8Buf,
7524+
interp_count: &mut u32,
7525+
) -> CompileResult<()> {
7526+
for element in &tstring.elements {
7527+
match element {
7528+
InterpolatedStringElement::Literal(lit) => {
7529+
// Accumulate literal parts into current_string
7530+
current_string.push_str(&lit.value);
7531+
}
7532+
InterpolatedStringElement::Interpolation(interp) => {
7533+
// Finish current string segment
7534+
strings.push(std::mem::take(current_string));
7535+
7536+
// Compile the interpolation value
7537+
self.compile_expression(&interp.expression)?;
7538+
7539+
// Load the expression source string
7540+
let expr_range = interp.expression.range();
7541+
let expr_source = self.source_file.slice(expr_range);
7542+
self.emit_load_const(ConstantData::Str {
7543+
value: expr_source.to_string().into(),
7544+
});
7545+
7546+
// Determine conversion code
7547+
let conversion: u32 = match interp.conversion {
7548+
ConversionFlag::None => 0,
7549+
ConversionFlag::Str => 1,
7550+
ConversionFlag::Repr => 2,
7551+
ConversionFlag::Ascii => 3,
7552+
};
7553+
7554+
// Handle format_spec
7555+
let has_format_spec = interp.format_spec.is_some();
7556+
if let Some(format_spec) = &interp.format_spec {
7557+
// Compile format_spec as a string using fstring element compilation
7558+
// Use default FStringFlags since format_spec syntax is independent of t-string flags
7559+
self.compile_fstring_elements(
7560+
FStringFlags::empty(),
7561+
&format_spec.elements,
7562+
)?;
7563+
}
7564+
7565+
// Emit BUILD_INTERPOLATION
7566+
// oparg encoding: (conversion << 2) | has_format_spec
7567+
let oparg = (conversion << 2) | (has_format_spec as u32);
7568+
emit!(self, Instruction::BuildInterpolation { oparg });
7569+
7570+
*interp_count += 1;
7571+
}
7572+
}
7573+
}
7574+
7575+
Ok(())
7576+
}
74697577
}
74707578

74717579
trait EmitArg<Arg: OpArgType> {

crates/codegen/src/symboltable.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,14 +1441,19 @@ impl SymbolTableBuilder {
14411441
}
14421442
}
14431443
Expr::TString(tstring) => {
1444-
return Err(SymbolTableError {
1445-
error: "not yet implemented".into(),
1446-
location: Some(
1447-
self.source_file
1448-
.to_source_code()
1449-
.source_location(tstring.range.start(), PositionEncoding::Utf8),
1450-
),
1451-
});
1444+
// Scan t-string interpolation expressions (similar to f-strings)
1445+
for expr in tstring
1446+
.value
1447+
.elements()
1448+
.filter_map(|x| x.as_interpolation())
1449+
{
1450+
self.scan_expression(&expr.expression, ExpressionContext::Load)?;
1451+
if let Some(format_spec) = &expr.format_spec {
1452+
for element in format_spec.elements.interpolations() {
1453+
self.scan_expression(&element.expression, ExpressionContext::Load)?
1454+
}
1455+
}
1456+
}
14521457
}
14531458
// Constants
14541459
Expr::StringLiteral(_)

crates/compiler-core/src/bytecode/instruction.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,19 @@ pub enum Instruction {
267267
BuildTupleFromTuples {
268268
size: Arg<u32>,
269269
} = 124,
270+
/// Build a Template from strings tuple and interpolations tuple on stack.
271+
/// Stack: [strings_tuple, interpolations_tuple] -> [template]
272+
BuildTemplate = 125,
273+
/// Build an Interpolation from value, expression string, and optional format_spec on stack.
274+
///
275+
/// oparg encoding: (conversion << 2) | has_format_spec
276+
/// - has_format_spec (bit 0): if 1, format_spec is on stack
277+
/// - conversion (bits 2+): 0=None, 1=Str, 2=Repr, 3=Ascii
278+
///
279+
/// Stack: [value, expression_str, format_spec?] -> [interpolation]
280+
BuildInterpolation {
281+
oparg: Arg<u32>,
282+
} = 126,
270283
Continue {
271284
target: Arg<Label>,
272285
} = 128,
@@ -326,7 +339,11 @@ impl TryFrom<u8> for Instruction {
326339
u8::from(Self::BuildTupleFromTuples {
327340
size: Arg::marker(),
328341
}),
329-
// 125, 126, 127 are unused
342+
u8::from(Self::BuildTemplate),
343+
u8::from(Self::BuildInterpolation {
344+
oparg: Arg::marker(),
345+
}),
346+
// 127 is unused
330347
u8::from(Self::Continue {
331348
target: Arg::marker(),
332349
}),
@@ -589,6 +606,14 @@ impl InstructionMetadata for Instruction {
589606
Self::PopJumpIfNone { .. } => 0,
590607
Self::PopJumpIfNotNone { .. } => 0,
591608
Self::LoadClosure(_) => 1,
609+
// BuildTemplate: pops [strings_tuple, interpolations_tuple], pushes [template]
610+
Self::BuildTemplate => -1,
611+
// BuildInterpolation: pops [value, expr_str, format_spec?], pushes [interpolation]
612+
// has_format_spec is bit 0 of oparg
613+
Self::BuildInterpolation { oparg } => {
614+
let has_format_spec = oparg.get(arg) & 1 != 0;
615+
if has_format_spec { -2 } else { -1 }
616+
}
592617
}
593618
}
594619

@@ -783,6 +808,8 @@ impl InstructionMetadata for Instruction {
783808
Self::UnaryNot => w!(UNARY_NOT),
784809
Self::YieldValue { arg } => w!(YIELD_VALUE, arg),
785810
Self::GetYieldFromIter => w!(GET_YIELD_FROM_ITER),
811+
Self::BuildTemplate => w!(BUILD_TEMPLATE),
812+
Self::BuildInterpolation { oparg } => w!(BUILD_INTERPOLATION, oparg),
786813
_ => w!(RUSTPYTHON_PLACEHOLDER),
787814
}
788815
}

0 commit comments

Comments
 (0)