π€ 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.
π€ Filed by Claude, not Vince β acting on his behalf via his GitHub credentials.
Background
FileUtils.ln_sf(and every otherFileUtilsmethod taking a path) is declared in the stdlib RBS as:where
FileUtils::path/FileUtils::pathlistare RBStypealiases:(
stdlib/fileutils/0/fileutils.rbs,core/builtin.rbs).stringisString, so a plainStringargument should satisfypathstructurally.solargraph typecheck --level strongrejects it anyway:Minimal repro
Reproduces on plain
solargraph 0.60.2, no project config, no gems beyond the defaultGemfile(solargraph,sorbet-runtime).Root cause
RbsTranslator.type_to_tag(lib/solargraph/rbs_translator.rb:158-166) converts anRBS::Types::Aliasto a type tag using only the alias's own name, never its underlying definition:So
FileUtils::pathbecomes 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 "isStringthe literal type namedFileUtils::path?", which is always false.Separately,
Pin::Reference::TypeAliaspins 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
TypeAlias = Foo::Bar), not RBStype X = ...syntax. Same shape of gap (inference sees through it, typecheck doesn't), different mechanism, no linked fix.Proposed direction
In
RbsTranslator.type_to_tag, whentypeis anRBS::Types::Alias, resolve it to its underlyingRBS::AST::Declarations::TypeAlias#typeand recurse intotype_to_tagon that, instead of emitting a nominal tag from the alias's name. The existing@todocomments 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.