Skip to content

Bug: Type checking rejects valid subclass instances — strict equality instead of is_a? #28

@obsidiannnn

Description

@obsidiannnn

type_matches_value? in type_expression.rb (line 134) uses strict class equality (type == value.class) to validate argument types. This causes subclass instances to be incorrectly rejected, even though they satisfy the type contract via inheritance.

Reproduction

class Animal; end
class Dog < Animal; end

class Kennel
  include LowType

  def admit(pet: Animal)
    "Welcome, #{pet.class}!"
  end
end
Kennel.new.admit(pet: Dog.new)
# => Raises Low::ArgumentTypeError: Invalid argument type 'Dog' for parameter 'pet'. Valid types: 'Animal'
# Expected: "Welcome, Dog!" — because Dog IS an Animal

This also affects IO/File, Numeric/Integer/Float, and any user-defined class hierarchy.

Root Cause
In lib/expressions/type_expression.rb line 134:

def type_matches_value?(type:, value:, proxy:)
  if type.instance_of?(Class)
    return type.match?(value:) if Low::TypeQuery.complex_type?(expression: type)
    return type == value.class  # strict equality rejects subclasses
  end
  ...
end

Proposed Fix
Replace strict equality with Ruby's is_a? check:

- return type == value.class
+ return value.is_a?(type)

This aligns with how Ruby itself handles type checking — is_a? is what case/when, ===, and idiomatic Ruby type guards use.

Test Plan

  • Subclass instance accepted for parent type (Dog for Animal)
  • Multi-level inheritance (GoldenRetriever < Dog < Animal)
  • Core Ruby hierarchies (File for IO, Integer for Numeric)
  • Existing tests still pass (no regressions)

I would like to open a PR to fix this if the approach looks good. Open to discuss alternative solutions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions