Update solargraph to apiology/solargraph@92e4f816 - #466
Merged
Conversation
…needed @sg-ignore comments Bumps the pinned solargraph fork branch (2026-08-04) past two regressions found and fixed upstream this session: - Gems whose require path differs from their RubyGems package name (e.g. activesupport/active_support) failed to resolve, silently dropping their transitive dependencies (broke Minitest discovery entirely). Filed and fixed via castwide/solargraph#1252. - Restarg parameters with a bare (non-generic) per-element RBS type, like String#start_with?(*prefixes), produced a blank "expected" type in typecheck error messages. Filed and fixed via castwide/solargraph#1223. With both fixes in place, 54 @sg-ignore markers tagged in the earlier sg-ignore audit are no longer needed (the underlying gaps they suppressed are now resolved) and have been removed after iterative `solargraph typecheck --level strong` verification converged to zero "Unneeded @sg-ignore comment" results. rubocop and the full test suite (308 tests) pass clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kk5dVYpJr49U3YqNtFHwTL
Picks up further upstream progress; removes one more now-unneeded @sg-ignore comment revealed by it. rubocop and full test suite (308 tests) still pass clean; typecheck problem count unchanged (127). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kk5dVYpJr49U3YqNtFHwTL
The 26 markers tagged upstream-type-annotation:rbs-4-1-regression (citing castwide/solargraph#1228) are all instances of the same Hash#fetch(...)-on-interface-typed-key pattern. #1228 is a narrow, still-unmerged patch for one specific shape (Hash{Symbol => Class<X>}) and doesn't cover these. The real root cause is a genuine solargraph engine gap, not missing/wrong RBS data: Hash::_Key's signature is correct, solargraph just has no structural conformance check for RBS interface-typed parameters generally (Hash::_Key, _ToAry, _Each, _ToS, etc.), falling back to a blanket allow instead. That's tracked generally at castwide/solargraph#1232, which is a better citation and correctly buckets this as tool-limitation rather than upstream-type-annotation. Pure comment relabel; typecheck problem count unchanged (127). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kk5dVYpJr49U3YqNtFHwTL
Appears to fix castwide/solargraph#1232 (RBS interface-typed parameter structural conformance): 26 of 27 @sg-ignore tool-limitation:issue-1232 markers became unneeded and were removed after iterative `solargraph typecheck --level strong` verification converged to zero "Unneeded @sg-ignore comment" results. rubocop and the full test suite (308 tests) pass clean; typecheck problem count 127 (was 125 before this update, +2 unexplained, not yet investigated). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kk5dVYpJr49U3YqNtFHwTL
No observable effect on checkoff: typecheck output identical (127 problems, same breakdown, 0 unneeded @sg-ignore comments) before and after. rubocop and full test suite (308 tests) pass clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kk5dVYpJr49U3YqNtFHwTL
This marker's real cause isn't a solargraph flow-sensitivity gap: out's non-nil-ness after the loop depends on date_url_params.empty?, which is only true because convert_for_prefix (called inside the loop) mutates date_url_params as a side effect. Proving that requires tracing a side effect across a method boundary, which no static type checker attempts (confirmed: TypeScript intentionally doesn't narrow closure-captured variables across function boundaries for the same reason; mypy is documented as losing narrowing across loop bodies generally). Unlike the other 9 remaining type-narrowing markers -- all local, single-method guard shapes matching castwide/solargraph#1249/#1254 exactly -- this one isn't something to wait on upstream for. Tagged with a new inherent-limit:cross-procedural-narrowing slug instead, distinct from tool-limitation since there's no tool gap to close. Real fix, if wanted, is refactoring convert to check out.nil? directly rather than via the date_url_params proxy. Pure comment relabel; typecheck problem count unchanged (127). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kk5dVYpJr49U3YqNtFHwTL
def_delegators(:@Mocks, ...) already has a working Solargraph macro (config/annotations_misc.rb), but only emits a generic @return [Mocha::Mock] with no type parameter. Where a test wanted a more precise Mocha::Mock & SomeType, the only option was a hand-written wrapper calling mocks.foo directly, which needed two @sg-ignore dynamic-metaprogramming markers each (MyOpenStruct's method_missing dispatch isn't statically resolvable). Adds typed_delegate(mock_sym, type), modeled directly on the existing typed_let_mock macro ($1/$2 positional substitution for name and type), backed by a real define_method delegating to mocks.public_send(mock_sym). Replaces the 5 hand-written wrapper methods (test_project_selectors.rb, test_tasks.rb, test_task_selectors.rb x3) with one-line typed_delegate calls. Verified: rubocop clean, 308 tests pass (functionally identical behavior), solargraph typecheck shows zero errors on the new call sites and zero "Unneeded @sg-ignore comment" results -- the macro resolves the precise intersection type correctly, no ignore needed anywhere. Typecheck problem count unchanged at 127 (these 10 were already being suppressed by the removed ignores, not counted before either). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kk5dVYpJr49U3YqNtFHwTL
typed_let_mock silently skipped Mocha's responds_like_instance_of runtime check to work around ruby-asana's Resource#respond_to_missing? crashing on Class#allocate-bypassed @attributes. Using typed_mock everywhere instead restores that runtime duck-type verification, so Solargraph's static typing and Mocha's runtime checking both apply to every mocked call site.
Switching test call sites from typed_let_mock to typed_mock turned on Mocha's responds_like_instance_of check against these classes, which were previously empty. Add the specific methods checkoff actually calls on each ruby-asana collection proxy, with matching arity, so responds_like_instance_of can verify both that the method exists and that the call signature is compatible -- instead of just providing a type label for Solargraph.
Three independent issues surfaced once every mock in the suite went through responds_like_instance_of instead of typed_let_mock's skip: - Asana::Resources::Resource#respond_to_missing? calls to_h.key?, which crashes on the nil @_data left behind when Mocha allocates a responder via Class#allocate (bypassing #initialize). Prepend a module that treats a nil @_data as "responds to anything" -- statically declared attrs (attr_reader :gid, etc.) are real methods and were never affected; this only covers the dynamically-proxied ones, which have no real data to check against anyway. - responds_like_instance_of can't build a responder for value types (Symbol, Integer, ...) since Ruby has no allocator for them (typed_mock :period, Symbol and similar). Rescue and skip runtime checking for just those -- the static type is all we get there. - test_clients.rb typed the yielded Asana::Client.new block param as Asana::Client, but ruby-asana actually yields an Asana::Client::Configuration (that's what really has #authentication and #default_headers). typed_let_mock's skip had been masking this mismatch; split it into two correctly-typed mocks. With all three fixed, typed_let_mock (and the let_single_mock helper it was built on) has no reason to differ from typed_mock and no remaining call sites, so delete both along with their Solargraph macros.
responds_like_instance_of can't build a responder for Symbol/Integer/ Float/etc. since Class#allocate has no heap representation to carve out for them. responds_like takes a real instance directly, so a literal value works without needing allocate at all -- this restores actual runtime checking for typed_mock :period, Symbol and friends instead of silently skipping it.
Curate attrs, then reject anything else Extend the same curated-allowlist pattern already used for Asana::ProxiedResourceClasses to Asana::Resources: Task#assignee_section and #start_at are real, current Asana API fields checkoff uses that this pinned ruby-asana gem never declared as attr_readers; #name is declared separately on every concrete resource subclass instead of the shared Resource base, which is what code handling a resource of unknown type (Checkoff::Resources#resource_by_gid) is typed against. Add attr_readers for all three. With the real gaps curated, flip respond_to_missing? on a nil-@_data responder from "responds to anything" to "responds to nothing extra" -- an uncurated attribute call now fails loudly instead of being silently accepted, which is what actually catches a typo like .nmae instead of .name. Guard #to_s/#inspect the same way, since Mocha's own error formatting calls back into them when rejecting a call and would otherwise crash on the same nil @_data before the real error is shown. Also fixes test_attachments.rb, which typed its resource mock as the generic Asana::Resources::Resource while calling Task-only methods (#gid, #name, #attach) -- retype it as Asana::Resources::Task to match what the production code actually documents.
json/parser/rbs/rubocop/yard were bumped in Gemfile.lock by an earlier bundle update in this branch's history, but the checked-in sorbet/rbi/gems/*.rbi stubs and rbs_collection.lock.yaml were never regenerated to match, so CI's "check for uncommitted diff after fresh install" step failed. Regenerated via ./fix.sh (bin/tapioca gems + bin/spoom srb bump), which also promotes test/unit/test_custom_field_param_converter.rb's sigil from typed: false to typed: true (it now passes at that level) and drops sorbet/rbi/todo.rbi, which now has nothing to generate.
Array(A, B) (RBS Tuple) and Array<A, B> (generic Array) describe the same runtime value, but Solargraph resolves inherited Array instance methods (#first, #empty?, #[]) against the two differently -- against a Tuple it either fails to resolve them at all or picks the wrong #[] overload (range instead of index). Switch to the generic form everywhere it's actually the correct type (not the rigid two-element shape it looked like at a glance): - Checkoff::Timing#in_period? and Checkoff::Projects#in_period?/ #project_ready?'s `period` isn't a fixed [Symbol, Integer] pair -- compound_in_period? splats a variable-length *args after the leading Symbol, and between_relative_days? takes two Integer args, so a valid period can be a 3-element array. Array<Symbol, Integer> is what this type actually is. - The four SelectorClasses::*::FunctionEvaluator#selector readers (and the shared FunctionEvaluator#fn? predicate they all feed) had the same shape, plus a `String` in the @PARAM tag that doesn't match anything actually passed at any call site or the base SelectorEvaluator#evaluate contract ([Symbol, Array<Symbol, Integer, Array>]) -- looks like a typo for `Symbol`, fixed as part of the same pass. #fn? also gained an explicit @return [Boolean] and was restructured from a chained && into an early return, which resolved a separate "return type could not be inferred" finding on the same line.
Same fix as 5a247df, just a spelling variant (no space after the comma) that the earlier replace_all missed.
URI::Generic#path is nilable in its RBS signature (not every URI scheme has one), and File.basename doesn't accept nil. The very same nilable uri.path is already guarded two lines below with `|| ''` for content_type_from_filename; apply the same fallback here for consistency and to close the real (if narrow) nil-crash risk.
@item is assigned from the section: constructor param (Asana::Resources::Section) -- the Project tag was a copy-paste leftover from ProjectSelectorEvaluator's identical structure.
[idx - 1, 0].max is real Array#max's signature, which is nilable because *some* array could be empty -- this one, being a 2-element literal, never is. Solargraph reports the resulting downto call on the unstripped Integer|nil as "Unresolved call" rather than a nil- safety warning; T.must documents the real invariant instead of leaving that ambiguous.
Overcommit::Hook::Message is defined upstream as `Message = Struct.new(:type, :file, :line, :content)`. The existing @sg-ignore on this method already documents that Solargraph misresolves the Overcommit::Hook::Message constant against this class's own nesting; combined with that, it also now checks the .new call against Struct.new's class-defining overload instead of the struct subclass's own instance constructor. Same root cause as the existing tag, but reported on a different line (inside the method body, not the method signature), so it needs its own @sg-ignore to be recognized.
Both were classified as tool-limitation:type-narrowing / issue #1254. Verified empirically that castwide/solargraph#1259 (already in our tracked branch) doesn't actually fix either shape: - sections.rb's raise-guard has an extra statement before the raise; #1259 only handles a branch that's exactly one raise/fail statement. - task_selectors.rb's `ARGV[n] || raise(...)` idiom goes through an entirely different code path (an :or node, not an :if node) that #1259 never touches at all. Reclassified to a dedicated subcategory pointing at the two follow-up comments filed on #1259, rather than the original #1254 issue, since that issue is (partially) fixed and these are what's left.
Restructure the guard into an if/else and assign the narrowed value to a new local with an explicit @type [Array<Symbol, Array>] tag before indexing it. Same net effect as the existing method-level param type, but the downcast is now explicit at the point it's needed rather than relying on Solargraph's overload resolution to infer a bound element type from the parameter alone.
An earlier commit (0322312) deleted this because local `bin/tapioca todo` said "Nothing to do", even after clearing tapioca.installed/ types.installed. CI's fresh clone disagreed -- its build job failed with `?? sorbet/rbi/todo.rbi` in git status, meaning its own fresh `bin/tapioca todo` run regenerates real content. This local environment's long-lived gem/cache state apparently resolves Parlour/Tapioca's own internal constants that a genuinely clean environment doesn't, unrelated to anything else in this PR. Restoring the pre-existing content (three unresolved Parlour/Tapioca constants) rather than guessing at new content, since CI's own regeneration should independently produce the same thing.
rbs 4.1.2 (pulled in by this Solargraph bump) adds real stdlib coverage for Forwardable#def_delegators. Combined with this repo's local YARD macro override on that same method (used to synthesize per-call-site @Mocks accessors), the combined pin no longer triggers macro expansion, so every method it used to synthesize resolved as an unknown call under strong typecheck. typed_delegate (already used elsewhere in these files) is not a real stdlib method, so it is not shadowed the same way. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EE1X8n7hnhDtkLPzUwYNE
FunctionEvaluator#fn? matches a selector's function-name element against both the Symbol and its to_s form, so string function names (e.g. 'in_portfolio_named?' alongside :in_portfolio_named?) are genuinely valid input, not just Symbols. The declared types only listed Symbol, Integer, Array (task/base) or a fixed Array(Symbol, Array) tuple (project), so under strong typecheck the stricter Solargraph fork now flags real call sites (e.g. [:in_portfolio_named?, 'My Portfolio']) as type mismatches. Add String to the allowed element types. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EE1X8n7hnhDtkLPzUwYNE
This Solargraph bump pulls in RuboCop 1.89.0 (from 1.88.2); the ratchet file's version comment needs to match or `make rubocop-ratchet` fails CI on the diff. No offenses changed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EE1X8n7hnhDtkLPzUwYNE
Sorbet has native, built-in understanding of Forwardable#def_delegators (synthesizing T.untyped methods for each delegated name), but no such support for this repo's custom typed_delegate define_method macro. Every other file converted from def_delegators to typed_delegate is typed: false, so Sorbet's method-existence check never runs there -- but test_timing.rb is typed: true and calls today_getter directly, so Sorbet's srb tc flagged it as undefined after the conversion. Define it as a literal method instead, which both typecheckers can see. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EE1X8n7hnhDtkLPzUwYNE
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.
Summary
solargraphfork branch (2026-08-04) to pick up two upstream fixes found and filed this session:activesupport/active_support) silently failed to resolve, breaking transitive-dependency discovery (this brokeMinitestresolution entirely). Filed and fixed via castwide/solargraph#1252.String#start_with?(*prefixes), produced a blankexpectedtype in typecheck error messages. Filed and fixed via castwide/solargraph#1223.@sg-ignoremarkers from the earlier sg-ignore audit are no longer needed (the gaps they suppressed are now resolved). Removed them, iteratingsolargraph typecheck --level strongto convergence (0 "Unneeded @sg-ignore comment" results).Test plan
bundle exec rubocopon all changed files ��� no offensesbundle exec rake test��� 308 tests, 0 failuresbin/solargraph typecheck --level strong��� 127 problems remain (down from 653 before these fixes), zero "Unneeded @sg-ignore comment" results