release: 2026-05-08#683
Closed
aviatesk wants to merge 16 commits into
Closed
Conversation
`rewrite_closure_block`'s `any_changed=false` fallback called `JS.mapchildren` over the same children the loop above had already recursed into via `_rewrite_local_closures_to_opaque`, doubling the work at every block. Across nested blocks (each `function` / `for` / `if` / `try` body) the cost compounded to `O(2^depth)`, so a real- world file like `CSV/src/file.jl` (957 lines, ~10–20 levels of nested blocks) never finished — 6+ minutes and 3+ GB RSS before being killed. `new_children` already holds the recursively-rewritten children regardless of whether sibling collapse fired, so dropping the fallback (and the now-unused `any_changed` flag) is sufficient. After the fix the same file completes in roughly 0.5–0.8 s post-warmup across repeated runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The old-style `Base.@assume_effects` macro expansion collapses source positions to line granularity, which breaks LSP features (hover, diagnostics, find-references, ...) for identifiers inside the annotated body. This stub re-implements `@assume_effects` as a JuliaLowering new-style macro so provenance is preserved through expansion. Since the `Expr(:purity)` / `Expr(:meta)` directives the real macro emits are irrelevant to LSP analysis, the stub: - validates the effect setting names against the list in `Base.compute_assumed_setting` (`base/expr.jl`), with `!` negation and `!!` chains supported, - routes the body (function definition, `@ccall`, or call-site annotation) through unchanged, and - expands the no-body declaration form to a placeholder. `:consistent_overlay` and `:nortcall` are deliberately rejected as standalone inputs since Base only sets them through the `:foldable` and `:total` shortcuts. Unrecognized setting names produce a `MacroExpansionError` pointing at the offending argument so JETLS surfaces them as `lowering/macro-expansion-error` diagnostics. `@assume_effects` is also added to `NEW_STYLE_MACROCALL_NAMES` so `_remove_macrocalls` doesn't fall back to the old-style rewrite path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add new-style JuliaLowering implementations of `Base.@inline`, `Base.@noinline`, `Base.@propagate_inbounds`, and `Base.@inbounds` in `src/utils/jl-syntax-macros.jl`. These decoration macros previously went through legacy expansion that injected `Expr(:meta, …)` markers, producing synthetic nodes with no source byte range. The new implementations drop the markers and return the wrapped expression unchanged, preserving the inner funcdef's provenance through the lowering pipeline. TypeAnnotation.jl now registers *every* provenance entry, not just the first provenance. For macro-wrapped surface forms like `@inline f(x) = body` whose chain is `[macrocall, function]`, it allows the inner funcdef's span to be queryable in addition to the macrocall's outer span.
Add a regression test for 3a648d0. `rewrite_closure_block`'s old `any_changed=false` fallback re-recursed via `JS.mapchildren` over children the loop above had already rewritten, doubling the work at every block. Across `D` nested blocks the cost compounded to `O(2^D)`, so real-world files like `CSV/src/file.jl` (~10–20 levels of nested blocks) took unreasonable time to be rewritten.
Extract the repeated "look up a binding by kind/name and verify its last-provenance entry's sourcetext" pattern in `test/utils/test_jl_syntax_macros.jl` into a shared `assert_binding_provenance` helper, and rewrite the existing `@spawn`/`@something`/`@test`/`@testset`/`@assume_effects` "binding resolution preserves provenance" testsets to use it. The helper returns `(binfo, provs)` so callers that need to assert further on the provenance entry (e.g. `@testset`'s scope isolation test, which also checks `JS.source_location`) can chain those assertions without re-running the lookup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add new-style JuliaLowering stubs for `Test.@test_throws`, `Test.@test_broken`, `Test.@test_skip`, `Test.@test_warn`, `Test.@test_nowarn`, `Test.@test_logs`, `Test.@test_deprecated`, and `Test.@inferred`. The real macros wrap user-written bodies in test-recording / exception-catching scaffolding that destroys provenance during expansion; the stubs drop the scaffolding and emit the user-written sub-expressions in a `block` (or alone, for single-body forms) so identifiers inside every argument flow through scope resolution with their byte ranges intact. For macros with the `body kws...` shape (`@test_broken`, `@test_skip`) we reuse `_validate_test_kw` and drop the kws after shape validation. For `@test_logs`, where kws sit alongside a list of patterns and a body, we keep only the kw RHS — that's where any user-written identifier needing scope resolution lives. Register the new names in `NEW_STYLE_MACROCALL_NAMES`, merge the previously-split `@test`/`@testset` doc comments into a single header covering all `Test.jl` stubs, and move the `@testset` rationale about `let`-block scope isolation inline next to the let-block construction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…es (#676) When a comprehension / `map` / `filter` auto-generated lambda's source bytes coincide with the user's yield expression, the lowered tree puts `K"new_opaque_closure"`, `K"opaque_closure_method"`, the argt/rt_lb svec helpers, and the OC binding `K"="` / `K"slot"` all at that same byte range. `tmerge_at_range` would otherwise surface `Union{T, Method, OpaqueClosure, Type}` instead of the body's actual value type. Note that comprehension with untyped iterator masked this currently — `Any` from `most_general_argtypes` absorbed everything — but typed forms (`for x::T in xs`) make it visible. Filter structurally rather than filtering by matching against inferred lattice element: track which `K"opaque_closure_method"`'s body each lowered node belongs to (the new `oc_body_scope` index in `InferredTreeContext`) and at query time keep only the nodes whose body matches the queried range. Inner-OC construction noise sitting inside an outer OC body — multi-`for` comprehension, closure-of-closure — is filtered when querying at the inner OC's byte range, while a reference to an explicit closure binding (no OC construction at the byte range) still surfaces its `OpaqueClosure{…}` type unchanged. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`find_method_and_sig_call` matched `sig_call` by "first svec_call DFS
finds", which mis-attributed the inner OC's user-argtypes to the outer
OC when nested closures embedded the inner's `method_defs` inside the
outer's lambda body (cartesian comprehensions, manually nested typed
closures, etc.). The outer OC was then built with the inner's argt
(`Tuple{Float64}` instead of `Tuple{Int}`), surfacing as a `TypeError`
at the OC call site at runtime, and as a wrongly-typed captured
parameter (`x::Float64` for `for x::Int in ...`) in inlay hints.
Split the lookup into `find_method_node` / `find_sig_call_for` and
match the sig_call by `var_id` against `method_node[2]`.
`compute_diagnostic_result_id` previously keyed the resultId only on the file version (and dependency versions for files with explicit imports). When `[diagnostic]` config changed, `handle_lsp_config_change!` sent `workspace/diagnostic/refresh`, but the client's re-request with the previous `previousResultId` matched the freshly-computed resultId and the server returned `Unchanged`, so the refresh was effectively a no-op and stale diagnostics persisted on the client. Fold the current `DiagnosticConfig` value into the resultId so any `[diagnostic]` config mutation flips the resultId and the refresh takes effect. Also defines `Base.hash(::DiagnosticConfig, ::UInt)` content-based. `@option` from Configurations.jl generates `==` but not `hash`, so the default `hash` fell back to `objectid` and broke the `==`/`hash` contract — equal-valued `DiagnosticConfig` instances hashed differently because of the embedded `patterns::Vector`. Without this, the resultId fold above would spuriously invalidate the client cache whenever `ConfigManagerData` was rebuilt with the same values. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `textDocument/diagnostic message cycle` test only checked `resultId` behavior, so plumbing regressions that silently dropped diagnostic content would go unnoticed. The previous fixture (`x = 1` → `x = 2`) also produced no diagnostics in any state, meaning the `allow_unused_underscore` config flip exercised the `resultId` path but had no effect on actual content. Switch the fixture to `func(x) = nothing` → `func(_x) = nothing`, so each transition meaningfully changes both `resultId` and diagnostic content: - initial: 1 unused-argument diagnostic on `x` - after edit to `_x`: 0 diagnostics (default `allow_unused_underscore=true` suppresses `_`-prefixed names) - after config flip to `allow_unused_underscore=false`: 1 unused-argument diagnostic on `_x` Each full-report assertion now also checks the diagnostic count and `code`, so a regression that breaks pull-mode diagnostic delivery or config propagation would surface here. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the `@option` macro from Configurations.jl with `@kwdef` plus
`@define_eq_overloads` for the eleven config structs in `src/types.jl`,
and hand-roll the dict-to-struct hydration that the package needs.
`parse_config_from_dict` (was `Configurations.from_dict`) recursively
populates a `ConfigSection` from a config dict, threading a dotted
key path through nested calls. `InvalidKeyError` now carries that
full path directly, which lets `parse_config_dict` report the
offending key without re-walking the user's dict against a canonical
default — eliminating `collect_unmatched_keys`,
`UntypedConfigDict`, `DEFAULT_UNTYPED_CONFIG_DICT`, and the
`Configurations.to_dict`-based scaffolding.
`parse_config_dict_value` handles the small type tree we actually
use: `Maybe{T}`, nested `ConfigSection`, vectors, the formatter
union, and plain `convert`-able leaves. Errors at every level are
formatted via `parse_dict_error` so users get
`Invalid value at \`a.b.c\`: expected X, got Y` regardless of where
in the type tree the mismatch occurred. The bespoke
`DiagnosticPattern` / `AnalysisOverride` validators are inlined in
`parse_config_dict_value` rather than dispatched through a hook,
matching how `Maybe{FormatterConfig}` is handled — the set of
special cases is small and closed.
`Maybe{T} = Union{Nothing,T}` is now defined locally. Tests that
poked into `Configurations.from_dict` / `Configurations.InvalidKeyError`
are updated to use the JETLS-side equivalents, and new tests cover
`InvalidKeyError.path` and `parse_config_dict_value`'s path-aware
error messages.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `hash` method generated by `@define_eq_overloads` started with `h = $h_init`, which discarded the input `h::UInt` argument and broke the standard `hash(x, h)` contract. Calls like `Base.hash(x.$fld, h)` for fields wrapped in such types would lose the running hash state. Drop the per-type random salt entirely — type dispatch and field values already disambiguate — and chain `Base.hash` calls through the input `h`. Also drop a redundant `::DiagnosticConfig` annotation at the `compute_diagnostic_result_id` callsite.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR releases version
2026-05-08.Checklist
release / Test JETLS.jl with release environmentrelease / test_app / Test jetls serverelease / test_app / Test jetls checkrelease / test_app / Test jetls schemarelease / check_schemas / Check schemas are up to datePost-merge
releases/2026-05-08branch can be deleted after merging