Skip to content

Narrow other via self.class == other.class in flow-sensitive typing - #1367

Draft
apiology wants to merge 1 commit into
castwide:masterfrom
apiology:fix-class-equality-narrowing
Draft

apiology wants to merge 1 commit into
castwide:masterfrom
apiology:fix-class-equality-narrowing

Conversation

@apiology

Copy link
Copy Markdown
Contributor

Claude: This PR was written by Claude Code on behalf of @apiology.

Problem: A common #==/#eql? idiom checks self.class == other.class before comparing fields, but flow-sensitive typing does not narrow other from that check, so every field comparison after it fails at strong.

class UniqueType
  def eql? other
    self.class == other.class &&
      # Unresolved call to name on Object
      @name == other.name
  end
end

other stays declared Object even though the guard proves it shares self's runtime class.

Solution: self.class == other.class guarantees other's runtime class equals self's, and that runtime class is always a subtype of the enclosing method's declared instance type — so narrowing other to that declared type is a sound upper bound, the same tradeoff #process_isa documents for is_a?. FlowSensitiveTyping never received the enclosing closure, so it could not resolve self's type; this threads it through and adds #process_class_eq, covering both an && chain and a return false unless guard. Skipped for class methods, whose self.class is always ::Class.

Clears all 9 @sg-ignore markers on this repo's own #==/#eql? methods, confirmed by a byte-identical full-workspace typecheck problem set once the markers are removed.

A `self.class == other.class` guard (or `self.class.eql?(other.class)`)
guarantees other has the same runtime class as self. Since self is an
instance of the enclosing method's declared type, other is too --
narrowing other to that declared type is a sound upper bound, the same
tradeoff #process_isa already documents for is_a?.

FlowSensitiveTyping had no path to this fact: it never received the
enclosing closure at all, so it could not resolve self's declared
type. Threading closure through (already the shape #process_isa's
sibling would need for a class-scope check) and adding
#process_class_eq covers the guard in both an && chain and a
return-unless-style guard.

This clears all 9 `@sg-ignore flow sensitive typing should support
.class == .class` / `needs to handle self.class == other.class`
markers already present on master's own eql?/== methods
(unique_type.rb, type_methods.rb, equality.rb, api_map.rb), confirmed
via full-workspace `solargraph typecheck --level strong`: message set
is byte-identical to the pre-fix baseline (531 problems) once the
markers are removed, so nothing regresses.
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