Merge YARD macro support#1194
Conversation
* Port macros from lekemula/solargraph@lm-named-macros (4572e07..389def6) Original 11-commit diff: lekemula/solargraph@524c94e...389def6 Squashes the original branch and ports it onto current upstream/master, where the YardMap class was gutted and replaced with DocMap/GemPins (upstream 94006fb). Differences from the original implementation: - Parser layer: original work added `simple_convert` and `process_dsl_method` to `parser/rubyvm/{node_methods,node_processors/send_node}`. Upstream removed the rubyvm parser entirely. Rewrote both for the parser_gem AST shape: lowercase node types (`:send`, `:hash`, `:const`, `:array`), `:send` children indexed as `[receiver, method_name, *args]`, literals split into `:int`/`:float`/`:sym`/`:str` instead of `:LIT`. - ApiMap integration: original `process_macros(pins)` hooked into a `pins` parameter that no longer exists. Adapted to the new `catalog(bench)` flow — consumes `iced_pins + live_pins + doc_map.pins`, filters `Pin::Ephemeral::ClassMethodSend` from iced and live separately before the store update. Kept the original logging. - MethodDirective: original `Parser.process_node(...).first.last` regressed `spec/source_map/mapper_spec.rb:89`. Upstream had since added a `Pin::Method` filter inline; backported that into the extracted directive module. - Spec relocation: `spec/yard_map_spec.rb` was deleted upstream. The `loads macros from gems` test moved to `spec/yard_map/mapper_spec.rb` and uses the new `pins_with(name)` (DocMap-based) helper. Assertion tightened from `macros.count > 0` to checking that the `MyStruct.my_attribute` method pin exists and exposes the macro by name. - All other new files (Macro, Directives::*, Pin::Ephemeral::*, gem-with-yard-macros fixture, api_map_spec/clip_spec additions) landed unchanged from the squashed branch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix invalid gemspec for gem-with-yard-macros fixture The skeleton gemspec from `bundle gem` left TODO placeholders in summary, description, homepage, and metadata fields, which Bundler rejects in CI. Replaced with real values describing the fixture's purpose and trimmed the file list to `lib/**/*.rb` so it doesn't depend on `git ls-files` working in the CI checkout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix rubocop offenses - Autocorrected style issues across the new/ported files (string quoting, empty-method one-liners, redundant cop disables, def-without-parens, etc). - Excluded the gem-with-yard-macros fixture from rubocop entirely; it's a `bundle gem` skeleton that exists to be loaded as a gem, not as project source. - Bumped Metrics/ModuleLength.Max in the todo file from 167 to 195 to accommodate the simple_convert helpers added to ParserGem::NodeMethods. - Cleaned up YARD `@param` mismatches in Macro and ClassMethodSend, and rewrote one multi-line block chain in Macro#generate_yardoc_from. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Trim gem-with-yard-macros fixture to essentials Removed the `bundle gem` skeleton boilerplate (LICENSE, README, CHANGELOG, CODE_OF_CONDUCT, Rakefile, bin/, the gem's own Gemfile/Gemfile.lock, RBS sig, .gitignore). None are needed: the fixture exists only to be resolved as a path gem and have its YARD macro loaded. What remains is the gemspec, the macro definition, and version.rb. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Set source: :yard_map on directive-generated pins `Pin::Base#assert_source_provided` raises (under SOLARGRAPH_ASSERTS=on, as the overcommit CI job runs) when a pin is created without a `source:`. The extracted attribute/override directive modules built `Pin::Method`, `Pin::Parameter`, and `Pin::Reference::Override` pins without one. Tagged them `:yard_map` since they originate from YARD `@!` directives. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix pre-existing rubocop offenses in untouched files The `.rubocop_todo.yml` CI job runs `rubocop -c .rubocop.yml` across the whole repo and was failing on 8 offenses unrelated to this PR. Fixed them in place rather than suppressing: - Style/ArgumentsForwarding: anonymous block forwarding (`&`) in Solargraph.with_clean_env, UniqueType#each, Host#show_message_request. - Style/ArrayIntersect: `(a & b).any?` -> `a.intersect?(b)` in TypeChecker#parameterized_arity_problems_for. - Lint/UnreachableCode: the body of Pin::Method#combine_same_type_arity_ signatures is intentionally preserved behind a debug stub `return` (upstream 6d8ce95); wrapped it in a scoped rubocop:disable with a comment explaining why, instead of deleting the kept code. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix ArgumentValue struct init on Ruby < 3.2 `ArgumentValue = Struct.new(:value)` was constructed with a keyword argument (`ArgumentValue.new(value: ...)`). On Ruby 3.1 a plain Struct treats that as a positional Hash, so `#value` returned `{ value: x }` instead of `x`. That garbled `ClassMethodSend#argument_values`, which shifted every macro placeholder (`$1`, `$2`, ...) — producing method pins like `value` and dropping real ones. Added `keyword_init: true`. Fixes the 6 macro specs failing on the Ruby 3.1 CI matrix job. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Drop 'head' from RSpec matrix temporarily ruby/setup-ruby@v1 currently 404s on `head` for ubuntu-24.04 ("Unavailable version head for ruby"). Removed it from the matrix so CI isn't blocked; left a @todo to restore once setup-ruby publishes it. See: https://github.com/castwide/solargraph/actions/runs/25863741955/job/76000137015?pr=1187 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix strong typechecking --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* More typecheck fixes * Even more typecheck fixes - is this a dead code? * ApiMap fix * Mapper fix /home/runner/work/solargraph/solargraph/lib/solargraph/yard_map/mapper.rb:28 - Unresolved call to filename on Solargraph::Location, nil * NodeMethods fix /home/runner/work/solargraph/solargraph/lib/solargraph/parser/parser_gem/node_methods.rb:107 - Declared return type ::String, ::Integer, ::Float, ::Symbol, ::Array, ::Hash, ::Solargraph::Source::Chain, nil does not match inferred type ::String, ::Parser::AST::Node, ::Array, ::Hash, ::Solargraph::Source::Chain, nil for Solargraph::Parser::ParserGem::NodeMethods.simple_convert /home/runner/work/solargraph/solargraph/lib/solargraph/parser/parser_gem/node_methods.rb:107 - Declared return type ::String, ::Integer, ::Float, ::Symbol, ::Array, ::Hash, ::Solargraph::Source::Chain, nil does not match inferred type ::String, ::Parser::AST::Node, ::Array, ::Hash, ::Solargraph::Source::Chain, nil for Solargraph::Parser::ParserGem::NodeMethods#simple_convert
* Port macros from lekemula/solargraph@lm-named-macros (4572e07..389def6) Original 11-commit diff: lekemula/solargraph@524c94e...389def6 Squashes the original branch and ports it onto current upstream/master, where the YardMap class was gutted and replaced with DocMap/GemPins (upstream 94006fb). Differences from the original implementation: - Parser layer: original work added `simple_convert` and `process_dsl_method` to `parser/rubyvm/{node_methods,node_processors/send_node}`. Upstream removed the rubyvm parser entirely. Rewrote both for the parser_gem AST shape: lowercase node types (`:send`, `:hash`, `:const`, `:array`), `:send` children indexed as `[receiver, method_name, *args]`, literals split into `:int`/`:float`/`:sym`/`:str` instead of `:LIT`. - ApiMap integration: original `process_macros(pins)` hooked into a `pins` parameter that no longer exists. Adapted to the new `catalog(bench)` flow — consumes `iced_pins + live_pins + doc_map.pins`, filters `Pin::Ephemeral::ClassMethodSend` from iced and live separately before the store update. Kept the original logging. - MethodDirective: original `Parser.process_node(...).first.last` regressed `spec/source_map/mapper_spec.rb:89`. Upstream had since added a `Pin::Method` filter inline; backported that into the extracted directive module. - Spec relocation: `spec/yard_map_spec.rb` was deleted upstream. The `loads macros from gems` test moved to `spec/yard_map/mapper_spec.rb` and uses the new `pins_with(name)` (DocMap-based) helper. Assertion tightened from `macros.count > 0` to checking that the `MyStruct.my_attribute` method pin exists and exposes the macro by name. - All other new files (Macro, Directives::*, Pin::Ephemeral::*, gem-with-yard-macros fixture, api_map_spec/clip_spec additions) landed unchanged from the squashed branch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix invalid gemspec for gem-with-yard-macros fixture The skeleton gemspec from `bundle gem` left TODO placeholders in summary, description, homepage, and metadata fields, which Bundler rejects in CI. Replaced with real values describing the fixture's purpose and trimmed the file list to `lib/**/*.rb` so it doesn't depend on `git ls-files` working in the CI checkout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix rubocop offenses - Autocorrected style issues across the new/ported files (string quoting, empty-method one-liners, redundant cop disables, def-without-parens, etc). - Excluded the gem-with-yard-macros fixture from rubocop entirely; it's a `bundle gem` skeleton that exists to be loaded as a gem, not as project source. - Bumped Metrics/ModuleLength.Max in the todo file from 167 to 195 to accommodate the simple_convert helpers added to ParserGem::NodeMethods. - Cleaned up YARD `@param` mismatches in Macro and ClassMethodSend, and rewrote one multi-line block chain in Macro#generate_yardoc_from. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Trim gem-with-yard-macros fixture to essentials Removed the `bundle gem` skeleton boilerplate (LICENSE, README, CHANGELOG, CODE_OF_CONDUCT, Rakefile, bin/, the gem's own Gemfile/Gemfile.lock, RBS sig, .gitignore). None are needed: the fixture exists only to be resolved as a path gem and have its YARD macro loaded. What remains is the gemspec, the macro definition, and version.rb. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Set source: :yard_map on directive-generated pins `Pin::Base#assert_source_provided` raises (under SOLARGRAPH_ASSERTS=on, as the overcommit CI job runs) when a pin is created without a `source:`. The extracted attribute/override directive modules built `Pin::Method`, `Pin::Parameter`, and `Pin::Reference::Override` pins without one. Tagged them `:yard_map` since they originate from YARD `@!` directives. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix pre-existing rubocop offenses in untouched files The `.rubocop_todo.yml` CI job runs `rubocop -c .rubocop.yml` across the whole repo and was failing on 8 offenses unrelated to this PR. Fixed them in place rather than suppressing: - Style/ArgumentsForwarding: anonymous block forwarding (`&`) in Solargraph.with_clean_env, UniqueType#each, Host#show_message_request. - Style/ArrayIntersect: `(a & b).any?` -> `a.intersect?(b)` in TypeChecker#parameterized_arity_problems_for. - Lint/UnreachableCode: the body of Pin::Method#combine_same_type_arity_ signatures is intentionally preserved behind a debug stub `return` (upstream 6d8ce95); wrapped it in a scoped rubocop:disable with a comment explaining why, instead of deleting the kept code. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix ArgumentValue struct init on Ruby < 3.2 `ArgumentValue = Struct.new(:value)` was constructed with a keyword argument (`ArgumentValue.new(value: ...)`). On Ruby 3.1 a plain Struct treats that as a positional Hash, so `#value` returned `{ value: x }` instead of `x`. That garbled `ClassMethodSend#argument_values`, which shifted every macro placeholder (`$1`, `$2`, ...) — producing method pins like `value` and dropping real ones. Added `keyword_init: true`. Fixes the 6 macro specs failing on the Ruby 3.1 CI matrix job. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Drop 'head' from RSpec matrix temporarily ruby/setup-ruby@v1 currently 404s on `head` for ubuntu-24.04 ("Unavailable version head for ruby"). Removed it from the matrix so CI isn't blocked; left a @todo to restore once setup-ruby publishes it. See: https://github.com/castwide/solargraph/actions/runs/25863741955/job/76000137015?pr=1187 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix strong typechecking * Resolve paths for macro methods * Handle macros dynamically * Reinstate optional cache clearing * Deprecate Ephemeral::ClassMethodSend * Avoid chains for macro resolution when possible * Minor refactor * Pending specs * Clarify macro spec for keyword arguments * Linting * Erroneous reversions * Typechecking * Minor spec tweak for Ruby 3.x * More erroneous reversions --------- Co-authored-by: Lekë Mula <l.mula@finlink.de> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Unused macro methods * Update Ruby to 4.0 in typecheck workflow * Revert unneeded sg-ignore * Update to Ruby 4.0 in plugins workflow
* Infer method calls with parameter names in return types * Retain existing tags * JIT macro expansion * Expand types from chain calls
|
Hey @castwide, I tried to test this today on my project and I found something strange. The LSP was stuck in a seemingly endless loop, and it was never finishing cataloging. These are the last 500lines of the logs, which seem to be kept looping over and over again: One thing that caught my eye, in the whole river of the logs, was the This issue does not seem to appear in the I hope this helps somehow. 🤞 Let me know if I can provide you with further details. |
|
@lekemula Did you encounter this with the
Is there a particular operation you attempted that triggered the endless loop? If you encountered the problem on a different project, can you provide a reproducible example? |
|
@castwide, sorry for the ambiguity, but I meant the closed-source project at my current job. So I ran the
v0.59.2_definition_benchmark.json.gz main_definition_benchmark.json.gz Note: The process on Here are some stats regarding our project: I fear that the exact DSL methods inference might be a little too expensive an operation. Maybe we should go with a more "naive approach" for the sake of performance? SIDE NOTE: regarding |
|
@lekemula Thanks for clarifying. It looks like my attempt to minimize the amount of macro processing performed during catalog operations is insufficient for very large codebases.
I tend to agree. Would you like to profile your solution in #1202? If its performance is acceptable, we can merge that instead. AFAICT, the worst case scenario is that the language server might surface some incompletely inferred macros as |
@castwide Unfortunately, the #1202 showed no difference. However, while removing the lines that seem to be causing the performance bottleneck based on vernier traces, the performance improved, and there were no failing specs (at least locally). I'm wondering what that call was added for? |
This seem to be causing the performance issues reported #1194 (comment)

No description provided.