Class: Rigor::Type::Refined

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/type/refined.rb

Overview

‘Refined[base, predicate_id]` — predicate-subset half of the OQ3 refinement-carrier strategy ([ADR-3](docs/adr/3-type-representation.md), Working Decision Option C). Sibling of `Type::Difference`, which carries the point-removal half.

lowercase-string = Refined[Nominal[String], :lowercase]
uppercase-string = Refined[Nominal[String], :uppercase]
numeric-string   = Refined[Nominal[String], :numeric]

The carrier wraps a base type and a ‘predicate_id` Symbol drawn from PREDICATES. The recogniser is invoked at constant-fold and acceptance time over a `Constant<base>` value; for non-Constant receivers the carrier is a marker the catalog tier consults to project `String#downcase` / `String#upcase` (etc.) into the matching refinement.

Display routes through CANONICAL_NAMES: registered ‘(base_class_name, predicate_id)` pairs print in their kebab-case spelling (`lowercase-string`); unregistered combinations fall back to the `base & predicate?` operator form per [`type-operators.md`](docs/type-specification/type-operators.md).

Construction MUST go through ‘Type::Combinator.refined` / the per-name factories (`Combinator.lowercase_string`, `Combinator.uppercase_string`, `Combinator.numeric_string`). Direct `.new` is an internal escape hatch for tests and combinator’s own implementation.

Constant Summary collapse

PREDICATES =
{
  lowercase: ->(v) { v.is_a?(String) && v == v.downcase },
  uppercase: ->(v) { v.is_a?(String) && v == v.upcase },
  numeric: ->(v) { v.is_a?(String) && NUMERIC_STRING_PATTERN.match?(v) },
  decimal_int: ->(v) { v.is_a?(String) && DECIMAL_INT_STRING_PATTERN.match?(v) },
  octal_int: ->(v) { v.is_a?(String) && OCTAL_INT_STRING_PATTERN.match?(v) },
  hex_int: ->(v) { v.is_a?(String) && HEX_INT_STRING_PATTERN.match?(v) }
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base, predicate_id) ⇒ Refined

Returns a new instance of Refined.

Raises:

  • (ArgumentError)


39
40
41
42
43
44
45
# File 'lib/rigor/type/refined.rb', line 39

def initialize(base, predicate_id)
  raise ArgumentError, "predicate_id must be a Symbol" unless predicate_id.is_a?(Symbol)

  @base = base
  @predicate_id = predicate_id
  freeze
end

Instance Attribute Details

#baseObject (readonly)

Returns the value of attribute base.



37
38
39
# File 'lib/rigor/type/refined.rb', line 37

def base
  @base
end

#predicate_idObject (readonly)

Returns the value of attribute predicate_id.



37
38
39
# File 'lib/rigor/type/refined.rb', line 37

def predicate_id
  @predicate_id
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



76
77
78
# File 'lib/rigor/type/refined.rb', line 76

def ==(other)
  other.is_a?(Refined) && base == other.base && predicate_id == other.predicate_id
end

#accepts(other, mode: :gradual) ⇒ Object



72
73
74
# File 'lib/rigor/type/refined.rb', line 72

def accepts(other, mode: :gradual)
  Inference::Acceptance.accepts(self, other, mode: mode)
end

#botObject



64
65
66
# File 'lib/rigor/type/refined.rb', line 64

def bot
  Trinary.no
end

#describe(verbosity = :short) ⇒ Object



47
48
49
50
51
52
# File 'lib/rigor/type/refined.rb', line 47

def describe(verbosity = :short)
  named = canonical_name
  return named if named

  "#{base.describe(verbosity)} & #{predicate_id}?"
end

#dynamicObject



68
69
70
# File 'lib/rigor/type/refined.rb', line 68

def dynamic
  base.respond_to?(:dynamic) ? base.dynamic : Trinary.no
end

#erase_to_rbsObject

Erases to the base nominal: every refinement MUST erase to its base per [‘rbs-erasure.md`](docs/type-specification/rbs-erasure.md).



56
57
58
# File 'lib/rigor/type/refined.rb', line 56

def erase_to_rbs
  base.erase_to_rbs
end

#hashObject



81
82
83
# File 'lib/rigor/type/refined.rb', line 81

def hash
  [Refined, base, predicate_id].hash
end

#inspectObject



85
86
87
# File 'lib/rigor/type/refined.rb', line 85

def inspect
  "#<Rigor::Type::Refined #{describe(:short)}>"
end

#matches?(value) ⇒ Boolean

Recognises a Ruby value against this carrier’s predicate. The trinary return is intentional: ‘true` / `false` when the predicate registry decides, `nil` when the predicate is unknown to the registry, so callers (today Inference::Acceptance) can fall through to gradual-mode `:maybe`. rubocop:disable Style/ReturnNilInPredicateMethodDefinition

Returns:

  • (Boolean)


96
97
98
99
100
101
# File 'lib/rigor/type/refined.rb', line 96

def matches?(value)
  recogniser = PREDICATES[predicate_id]
  return nil if recogniser.nil?

  !!recogniser.call(value)
end

#topObject



60
61
62
# File 'lib/rigor/type/refined.rb', line 60

def top
  Trinary.no
end