Skip to content

Commit c92cb02

Browse files
committed
typecheck more expressions
respect phase boundaries resolve conflicts
1 parent 700ed1a commit c92cb02

File tree

43 files changed

+1099
-644
lines changed

Some content is hidden

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

43 files changed

+1099
-644
lines changed

baml_language/Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

baml_language/crates/baml_codegen/src/lib.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub use baml_vm::{
3030
BinOp, Bytecode, Class, CmpOp, Enum, Function, FunctionKind, Instruction, Object, Program,
3131
UnaryOp, Value,
3232
};
33-
use baml_workspace::ProjectRoot;
33+
use baml_workspace::Project;
3434
pub use compiler::{Compiler, compile_function};
3535

3636
/// Generate bytecode for all functions in a project.
@@ -39,7 +39,7 @@ pub use compiler::{Compiler, compile_function};
3939
/// It collects all functions from HIR, type-checks them via THIR,
4040
/// and compiles them to bytecode.
4141
#[salsa::tracked]
42-
pub fn generate_project_bytecode(db: &dyn baml_thir::Db, root: ProjectRoot) -> Program {
42+
pub fn generate_project_bytecode(db: &dyn baml_thir::Db, root: Project) -> Program {
4343
let files = baml_workspace::project_files(db, root);
4444
compile_files(db, &files)
4545
}
@@ -61,7 +61,7 @@ pub fn compile_files(db: &dyn baml_thir::Db, files: &[SourceFile]) -> Program {
6161
let items_struct = baml_hir::file_items(db, *file);
6262
for item in items_struct.items(db) {
6363
if let ItemId::Function(func_loc) = item {
64-
let signature = function_signature(db, *file, *func_loc);
64+
let signature = function_signature(db, *func_loc);
6565
globals.insert(signature.name.to_string(), global_idx);
6666
global_idx += 1;
6767
}
@@ -73,12 +73,17 @@ pub fn compile_files(db: &dyn baml_thir::Db, files: &[SourceFile]) -> Program {
7373
let items_struct = baml_hir::file_items(db, *file);
7474
for item in items_struct.items(db) {
7575
if let ItemId::Function(func_loc) = item {
76-
let signature = function_signature(db, *file, *func_loc);
77-
let body = function_body(db, *file, *func_loc);
76+
let signature = function_signature(db, *func_loc);
77+
let body = function_body(db, *func_loc);
7878

7979
// Run type inference
80-
let inference =
81-
baml_thir::infer_function(db, &signature, &body, Some(typing_context.clone()));
80+
let inference = baml_thir::infer_function(
81+
db,
82+
&signature,
83+
&body,
84+
Some(typing_context.clone()),
85+
None,
86+
);
8287

8388
// Get parameter names
8489
let params: Vec<Name> = signature.params.iter().map(|p| p.name.clone()).collect();
@@ -127,7 +132,7 @@ fn build_typing_context<'db>(
127132
let items_struct = baml_hir::file_items(db, *file);
128133
for item in items_struct.items(db) {
129134
if let ItemId::Function(func_loc) = item {
130-
let signature = function_signature(db, *file, *func_loc);
135+
let signature = function_signature(db, *func_loc);
131136

132137
// Build the arrow type: (param_types) -> return_type
133138
let param_types: Vec<baml_thir::Ty<'db>> = signature

baml_language/crates/baml_codegen/src/tests/common.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
use std::collections::HashMap;
66

7-
use baml_db::{
8-
RootDatabase, baml_hir, baml_thir, build_typing_context_from_files, function_body,
9-
function_signature,
10-
};
7+
use baml_db::{RootDatabase, baml_hir, baml_thir};
8+
use baml_hir::{function_body, function_signature};
9+
use baml_thir::build_typing_context_from_files;
1110
use baml_vm::test::{Instruction, Value};
1211

1312
/// Helper struct for testing bytecode compilation.
@@ -167,7 +166,7 @@ fn compile_source(source: &str) -> CompileResult {
167166
let mut global_idx = 0;
168167
for item in items {
169168
if let baml_hir::ItemId::Function(func_loc) = item {
170-
let sig = function_signature(&db, file, *func_loc);
169+
let sig = function_signature(&db, *func_loc);
171170
globals.insert(sig.name.to_string(), global_idx);
172171
global_idx += 1;
173172
}
@@ -180,12 +179,17 @@ fn compile_source(source: &str) -> CompileResult {
180179
let mut functions = Vec::new();
181180
for item in items {
182181
if let baml_hir::ItemId::Function(func_loc) = item {
183-
let signature = function_signature(&db, file, *func_loc);
184-
let body = function_body(&db, file, *func_loc);
182+
let signature = function_signature(&db, *func_loc);
183+
let body = function_body(&db, *func_loc);
185184

186185
// Run type inference
187-
let inference =
188-
baml_thir::infer_function(&db, &signature, &body, Some(typing_context.clone()));
186+
let inference = baml_thir::infer_function(
187+
&db,
188+
&signature,
189+
&body,
190+
Some(typing_context.clone()),
191+
None,
192+
);
189193

190194
// Get parameter names
191195
let params: Vec<baml_base::Name> =

baml_language/crates/baml_db/src/lib.rs

Lines changed: 6 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
//! Root database that assembles all compiler phases.
22
//!
33
//! This crate purely combines all the compiler traits into a single database.
4-
//! All testing happens in the separate `baml_tests` crate.
54
65
use std::{
76
path::PathBuf,
@@ -75,9 +74,12 @@ impl RootDatabase {
7574
SourceFile::new(self, text.into(), path.into(), file_id)
7675
}
7776

78-
/// Create a project root
79-
pub fn set_project_root(&mut self, path: impl Into<PathBuf>) -> baml_workspace::ProjectRoot {
80-
baml_workspace::ProjectRoot::new(self, path.into())
77+
/// Create a project root with an empty file list.
78+
///
79+
/// After creating the project root, use `add_file()` to add source files,
80+
/// then update the project root's file list with `root.set_files()`.
81+
pub fn set_project_root(&mut self, path: impl Into<PathBuf>) -> baml_workspace::Project {
82+
baml_workspace::Project::new(self, path.into(), vec![])
8183
}
8284
}
8385

@@ -86,60 +88,3 @@ impl Default for RootDatabase {
8688
Self::new()
8789
}
8890
}
89-
90-
//
91-
// ────────────────────────────────────────────────── FUNCTION QUERIES ─────
92-
//
93-
94-
// Re-export function queries from baml_hir
95-
pub use baml_hir::{function_body, function_signature};
96-
97-
//
98-
// ────────────────────────────────────────────────── TYPING CONTEXT ─────
99-
//
100-
101-
/// Build typing context from a list of source files.
102-
///
103-
/// This maps function names to their arrow types, e.g.:
104-
/// `Foo` -> `(int) -> int` for `function Foo(x: int) -> int`
105-
///
106-
/// This is used as the starting scope when type-checking function bodies,
107-
/// allowing function calls to be properly typed.
108-
///
109-
/// Note: This is not a Salsa query because it returns `Ty<'db>` which contains
110-
/// lifetime-parameterized data. Callers should cache the result if needed.
111-
pub fn build_typing_context_from_files<'db>(
112-
db: &'db dyn baml_thir::Db,
113-
files: &[SourceFile],
114-
) -> std::collections::HashMap<baml_base::Name, baml_thir::Ty<'db>> {
115-
let mut context = std::collections::HashMap::new();
116-
117-
for file in files {
118-
let items_struct = baml_hir::file_items(db, *file);
119-
let items = items_struct.items(db);
120-
121-
for item in items {
122-
if let baml_hir::ItemId::Function(func_loc) = item {
123-
let signature = function_signature(db, *file, *func_loc);
124-
125-
// Build the arrow type: (param_types) -> return_type
126-
let param_types: Vec<baml_thir::Ty<'db>> = signature
127-
.params
128-
.iter()
129-
.map(|p| baml_thir::lower_type_ref(db, &p.type_ref))
130-
.collect();
131-
132-
let return_type = baml_thir::lower_type_ref(db, &signature.return_type);
133-
134-
let func_type = baml_thir::Ty::Function {
135-
params: param_types,
136-
ret: Box::new(return_type),
137-
};
138-
139-
context.insert(signature.name.clone(), func_type);
140-
}
141-
}
142-
}
143-
144-
context
145-
}

baml_language/crates/baml_hir/src/body.rs

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -760,22 +760,21 @@ impl LoweringContext {
760760
}
761761

762762
fn lower_field_access_expr(&mut self, node: &baml_syntax::SyntaxNode) -> ExprId {
763-
use baml_syntax::SyntaxKind;
763+
use baml_syntax::ast::FieldAccessExpr;
764+
use rowan::ast::AstNode;
764765

765766
// FIELD_ACCESS_EXPR structure: base expression, DOT token, field name (WORD)
766-
// The parser wraps the left side as a child expression node
767-
let base = node
768-
.children()
769-
.next()
767+
let Some(field_access) = FieldAccessExpr::cast(node.clone()) else {
768+
return self.exprs.alloc(Expr::Missing);
769+
};
770+
771+
let base = field_access
772+
.base()
770773
.map(|n| self.lower_expr(&n))
771774
.unwrap_or_else(|| self.exprs.alloc(Expr::Missing));
772775

773-
// Find the field name (WORD token after DOT)
774-
let field = node
775-
.children_with_tokens()
776-
.filter_map(baml_syntax::NodeOrToken::into_token)
777-
.filter(|token| token.kind() == SyntaxKind::WORD)
778-
.last() // Get the last WORD (the field name, not part of the base expression)
776+
let field = field_access
777+
.field()
779778
.map(|token| Name::new(token.text()))
780779
.unwrap_or_else(|| Name::new(""));
781780

@@ -858,21 +857,44 @@ impl LoweringContext {
858857
}
859858

860859
fn lower_path_expr(&mut self, node: &baml_syntax::SyntaxNode) -> ExprId {
861-
use baml_syntax::SyntaxKind;
860+
use baml_syntax::ast::PathExpr;
861+
use rowan::ast::AstNode;
862862

863-
// PATH_EXPR can be a simple identifier or a qualified path
864-
// Collect all WORD tokens and join them
865-
let path_parts: Vec<String> = node
866-
.children_with_tokens()
867-
.filter_map(baml_syntax::NodeOrToken::into_token)
868-
.filter(|token| token.kind() == SyntaxKind::WORD)
863+
// PATH_EXPR can be:
864+
// 1. A simple identifier: `foo`
865+
// 2. A qualified path with `.`: `mod.foo`
866+
// 3. A field access chain with `.`: `obj.field.nested`
867+
868+
let Some(path_expr) = PathExpr::cast(node.clone()) else {
869+
return self.exprs.alloc(Expr::Missing);
870+
};
871+
872+
let segments: Vec<String> = path_expr
873+
.segments()
869874
.map(|token| token.text().to_string())
870875
.collect();
871876

872-
if path_parts.is_empty() {
873-
self.exprs.alloc(Expr::Missing)
877+
if segments.is_empty() {
878+
return self.exprs.alloc(Expr::Missing);
879+
}
880+
881+
if path_expr.has_dots() {
882+
// Field access chain: build nested FieldAccess expressions
883+
// Start with the first segment as a Path
884+
let mut current = self.exprs.alloc(Expr::Path(Name::new(&segments[0])));
885+
886+
// Chain the rest as FieldAccess
887+
for segment in &segments[1..] {
888+
current = self.exprs.alloc(Expr::FieldAccess {
889+
base: current,
890+
field: Name::new(segment),
891+
});
892+
}
893+
894+
current
874895
} else {
875-
let path_text = path_parts.join("::");
896+
// Module path with :: or simple identifier
897+
let path_text = segments.join(".");
876898
self.exprs.alloc(Expr::Path(Name::new(&path_text)))
877899
}
878900
}

baml_language/crates/baml_hir/src/item_tree.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ impl ItemTree {
111111
self.tests.insert(id, test);
112112
id
113113
}
114+
115+
/// Iterate over all classes in the item tree.
116+
pub fn iter_classes(&self) -> impl Iterator<Item = (&LocalItemId<ClassMarker>, &Class)> {
117+
self.classes.iter()
118+
}
114119
}
115120

116121
/// A function definition in the `ItemTree`.

0 commit comments

Comments
 (0)