Skip to content

@!override with @overload doesn't dispatch per call site -- applies only the first overload's signature to every call #1283

Description

@apiology

@!override with @overload doesn't dispatch per call site -- it applies only the first overload's signature to every call, unlike an identical @overload block on a real def

Issue drafted by Claude (Anthropic), filed on behalf of @apiology.

Summary

A method with multiple @overload tags on its own def dispatches
correctly per call site (the matching overload's param/return types
apply). The identical @overload block applied via @!override to an
existing method pin does not -- every call site gets the first
overload's signature, regardless of which overload actually matches the
arguments, including a false-positive argument-type rejection when a
later call's arguments only match a later overload.

Reproduction

module Passthrough
  extend self

  # @overload identity(arguments)
  #   @param arguments [Array<Hash>]
  #   @return [Array<Hash>]
  # @overload identity(arguments)
  #   @param arguments [Array<String>]
  #   @return [Array<String>]
  def identity(arguments)
    arguments
  end
end

# @param [Hash] hash
# @return [Hash]
def wrap_hash(hash)
  Passthrough.identity([hash])[0]
end

# @param [String] str
# @return [String]
def wrap_str(str)
  Passthrough.identity([str])[0]
end

solargraph typecheck --level strong on this: 0 problems -- both
wrap_hash and wrap_str resolve against the correct overload.

Now the same @overload block, moved off the def and onto an
@!override targeting an otherwise-plain method:

module Passthrough
  extend self
  def identity(arguments)
    arguments
  end
end

# @!override Passthrough#identity
#   @overload identity(arguments)
#     @param arguments [Array<Hash>]
#     @return [Array<Hash>]
#   @overload identity(arguments)
#     @param arguments [Array<String>]
#     @return [Array<String>]

# @param [Hash] hash
# @return [Hash]
def wrap_hash(hash)
  Passthrough.identity([hash])[0]
end

# @param [String] str
# @return [String]
def wrap_str(str)
  Passthrough.identity([str])[0]
end

Actual

Passthrough#identity return type could not be inferred
Declared return type ::String does not match inferred type ::Hash, nil for #wrap_str
Wrong argument type for Passthrough#identity: arguments expected Array<Hash>, received Array<String>

wrap_hash (matches overload #1) resolves fine. wrap_str (needs
overload #2) instead gets overload #1's Array<Hash> param type and
Array<Hash> return type forced onto it -- a genuine false positive,
since Array<String> is valid input per the second overload.

Expected

@!override-attached @overloads dispatch per call site identically to
@overloads on a real def -- the two reproductions above should
produce the same (clean) result.

Impact

This blocks using @!override/config/annotations_*.rb-style local
stubs to give multiple, argument-dependent signatures to third-party gem
methods whose source can't be edited directly -- exactly the case
@!override exists for. A single generic (@generic T) signature is
also not always sufficient (e.g. when a method's return type genuinely
differs by argument category, not just by a uniform type parameter), so
@overload is the only way to express that -- and it's broken
specifically in the one context (@!override) where it's needed most.

Environment

  • solargraph 0.60.3 (apiology/solargraph fork, branch 2026-08-04, revision 71f16f832d6e)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions