π€ Filed by Claude, not the account owner β acting on their behalf via their GitHub credentials.
Summary
RBS type syntax supports intersection types (A & B, see RBS syntax docs), and Solargraph parses & in inline RBS method signatures (#1173) without error. But the semantics aren't implemented: RbsTranslator#type_to_tag converts RBS::Types::Intersection the same way it converts RBS::Types::Union β by joining member types with , :
https://github.com/castwide/solargraph/blob/v0.60.2/lib/solargraph/rbs_translator.rb#L154-L155
when RBS::Types::Intersection
type.types.map { |member| type_to_tag(member) }.join(', ')
Since Solargraph::ComplexType has no internal representation for intersections (only comma-separated unions), A & B ends up behaving exactly like the union (A, B) once translated β assignable only where every member type would independently be accepted, rather than assignable anywhere any one member type is expected (the actual subtyping rule for intersections: A & B <: A and A & B <: B).
Reproduction
class Consumer
# @param project_obj [Asana::Resources::Project]
# @return [void]
def project_to_h(project_obj); end
end
class MockFactory
#: () -> (Mocha::Mock & Asana::Resources::Project)
def make_mock
Mocha::Mock.new
end
end
Consumer.new.project_to_h(MockFactory.new.make_mock)
solargraph typecheck --level strong reports:
Wrong argument type for Consumer#project_to_h: project_obj expected Asana::Resources::Project, received Mocha::Mock, Asana::Resources::Project
The "received Mocha::Mock, Asana::Resources::Project" is the union tag β proof the intersection collapsed into a union rather than being checked as a true intersection (which would have passed silently, since the argument's declared type includes Asana::Resources::Project as one conjunct).
Motivating use case
This came up trying to type Minitest+Mocha test doubles precisely: a Mocha::Mock configured via responds_like_instance_of(RealClass) duck-types correctly as RealClass at every call site the code under test exercises, but is statically only a Mocha::Mock. An intersection type would let call sites pass such doubles to methods declared @param x [RealClass] without widening to Object, adding a misleading union, or suppressing with @sg-ignore per call site (see apiology/checkoff#462, e.g. this thread, which hit this ~15-20 times).
What's needed
RbsTranslator (and/or ComplexType) needs an actual intersection representation, distinct from union, so type-compatibility checks honor A & B <: A / A & B <: B rather than flattening to a union tag.
π€ Filed by Claude, not the account owner β acting on their behalf via their GitHub credentials.
Summary
RBS type syntax supports intersection types (
A & B, see RBS syntax docs), and Solargraph parses&in inline RBS method signatures (#1173) without error. But the semantics aren't implemented:RbsTranslator#type_to_tagconvertsRBS::Types::Intersectionthe same way it convertsRBS::Types::Unionβ by joining member types with,:https://github.com/castwide/solargraph/blob/v0.60.2/lib/solargraph/rbs_translator.rb#L154-L155
Since
Solargraph::ComplexTypehas no internal representation for intersections (only comma-separated unions),A & Bends up behaving exactly like the union(A, B)once translated β assignable only where every member type would independently be accepted, rather than assignable anywhere any one member type is expected (the actual subtyping rule for intersections:A & B <: AandA & B <: B).Reproduction
solargraph typecheck --level strongreports:The "received Mocha::Mock, Asana::Resources::Project" is the union tag β proof the intersection collapsed into a union rather than being checked as a true intersection (which would have passed silently, since the argument's declared type includes
Asana::Resources::Projectas one conjunct).Motivating use case
This came up trying to type Minitest+Mocha test doubles precisely: a
Mocha::Mockconfigured viaresponds_like_instance_of(RealClass)duck-types correctly asRealClassat every call site the code under test exercises, but is statically only aMocha::Mock. An intersection type would let call sites pass such doubles to methods declared@param x [RealClass]without widening toObject, adding a misleading union, or suppressing with@sg-ignoreper call site (see apiology/checkoff#462, e.g. this thread, which hit this ~15-20 times).What's needed
RbsTranslator(and/orComplexType) needs an actual intersection representation, distinct from union, so type-compatibility checks honorA & B <: A/A & B <: Brather than flattening to a union tag.