Skip to content

Typecheck rejects arguments that satisfy an RBS type alias, because aliases aren't expanded before comparisonΒ #1255

Description

@apiology

πŸ€– Filed by Claude, not Vince β€” acting on his behalf via his GitHub credentials.

Background

FileUtils.ln_sf (and every other FileUtils method taking a path) is declared in the stdlib RBS as:

def self?.ln_sf: (src ::FileUtils::pathlist, dest ::FileUtils::path, ?noop: boolish, ?verbose: boolish) -> void

where FileUtils::path/FileUtils::pathlist are RBS type aliases:

type path = string | _ToPath
type pathlist = path | Array[path]

(stdlib/fileutils/0/fileutils.rbs, core/builtin.rbs). string is String, so a plain String argument should satisfy path structurally.

solargraph typecheck --level strong rejects it anyway:

Wrong argument type for FileUtils.ln_sf: src expected FileUtils::path, Array<FileUtils::path>, received String

Minimal repro

# typed: true
# frozen_string_literal: true

require 'fileutils'

class Repro
  # @param source [String]
  # @param dest [String]
  # @return [void]
  def link(source, dest)
    FileUtils.ln_sf(source, dest)
  end
end
$ bundle exec solargraph typecheck --level strong repro.rb
repro.rb:10: Wrong argument type for FileUtils.ln_sf: src expected FileUtils::path, Array<FileUtils::path>, received String

Reproduces on plain solargraph 0.60.2, no project config, no gems beyond the default Gemfile (solargraph, sorbet-runtime).

Root cause

RbsTranslator.type_to_tag (lib/solargraph/rbs_translator.rb:158-166) converts an RBS::Types::Alias to a type tag using only the alias's own name, never its underlying definition:

when RBS::Types::ClassInstance, RBS::Types::Alias, RBS::Types::Interface
  # `Alias` is a top-level type alias, e.g., 'bool' in "type bool = true | false"
  # @todo ensure these get resolved after processing all aliases
  # @todo handle recursive aliases
  #
  # `Interface represents a mix-in module which can be considered a
  # subtype of a consumer of it
  #
  type_tag(type.name, type.args)

So FileUtils::path becomes the nominal type tag "FileUtils::path", not the union "String, FileUtils::_ToPath" it's actually defined as. ComplexType::Conformance (lib/solargraph/complex_type/conformance.rb) has no handling for aliases at all β€” it never expands one before checking whether an argument's inferred type conforms, so the comparison degenerates to "is String the literal type named FileUtils::path?", which is always false.

Separately, Pin::Reference::TypeAlias pins do get created and indexed (rbs_map/conversions.rb:71-90) β€” so the alias is known to Solargraph, just not consulted during argument compatibility checking.

Related but distinct

Proposed direction

In RbsTranslator.type_to_tag, when type is an RBS::Types::Alias, resolve it to its underlying RBS::AST::Declarations::TypeAlias#type and recurse into type_to_tag on that, instead of emitting a nominal tag from the alias's name. The existing @todo comments at that exact call site already flag this as known/intended future work β€” this issue is a repro plus root-cause pointer for picking it up. Recursive aliases (also flagged in the @todo) would need cycle detection if tackled the same way.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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