Skip to content

Update solargraph to apiology/solargraph@92e4f816 - #466

Merged
apiology merged 26 commits into
mainfrom
update-solargraph-2026-08-04
Aug 6, 2026
Merged

Update solargraph to apiology/solargraph@92e4f816#466
apiology merged 26 commits into
mainfrom
update-solargraph-2026-08-04

Conversation

@apiology

@apiology apiology commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • Bumps the pinned solargraph fork branch (2026-08-04) to pick up two upstream fixes found and filed this session:
    • Gems whose require path differs from their RubyGems package name (e.g. activesupport/active_support) silently failed to resolve, breaking transitive-dependency discovery (this broke Minitest resolution 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 from the earlier sg-ignore audit are no longer needed (the gaps they suppressed are now resolved). Removed them, iterating solargraph typecheck --level strong to convergence (0 "Unneeded @sg-ignore comment" results).

Test plan

  • bundle exec rubocop on all changed files ��� no offenses
  • bundle exec rake test ��� 308 tests, 0 failures
  • bin/solargraph typecheck --level strong ��� 127 problems remain (down from 653 before these fixes), zero "Unneeded @sg-ignore comment" results

apiology and others added 26 commits August 5, 2026 22:03
…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
@apiology
apiology marked this pull request as ready for review August 6, 2026 23:49
@apiology
apiology merged commit 18b8a2f into main Aug 6, 2026
2 checks passed
@apiology
apiology deleted the update-solargraph-2026-08-04 branch August 6, 2026 23:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant