Class: Rigor::Type::Refined

Inherits:
Object
  • Object
show all
Includes:
AcceptanceRouter, ValueSemantics
Defined in:
lib/rigor/type/refined.rb,
sig/rigor/type.rbs

Overview

Refined[base, predicate_id] — predicate-subset half of the OQ3 refinement-carrier strategy (ADR-3, 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.

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 },
  not_lowercase: ->(v) { v.is_a?(String) && v != v.downcase },
  uppercase: ->(v) { v.is_a?(String) && v == v.upcase },
  not_uppercase: ->(v) { v.is_a?(String) && v != v.upcase },
  numeric: ->(v) { ruby_numeric_literal?(v) },
  not_numeric: ->(v) { v.is_a?(String) && !ruby_numeric_literal?(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) },
  # `literal-string` is a flow-tracked predicate, not a value-level predicate: a String is
  # literal-string when it is known to come from a source-code literal (or composition of
  # literals). Every concrete `Constant<String>` is already literal by construction, so the
  # inspection recogniser returns true for any String — the property is really tracked in the flow
  # analysis (interpolation, concatenation, RBS::Extended `return: literal-string`) rather than
  # recovered by inspecting an arbitrary string.
  literal_string: ->(v) { v.is_a?(String) }
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ValueSemantics

included

Constructor Details

#initialize(base, predicate_id) ⇒ Refined

Returns a new instance of Refined.

Parameters:

  • base (Type::t)
  • predicate_id (Symbol)

Raises:

  • (ArgumentError)


34
35
36
37
38
39
40
# File 'lib/rigor/type/refined.rb', line 34

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

#baseType::t (readonly)

Returns the value of attribute base.

Returns:

  • (Type::t)


32
33
34
# File 'lib/rigor/type/refined.rb', line 32

def base
  @base
end

#predicate_idSymbol (readonly)

Returns the value of attribute predicate_id.

Returns:

  • (Symbol)


32
33
34
# File 'lib/rigor/type/refined.rb', line 32

def predicate_id
  @predicate_id
end

Class Method Details

.ruby_numeric_literal?(value) ⇒ Boolean

Returns true when value is a String that is a single, complete Ruby numeric literal. Total over arbitrary input — never raises (Prism reports malformed input through errors, it does not throw).

Parameters:

  • value (Object)

    typically a Constant#value

Returns:

  • (Boolean)

    true when value is a String that is a single, complete Ruby numeric literal. Total over arbitrary input — never raises (Prism reports malformed input through errors, it does not throw).



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/rigor/type/refined.rb', line 144

def self.ruby_numeric_literal?(value)
  return false unless value.is_a?(String)
  return false if value.empty?
  # A numeric literal carries no whitespace; reject any leading / trailing / interior space so the
  # *whole* string must be the literal (Prism would otherwise accept a trailing-space `"1 "`).
  return false if value.match?(/\s/)
  return false unless value.match?(NUMERIC_LITERAL_PREFIX)

  result = Prism.parse(value)
  return false unless result.errors.empty?

  body = result.value.statements&.body
  return false unless body && body.size == 1

  node = body.first
  NUMERIC_LITERAL_NODES.any? { |klass| node.is_a?(klass) }
end

Instance Method Details

#==Boolean

Parameters:

  • other (Object)

Returns:

  • (Boolean)


177
# File 'sig/rigor/type.rbs', line 177

def ==: (untyped other) -> bool

#acceptsAcceptsResult

Parameters:

  • other (Type::t)
  • mode: (accepts_mode)

Returns:



175
# File 'sig/rigor/type.rbs', line 175

def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult

#botTrinary

Returns:



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

def bot
  Trinary.no
end

#complement_predicate_idSymbol?

Returns the registered complement predicate id, or nil when no pair is registered for this predicate.

Returns:

  • (Symbol, nil)

    the registered complement predicate id, or nil when no pair is registered for this predicate.



222
223
224
# File 'lib/rigor/type/refined.rb', line 222

def complement_predicate_id
  COMPLEMENT_PAIRS[predicate_id]
end

#describe(verbosity = :short) ⇒ String

Parameters:

  • verbosity (Symbol) (defaults to: :short)

Returns:

  • (String)


42
43
44
45
46
47
# File 'lib/rigor/type/refined.rb', line 42

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

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

#dynamicTrinary

Returns:



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

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

#erase_to_rbsString

Erases to the base nominal: every refinement MUST erase to its base per rbs-erasure.md.

Returns:

  • (String)


51
52
53
# File 'lib/rigor/type/refined.rb', line 51

def erase_to_rbs
  base.erase_to_rbs
end

#hashInteger

Returns:

  • (Integer)


178
# File 'sig/rigor/type.rbs', line 178

def hash: () -> Integer

#inspectString

Returns:

  • (String)


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

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

Parameters:

  • value (Object)

Returns:

  • (Boolean)


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

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

  !!recogniser.call(value)
end

#topTrinary

Returns:



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

def top
  Trinary.no
end