Skip to content

Introduce Boolean and Enum type support in LowType#34

Open
purvi1239 wants to merge 1 commit intolow-rb:mainfrom
purvi1239:main
Open

Introduce Boolean and Enum type support in LowType#34
purvi1239 wants to merge 1 commit intolow-rb:mainfrom
purvi1239:main

Conversation

@purvi1239
Copy link
Copy Markdown

Overview

This PR introduces support for Boolean and Enum types in LowType, improving expressiveness and allowing more precise runtime validation.

What’s Added

Boolean Type

  • Introduced a dedicated Boolean type
  • Validates strictly against true and false
  • Simplifies usage compared to TrueClass | FalseClass

Enum Type

  • Added Enum[val1, val2, ...] type constructor
  • Restricts values to a predefined set
  • Implemented using Enum::Definition with match? validation

Integration Details

  • Extended TypeExpression to support non-Class types via match?
  • Updated TypeQuery to recognize Enum definitions as valid types
  • Ensured compatibility with existing union logic (|)

Example Usage

def toggle(flag: Boolean | false)
end

def publish(status: Enum[:draft, :published] | :draft)
end

@maedi
Copy link
Copy Markdown
Member

maedi commented Mar 27, 2026

Thank you for this PR. There are a few people interested in this issue via GSoC and it will take a while to sort through the solutions and apply the most suitable one. I have a few proposals via GSoC, including a strong proposal, so I will have to get all of the solutions in my head at once somehow :) Your contribution is appreciated and I will read it and provide feedback

Comment thread lib/types/enum.rb
raise Low::ConfigError, "Enum.symbols expects only symbols, got #{non_symbols.map(&:inspect).join(', ')}"
end

private_class_method
Copy link
Copy Markdown
Member

@maedi maedi May 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The examples I see use private_class_method :build_enum_type the line after the class method is defined, can we use it this way?

require_relative '../../lib/expressions/type_expression'
require_relative '../../lib/syntax/union_types'

RSpec.describe 'Low::Types::Boolean and Low::Types::Enum' do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be 2 separate files, unit tests should test individual units

@purvi1239
Copy link
Copy Markdown
Author

purvi1239 commented May 3, 2026 via email

@purvi1239
Copy link
Copy Markdown
Author

purvi1239 commented May 3, 2026 via email

Comment thread lib/types/boolean.rb
"Expected true/false, got #{value.inspect}"
end

def self.inspect
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the normal output of .inspect to be avoided? Can we just use that?

Comment thread lib/types/enum.rb

private

def normalize_values(values)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If normalize_values ultimately ends up in Set.new() then they will be made unique automatically by Set

Comment thread lib/types/enum.rb
# type = Low::Types::Enum[:draft, :published]
# type.match?(value: :draft) # => true
def self.[](*values, strict: false)
definition = Definition.new(values: values, strict: strict)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definition.new(values:, strict:) is equivalent

Comment thread lib/types/enum.rb
end

# Convenience helper for symbol-only enums.
def self.symbols(*symbols, strict: false)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this only called from a spec? If so it shouldn't be in the implementation as it's just expecting data be a certain way. Move the method to spec_helper.rb or create a more literal/simpler expect in the spec just checking for the values. The concept of Enums be restricted to a certain type is not something the implementation needs... we don't have a feature like this to my memory. So it must be just a test/spec side thing

Comment thread lib/types/enum.rb
private_class_method

def self.build_enum_type(definition)
Class.new do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class feels like we don't need it. It's a wrapper around the Definition class... when we could just instantiate that class instead instead of this one

Comment thread lib/queries/type_query.rb
def matchable_type?(expression:)
expression.is_a?(Class) &&
expression.respond_to?(:match?) &&
expression.ancestors.include?(Low::Types::MatchableType)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep the first version simple. We can remove include Low::Types::MatchableType everywhere and just rely on expression.respond_to?(:match?) for now. We check the behaviour of the object rather than the type of the object (duck typing)

Copy link
Copy Markdown
Member

@maedi maedi May 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And a better type based way of checking this without adding an extra module would be to check if the class has Low::Types as one of its ancestors (works for both Enum and Definition. This would work in a perfect world where other people in other namespaces aren't adding their own complex types... which they're currently not. So match? is probably enough for now and could be added by anyone anywhere in another complex type in another namespace

@maedi
Copy link
Copy Markdown
Member

maedi commented May 4, 2026

Please see this discussion for a few good pointers on this issue:
#27

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.

2 participants