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.
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
This also affects IO/File, Numeric/Integer/Float, and any user-defined class hierarchy.
Root Cause
In lib/expressions/type_expression.rb line 134:
Proposed Fix
Replace strict equality with Ruby's is_a? check:
This aligns with how Ruby itself handles type checking — is_a? is what case/when, ===, and idiomatic Ruby type guards use.
Test Plan
I would like to open a PR to fix this if the approach looks good. Open to discuss alternative solutions.