Skip to content

feat(baml-toml): configure code generators in baml.toml - #3816

Merged
sxlijin merged 1 commit into
canaryfrom
sam/baml-toml
Jun 22, 2026
Merged

sxlijin merged 1 commit into
canaryfrom
sam/baml-toml

Conversation

@sxlijin

@sxlijin sxlijin commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

What

Moves code-generation config out of .baml generator {} blocks and into a typed [generator.<name>] section of the project manifest (baml.toml). Rebased directly onto canary — clean baml-toml-only diff.

Changes

Phase 1 — typed baml.toml (baml_cli/src/manifest.rs)

  • New serde structs: BamlToml / Package / Script / GeneratorManifest. [package] and [scripts] parsing now share one deserialization (replacing two ad-hoc toml::Table sites).
  • Unknown keys warn rather than silently no-op (via #[serde(flatten)] catch-alls), so typos surface but forward-compat manifests still load.

Phase 2 — generators in baml.toml

  • baml generate reads [generator.<name>] (output_type, naming_convention, output_dir) instead of the HIR item tree. Values use toml::Spanned, so missing/invalid values render precise ariadne diagnostics pointing into baml.toml.
  • baml init scaffolds a commented-out [generator.my_client] block.

Deprecation of .baml generator {}

  • The parser still recognizes generator NAME { … } but consumes its body opaquely (no interior parsing, so stale config never errors) and emits an unclosed-brace diagnostic on EOF. CST→AST lowering emits a migration warning (E0017) pointing to baml.toml.
  • The AST Item::Generator, HIR Generator/Definition::Generator/DefinitionKind::Generator, MIR, codegen discovery, and the generator completion are removed. The independent baml_fmt parser is untouched, so baml fmt still preserves blocks for migration.

Verification

  • mise run fmt, mise run clippy clean.
  • cargo nextest run — 6396 passed, 0 failed, 41 skipped.
  • Addressed CodeRabbit's review note on the parser (unclosed-brace diagnostic).

🤖 Generated with Claude Code

Summary by CodeRabbit

Changes

  • New Features

    • Generator configuration is now read from baml.toml via [generator.<name>], and baml generate validates and reports issues based on the manifest.
    • Improved diagnostics now point to baml.toml, and unknown manifest fields produce advisory warnings.
  • Deprecations

    • Inline generator blocks in .baml files are deprecated and emit warnings (E0017) to move configuration into baml.toml.
  • Documentation

    • Project initialization now includes an example [generator.<name>] block in the generated baml.toml.

@vercel

vercel Bot commented Jun 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Jun 21, 2026 10:35pm
promptfiddle Ready Ready Preview, Comment Jun 21, 2026 10:35pm
promptfiddle2 Ready Ready Preview, Comment Jun 21, 2026 10:35pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Generator declarations are removed from the compiler/HIR item model, AST, and all downstream symbol surfaces. Legacy generator { ... } blocks in .baml files now emit a deprecation warning (E0017) instead of being compiled. The CLI discovers and validates generators exclusively from baml.toml via a new typed manifest module that provides parse, package validation, and unknown-field warning helpers.

Changes

Generator Configuration Migration to baml.toml

Layer / File(s) Summary
Manifest module: typed baml.toml parsing
baml_language/crates/baml_cli/Cargo.toml, baml_language/crates/baml_cli/src/lib.rs, baml_language/crates/baml_cli/src/manifest.rs
Introduces manifest module with serde-derived BamlToml, Package, Script, and GeneratorManifest structures. Adds parse(), package_name(), and unknown_field_warnings() helpers with unit tests covering script tokenization, validation, and unknown-key detection. Adds indexmap workspace dependency.
CLI commands adopt manifest-based generator discovery and script parsing
baml_language/crates/baml_cli/src/generate.rs, baml_language/crates/baml_cli/src/project_load.rs, baml_language/crates/baml_cli/src/run_command.rs, baml_language/crates/baml_cli/src/grep_command.rs, baml_language/crates/baml_cli/src/init_command.rs
generate.rs replaces HIR-based discovery with manifest-driven discover_generators(), injects baml.toml spans into diagnostics via manifest_file_id(), updates missing-generator error guidance with [generator.<name>] example. project_load and run_command delegate to manifest parsing. init_command template expanded with generator configuration example; grep_command removes generator kind.
E2E tests and project templates
baml_language/crates/baml_cli/tests/exit_code_e2e.rs
create_project_with_generator writes [generator.py] to baml.toml instead of inline generator block. Manifest-less test expects failure with [generator hint in stderr.
Parser and lowering convert generator blocks to E0017 warnings
baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs, baml_language/crates/baml_compiler_lexer/src/tokens.rs, baml_language/crates/baml_compiler_parser/src/parser.rs, baml_language/crates/baml_compiler2_ast/src/lower_cst.rs, baml_language/crates/baml_compiler2_ast/src/lowering_diagnostic.rs
DiagnosticId::UnknownGeneratorProperty replaced with GeneratorBlockUnsupported (E0017). Lexer marks generator token deprecated. Parser swallows generator body opaquely without interior parsing. CST lowering emits GeneratorBlockInBaml diagnostic with focused span and "move this to baml.toml" label instead of lowering GeneratorDef.
Remove GeneratorDef from AST and syntax
baml_language/crates/baml_compiler2_ast/src/ast.rs, baml_language/crates/baml_compiler_syntax/src/ast.rs
Item enum drops Generator(GeneratorDef) variant; GeneratorDef struct deleted. Syntax AST node wrapper and accessor methods removed.
HIR builder removes generator lowering
baml_language/crates/baml_compiler2_hir/src/builder.rs
lower_item dispatch removes ast::Item::Generator branch; lower_generator() function deleted; GeneratorLoc import dropped.
HIR removes Generator from definitions and item system
baml_language/crates/baml_compiler2_hir/src/contributions.rs, baml_language/crates/baml_compiler2_hir/src/ids.rs, baml_language/crates/baml_compiler2_hir/src/item_tree.rs, baml_language/crates/baml_compiler2_hir/src/loc.rs, baml_language/crates/baml_compiler2_hir/src/namespace.rs, baml_language/crates/baml_compiler2_hir/src/scope.rs
DefinitionKind::Generator and Definition::Generator variants removed from contributions. GeneratorMarker marker type and ItemKind::Generator variant deleted. GeneratorLoc interned struct and all item-tree generator storage, allocation, and indexing removed. ItemId::Generator variant dropped. Documentation comments cleaned.
MIR and compiler tooling cleanup
baml_language/crates/baml_compiler2_mir/src/lower.rs, baml_language/crates/tools_onionskin/src/compiler.rs
MIR def_to_item_ref removes Definition::Generator match arm. Onionskin stops counting generators in total_items, removes generator rendering loop, removes "gen" component from file summaries.
LSP and symbol surfaces
baml_language/crates/baml_lsp2_actions/src/completions.rs, baml_language/crates/baml_lsp2_actions/src/describe.rs, baml_language/crates/baml_lsp2_actions/src/tokens.rs, baml_language/crates/baml_lsp2_actions/src/type_info.rs, baml_language/crates/bex_project/src/bex_lsp/multi_project/request.rs
Completions drop generator keyword. is_item_node and find_dependencies remove GENERATOR_DEF/Definition::Generator cases. Semantic token dispatch removes GENERATOR_DEF from Struct group. type_info_for_definition removes Definition::Generator arm. LSP symbol-kind mapping removes Generator→INTERFACE.
Test fixtures aligned to new behavior
baml_language/crates/baml_lsp2_actions_tests/test_files/...
Completion test removes generator from SHOULD_CONTAIN. Class/enum/generator test files remove inline generator blocks or add E0017 warning expectations with migration guidance. Semantic token expectations updated; v1.baml adds diagnostics blocks for all seven generator definitions.
Codegen types update output_type version
baml_language/crates/baml_codegen_types/src/generator_fields.rs, baml_language/crates/baml_builtins2/keyword_docs/baml_keywords.yaml
OutputType enum and keyword documentation examples updated to surface python/pydantic2 instead of python/pydantic.

Sequence Diagram(s)

sequenceDiagram
  participant GenerateArgs
  participant discover_generators
  participant manifest_parse
  participant diagnostics
  participant CodeGenerator

  GenerateArgs->>discover_generators: project root path
  discover_generators->>manifest_parse: read and parse baml.toml
  manifest_parse-->>discover_generators: BamlToml with generator sections
  discover_generators->>discover_generators: validate output_type, naming_convention
  discover_generators->>discover_generators: resolve output_dir relative to root
  discover_generators-->>GenerateArgs: resolved generators + diagnostics
  alt has diagnostics
    GenerateArgs->>diagnostics: inject baml.toml under manifest_file_id
    diagnostics-->>GenerateArgs: rendered ariadne spans with toml locations
  end
  GenerateArgs->>CodeGenerator: run generation for each valid target
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • BoundaryML/baml#3421: Both PRs modify baml_cli/src/generate.rs generator codegen flow; the retrieved PR changes generator inputs and output layout while this PR switches the discovery source entirely to baml.toml.
  • BoundaryML/baml#3490: Both PRs modify baml_cli/src/generate.rs generator discovery to parse naming_convention and output_type from baml.toml sections and validate them for code generation.
  • BoundaryML/baml#3604: Both PRs update baml_cli/tests/exit_code_e2e.rs generator test setup and expectations, changing from inline generator blocks to baml.toml [generator.<name>] configuration.

Poem

🐇 Hopping through the .baml files with glee,
I found old generator blocks—time to flee!
Now baml.toml holds the codegen key,
With [generator.name] set neat as can be.
No more inline blocks shall clutter your tree! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(baml-toml): configure code generators in baml.toml' clearly and concisely summarizes the main change: moving generator configuration from .baml files to baml.toml manifest sections.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sam/baml-toml

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel
vercel Bot temporarily deployed to Preview – beps June 20, 2026 06:01 Inactive

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 812c8d3532

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -1,15647 +0,0 @@
/**

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the generated Node proto runtime

Deleting this generated runtime leaves the checked-in dist package broken: dist/proto.js and dist/host_value_registry.js both import ./proto/baml_cffi.js, and package.json publishes only dist, so consumers of the Node bridge hit a module-not-found error as soon as those paths load. The updated dist/proto/baml_cffi.d.ts still exists, but the matching JS implementation needs to be kept or regenerated into dist/proto/.

Useful? React with 👍 / 👎.

Comment on lines +1233 to +1235
let BexExternalValue::Instance { type_args, .. } = value else {
return Ok(());
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-instance values after generic substitution

For a generic host call whose _types binding substitutes a parameter to a concrete primitive/container type, this early return accepts every non-Instance value without checking it against expected. For example, identity[int]("oops") reaches the VM with T = int but a string argument and can return a string through a generated int signature; the new structural validation should also reject positive mismatches for primitives/lists/maps/unions instead of only checking class instances.

Useful? React with 👍 / 👎.

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 20, 2026 06:07 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 20, 2026 06:19 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
baml_language/crates/bex_engine/src/conversion.rs (1)

423-427: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use substituted field types when decoding generic instances.

Line 426 passes class_field.field_type directly, so nested conversion ignores the just-decoded instance type_args. For generic classes, field decoding should use the field template substituted with those type_args.

Suggested fix
                 for class_field in class_fields {
                     let ext = fields.get(&class_field.name).ok_or_else(|| {
                         EngineError::TypeMismatch {
                             message: format!(
                                 "Missing field `{}` in external Instance for class `{class_name}`",
                                 class_field.name
                             ),
                         }
                     })?;
+                    let field_ty = class_field.field_template.substitute(&type_args);
                     values.push(self.convert_external_to_vm_value_with_ty(
                         holder,
                         ext.clone(),
-                        Some(&class_field.field_type),
+                        Some(&field_ty),
                     )?);
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/bex_engine/src/conversion.rs` around lines 423 - 427,
When decoding fields of a generic class instance in the
convert_external_to_vm_value_with_ty function call, the code currently passes
class_field.field_type directly without accounting for the type_args that were
just decoded. Instead of passing the raw class_field.field_type, substitute the
field type with the instance's type_args to properly handle generic field types,
so that nested conversion respects the concrete types of the generic instance
rather than the generic template.
baml_language/crates/bridge_cffi/src/ffi/host_value.rs (1)

126-152: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate is_error before constructing the raw byte slice.

from_raw_parts is reached before the is_error contract check. On malformed calls with an invalid is_error, this still executes unsafe pointer handling unnecessarily. Move the is_error validation above slice construction.

Proposed fix
-    // SAFETY: caller promises ptr is valid for `length` bytes; the guard above
-    // ruled out a null pointer whenever `length > 0`.
-    let bytes: &[u8] = if length == 0 {
-        &[]
-    } else {
-        unsafe { std::slice::from_raw_parts(content as *const u8, length) }
-    };
-
     // Strict 0/1 contract: any other value is a bridge wire-protocol bug
@@
     if is_error != 0 && is_error != 1 {
@@
         return;
     }
+
+    // SAFETY: caller promises ptr is valid for `length` bytes; guards above
+    // ruled out a null pointer whenever `length > 0`.
+    let bytes: &[u8] = if length == 0 {
+        &[]
+    } else {
+        unsafe { std::slice::from_raw_parts(content as *const u8, length) }
+    };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/bridge_cffi/src/ffi/host_value.rs` around lines 126 -
152, Move the is_error validation check (the if statement that validates
is_error is either 0 or 1 and calls host_dispatch::complete_with_error) to occur
before the byte slice construction using from_raw_parts. Currently the unsafe
pointer dereference happens first and then the validation check occurs, but the
validation should happen earlier to avoid unnecessary unsafe operations when the
call parameters are invalid.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@baml_language/crates/baml_compiler_parser/src/parser.rs`:
- Around line 7605-7615: The brace-matching loop in the generator block parsing
logic exits without emitting an error when reaching EOF (p.at_end()) while depth
is still greater than 0, meaning a closing brace is missing. This silently
accepts malformed generator blocks with unmatched opening braces. After the
while loop completes, add a check that if depth is still greater than 0, emit a
diagnostic error for the missing closing brace to properly report the malformed
input instead of silently consuming the rest of the file into this deprecated
node.

In `@baml_language/crates/bex_engine/src/conversion.rs`:
- Around line 1153-1156: The condition in the class_type_args_compatible
function currently returns true when wire_args or expected_args is empty, or
when their lengths differ, which incorrectly allows arity mismatches to pass
validation. Fix this by changing the logic to return false when there is an
arity mismatch (i.e., when the lengths are not equal), while only returning true
when both are empty (truly compatible) or when both are non-empty with matching
lengths. This ensures that instances with missing or extra generic type
arguments are properly rejected rather than accepted.

In `@baml_language/crates/bex_external_types/src/bex_external_value.rs`:
- Around line 164-171: The type_args field in the external value is not being
validated against the declared class type before being stored in
Object::Instance::class_type_args in the VM, allowing mismatched type arguments
to pass field validation. Add a compatibility check in the class-return
validation path in conversion.rs that validates the supplied type_args against
the declared generic class type parameters before conversion occurs, ensuring
that host values like Box<string> returned where Box<int> is declared will be
rejected. Cover this validation logic with a Rust unit test that demonstrates
the check catches type argument mismatches.

In `@baml_language/crates/bex_sap/src/to_external.rs`:
- Around line 225-227: The BexExternalValue::Instance creation at lines 225-227
and 247-250 both hardcode type_args to an empty vector, which erases generic
type information and makes instances like Foo<int> and Foo<string>
indistinguishable. Since the function receives typed context through the ty
parameter, extract and propagate the concrete class type arguments from ty for
both BexExternalValue::Instance creations instead of using an empty vec![],
ensuring generic instances preserve their identity through the conversion.

In `@baml_language/crates/bridge_ctypes/types/baml_core/cffi/v1/baml_type.proto`:
- Around line 10-14: The multi-line comment block describing type_args is
outdated and references an old wire format where type_args was an ordered Ty
list. Update this comment block to accurately describe the current wire contract
format which uses repeated TyArg entries with named fields (type_var and
type_value) instead of an ordered list. Ensure the updated documentation clearly
explains the named-binding approach to prevent client implementations from using
the incorrect format.

In `@baml_language/sdks/python/rust/sdkgen_python_pydantic2/src/leaf.rs`:
- Around line 1475-1478: Broaden the type annotation for the _types parameter in
the generated stub files from dict[str, type] to dict[str, object]. This applies
to two locations: first, in the push_str calls around lines 1475-1478 where
typed_params is being updated with the _types parameter (both in the branch with
the keyword-only marker and without), and second, in the similar push_str calls
around lines 1678-1682. This change allows the stub to accurately reflect the
runtime's acceptance of non-type forms like list[int], unions, and None sentinel
values.

In `@baml_language/sdks/python/src/baml_core/proto.py`:
- Around line 423-440: The union type check in the _fill_wire_ty function only
handles typing.Union but misses PEP 604 unions (int | str syntax) which use
types.UnionType instead. Update the condition at the union check (currently
checking only if origin is typing.Union) to also include types.UnionType so both
legacy Union style and modern | style unions are processed by the same union
handling logic. Make sure to import types module if not already imported.

---

Outside diff comments:
In `@baml_language/crates/bex_engine/src/conversion.rs`:
- Around line 423-427: When decoding fields of a generic class instance in the
convert_external_to_vm_value_with_ty function call, the code currently passes
class_field.field_type directly without accounting for the type_args that were
just decoded. Instead of passing the raw class_field.field_type, substitute the
field type with the instance's type_args to properly handle generic field types,
so that nested conversion respects the concrete types of the generic instance
rather than the generic template.

In `@baml_language/crates/bridge_cffi/src/ffi/host_value.rs`:
- Around line 126-152: Move the is_error validation check (the if statement that
validates is_error is either 0 or 1 and calls
host_dispatch::complete_with_error) to occur before the byte slice construction
using from_raw_parts. Currently the unsafe pointer dereference happens first and
then the validation check occurs, but the validation should happen earlier to
avoid unnecessary unsafe operations when the call parameters are invalid.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ee7ca421-1e27-48d6-a68f-1585b2d62902

📥 Commits

Reviewing files that changed from the base of the PR and between 56bbc20 and 812c8d3.

⛔ Files ignored due to path filters (8)
  • baml_language/Cargo.lock is excluded by !**/*.lock
  • baml_language/crates/baml_tests/snapshots/broken_syntax/generator/baml_tests__broken_syntax__generator__02_parser__valid_generator.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/broken_syntax/generator/baml_tests__broken_syntax__generator__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/sdks/go/bridge_go/cffi/proto/baml_core/cffi/v1/baml_inbound.pb.go is excluded by !**/*.pb.go
  • baml_language/sdks/go/bridge_go/cffi/proto/baml_core/cffi/v1/baml_outbound.pb.go is excluded by !**/*.pb.go
  • baml_language/sdks/go/bridge_go/cffi/proto/baml_core/cffi/v1/baml_type.pb.go is excluded by !**/*.pb.go
  • baml_language/sdks/nodejs/bridge_nodejs/dist/proto/baml_cffi.d.ts is excluded by !**/dist/**
  • baml_language/sdks/nodejs/bridge_nodejs/dist/proto/baml_cffi.js is excluded by !**/dist/**
📒 Files selected for processing (104)
  • baml_language/crates/baml_builtins2_codegen/src/codegen_io.rs
  • baml_language/crates/baml_cli/Cargo.toml
  • baml_language/crates/baml_cli/src/generate.rs
  • baml_language/crates/baml_cli/src/grep_command.rs
  • baml_language/crates/baml_cli/src/init_command.rs
  • baml_language/crates/baml_cli/src/lib.rs
  • baml_language/crates/baml_cli/src/manifest.rs
  • baml_language/crates/baml_cli/src/project_load.rs
  • baml_language/crates/baml_cli/src/run_command.rs
  • baml_language/crates/baml_cli/src/test_command.rs
  • baml_language/crates/baml_cli/tests/exit_code_e2e.rs
  • baml_language/crates/baml_compiler2_ast/src/ast.rs
  • baml_language/crates/baml_compiler2_ast/src/lower_cst.rs
  • baml_language/crates/baml_compiler2_ast/src/lowering_diagnostic.rs
  • baml_language/crates/baml_compiler2_hir/src/builder.rs
  • baml_language/crates/baml_compiler2_hir/src/contributions.rs
  • baml_language/crates/baml_compiler2_hir/src/ids.rs
  • baml_language/crates/baml_compiler2_hir/src/item_tree.rs
  • baml_language/crates/baml_compiler2_hir/src/loc.rs
  • baml_language/crates/baml_compiler2_hir/src/namespace.rs
  • baml_language/crates/baml_compiler2_hir/src/scope.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs
  • baml_language/crates/baml_compiler_lexer/src/tokens.rs
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/baml_compiler_syntax/src/ast.rs
  • baml_language/crates/baml_exec/Cargo.toml
  • baml_language/crates/baml_exec/src/dispatch.rs
  • baml_language/crates/baml_exec/src/output.rs
  • baml_language/crates/baml_lsp2_actions/src/completions.rs
  • baml_language/crates/baml_lsp2_actions/src/describe.rs
  • baml_language/crates/baml_lsp2_actions/src/tokens.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/completion/top_level_keywords.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/generator.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/class/generator_keywords1.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/class/generator_keywords2.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/enum/enum_is_valid.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/generators/error.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/generators/quoted_invalid_value.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/generators/v1.baml
  • baml_language/crates/baml_tests/tests/exceptions.rs
  • baml_language/crates/baml_tests/tests/fs.rs
  • baml_language/crates/baml_tests/tests/http.rs
  • baml_language/crates/baml_tests/tests/interfaces.rs
  • baml_language/crates/baml_tests/tests/parse_companions.rs
  • baml_language/crates/baml_tests/tests/prompt_tag_runtime.rs
  • baml_language/crates/bex_engine/src/conversion.rs
  • baml_language/crates/bex_engine/src/function_call_context.rs
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_engine/tests/cancellation.rs
  • baml_language/crates/bex_engine/tests/collect_tests.rs
  • baml_language/crates/bex_engine/tests/common/mod.rs
  • baml_language/crates/bex_engine/tests/function_resolution.rs
  • baml_language/crates/bex_engine/tests/generics_runtime.rs
  • baml_language/crates/bex_engine/tests/host_value_callable.rs
  • baml_language/crates/bex_engine/tests/llm_render.rs
  • baml_language/crates/bex_engine/tests/media_roundtrip.rs
  • baml_language/crates/bex_engine/tests/orchestration.rs
  • baml_language/crates/bex_engine/tests/registry.rs
  • baml_language/crates/bex_external_types/src/bex_external_value.rs
  • baml_language/crates/bex_external_types/src/host_return.rs
  • baml_language/crates/bex_heap/src/accessor.rs
  • baml_language/crates/bex_heap/src/tlab.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/mod.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/request.rs
  • baml_language/crates/bex_sap/src/to_external.rs
  • baml_language/crates/bex_vm/src/vm.rs
  • baml_language/crates/bridge_cffi/src/baml_to_host.rs
  • baml_language/crates/bridge_cffi/src/ffi/host_value.rs
  • baml_language/crates/bridge_cffi/src/lib.rs
  • baml_language/crates/bridge_ctypes/src/lib.rs
  • baml_language/crates/bridge_ctypes/src/ty_decode.rs
  • baml_language/crates/bridge_ctypes/src/value_decode.rs
  • baml_language/crates/bridge_ctypes/src/value_encode.rs
  • baml_language/crates/bridge_ctypes/types/baml_core/cffi/v1/baml_inbound.proto
  • baml_language/crates/bridge_ctypes/types/baml_core/cffi/v1/baml_outbound.proto
  • baml_language/crates/bridge_ctypes/types/baml_core/cffi/v1/baml_type.proto
  • baml_language/crates/bridge_wasm/src/wasm_sys.rs
  • baml_language/crates/bridge_wasm/tests/host_callable.rs
  • baml_language/crates/sys_llm/src/jinja/render.rs
  • baml_language/crates/sys_native/src/io_impls.rs
  • baml_language/crates/sys_ops/src/lib.rs
  • baml_language/crates/sys_types/src/lib.rs
  • baml_language/crates/tools_onionskin/src/compiler.rs
  • baml_language/sdk_tests/crates/python_pydantic2/function_calls/customizable/test_generic_calls.py
  • baml_language/sdk_tests/fixtures/function_calls/baml_src/ns_generic_tests/types.baml
  • baml_language/sdks/go/bridge_go/build.sh
  • baml_language/sdks/nodejs/bridge_nodejs/typescript_src/proto/baml_cffi.d.ts
  • baml_language/sdks/nodejs/bridge_nodejs/typescript_src/proto/baml_cffi.js
  • baml_language/sdks/python/rust/bridge_python/Cargo.toml
  • baml_language/sdks/python/rust/bridge_python/src/host_value.rs
  • baml_language/sdks/python/rust/bridge_python/src/runtime.rs
  • baml_language/sdks/python/rust/sdkgen_python_pydantic2/src/leaf.rs
  • baml_language/sdks/python/src/baml_core/__init__.py
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_inbound_pb2.py
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_inbound_pb2.pyi
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_outbound_pb2.py
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_outbound_pb2.pyi
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_type_pb2.py
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_type_pb2.pyi
  • baml_language/sdks/python/src/baml_core/proto.py
  • baml_language/sdks/python/tests/test_proto_generics.py
  • typescript2/pkg-proto/src/encode.ts
💤 Files with no reviewable changes (9)
  • baml_language/crates/baml_compiler2_ast/src/ast.rs
  • baml_language/crates/baml_lsp2_actions/src/completions.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/request.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info.rs
  • baml_language/crates/baml_compiler2_hir/src/ids.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/tools_onionskin/src/compiler.rs
  • baml_language/crates/baml_compiler_syntax/src/ast.rs
  • baml_language/crates/baml_lsp2_actions/src/describe.rs

Comment on lines +1153 to +1156
fn class_type_args_compatible(wire_args: &[RuntimeTy], expected_args: &[RuntimeTy]) -> bool {
if wire_args.is_empty() || expected_args.is_empty() || wire_args.len() != expected_args.len() {
return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject non-empty class type-arg arity mismatches.

Line 1154 currently returns true when arity differs, which allows malformed instances (e.g. extra/missing generic args) to pass compatibility checks instead of being rejected.

Suggested fix
 fn class_type_args_compatible(wire_args: &[RuntimeTy], expected_args: &[RuntimeTy]) -> bool {
-    if wire_args.is_empty() || expected_args.is_empty() || wire_args.len() != expected_args.len() {
+    if wire_args.is_empty() || expected_args.is_empty() {
         return true;
     }
+    if wire_args.len() != expected_args.len() {
+        return false;
+    }
     wire_args
         .iter()
         .zip(expected_args)
         .all(|(wire, expected)| runtime_ty_compatible(wire, expected))
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/bex_engine/src/conversion.rs` around lines 1153 - 1156,
The condition in the class_type_args_compatible function currently returns true
when wire_args or expected_args is empty, or when their lengths differ, which
incorrectly allows arity mismatches to pass validation. Fix this by changing the
logic to return false when there is an arity mismatch (i.e., when the lengths
are not equal), while only returning true when both are empty (truly compatible)
or when both are non-empty with matching lengths. This ensures that instances
with missing or extra generic type arguments are properly rejected rather than
accepted.

Comment on lines +164 to +171
/// Concrete class type arguments for a generic class instance, in De
/// Bruijn (declaration) order; empty for non-generic classes. Carries a
/// `GenericBox<int>` instance's `[int]` across the FFI boundary (the
/// value-level type channel — distinct from a call's
/// `CallFunctionArgs.type_args`). Populated inbound from
/// `InboundClassValue.class_ty`; landed into the VM
/// `Object::Instance::class_type_args` in Phase 3.
type_args: Vec<RuntimeTy>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate type_args against the declared class type before landing them in the VM.

The new field is later stored into Object::Instance::class_type_args, but the supplied host-return schema path in baml_language/crates/bex_engine/src/conversion.rs matches Instance { class_name, fields, .. } and validates fields using the expected class args while ignoring the wire type_args. A host value like Box<string> returned where Box<int> is declared can pass field validation and still materialize with class_type_args = [string].

Please add a compatibility check in the class-return validation path before conversion, and cover it with a Rust unit test where possible.

As per coding guidelines, prefer writing Rust unit tests over integration tests where possible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/bex_external_types/src/bex_external_value.rs` around
lines 164 - 171, The type_args field in the external value is not being
validated against the declared class type before being stored in
Object::Instance::class_type_args in the VM, allowing mismatched type arguments
to pass field validation. Add a compatibility check in the class-return
validation path in conversion.rs that validates the supplied type_args against
the declared generic class type parameters before conversion occurs, ensuring
that host values like Box<string> returned where Box<int> is declared will be
rejected. Cover this validation logic with a Rust unit test that demonstrates
the check catches type argument mismatches.

Source: Coding guidelines

Comment on lines +225 to +227
BexExternalValue::Instance {
class_name: c.name.to_string(),
type_args: vec![],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve concrete class type_args instead of zeroing them.

Line 227 and Line 249 hardcode type_args: vec![], which erases generic instance identity on this conversion path (Foo<int> and Foo<string> become indistinguishable). Since this function already receives typed context (ty), please derive and propagate class args here as well.

Also applies to: 247-250

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/bex_sap/src/to_external.rs` around lines 225 - 227, The
BexExternalValue::Instance creation at lines 225-227 and 247-250 both hardcode
type_args to an empty vector, which erases generic type information and makes
instances like Foo<int> and Foo<string> indistinguishable. Since the function
receives typed context through the ty parameter, extract and propagate the
concrete class type arguments from ty for both BexExternalValue::Instance
creations instead of using an empty vec![], ensuring generic instances preserve
their identity through the conversion.

Comment on lines +10 to +14
// Primary use: binding a generic function's / generic method's TypeVars at a
// host call. The host sends an ordered `Ty` list (De Bruijn order — enclosing
// class params first, then the callee's own params) in
// `CallFunctionArgs.type_args`; the engine decodes each into a
// `baml_type::RuntimeTy` and seeds the entry frame's `type_args` slot.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale wire-contract comment for type_args shape.

This comment still says CallFunctionArgs.type_args is an ordered Ty list, but the transport now uses repeated TyArg { type_var, type_value }. Please update this header text to match the named-binding wire format to avoid incorrect client implementations.

Suggested doc fix
-// Primary use: binding a generic function's / generic method's TypeVars at a
-// host call. The host sends an ordered `Ty` list (De Bruijn order — enclosing
-// class params first, then the callee's own params) in
-// `CallFunctionArgs.type_args`; the engine decodes each into a
-// `baml_type::RuntimeTy` and seeds the entry frame's `type_args` slot.
+// Primary use: binding a generic function's / generic method's TypeVars at a
+// host call. The host sends named bindings in `CallFunctionArgs.type_args`
+// (`TyArg { type_var, type_value }`), with insertion order matching De Bruijn
+// order (enclosing class params first, then the callee's own params). The
+// engine decodes each into `baml_type::RuntimeTy` and seeds the entry frame's
+// `type_args` slot.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Primary use: binding a generic function's / generic method's TypeVars at a
// host call. The host sends an ordered `Ty` list (De Bruijn order — enclosing
// class params first, then the callee's own params) in
// `CallFunctionArgs.type_args`; the engine decodes each into a
// `baml_type::RuntimeTy` and seeds the entry frame's `type_args` slot.
// Primary use: binding a generic function's / generic method's TypeVars at a
// host call. The host sends named bindings in `CallFunctionArgs.type_args`
// (`TyArg { type_var, type_value }`), with insertion order matching De Bruijn
// order (enclosing class params first, then the callee's own params). The
// engine decodes each into `baml_type::RuntimeTy` and seeds the entry frame's
// `type_args` slot.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/bridge_ctypes/types/baml_core/cffi/v1/baml_type.proto`
around lines 10 - 14, The multi-line comment block describing type_args is
outdated and references an old wire format where type_args was an ordered Ty
list. Update this comment block to accurately describe the current wire contract
format which uses repeated TyArg entries with named fields (type_var and
type_value) instead of an ordered list. Ensure the updated documentation clearly
explains the named-binding approach to prevent client implementations from using
the incorrect format.

Comment on lines +1475 to +1478
typed_params.push_str(", *, _types: dict[str, type]");
} else {
// optionals already introduced the `*` keyword-only marker.
typed_params.push_str(", _types: dict[str, type]");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "_types: dict\\[str, type\\]" baml_language/sdks/python/rust/sdkgen_python_pydantic2/src/leaf.rs
rg -n "def _fill_wire_ty|typing.get_origin|py_type is None|origin in \\(list, typing.List\\)|origin in \\(dict, typing.Dict\\)|origin is typing.Union" baml_language/sdks/python/src/baml_core/proto.py

Repository: BoundaryML/baml

Length of output: 683


🏁 Script executed:

cat -n baml_language/sdks/python/src/baml_core/proto.py | sed -n '403,450p'

Repository: BoundaryML/baml

Length of output: 2483


🏁 Script executed:

rg -A 20 "def _fill_wire_ty" baml_language/sdks/python/src/baml_core/proto.py

Repository: BoundaryML/baml

Length of output: 863


🏁 Script executed:

rg -B 5 -A 15 "_types" baml_language/sdks/python/src/baml_core/proto.py | head -80

Repository: BoundaryML/baml

Length of output: 3020


🏁 Script executed:

fd -name "*.py" baml_language/sdks/python | xargs rg "_types:" | grep -E "\.(py|pyi):" | head -20

Repository: BoundaryML/baml

Length of output: 290


🏁 Script executed:

rg -A 5 "def python_type_to_wire_ty" baml_language/sdks/python/src/baml_core/proto.py

Repository: BoundaryML/baml

Length of output: 265


🏁 Script executed:

fd -name "*.pyi" baml_language/sdks/python | xargs rg "_types" 2>/dev/null | head -30

Repository: BoundaryML/baml

Length of output: 3101


🏁 Script executed:

rg -B 3 -A 3 "_types.*=" baml_language/sdks/python | grep -E "(pyi|\.py):" | head -40

Repository: BoundaryML/baml

Length of output: 3842


🏁 Script executed:

rg "\.pyi" baml_language/sdks/python | head -5

Repository: BoundaryML/baml

Length of output: 760


🏁 Script executed:

find baml_language/sdks/python -name "*.pyi" -type f

Repository: BoundaryML/baml

Length of output: 464


🏁 Script executed:

rg "_types.*dict" baml_language/sdks/python/src/baml_core -A 1 -B 1

Repository: BoundaryML/baml

Length of output: 715


🏁 Script executed:

rg "_types: dict" baml_language/sdks/python -B 2 -A 2

Repository: BoundaryML/baml

Length of output: 1891


🏁 Script executed:

cat baml_language/sdks/python/src/baml_core/baml_py.pyi | grep -A 5 -B 5 "_types"

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

rg "python_type_to_wire_ty|_fill_wire_ty" baml_language/sdks/python/src/baml_core/__init__.py -B 3 -A 8

Repository: BoundaryML/baml

Length of output: 1325


🏁 Script executed:

cat baml_language/sdks/python/src/baml_core/__init__.py | sed -n '1,50p'

Repository: BoundaryML/baml

Length of output: 1236


🏁 Script executed:

rg "from typing import" baml_language/sdks/python/src/baml_core/__init__.py | head -5

Repository: BoundaryML/baml

Length of output: 133


🏁 Script executed:

rg -B 5 -A 10 "def resolve_generic_call_wire" baml_language/sdks/python/src/baml_core/__init__.py

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

rg "def resolve_generic_call_wire" baml_language/sdks/python/src/baml_core

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

rg "resolve_generic_call_wire" baml_language/sdks/python/src/baml_core/__init__.py -B 5 -A 15

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

cat -n baml_language/sdks/python/src/baml_core/__init__.py | grep -A 20 "def resolve_generic_call_wire"

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

rg "def.*generic" baml_language/sdks/python/src/baml_core/__init__.py

Repository: BoundaryML/baml

Length of output: 88


🏁 Script executed:

rg "types_kwarg" baml_language/sdks/python/src/baml_core/__init__.py -B 3 -A 10 | head -60

Repository: BoundaryML/baml

Length of output: 2655


🏁 Script executed:

cat -n baml_language/sdks/python/src/baml_core/__init__.py | sed -n '200,300p'

Repository: BoundaryML/baml

Length of output: 4664


🏁 Script executed:

rg "def python_type_to_wire_ty" baml_language/sdks/python/src/baml_core/proto.py -A 2

Repository: BoundaryML/baml

Length of output: 219


🏁 Script executed:

cat baml_language/sdks/python/tests/test_proto_generics.py | head -80

Repository: BoundaryML/baml

Length of output: 2335


🏁 Script executed:

rg "_types=\{" baml_language/sdks/python/tests/test_proto_generics.py -B 2 -A 5

Repository: BoundaryML/baml

Length of output: 462


🏁 Script executed:

rg "typing.Any|from typing import Any" baml_language/sdks/python/rust/sdkgen_python_pydantic2/src/leaf.rs

Repository: BoundaryML/baml

Length of output: 205


🏁 Script executed:

rg "dict\[str," baml_language/sdks/python/rust/sdkgen_python_pydantic2/src/leaf.rs | grep -v "_types"

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

cat -n baml_language/sdks/python/rust/sdkgen_python_pydantic2/src/leaf.rs | sed -n '1470,1490p'

Repository: BoundaryML/baml

Length of output: 1313


🏁 Script executed:

rg "typing.Any" baml_language/sdks/python/rust/sdkgen_python_pydantic2/src/leaf.rs | head -10

Repository: BoundaryML/baml

Length of output: 205


🏁 Script executed:

cat -n baml_language/sdks/python/rust/sdkgen_python_pydantic2/src/leaf.rs | sed -n '1, 100p' | grep -E "import|use"

Repository: BoundaryML/baml

Length of output: 617


🏁 Script executed:

rg "def.*_types" baml_language/sdks/python/tests -B 2 -A 10 | head -50

Repository: BoundaryML/baml

Length of output: 4439


🏁 Script executed:

rg "def python_type_to_wire_ty" baml_language/sdks/python -B 5 -A 5

Repository: BoundaryML/baml

Length of output: 1029


🏁 Script executed:

python3 << 'PY'
# Check if object vs Any matters in Python typing stubs
import typing

# The function accepts these:
examples = [
    int,           # type
    None,          # NoneType
    list[int],     # generic alias
    typing.Union[int, str],  # union
    typing.Optional[int],    # optional
]

for ex in examples:
    print(f"{ex!r}: isinstance(type)={isinstance(ex, type)}, isinstance(object)={isinstance(ex, object)}")
PY

Repository: BoundaryML/baml

Length of output: 376


Broaden _types stub value typing to match runtime-accepted inputs.

The generated .pyi currently enforces _types: dict[str, type], but runtime lowering via python_type_to_wire_ty() accepts non-type forms too (e.g. list[int], unions, and None sentinel). Using object instead avoids static-checker false positives for valid calls.

Proposed fix
-typed_params.push_str(", *, _types: dict[str, type]");
+typed_params.push_str(", *, _types: dict[str, object]");
 ...
-typed_params.push_str(", _types: dict[str, type]");
+typed_params.push_str(", _types: dict[str, object]");
 ...
-typed_params.push_str("*, _types: dict[str, type]");
+typed_params.push_str("*, _types: dict[str, object]");

Also applies to: lines 1678-1682

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/sdks/python/rust/sdkgen_python_pydantic2/src/leaf.rs` around
lines 1475 - 1478, Broaden the type annotation for the _types parameter in the
generated stub files from dict[str, type] to dict[str, object]. This applies to
two locations: first, in the push_str calls around lines 1475-1478 where
typed_params is being updated with the _types parameter (both in the branch with
the keyword-only marker and without), and second, in the similar push_str calls
around lines 1678-1682. This change allows the stub to accurately reflect the
runtime's acceptance of non-type forms like list[int], unions, and None sentinel
values.

Comment on lines +423 to +440
origin = typing.get_origin(py_type)
if origin is not None:
targs = typing.get_args(py_type)
if origin in (list, typing.List):
_fill_inner(ty.list.item, targs[0] if targs else None)
return
if origin in (dict, typing.Dict):
_fill_inner(ty.map.key, targs[0] if targs else None)
_fill_inner(ty.map.value, targs[1] if len(targs) > 1 else None)
return
if origin is typing.Union:
non_none = [a for a in targs if a is not type(None)]
if len(non_none) == 1 and len(non_none) != len(targs):
_fill_inner(ty.optional.inner, non_none[0])
return
for arg in targs:
_fill_inner(ty.union.options.add(), arg)
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import typing, types
u = int | str
print("get_origin(int|str):", typing.get_origin(u))
print("typing.Union:", typing.Union)
print("is typing.Union?:", typing.get_origin(u) is typing.Union)
print("has types.UnionType?:", hasattr(types, "UnionType"))
PY

rg -n "origin is typing.Union" baml_language/sdks/python/src/baml_core/proto.py

Repository: BoundaryML/baml

Length of output: 223


🏁 Script executed:

head -50 baml_language/sdks/python/src/baml_core/proto.py | grep -n "^import\|^from"

Repository: BoundaryML/baml

Length of output: 450


🏁 Script executed:

sed -n '400,450p' baml_language/sdks/python/src/baml_core/proto.py

Repository: BoundaryML/baml

Length of output: 2187


🏁 Script executed:

rg "int \| str|Union\[" baml_language/sdks/python --type py -n | head -20

Repository: BoundaryML/baml

Length of output: 279


Handle PEP 604 unions (|) in _fill_wire_ty.

At line 433, checking only origin is typing.Union misses int | str / T | None forms, which return types.UnionType and will fall through to unknown, losing explicit type bindings.

Proposed fix
+import types
 import typing
 ...
-        if origin is typing.Union:
+        if origin in (typing.Union, getattr(types, "UnionType", object)):
             non_none = [a for a in targs if a is not type(None)]
             if len(non_none) == 1 and len(non_none) != len(targs):
                 _fill_inner(ty.optional.inner, non_none[0])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
origin = typing.get_origin(py_type)
if origin is not None:
targs = typing.get_args(py_type)
if origin in (list, typing.List):
_fill_inner(ty.list.item, targs[0] if targs else None)
return
if origin in (dict, typing.Dict):
_fill_inner(ty.map.key, targs[0] if targs else None)
_fill_inner(ty.map.value, targs[1] if len(targs) > 1 else None)
return
if origin is typing.Union:
non_none = [a for a in targs if a is not type(None)]
if len(non_none) == 1 and len(non_none) != len(targs):
_fill_inner(ty.optional.inner, non_none[0])
return
for arg in targs:
_fill_inner(ty.union.options.add(), arg)
return
origin = typing.get_origin(py_type)
if origin is not None:
targs = typing.get_args(py_type)
if origin in (list, typing.List):
_fill_inner(ty.list.item, targs[0] if targs else None)
return
if origin in (dict, typing.Dict):
_fill_inner(ty.map.key, targs[0] if targs else None)
_fill_inner(ty.map.value, targs[1] if len(targs) > 1 else None)
return
if origin in (typing.Union, getattr(types, "UnionType", object)):
non_none = [a for a in targs if a is not type(None)]
if len(non_none) == 1 and len(non_none) != len(targs):
_fill_inner(ty.optional.inner, non_none[0])
return
for arg in targs:
_fill_inner(ty.union.options.add(), arg)
return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/sdks/python/src/baml_core/proto.py` around lines 423 - 440, The
union type check in the _fill_wire_ty function only handles typing.Union but
misses PEP 604 unions (int | str syntax) which use types.UnionType instead.
Update the condition at the union check (currently checking only if origin is
typing.Union) to also include types.UnionType so both legacy Union style and
modern | style unions are processed by the same union handling logic. Make sure
to import types module if not already imported.

@vercel
vercel Bot temporarily deployed to Preview – beps June 20, 2026 06:28 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 20, 2026 06:34 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 20, 2026 06:46 Inactive
@github-actions

github-actions Bot commented Jun 20, 2026 •

Copy link
Copy Markdown

⏭️ Performance benchmarks were skipped

Perf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to canary/main.

To run them on this PR, do any of the following, then push a commit (or re-run CI):

  • Add RUN_CODSPEED=1 to the PR description, or
  • Include run-perf or /perf in the PR title or any commit message.

@vercel
vercel Bot temporarily deployed to Preview – beps June 20, 2026 21:54 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 20, 2026 22:00 Inactive
@github-actions

Copy link
Copy Markdown

No description provided.

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 20, 2026 22:10 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps June 20, 2026 23:58 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 21, 2026 00:03 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 21, 2026 00:16 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps June 21, 2026 18:47 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
baml_language/crates/baml_builtins2/keyword_docs/baml_keywords.yaml (1)

139-145: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Generator example still uses deprecated .baml block syntax.

This now documents output_type correctly, but the surrounding example still points to generator ... {} instead of the new baml.toml [generator.<name>] flow from this migration, which can mislead users.

Suggested doc update
 generator:
-  summary: "Configures code generation for a target language."
+  summary: "Deprecated in .baml; configure code generation in baml.toml."
   syntax: |
-    generator python {
-      output_type "python/pydantic2"
-      output_dir "../generated"
-    }
+    [generator.python]
+    output_type = "python/pydantic2"
+    output_dir = "../generated"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/baml_builtins2/keyword_docs/baml_keywords.yaml` around
lines 139 - 145, The generator example in the keyword documentation is showing
the deprecated `.baml` block syntax with the `generator python { ... }` format.
Update the example in the syntax section to reflect the new `baml.toml`
configuration format using `[generator.<name>]` table syntax with the correct
key-value pairs (like `output_type` and `output_dir`) to match the current
migration and prevent misleading users about the correct way to configure
generators.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@baml_language/crates/baml_builtins2/keyword_docs/baml_keywords.yaml`:
- Around line 139-145: The generator example in the keyword documentation is
showing the deprecated `.baml` block syntax with the `generator python { ... }`
format. Update the example in the syntax section to reflect the new `baml.toml`
configuration format using `[generator.<name>]` table syntax with the correct
key-value pairs (like `output_type` and `output_dir`) to match the current
migration and prevent misleading users about the correct way to configure
generators.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c9317faa-719f-4c53-a885-53fbded22941

📥 Commits

Reviewing files that changed from the base of the PR and between 2dfc563 and d7c5f38.

⛔ Files ignored due to path filters (4)
  • baml_language/Cargo.lock is excluded by !**/*.lock
  • baml_language/crates/baml_tests/snapshots/broken_syntax/generator/baml_tests__broken_syntax__generator__01_lexer__valid_generator.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/broken_syntax/generator/baml_tests__broken_syntax__generator__02_parser__valid_generator.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/broken_syntax/generator/baml_tests__broken_syntax__generator__05_diagnostics.snap is excluded by !**/*.snap
📒 Files selected for processing (41)
  • baml_language/crates/baml_builtins2/keyword_docs/baml_keywords.yaml
  • baml_language/crates/baml_cli/Cargo.toml
  • baml_language/crates/baml_cli/src/generate.rs
  • baml_language/crates/baml_cli/src/grep_command.rs
  • baml_language/crates/baml_cli/src/init_command.rs
  • baml_language/crates/baml_cli/src/lib.rs
  • baml_language/crates/baml_cli/src/manifest.rs
  • baml_language/crates/baml_cli/src/project_load.rs
  • baml_language/crates/baml_cli/src/run_command.rs
  • baml_language/crates/baml_cli/tests/exit_code_e2e.rs
  • baml_language/crates/baml_codegen_types/src/generator_fields.rs
  • baml_language/crates/baml_compiler2_ast/src/ast.rs
  • baml_language/crates/baml_compiler2_ast/src/lower_cst.rs
  • baml_language/crates/baml_compiler2_ast/src/lowering_diagnostic.rs
  • baml_language/crates/baml_compiler2_hir/src/builder.rs
  • baml_language/crates/baml_compiler2_hir/src/contributions.rs
  • baml_language/crates/baml_compiler2_hir/src/ids.rs
  • baml_language/crates/baml_compiler2_hir/src/item_tree.rs
  • baml_language/crates/baml_compiler2_hir/src/loc.rs
  • baml_language/crates/baml_compiler2_hir/src/namespace.rs
  • baml_language/crates/baml_compiler2_hir/src/scope.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs
  • baml_language/crates/baml_compiler_lexer/src/tokens.rs
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/baml_compiler_syntax/src/ast.rs
  • baml_language/crates/baml_lsp2_actions/src/completions.rs
  • baml_language/crates/baml_lsp2_actions/src/describe.rs
  • baml_language/crates/baml_lsp2_actions/src/tokens.rs
  • baml_language/crates/baml_lsp2_actions/src/type_info.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/completion/top_level_keywords.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/generator.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/class/generator_keywords1.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/class/generator_keywords2.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/enum/enum_is_valid.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/generators/error.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/generators/quoted_invalid_value.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/generators/v1.baml
  • baml_language/crates/baml_tests/projects/broken_syntax/generator/valid_generator.baml
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/request.rs
  • baml_language/crates/tools_onionskin/src/compiler.rs
💤 Files with no reviewable changes (16)
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/generators/quoted_invalid_value.baml
  • baml_language/crates/baml_tests/projects/broken_syntax/generator/valid_generator.baml
  • baml_language/crates/baml_compiler2_ast/src/ast.rs
  • baml_language/crates/baml_lsp2_actions/src/completions.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/generator.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/enum/enum_is_valid.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/generators/error.baml
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/request.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/class/generator_keywords2.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/generators/v1.baml
  • baml_language/crates/baml_lsp2_actions/src/type_info.rs
  • baml_language/crates/baml_lsp2_actions/src/describe.rs
  • baml_language/crates/baml_compiler2_hir/src/ids.rs
  • baml_language/crates/tools_onionskin/src/compiler.rs
  • baml_language/crates/baml_compiler_syntax/src/ast.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
✅ Files skipped from review due to trivial changes (5)
  • baml_language/crates/baml_compiler_lexer/src/tokens.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/completion/top_level_keywords.baml
  • baml_language/crates/baml_compiler2_hir/src/scope.rs
  • baml_language/crates/baml_compiler2_hir/src/namespace.rs
  • baml_language/crates/baml_cli/src/init_command.rs
🚧 Files skipped from review as they are similar to previous changes (18)
  • baml_language/crates/baml_cli/src/lib.rs
  • baml_language/crates/baml_cli/Cargo.toml
  • baml_language/crates/baml_compiler2_ast/src/lowering_diagnostic.rs
  • baml_language/crates/baml_cli/src/grep_command.rs
  • baml_language/crates/baml_compiler2_hir/src/contributions.rs
  • baml_language/crates/baml_compiler2_ast/src/lower_cst.rs
  • baml_language/crates/baml_lsp2_actions/src/tokens.rs
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/baml_cli/src/project_load.rs
  • baml_language/crates/baml_compiler2_hir/src/item_tree.rs
  • baml_language/crates/baml_cli/tests/exit_code_e2e.rs
  • baml_language/crates/baml_cli/src/generate.rs
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/class/generator_keywords1.baml
  • baml_language/crates/baml_compiler2_hir/src/builder.rs
  • baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs
  • baml_language/crates/baml_cli/src/manifest.rs
  • baml_language/crates/baml_compiler2_hir/src/loc.rs
  • baml_language/crates/baml_cli/src/run_command.rs

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 21, 2026 18:52 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 21, 2026 19:05 Inactive
Move code-generation config out of `.baml` `generator {}` blocks and into a
typed `[generator.<name>]` section of the project manifest.

- Add typed serde structs for `baml.toml` (baml_cli/src/manifest.rs):
  BamlToml/Package/Script/GeneratorManifest. `[package]` and `[scripts]`
  now share one deserialization; unknown keys warn instead of silently
  no-op'ing.
- `baml generate` reads `[generator.<name>]` (output_type, naming_convention,
  output_dir) via toml::Spanned, rendering precise diagnostics into baml.toml.
- `baml init` scaffolds a commented-out generator section.
- Deprecate `generator {}` in `.baml`: the parser still recognizes the block
  but consumes its body opaquely (no interior parsing) and emits an
  unclosed-brace error on EOF; lowering raises a migration warning (E0017)
  pointing to baml.toml. AST/HIR/codegen generator plumbing is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@vercel
vercel Bot temporarily deployed to Preview – beps June 21, 2026 22:17 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 21, 2026 22:23 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 21, 2026 22:35 Inactive
@sxlijin
sxlijin added this pull request to the merge queue Jun 22, 2026
Merged via the queue into canary with commit ef06f13 Jun 22, 2026
86 of 88 checks passed
@sxlijin
sxlijin deleted the sam/baml-toml branch June 22, 2026 01:20

This branch was previously deployed

3 inactive deployments
Preview – promptfiddle — ff2dc6d4 Deployed Jun 21, 2026 by vercel[bot]
Preview – promptfiddle2 — ff2dc6d4 Deployed Jun 21, 2026 by vercel[bot]
Preview – beps — ff2dc6d4 Deployed Jun 21, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant