Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Lib/_opcode_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@
'BUILD_SET_FROM_TUPLES': 122,
'BUILD_TUPLE_FROM_ITER': 123,
'BUILD_TUPLE_FROM_TUPLES': 124,
'BUILD_TEMPLATE': 125,
'BUILD_INTERPOLATION': 126,
'CONTINUE': 128,
'JUMP_IF_FALSE_OR_POP': 129,
'JUMP_IF_TRUE_OR_POP': 130,
Expand Down
120 changes: 114 additions & 6 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ use num_traits::{Num, ToPrimitive};
use ruff_python_ast::{
Alias, Arguments, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText, Decorator, DictItem,
ExceptHandler, ExceptHandlerExceptHandler, Expr, ExprAttribute, ExprBoolOp, ExprContext,
ExprFString, ExprList, ExprName, ExprSlice, ExprStarred, ExprSubscript, ExprTuple, ExprUnaryOp,
FString, FStringFlags, FStringPart, Identifier, Int, InterpolatedStringElement,
ExprFString, ExprList, ExprName, ExprSlice, ExprStarred, ExprSubscript, ExprTString, ExprTuple,
ExprUnaryOp, FString, FStringFlags, FStringPart, Identifier, Int, InterpolatedStringElement,
InterpolatedStringElements, Keyword, MatchCase, ModExpression, ModModule, Operator, Parameters,
Pattern, PatternMatchAs, PatternMatchClass, PatternMatchMapping, PatternMatchOr,
PatternMatchSequence, PatternMatchSingleton, PatternMatchStar, PatternMatchValue, Singleton,
Stmt, StmtExpr, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
TypeParams, UnaryOp, WithItem,
Stmt, StmtExpr, TString, TypeParam, TypeParamParamSpec, TypeParamTypeVar,
TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem,
visitor::{Visitor, walk_expr},
};
use ruff_text_size::{Ranged, TextRange};
Expand Down Expand Up @@ -6282,8 +6282,8 @@ impl Compiler {
Expr::FString(fstring) => {
self.compile_expr_fstring(fstring)?;
}
Expr::TString(_) => {
return Err(self.error(CodegenErrorType::NotImplementedYet));
Expr::TString(tstring) => {
self.compile_expr_tstring(tstring)?;
}
Expr::StringLiteral(string) => {
let value = string.value.to_str();
Expand Down Expand Up @@ -7466,6 +7466,114 @@ impl Compiler {

Ok(())
}

fn compile_expr_tstring(&mut self, expr_tstring: &ExprTString) -> CompileResult<()> {
// TStringValue can contain multiple TString parts (implicit concatenation)
// Each TString part should be compiled and the results merged into a single Template
let tstring_value = &expr_tstring.value;

// Collect all strings and compile all interpolations
let mut all_strings: Vec<Wtf8Buf> = Vec::new();
let mut current_string = Wtf8Buf::new();
let mut interp_count: u32 = 0;

for tstring in tstring_value.iter() {
self.compile_tstring_into(
tstring,
&mut all_strings,
&mut current_string,
&mut interp_count,
)?;
}

// Add trailing string
all_strings.push(std::mem::take(&mut current_string));

// Now build the Template:
// Stack currently has all interpolations from compile_tstring_into calls

// 1. Build interpolations tuple from the interpolations on the stack
emit!(self, Instruction::BuildTuple { size: interp_count });

// 2. Load all string parts
let string_count: u32 = all_strings
.len()
.try_into()
.expect("t-string string count overflowed");
for s in &all_strings {
self.emit_load_const(ConstantData::Str { value: s.clone() });
}

// 3. Build strings tuple
emit!(self, Instruction::BuildTuple { size: string_count });

// 4. Swap so strings is below interpolations: [interps, strings] -> [strings, interps]
emit!(self, Instruction::Swap { index: 2 });

// 5. Build the Template
emit!(self, Instruction::BuildTemplate);

Ok(())
}

fn compile_tstring_into(
&mut self,
tstring: &TString,
strings: &mut Vec<Wtf8Buf>,
current_string: &mut Wtf8Buf,
interp_count: &mut u32,
) -> CompileResult<()> {
for element in &tstring.elements {
match element {
InterpolatedStringElement::Literal(lit) => {
// Accumulate literal parts into current_string
current_string.push_str(&lit.value);
}
InterpolatedStringElement::Interpolation(interp) => {
// Finish current string segment
strings.push(std::mem::take(current_string));

// Compile the interpolation value
self.compile_expression(&interp.expression)?;

// Load the expression source string
let expr_range = interp.expression.range();
let expr_source = self.source_file.slice(expr_range);
self.emit_load_const(ConstantData::Str {
value: expr_source.to_string().into(),
});

// Determine conversion code
let conversion: u32 = match interp.conversion {
ConversionFlag::None => 0,
ConversionFlag::Str => 1,
ConversionFlag::Repr => 2,
ConversionFlag::Ascii => 3,
};

// Handle format_spec
let has_format_spec = interp.format_spec.is_some();
if let Some(format_spec) = &interp.format_spec {
// Compile format_spec as a string using fstring element compilation
// Use default FStringFlags since format_spec syntax is independent of t-string flags
self.compile_fstring_elements(
FStringFlags::empty(),
&format_spec.elements,
)?;
}

// Emit BUILD_INTERPOLATION
// oparg encoding: (conversion << 2) | has_format_spec
let oparg = (conversion << 2) | (has_format_spec as u32);
emit!(self, Instruction::BuildInterpolation { oparg });

*interp_count += 1;
}
}
}

Ok(())
}
}

trait EmitArg<Arg: OpArgType> {
Expand Down
21 changes: 13 additions & 8 deletions crates/codegen/src/symboltable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1441,14 +1441,19 @@ impl SymbolTableBuilder {
}
}
Expr::TString(tstring) => {
return Err(SymbolTableError {
error: "not yet implemented".into(),
location: Some(
self.source_file
.to_source_code()
.source_location(tstring.range.start(), PositionEncoding::Utf8),
),
});
// Scan t-string interpolation expressions (similar to f-strings)
for expr in tstring
.value
.elements()
.filter_map(|x| x.as_interpolation())
{
self.scan_expression(&expr.expression, ExpressionContext::Load)?;
if let Some(format_spec) = &expr.format_spec {
for element in format_spec.elements.interpolations() {
self.scan_expression(&element.expression, ExpressionContext::Load)?
}
}
}
}
// Constants
Expr::StringLiteral(_)
Expand Down
29 changes: 28 additions & 1 deletion crates/compiler-core/src/bytecode/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,19 @@ pub enum Instruction {
BuildTupleFromTuples {
size: Arg<u32>,
} = 124,
/// Build a Template from strings tuple and interpolations tuple on stack.
/// Stack: [strings_tuple, interpolations_tuple] -> [template]
BuildTemplate = 125,
/// Build an Interpolation from value, expression string, and optional format_spec on stack.
///
/// oparg encoding: (conversion << 2) | has_format_spec
/// - has_format_spec (bit 0): if 1, format_spec is on stack
/// - conversion (bits 2+): 0=None, 1=Str, 2=Repr, 3=Ascii
///
/// Stack: [value, expression_str, format_spec?] -> [interpolation]
BuildInterpolation {
oparg: Arg<u32>,
} = 126,
Continue {
target: Arg<Label>,
} = 128,
Expand Down Expand Up @@ -425,7 +438,11 @@ impl TryFrom<u8> for Instruction {
u8::from(Self::BuildTupleFromTuples {
size: Arg::marker(),
}),
// 125, 126, 127 are unused
u8::from(Self::BuildTemplate),
u8::from(Self::BuildInterpolation {
oparg: Arg::marker(),
}),
// 127 is unused
u8::from(Self::Continue {
target: Arg::marker(),
}),
Expand Down Expand Up @@ -782,6 +799,14 @@ impl InstructionMetadata for Instruction {
Self::InstrumentedPopJumpIfNone => 0,
Self::InstrumentedPopJumpIfNotNone => 0,
Self::InstrumentedLine => 0,
// BuildTemplate: pops [strings_tuple, interpolations_tuple], pushes [template]
Self::BuildTemplate => -1,
// BuildInterpolation: pops [value, expr_str, format_spec?], pushes [interpolation]
// has_format_spec is bit 0 of oparg
Self::BuildInterpolation { oparg } => {
let has_format_spec = oparg.get(arg) & 1 != 0;
if has_format_spec { -2 } else { -1 }
}
}
}

Expand Down Expand Up @@ -976,6 +1001,8 @@ impl InstructionMetadata for Instruction {
Self::UnaryNot => w!(UNARY_NOT),
Self::YieldValue { arg } => w!(YIELD_VALUE, arg),
Self::GetYieldFromIter => w!(GET_YIELD_FROM_ITER),
Self::BuildTemplate => w!(BUILD_TEMPLATE),
Self::BuildInterpolation { oparg } => w!(BUILD_INTERPOLATION, oparg),
_ => w!(RUSTPYTHON_PLACEHOLDER),
}
}
Expand Down
Loading