feat(lsp): re-land resolution-first semantic tokens rework (#3867) on #4000#4011
Conversation
…4000 Revert of the temporary revert (4a9ffe3), re-landed on top of #4000's position-encoding negotiation, connection-scoped sessions, and bounded ingress. The classification engine (resolution index, CST walk, keyword re-lexing, tools_semantic_tokens viewer) re-lands byte-for-byte; only the LSP boundary is adapted, per the revert's re-land guidance: - encode_semantic_tokens now goes through the connection's negotiated PositionCodec: delta_start and length are code units (UTF-16 by default, surrogate pairs included), not bytes, and multiline tokens split into per-line segments. This also fixes the byte-length miscount for non-ASCII identifiers that #3867 originally shipped. - semanticTokens full/delta/range all use the #4000 request idiom: encoding_for_request + read_for_request (bounded gate, ambient cancellation observed at entry/post-gate/completion) instead of try_lock_db. - semantic_tokens_cache is connection-scoped (fresh per connection_scoped_lsp_session, next to the encoding negotiation): cached arrays are per-encoding and each client owns its delta baseline. The result_id sequence stays process-global. - The tracked semantic_tokens query keeps #4000's returns(ref) + salsa::Update, avoiding the O(tokens) clone per request. Not re-landed: the revert's incidental pnpm-lock.yaml refresh (fiddle-proxy entries for an untracked package; unrelated to #3867 on both sides). New tests: negotiated-encoding token encoding (UTF-16 vs UTF-8 lengths, astral-plane surrogate pairs, multiline splitting) and connection-scoped token-cache freshness in the session test.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
📝 WalkthroughWalkthroughThis PR updates contextual keyword parsing, compiler scope and interface metadata, LSP semantic-token classification, binding resolution, semantic-token transport, extensive semantic-token fixtures, and a new local semantic-token viewer with snapshot reconciliation. ChangesSemantic token compiler and LSP pipeline
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Binary size checks passed✅ 7 passed
Generated by |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
baml_language/crates/bex_project/src/bex_lsp/multi_project/request.rs (1)
1104-1118: 🚀 Performance & Scalability | 🔵 TrivialCache entries are never evicted on
didClose.
semantic_tokens_cachekeeps the full encoded token array for every path touched during a connection, and nothing removes an entry when a document closes (ProjectRefreshMode::ClosedDocumentsdoesn't prune it). Growth is bounded by files opened per connection and freed when the session drops, so this is a mild retention concern rather than a leak. If long-lived connections open many files, consider dropping the entry when a document closes.🤖 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_project/src/bex_lsp/multi_project/request.rs` around lines 1104 - 1118, Update the semantic-token cache lifecycle around cache_semantic_tokens to remove the cached entry when the corresponding document is closed. In the didClose handling for ProjectRefreshMode::ClosedDocuments, derive the same FsPath key and delete it from semantic_tokens_cache while preserving existing caching behavior for open documents.baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/if_expression.baml (1)
1-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate fixture: identical to
if_else.baml.This file's source and full expected token list are byte-for-byte identical to
baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/if_else.baml. If the intent is to distinguish "if-statement" vs "if-as-expression" coverage, this file's source should exercise somethingif_else.bamldoesn't (e.g. anif-expression used inline as an argument/return, not just vialet x = if ...). Otherwise, consider dropping one of the two to avoid two fixtures drifting out of sync as the classifier changes.🤖 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_lsp2_actions_tests/test_files/semantic_tokens/if_expression.baml` around lines 1 - 60, Remove the duplicate semantic-token fixture, or revise it to cover an if-expression scenario not exercised by if_else.baml, such as using the expression inline in an argument or return. Update the source and complete expected token list together so the fixtures are no longer byte-for-byte identical and remain focused on distinct coverage.baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/interface_impl.baml (1)
1-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove duplicate test fixture.
The content of this file is an exact, line-by-line duplicate of
implements_for.baml. Consider removing one of the files to avoid duplicate test execution and maintainability overhead.🤖 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_lsp2_actions_tests/test_files/semantic_tokens/interface_impl.baml` around lines 1 - 46, Remove the duplicate test fixture represented by interface_impl.baml, retaining the existing implements_for.baml fixture as the single source for this semantic-token coverage.baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/index_access.baml (1)
19-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove leftover copy-pasted comments.
These
// error:comments appear to be copy-pasted artifacts fromexpr/access.baml(as indicated by the path in the text) and are not actively part of the//- diagnosticstest runner assertions below.Consider removing this block to keep the fixture clean. Note that you will also need to regenerate the semantic token snapshots at the bottom of the file to account for the removed lines.
🤖 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_lsp2_actions_tests/test_files/semantic_tokens/index_access.baml` around lines 19 - 43, Remove the leftover // error diagnostic comment block from the semantic token fixture, leaving the active //- diagnostics assertions intact. Then regenerate the semantic token snapshots at the bottom of the file so their expected positions reflect the removed lines.
🤖 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/tools_semantic_tokens/src/server.rs`:
- Around line 140-142: The async handlers perform synchronous fixture
resolution, loading, token computation, and acceptance on Tokio worker threads.
In baml_language/crates/tools_semantic_tokens/src/server.rs lines 140-142, wrap
resolve_fixture and analysis::load_fixture in tokio::task::spawn_blocking and
propagate both join and operation errors; at lines 161-163, wrap compute_tokens
likewise; at lines 180-182, wrap resolve_fixture and accept_fixture likewise,
preserving each handler’s existing response and error mapping.
- Around line 91-122: Update the fixtures handler to replace the
std::thread::scope computation with tokio::task::spawn_blocking, moving fixture
summary calculation into the blocking pool and awaiting its result. Preserve the
existing per-fixture parallelism, error-to-zero behavior, summary construction,
and panic handling while ensuring the async executor is not blocked.
---
Nitpick comments:
In
`@baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/if_expression.baml`:
- Around line 1-60: Remove the duplicate semantic-token fixture, or revise it to
cover an if-expression scenario not exercised by if_else.baml, such as using the
expression inline in an argument or return. Update the source and complete
expected token list together so the fixtures are no longer byte-for-byte
identical and remain focused on distinct coverage.
In
`@baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/index_access.baml`:
- Around line 19-43: Remove the leftover // error diagnostic comment block from
the semantic token fixture, leaving the active //- diagnostics assertions
intact. Then regenerate the semantic token snapshots at the bottom of the file
so their expected positions reflect the removed lines.
In
`@baml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/interface_impl.baml`:
- Around line 1-46: Remove the duplicate test fixture represented by
interface_impl.baml, retaining the existing implements_for.baml fixture as the
single source for this semantic-token coverage.
In `@baml_language/crates/bex_project/src/bex_lsp/multi_project/request.rs`:
- Around line 1104-1118: Update the semantic-token cache lifecycle around
cache_semantic_tokens to remove the cached entry when the corresponding document
is closed. In the didClose handling for ProjectRefreshMode::ClosedDocuments,
derive the same FsPath key and delete it from semantic_tokens_cache while
preserving existing caching behavior for open documents.
🪄 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: 67ac656b-705a-4643-9faa-9fdbe6b041c2
⛔ Files ignored due to path filters (66)
baml_language/Cargo.lockis excluded by!**/*.lockbaml_language/crates/baml_tests/snapshots/broken_syntax/optional_parameter_defaults/baml_tests__broken_syntax__optional_parameter_defaults__02_parser__function_type_defaults.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/broken_syntax/pending_greaters_fix/baml_tests__broken_syntax__pending_greaters_fix__02_parser__extra_greater.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/broken_syntax/test_invalid_contexts/baml_tests__broken_syntax__test_invalid_contexts__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/broken_syntax/type_annotation_errors/baml_tests__broken_syntax__type_annotation_errors__02_parser__type_malformed.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/config_dictionary/baml_tests__compiles__config_dictionary__02_parser__valid_dictionary.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/json_alias_basic/baml_tests__compiles__json_alias_basic__02_parser__json_alias_basic.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/json_map_literal/baml_tests__compiles__json_map_literal__02_parser__json_map_literal.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/lambda_advanced/baml_tests__compiles__lambda_advanced__02_parser__lambda_advanced.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/lexical_scoping/baml_tests__compiles__lexical_scoping__02_parser__lexical_scoping.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/literal_union_arithmetic/baml_tests__compiles__literal_union_arithmetic__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/llm_parse_catchable_parse_error/baml_tests__compiles__llm_parse_catchable_parse_error__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/method_explicit_type_args/baml_tests__compiles__method_explicit_type_args__02_parser__method_explicit_type_args.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/namespaces_type_resolution/baml_tests__compiles__namespaces_type_resolution__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/namespaces_type_resolution/baml_tests__compiles__namespaces_type_resolution__02_parser__ns_auth_auth.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/namespaces_type_resolution/baml_tests__compiles__namespaces_type_resolution__02_parser__ns_llm_models.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/optional_function_parameters/baml_tests__compiles__optional_function_parameters__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/parser_expressions/baml_tests__compiles__parser_expressions__02_parser__precedence.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/parser_statements/baml_tests__compiles__parser_statements__02_parser__break_continue.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__02_parser__patterns_new.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/stream_crossfile/baml_tests__compiles__stream_crossfile__02_parser__file_a.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/test_expr_name_concat/baml_tests__compiles__test_expr_name_concat__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/test_raw_string_name/baml_tests__compiles__test_raw_string_name__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/testset_basic/baml_tests__compiles__testset_basic__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/testset_dynamic/baml_tests__compiles__testset_dynamic__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/testset_nested/baml_tests__compiles__testset_nested__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/type_annotation/baml_tests__compiles__type_annotation__02_parser__type_alias_interaction.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/type_annotation/baml_tests__compiles__type_annotation__02_parser__type_positions.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/type_builder_test/baml_tests__compiles__type_builder_test__02_parser__test.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/attr_disambiguation/baml_tests__diagnostic_errors__attr_disambiguation__02_parser__non_field.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/backtick_strict_types/baml_tests__diagnostic_errors__backtick_strict_types__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/basic_types/baml_tests__diagnostic_errors__basic_types__02_parser__types.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/builtin_io/baml_tests__diagnostic_errors__builtin_io__02_parser__io_ops.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/builtin_io/baml_tests__diagnostic_errors__builtin_io__02_parser__shell_ops.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/catch_arm_braceless_return/baml_tests__diagnostic_errors__catch_arm_braceless_return__02_parser__repro.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/catch_return_type_mismatch/baml_tests__diagnostic_errors__catch_return_type_mismatch__02_parser__catch_return_type_mismatch.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/duplicate_class_span/baml_tests__diagnostic_errors__duplicate_class_span__02_parser__repro.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/format_checks/baml_tests__diagnostic_errors__format_checks__02_parser__binary_expr.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/format_checks/baml_tests__diagnostic_errors__format_checks__02_parser__config_decls.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/format_checks/baml_tests__diagnostic_errors__format_checks__02_parser__loop_stmts.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/format_checks/baml_tests__diagnostic_errors__format_checks__02_parser__match_exprs.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/format_checks/baml_tests__diagnostic_errors__format_checks__02_parser__other_exprs.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/format_checks/baml_tests__diagnostic_errors__format_checks__02_parser__type_alias_decls.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/function_type_throws/baml_tests__diagnostic_errors__function_type_throws__02_parser__negative.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/function_types/baml_tests__diagnostic_errors__function_types__02_parser__function_types.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/generics/baml_tests__diagnostic_errors__generics__02_parser__classes.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/headers_edge_cases/baml_tests__diagnostic_errors__headers_edge_cases__02_parser__edge_cases.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/match_exhaustiveness/baml_tests__diagnostic_errors__match_exhaustiveness__02_parser__match_exhaustiveness.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/null_handling/baml_tests__diagnostic_errors__null_handling__02_parser__null_handling.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/optional_parameter_defaults/baml_tests__diagnostic_errors__optional_parameter_defaults__02_parser__unsupported_contexts.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/parser_constructors/baml_tests__diagnostic_errors__parser_constructors__02_parser__array_constructor.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/parser_constructors/baml_tests__diagnostic_errors__parser_constructors__02_parser__map_constructor.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/stream_types/baml_tests__diagnostic_errors__stream_types__02_parser__complex.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/stream_types/baml_tests__diagnostic_errors__stream_types__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/stream_types/baml_tests__diagnostic_errors__stream_types__02_parser__ns_foo_with_top_level_refs.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/test_expr_name_type_error/baml_tests__diagnostic_errors__test_expr_name_type_error__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/test_expr_wrong_runner/baml_tests__diagnostic_errors__test_expr_wrong_runner__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/test_with_runner_ambiguity/baml_tests__diagnostic_errors__test_with_runner_ambiguity__02_parser__main.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/type_aliases/baml_tests__diagnostic_errors__type_aliases__02_parser__type_aliases.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/type_aliases/baml_tests__diagnostic_errors__type_aliases__02_parser__type_aliases_json.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/unknown_type_error/baml_tests__diagnostic_errors__unknown_type_error__02_parser__unknown_type_error.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/void_return_type/baml_tests__diagnostic_errors__void_return_type__02_parser__function_type_throws.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/void_return_type/baml_tests__diagnostic_errors__void_return_type__02_parser__void_function_type.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/diagnostic_errors/void_return_type/baml_tests__diagnostic_errors__void_return_type__02_parser__void_type_position_errors.snapis excluded by!**/*.snappnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (122)
baml_language/Cargo.tomlbaml_language/crates/baml_cli/src/paint.rsbaml_language/crates/baml_compiler2_ast/src/lower_expr_body.rsbaml_language/crates/baml_compiler2_hir/src/builder.rsbaml_language/crates/baml_compiler2_hir/src/item_tree.rsbaml_language/crates/baml_compiler2_hir/src/semantic_index.rsbaml_language/crates/baml_compiler2_mir/src/lower.rsbaml_language/crates/baml_compiler2_tir/src/inference.rsbaml_language/crates/baml_compiler2_tir/src/resolve.rsbaml_language/crates/baml_compiler_parser/src/parser.rsbaml_language/crates/baml_compiler_syntax/src/ast.rsbaml_language/crates/baml_compiler_syntax/src/syntax_kind.rsbaml_language/crates/baml_fmt/src/ast/declarations.rsbaml_language/crates/baml_fmt/src/ast/expressions.rsbaml_language/crates/baml_fmt/src/ast/tokens.rsbaml_language/crates/baml_fmt/src/ast/types.rsbaml_language/crates/baml_lsp2_actions/Cargo.tomlbaml_language/crates/baml_lsp2_actions/src/completions.rsbaml_language/crates/baml_lsp2_actions/src/definition.rsbaml_language/crates/baml_lsp2_actions/src/definition_at_tests.rsbaml_language/crates/baml_lsp2_actions/src/describe.rsbaml_language/crates/baml_lsp2_actions/src/lib.rsbaml_language/crates/baml_lsp2_actions/src/tokens.rsbaml_language/crates/baml_lsp2_actions/src/tokens/classify.rsbaml_language/crates/baml_lsp2_actions/src/tokens/index.rsbaml_language/crates/baml_lsp2_actions/src/type_info.rsbaml_language/crates/baml_lsp2_actions_tests/src/lib.rsbaml_language/crates/baml_lsp2_actions_tests/src/range_tokens_test.rsbaml_language/crates/baml_lsp2_actions_tests/src/runner.rsbaml_language/crates/baml_lsp2_actions_tests/src/typing_robustness_test.rsbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/array_pattern.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/attributes.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/backtick_string.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/bigint_literals.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/block_attributes.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/break_continue.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/byte_strings.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/call_named_args.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/cancel_token.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/catch_all_keyword.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/catch_param.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/catch_throw.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/class.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/client.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/config_dictionary.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/const_binding.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/const_let_else_defer.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/constructor_spread.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/deep_method_call.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/dynamic_type_builder.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/enum_decls.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/enum_member_access.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/exhaustive.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/extends_chain.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/field_alias.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/for_loops.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/function.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/generators.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/generic_field_chain.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/generic_function_call.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/header_comments.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/http_config.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/if_else.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/if_expression.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/if_let_chain.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/implements_for.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/index_access.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/interface_impl.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/interfaces_inferred_generic_type_args.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/interfaces_iter_core.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/interfaces_sort_comparable.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/is_operator.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/iterator.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/jinja_control_prompt.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/json_map_literal.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/json_parse_stringify_intrinsics.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/lambda_advanced.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/literal_types.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/llm_function.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/map_literal_keys.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/map_type_and_methods.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/match_expr.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/match_literal_types.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/method_explicit_type_args.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/multi_segment_path.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/ns_agent_clients.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/ns_ansi.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/ns_game_clients.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/ns_images_pipeline.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/ns_sentiment.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/patterns_new.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/remap_role.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/requires_clause.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/retry_policy_valid_retry.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/self_qualified_call.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/signature_variety.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/stream_llm_inferred_typeargs.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/task_group.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/template_string.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/template_string_calls.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/test_block.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/test_expr_throwing_body.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/testset.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/testset_vibes_nested.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/throws_clause.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/top_level_binding.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/type_alias.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/type_aliases_jinja.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/watch_let.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/while_loop.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/semantic_tokens/with_keyword.bamlbaml_language/crates/baml_lsp2_actions_tests/test_files/syntax/hover/catch_binding.bamlbaml_language/crates/bex_project/src/bex_lsp/multi_project/mod.rsbaml_language/crates/bex_project/src/bex_lsp/multi_project/request.rsbaml_language/crates/tools_semantic_tokens/Cargo.tomlbaml_language/crates/tools_semantic_tokens/README.mdbaml_language/crates/tools_semantic_tokens/src/analysis.rsbaml_language/crates/tools_semantic_tokens/src/index.htmlbaml_language/crates/tools_semantic_tokens/src/main.rsbaml_language/crates/tools_semantic_tokens/src/server.rsbaml_language/crates/tools_semantic_tokens/src/staleness.rsbaml_language/stow.toml
| async fn fixtures(State(state): State<AppState>) -> Result<Json<FixturesResponse>, ApiError> { | ||
| let dir = state.fixtures_dir.as_ref(); | ||
| let names = analysis::list_fixture_names(dir) | ||
| .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; | ||
|
|
||
| // Each fixture builds its own ProjectDatabase + runs the compiler, so compute | ||
| // the diff badges in parallel (one thread per fixture) rather than serially. | ||
| let summaries = std::thread::scope(|scope| { | ||
| let handles: Vec<_> = names | ||
| .iter() | ||
| .map(|name| { | ||
| scope.spawn(move || { | ||
| let diff_count = analysis::load_fixture(&dir.join(name)) | ||
| .map(|fx| analysis::diff_count(&fx.current, &fx.expected)) | ||
| .unwrap_or(0); | ||
| FixtureSummary { | ||
| name: name.clone(), | ||
| diff_count, | ||
| } | ||
| }) | ||
| }) | ||
| .collect(); | ||
| handles | ||
| .into_iter() | ||
| .map(|h| h.join().expect("fixture summary thread panicked")) | ||
| .collect() | ||
| }); | ||
|
|
||
| Ok(Json(FixturesResponse { | ||
| fixtures: summaries, | ||
| })) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Avoid blocking the async executor and spawning unconstrained OS threads.
Using std::thread::scope inside an async fn blocks the Tokio worker thread until all scoped threads finish, starving the async executor. Additionally, since it spawns an OS thread per fixture concurrently, it can cause severe resource contention.
Please offload this parallel computation to Tokio's built-in blocking thread pool using tokio::task::spawn_blocking.
⚡ Proposed refactor
-async fn fixtures(State(state): State<AppState>) -> Result<Json<FixturesResponse>, ApiError> {
- let dir = state.fixtures_dir.as_ref();
- let names = analysis::list_fixture_names(dir)
- .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
-
- // Each fixture builds its own ProjectDatabase + runs the compiler, so compute
- // the diff badges in parallel (one thread per fixture) rather than serially.
- let summaries = std::thread::scope(|scope| {
- let handles: Vec<_> = names
- .iter()
- .map(|name| {
- scope.spawn(move || {
- let diff_count = analysis::load_fixture(&dir.join(name))
- .map(|fx| analysis::diff_count(&fx.current, &fx.expected))
- .unwrap_or(0);
- FixtureSummary {
- name: name.clone(),
- diff_count,
- }
- })
- })
- .collect();
- handles
- .into_iter()
- .map(|h| h.join().expect("fixture summary thread panicked"))
- .collect()
- });
-
- Ok(Json(FixturesResponse {
- fixtures: summaries,
- }))
-}
+async fn fixtures(State(state): State<AppState>) -> Result<Json<FixturesResponse>, ApiError> {
+ let names = analysis::list_fixture_names(&state.fixtures_dir)
+ .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
+
+ // Each fixture builds its own ProjectDatabase + runs the compiler, so compute
+ // the diff badges in parallel using Tokio's blocking thread pool.
+ let mut handles = Vec::with_capacity(names.len());
+ for name in names {
+ let dir = Arc::clone(&state.fixtures_dir);
+ handles.push(tokio::task::spawn_blocking(move || {
+ let diff_count = analysis::load_fixture(&dir.join(&name))
+ .map(|fx| analysis::diff_count(&fx.current, &fx.expected))
+ .unwrap_or(0);
+ FixtureSummary { name, diff_count }
+ }));
+ }
+
+ let mut summaries = Vec::with_capacity(handles.len());
+ for handle in handles {
+ summaries.push(handle.await.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?);
+ }
+
+ Ok(Json(FixturesResponse {
+ fixtures: summaries,
+ }))
+}📝 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.
| async fn fixtures(State(state): State<AppState>) -> Result<Json<FixturesResponse>, ApiError> { | |
| let dir = state.fixtures_dir.as_ref(); | |
| let names = analysis::list_fixture_names(dir) | |
| .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; | |
| // Each fixture builds its own ProjectDatabase + runs the compiler, so compute | |
| // the diff badges in parallel (one thread per fixture) rather than serially. | |
| let summaries = std::thread::scope(|scope| { | |
| let handles: Vec<_> = names | |
| .iter() | |
| .map(|name| { | |
| scope.spawn(move || { | |
| let diff_count = analysis::load_fixture(&dir.join(name)) | |
| .map(|fx| analysis::diff_count(&fx.current, &fx.expected)) | |
| .unwrap_or(0); | |
| FixtureSummary { | |
| name: name.clone(), | |
| diff_count, | |
| } | |
| }) | |
| }) | |
| .collect(); | |
| handles | |
| .into_iter() | |
| .map(|h| h.join().expect("fixture summary thread panicked")) | |
| .collect() | |
| }); | |
| Ok(Json(FixturesResponse { | |
| fixtures: summaries, | |
| })) | |
| } | |
| async fn fixtures(State(state): State<AppState>) -> Result<Json<FixturesResponse>, ApiError> { | |
| let names = analysis::list_fixture_names(&state.fixtures_dir) | |
| .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; | |
| // Each fixture builds its own ProjectDatabase + runs the compiler, so compute | |
| // the diff badges in parallel using Tokio's blocking thread pool. | |
| let mut handles = Vec::with_capacity(names.len()); | |
| for name in names { | |
| let dir = Arc::clone(&state.fixtures_dir); | |
| handles.push(tokio::task::spawn_blocking(move || { | |
| let diff_count = analysis::load_fixture(&dir.join(&name)) | |
| .map(|fx| analysis::diff_count(&fx.current, &fx.expected)) | |
| .unwrap_or(0); | |
| FixtureSummary { name, diff_count } | |
| })); | |
| } | |
| let mut summaries = Vec::with_capacity(handles.len()); | |
| for handle in handles { | |
| summaries.push(handle.await.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?); | |
| } | |
| Ok(Json(FixturesResponse { | |
| fixtures: summaries, | |
| })) | |
| } |
🤖 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/tools_semantic_tokens/src/server.rs` around lines 91 -
122, Update the fixtures handler to replace the std::thread::scope computation
with tokio::task::spawn_blocking, moving fixture summary calculation into the
blocking pool and awaiting its result. Preserve the existing per-fixture
parallelism, error-to-zero behavior, summary construction, and panic handling
while ensuring the async executor is not blocked.
| let path = resolve_fixture(&state.fixtures_dir, &query.name)?; | ||
| let fx = analysis::load_fixture(&path) | ||
| .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Blocking I/O and CPU-intensive operations on the async executor.
These synchronous functions execute file I/O and run the heavy compiler pipeline. Executing them directly inside an async fn blocks the Tokio worker thread, preventing it from processing other requests. Offload these operations to Tokio's blocking thread pool.
baml_language/crates/tools_semantic_tokens/src/server.rs#L140-L142: wrap theresolve_fixtureandload_fixturecalls intokio::task::spawn_blocking.baml_language/crates/tools_semantic_tokens/src/server.rs#L161-L163: wrap thecompute_tokenscall intokio::task::spawn_blocking.baml_language/crates/tools_semantic_tokens/src/server.rs#L180-L182: wrap theresolve_fixtureandaccept_fixturecalls intokio::task::spawn_blocking.
📍 Affects 1 file
baml_language/crates/tools_semantic_tokens/src/server.rs#L140-L142(this comment)baml_language/crates/tools_semantic_tokens/src/server.rs#L161-L163baml_language/crates/tools_semantic_tokens/src/server.rs#L180-L182
🤖 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/tools_semantic_tokens/src/server.rs` around lines 140 -
142, The async handlers perform synchronous fixture resolution, loading, token
computation, and acceptance on Tokio worker threads. In
baml_language/crates/tools_semantic_tokens/src/server.rs lines 140-142, wrap
resolve_fixture and analysis::load_fixture in tokio::task::spawn_blocking and
propagate both join and operation errors; at lines 161-163, wrap compute_tokens
likewise; at lines 180-182, wrap resolve_fixture and accept_fixture likewise,
preserving each handler’s existing response and error mapping.
…okens (BoundaryML#4017) Follow-up to BoundaryML#4011 (now on canary). Fixes three gaps that made `baml describe` highlighting look worse than the same code in an editor: 1. **Background-agnostic palette.** Colors are restricted to the terminal's named ANSI palette (slots 0–15, always theme-controlled) plus dim/bold attributes. The fixed 256-color-cube values and the forced-white variables are gone: ordinary names render at the default foreground, comments/operators are dim. Output is legible on light and dark terminals alike, styled by the user's own scheme. 2. **Semantic-token modifiers are honored.** Declarations render bold, `defaultLibrary` (stdlib) entities italic, deprecated ones struck through — the same signals editor themes derive from the LSP modifier bitset, previously discarded. Dim-base token types trade dim for bold at their declaration instead of stacking contradictory weights. 3. **Fragments use the exact LSP classifier.** Synthesized snippets (keyword-doc examples, interface member signatures) are parsed as a scratch file in a thread-local in-memory project and rendered from real `semantic_tokens` — replacing the hand-rolled lexer scanner that colored only keywords/primitives/literals. Names a context-free fragment can't resolve stay neutral, exactly like an editor. The scanner (`PRIMITIVE_TYPES`, `decl_keyword_kind`, `is_keyword`) is deleted and `Highlighter::range`/`highlight_str` now share one render loop. ## Tests - New unit tests: variables keep the default foreground; declaration/defaultLibrary modifier overlays; a rendered body contains no fixed-cube 256-color codes and no forced white/black; fragments classify via the compiler (bold declaration + italic stdlib type — signals the old lexer could never emit); unresolvable fragment names stay neutral. - `baml_cli` suite 343/343 (describe snapshots are colorless and unaffected); clippy clean. - Verified end-to-end on a scratch project and on the `describe defer` keyword page with forced color. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved syntax highlighting so both real source code and generated fragments use the same semantic-token-based styling. * Correctly honors semantic modifiers (e.g., declaration emphasis, default-library italics, deprecation strikethrough, and dimming for non-declarations). * Keeps unresolved names neutral and avoids highlighting errors/crashes. * Preserves terminal default colors—no forced black/white and no fixed palette indices—while ensuring modifier overlays render correctly. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Re-lands #3867 (reverted in #4002 to let #4000 land first) as a revert-of-revert on top of #4000, following the re-land guidance agreed in #4002.
Byte-for-byte re-land
The classification engine is unchanged from what #3867 shipped: the span-keyed resolution index, the CST classification walk, interface-member resolution, the contextual-keyword re-lexing (
as/type/true/false/null), all 78 semantic-token fixtures, and thetools_semantic_tokensviewer.Adapted to #4000 (LSP boundary only)
encode_semantic_tokensnow encodes through the connection'sPositionCodec—delta_start/lengthare negotiated code units (UTF-16 default, surrogate pairs handled), and multiline tokens split into per-line segments. This also fixes the byte-length miscount for non-ASCII identifiers that feat(lsp): resolution-first semantic tokens rework #3867 originally shipped (called out in revert(lsp): temporarily revert #3867 to land #4000 first #4002).semanticTokens/full,full/delta, andrangeuseencoding_for_request+read_for_request— bounded gate with ambient cancellation observed at entry / post-gate / completion — instead oftry_lock_db.semantic_tokens_cacheis now per-connection (fresh inconnection_scoped_lsp_session, next to the encoding negotiation), so concurrent clients can't cross-serve wrongly-encoded tokens or trample each other'sprevious_result_id. Theresult_idsequence stays process-global.returns(ref): the trackedsemantic_tokensquery keeps fix(lsp): resolve the 0.14 mutex/latency regression — Phase 0 containment + Phase 1 bounded ingress #4000'sreturns(ref)+salsa::Update, avoiding an O(tokens) clone per request.Not re-landed: #4002's incidental
pnpm-lock.yamlrefresh (fiddle-proxy entries for an untracked package) — unrelated to #3867 on both sides; the diff is strictlybaml_language/-scoped.New tests
request.rs).mod.rs).Validation
baml_src baml_test2057/2057 standalone.bex_project52/52 incl. the new encoding + session tests.cargo check --target wasm32-unknown-unknown -p bex_projectclean.Summary by CodeRabbit
New Features
Bug Fixes