Skip to content

Commit 506e54b

Browse files
antoniosarosilukeramsdenaaronvghellovaicursoragent
authored
Method Declarations & Trailing Exprs & Constructors (#2337)
Method declarations and a bunch of other AST / HIR / THIR / Codegen fixes. <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Enhance BAML compiler and VM with method declarations, trailing expression handling, and improved constructor logic, including comprehensive test updates. > > - **Behavior**: > - Add support for method declarations in classes, including `self` parameter handling in `parse_named_args_list.rs` and `parse_type_expression_block.rs`. > - Implement trailing expression handling in blocks, replacing `produces_final_value()` with `trailing_expr` in `hir.rs` and `thir.rs`. > - Update `compile_thir_to_bytecode()` in `codegen.rs` to handle method bytecode generation. > - Modify `Vm::len()` in `native.rs` to use `std.Array.len`. > - **Parsing**: > - Update `parse_expr.rs` and `parse_named_args_list.rs` to support new syntax for method declarations and assignments. > - Adjust `parse_type_expression_block.rs` to parse methods within classes. > - **Validation**: > - Enhance `validate_expr_fns()` in `expr_fns.rs` to check for method name uniqueness and argument validity. > - **Testing**: > - Add and update tests in `bytecode_tests.rs` and `vm.rs` to cover new method declaration and execution scenarios. > - Update bytecode and HIR test files to reflect changes in method handling and trailing expressions. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup> for 7b80c60. You can [customize](https://app.ellipsis.dev/BoundaryML/settings/summaries) this summary. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN --> --------- Co-authored-by: Luke Ramsden <hello@lukeramsden.com> Co-authored-by: aaronvg <aaron@boundaryml.com> Co-authored-by: hellovai <vbv@boundaryml.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> Co-authored-by: Jesús Lapastora <jesus@boundaryml.com> Co-authored-by: Samuel Lijin <sam@boundaryml.com> Co-authored-by: Ayush Goyal <ayushg1214@gmail.com> Co-authored-by: Chris Watts <chris@boundaryml.com>
1 parent cfdd5ad commit 506e54b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1132
-499
lines changed

engine/baml-compiler/src/builtin.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub mod enums {
1717
pub fn builtin_classes() -> Vec<Class> {
1818
vec![Class {
1919
name: String::from(classes::REQUEST),
20+
methods: vec![],
2021
fields: vec![
2122
Field {
2223
name: String::from("base_url"),

engine/baml-compiler/src/codegen.rs

Lines changed: 225 additions & 112 deletions
Large diffs are not rendered by default.

engine/baml-compiler/src/hir/dump.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,18 +123,20 @@ impl Statement {
123123
.append(RcDoc::space())
124124
.append(RcDoc::text(name.clone()))
125125
.append(RcDoc::text(";")),
126-
Statement::Assign { name, value, .. } => RcDoc::text(name.clone())
126+
Statement::Assign { left, value, .. } => left
127+
.to_doc()
127128
.append(RcDoc::space())
128129
.append(RcDoc::text("="))
129130
.append(RcDoc::space())
130131
.append(value.to_doc())
131132
.append(RcDoc::text(";")),
132133
Statement::AssignOp {
133-
name,
134+
left,
134135
value,
135136
assign_op,
136137
..
137-
} => RcDoc::text(name.clone())
138+
} => left
139+
.to_doc()
138140
.append(RcDoc::space())
139141
.append(assign_op.to_doc())
140142
.append(RcDoc::space())
@@ -157,7 +159,7 @@ impl Statement {
157159
.append(condition.to_doc())
158160
.append(RcDoc::text(";")),
159161
Statement::Expression { expr, .. } => expr.to_doc(),
160-
Statement::SemicolonExpression { expr, .. } => expr.to_doc(),
162+
Statement::Semicolon { expr, .. } => expr.to_doc(),
161163
Statement::While {
162164
condition, block, ..
163165
} => RcDoc::text("while")
@@ -268,7 +270,8 @@ impl LlmFunction {
268270

269271
impl ExprFunction {
270272
pub fn to_doc(&self) -> RcDoc<'static, ()> {
271-
let body_doc = if self.body.statements.is_empty() {
273+
// TODO: Why nesting doesn't work if calling self.body.to_doc().nest(2)?
274+
let mut body_doc = if self.body.statements.is_empty() {
272275
RcDoc::nil()
273276
} else {
274277
// The key is to apply nest() to the entire content that includes line breaks
@@ -284,6 +287,15 @@ impl ExprFunction {
284287
.append(RcDoc::hardline())
285288
.nest(2)
286289
};
290+
291+
if let Some(expr) = &self.body.trailing_expr {
292+
body_doc = body_doc.append(
293+
RcDoc::hardline()
294+
.append(expr.to_doc().append(RcDoc::hardline()))
295+
.nest(2),
296+
);
297+
}
298+
287299
RcDoc::text("function")
288300
.append(RcDoc::space())
289301
.append(RcDoc::text(self.name.clone()))
@@ -308,7 +320,7 @@ impl ExprFunction {
308320

309321
impl Block {
310322
pub fn to_doc(&self) -> RcDoc<'static, ()> {
311-
if self.statements.is_empty() {
323+
let doc = if self.statements.is_empty() {
312324
RcDoc::nil()
313325
} else {
314326
RcDoc::intersperse(
@@ -318,6 +330,12 @@ impl Block {
318330
.collect::<Vec<_>>(),
319331
RcDoc::hardline(),
320332
)
333+
};
334+
335+
if let Some(expr) = &self.trailing_expr {
336+
doc.append(RcDoc::hardline()).append(expr.to_doc())
337+
} else {
338+
doc
321339
}
322340
}
323341
}
@@ -448,7 +466,7 @@ impl Expression {
448466
.append(RcDoc::space())
449467
})
450468
.append(RcDoc::text("}")),
451-
Expression::ExpressionBlock(block, _) => RcDoc::text("{")
469+
Expression::Block(block, _) => RcDoc::text("{")
452470
.append(RcDoc::hardline())
453471
.append(block.to_doc().nest(2))
454472
.append(RcDoc::hardline())

engine/baml-compiler/src/hir/lowering.rs

Lines changed: 31 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -267,65 +267,35 @@ impl ExprFunction {
267267
name: function.name.to_string(),
268268
parameters: lower_fn_args(&function.args),
269269
return_type: TypeM::from_ast_optional(function.return_type.as_ref()),
270-
body: Block::from_function_body(&function.body),
270+
body: Block::from_expr_block(&function.body),
271271
span: function.span.clone(),
272272
}
273273
}
274274
}
275275

276276
impl Block {
277-
/// Lower an expression block into HIR for function bodies (ends with Statement::Return).
278-
pub fn from_function_body(block: &ast::ExpressionBlock) -> Self {
279-
Self::from_ast_with_context(block, true)
280-
}
281-
282-
/// Lower an expression block into HIR for expression blocks (ends with Statement::Expression).
283-
pub fn from_expression_block(block: &ast::ExpressionBlock) -> Self {
284-
Self::from_ast_with_context(block, false)
285-
}
286-
287-
/// Lower an expression block into HIR with specified context.
288-
/// If is_function_body is true, the final expression becomes Statement::Return.
289-
/// If is_function_body is false, the final expression becomes Statement::Expression.
290-
fn from_ast_with_context(block: &ast::ExpressionBlock, is_function_body: bool) -> Self {
291-
let mut statements = vec![];
292-
293-
// Process statements, checking for if expressions in let bindings
294-
for stmt in &block.stmts {
295-
let hir_stmt = lower_stmt(stmt);
296-
statements.push(hir_stmt);
277+
/// Lower an expression block into HIR for expression blocks.
278+
pub fn from_expr_block(block: &ast::ExpressionBlock) -> Self {
279+
Block {
280+
statements: block.stmts.iter().map(lower_stmt).collect(),
281+
trailing_expr: block
282+
.expr
283+
.as_deref()
284+
.map(Expression::from_ast)
285+
.map(Box::new),
297286
}
298-
299-
if let Some(block_final_expr) = block.expr.as_ref() {
300-
let final_expr = Expression::from_ast(block_final_expr);
301-
302-
// Then add the final statement
303-
statements.push(if is_function_body {
304-
Statement::Return {
305-
expr: final_expr,
306-
span: block_final_expr.span().clone(),
307-
}
308-
} else {
309-
Statement::Expression {
310-
expr: final_expr,
311-
span: block_final_expr.span().clone(),
312-
}
313-
});
314-
}
315-
316-
Block { statements }
317287
}
318288
}
319289

320290
fn lower_stmt(stmt: &ast::Stmt) -> Statement {
321-
let hir_stmt = match stmt {
291+
match stmt {
322292
ast::Stmt::CForLoop(stmt) => {
323293
// we'll add a block if we an init statement, otherwise we'll just
324294
// use the current context to push the while statement.
325295

326296
let condition = stmt.condition.as_ref().map(Expression::from_ast);
327297
let init = stmt.init_stmt.as_ref().map(|b| lower_stmt(b));
328-
let block = Block::from_expression_block(&stmt.body);
298+
let block = Block::from_expr_block(&stmt.body);
329299
let after = stmt
330300
.after_stmt
331301
.as_ref()
@@ -349,9 +319,10 @@ fn lower_stmt(stmt: &ast::Stmt) -> Statement {
349319
Some(init) => {
350320
// use a block
351321
Statement::Expression {
352-
expr: Expression::ExpressionBlock(
322+
expr: Expression::Block(
353323
Block {
354324
statements: vec![init, inner_loop],
325+
trailing_expr: None,
355326
},
356327
stmt.span.clone(),
357328
),
@@ -373,30 +344,26 @@ fn lower_stmt(stmt: &ast::Stmt) -> Statement {
373344

374345
let condition = Expression::from_ast(condition);
375346

376-
let body = Block::from_expression_block(body);
347+
let body = Block::from_expr_block(body);
377348

378349
Statement::While {
379350
condition,
380351
block: body,
381352
span: span.clone(),
382353
}
383354
}
384-
ast::Stmt::Assign(ast::AssignStmt {
385-
identifier,
386-
expr,
387-
span,
388-
}) => Statement::Assign {
389-
name: identifier.to_string(),
355+
ast::Stmt::Assign(ast::AssignStmt { left, expr, span }) => Statement::Assign {
356+
left: Expression::from_ast(left),
390357
value: Expression::from_ast(expr),
391358
span: span.clone(),
392359
},
393360
ast::Stmt::AssignOp(ast::AssignOpStmt {
394-
identifier,
361+
left,
395362
assign_op,
396363
expr,
397364
span,
398365
}) => Statement::AssignOp {
399-
name: identifier.to_string(),
366+
left: Expression::from_ast(left),
400367
assign_op: match assign_op {
401368
ast::AssignOp::AddAssign => hir::AssignOp::AddAssign,
402369
ast::AssignOp::SubAssign => hir::AssignOp::SubAssign,
@@ -447,32 +414,18 @@ fn lower_stmt(stmt: &ast::Stmt) -> Statement {
447414
Statement::ForLoop {
448415
identifier: identifier.name().to_string(),
449416
iterator: Box::new(lifted_iterator),
450-
block: Block::from_expression_block(body),
417+
block: Block::from_expr_block(body),
451418
span: span.clone(),
452419
}
453420
}
454-
ast::Stmt::Expression(expr) => {
455-
let hir_expr = Expression::from_ast(expr);
456-
457-
// Expressions that contain blocks themselves will deal with
458-
// return expressions recursively. But expressions that have
459-
// no blocks (like function calls or 2 + 2) must drop the
460-
// returned value, so we insert semicolon expressions.
461-
if matches!(
462-
expr,
463-
ast::Expression::If(..) | ast::Expression::ExprBlock(..)
464-
) {
465-
Statement::Expression {
466-
expr: hir_expr,
467-
span: expr.span().clone(),
468-
}
469-
} else {
470-
Statement::SemicolonExpression {
471-
expr: hir_expr,
472-
span: expr.span().clone(),
473-
}
474-
}
475-
}
421+
ast::Stmt::Expression(expr) => Statement::Expression {
422+
expr: Expression::from_ast(expr),
423+
span: expr.span().clone(),
424+
},
425+
ast::Stmt::Semicolon(expr) => Statement::Semicolon {
426+
expr: Expression::from_ast(expr),
427+
span: expr.span().clone(),
428+
},
476429
ast::Stmt::Return(ReturnStmt { value, span }) => Statement::Return {
477430
expr: Expression::from_ast(value),
478431
span: span.clone(),
@@ -481,8 +434,7 @@ fn lower_stmt(stmt: &ast::Stmt) -> Statement {
481434
condition: Expression::from_ast(value),
482435
span: span.clone(),
483436
},
484-
};
485-
hir_stmt
437+
}
486438
}
487439

488440
impl Expression {
@@ -577,7 +529,7 @@ impl Expression {
577529
// Expression blocks are lowered to HIR preserving their structure
578530
// This maintains proper scoping - variables defined inside the block
579531
// are only visible within that block
580-
Expression::ExpressionBlock(Block::from_expression_block(block), span.clone())
532+
Expression::Block(Block::from_expr_block(block), span.clone())
581533
}
582534
ast::Expression::Lambda(_, _, _) => {
583535
todo!("lambdas are not yet implemented")
@@ -688,6 +640,7 @@ impl Class {
688640
span: field.span().clone(),
689641
})
690642
.collect(),
643+
methods: class.methods.iter().map(ExprFunction::from_ast).collect(),
691644
span: class.span().clone(),
692645
}
693646
}

engine/baml-compiler/src/hir/mod.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ pub struct LlmFunction {
279279
pub struct Class {
280280
pub name: String,
281281
pub fields: Vec<Field>,
282+
// TODO: Allow LLM functions here.
283+
pub methods: Vec<ExprFunction>,
282284
pub span: Span,
283285
}
284286

@@ -312,7 +314,11 @@ pub struct Parameter {
312314

313315
#[derive(Clone, Debug)]
314316
pub struct Block {
317+
/// List of statements.
315318
pub statements: Vec<Statement>,
319+
320+
/// Final expression in the block without semicolon (used as return).
321+
pub trailing_expr: Option<Box<Expression>>,
316322
}
317323

318324
/// A single unit of execution within a block.
@@ -333,12 +339,12 @@ pub enum Statement {
333339
},
334340
/// Assign a mutable variable.
335341
Assign {
336-
name: String,
342+
left: Expression,
337343
value: Expression,
338344
span: Span,
339345
},
340346
AssignOp {
341-
name: String,
347+
left: Expression,
342348
assign_op: AssignOp,
343349
value: Expression,
344350
span: Span,
@@ -359,7 +365,7 @@ pub enum Statement {
359365
expr: Expression,
360366
span: Span,
361367
},
362-
SemicolonExpression {
368+
Semicolon {
363369
expr: Expression,
364370
span: Span,
365371
},
@@ -456,7 +462,7 @@ pub enum Expression {
456462
// MethodCall(Box<Expression>, String, Vec<Expression>), // TODO.
457463
ClassConstructor(ClassConstructor, Span),
458464
/// Expression block - has its own scope with statements and evaluates to a value
459-
ExpressionBlock(Block, Span),
465+
Block(Block, Span),
460466
BinaryOperation {
461467
left: Box<Expression>,
462468
operator: BinaryOperator,
@@ -545,7 +551,7 @@ impl Expression {
545551
Expression::JinjaExpressionValue(_, span) => span.clone(),
546552
Expression::Call { span, .. } => span.clone(),
547553
Expression::ClassConstructor(_, span) => span.clone(),
548-
Expression::ExpressionBlock(_, span) => span.clone(),
554+
Expression::Block(_, span) => span.clone(),
549555
Expression::BinaryOperation { span, .. } => span.clone(),
550556
Expression::UnaryOperation { span, .. } => span.clone(),
551557
Expression::Paren(_, span) => span.clone(),

0 commit comments

Comments
 (0)