Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Loading