Module: Rigor::Builtins::HktBuiltins
- Defined in:
- lib/rigor/builtins/hkt_builtins.rb
Overview
ADR-20 slices 2c + 3 — Rigor-bundled Lightweight HKT registrations that ship with every analyzer instance. The set is intentionally small at v0.1.x: only the URIs whose payoff justifies hardcoded definitions. Plugin authors register more URIs through their manifests; user ‘.rbs` overlays register through the `%arigor:v1:hkt_register` / `%arigor:v1:hkt_define` annotations Slice 1 ships.
Today’s contents:
-
‘json::value` — the recursive sum stdlib’s ‘JSON.parse` returns. Body:
nil | true | false | Integer | Float | String | Array[App[json::value, K]] | Hash[K, App[json::value, K]]The reducer handles the self-recursive ‘App` nodes via lazy “tying-the-knot” (see HktReducer). `K = String` matches stdlib’s default key handling; ‘K = Symbol` matches `symbolize_names: true`.
Constant Summary collapse
- METHOD_RETURN_OVERRIDES =
{ # JSON — stdlib's `json` library. Upstream rbs declares # `(string, ?options) -> untyped`; the HKT-builtin tier # tightens to the recursive `json::value[K]` union. # `load_file` / `load_file!` share the `?options` slot # so the `symbolize_names: true` discriminator applies # to them too (just like `parse` / `load`). ["JSON", :parse, :singleton] => JSON_VALUE_SPEC, ["JSON", :parse!, :singleton] => JSON_VALUE_SPEC, ["JSON", :load, :singleton] => JSON_VALUE_SPEC, ["JSON", :load_file, :singleton] => JSON_VALUE_SPEC, ["JSON", :load_file!, :singleton] => JSON_VALUE_SPEC, # YAML.safe_load / Psych.safe_load — default # `permitted_classes: []` admits exactly the JSON # vocabulary (nil / true / false / Integer / Float / # String / Array / Hash), so the json::value tree # also describes them. When the call passes a literal # `permitted_classes: [Date, Symbol, ...]` Array, the # `:yaml_permitted_classes` post_reduce unions each # named class into the result. Non-literal options # (a variable, a constant reference, a `+ classes` # concat) silently no-op and the caller observes the # base json::value envelope only. YAML.load / # YAML.unsafe_load deliberately stay out of the # override table — they can return ANY Ruby object # and have no useful HKT envelope. ["YAML", :safe_load, :singleton] => YAML_SAFE_VALUE_SPEC, ["YAML", :safe_load_file, :singleton] => YAML_SAFE_VALUE_SPEC, ["Psych", :safe_load, :singleton] => YAML_SAFE_VALUE_SPEC, ["Psych", :safe_load_file, :singleton] => YAML_SAFE_VALUE_SPEC, # CSV.parse / CSV.read — no-headers shape only. # Upstream rbs declares broader return shapes but # the common case is `Array[Array[String?]]` which # the `csv::parsed[String]` URI matches. The # `headers: true` shape (`CSV::Table` of `CSV::Row`) # is NOT covered — calls passing the option fall # through to the upstream RBS type. CSV.foreach also # falls through (it yields rows rather than # returning a typed structure). ["CSV", :parse, :singleton] => CSV_PARSED_SPEC, ["CSV", :read, :singleton] => CSV_PARSED_SPEC }.freeze
Class Method Summary collapse
-
.apply_post_reduce(kind, reduced, arg_types) ⇒ Object
Slice 2c-bis — post-reduce hook.
-
.augment_with_yaml_permitted_classes(reduced, arg_types) ⇒ Object
Inspects arg_types for a ‘permitted_classes: [<Class>, …]` literal Array in the options Hash and unions each named class into the reduced result.
- .csv_parsed_body_tree ⇒ Object
- .csv_parsed_definition ⇒ Object
- .csv_parsed_registration ⇒ Object
-
.discriminated_args(spec, arg_types) ⇒ Object
Per-spec discriminator dispatch.
-
.json_symbolize_names?(arg_types) ⇒ Boolean
Returns true iff the call-site’s 2nd argument is a ‘Type::HashShape` carrying a literal `symbolize_names: true` entry.
- .json_value_body_tree ⇒ Object
- .json_value_definition ⇒ Object
- .json_value_registration ⇒ Object
-
.method_return_override(class_name:, method_name:, kind:, arg_types: nil, hkt_registry: nil) ⇒ Rigor::Type?
The reduced HKT type for the given (class_name, method_name, kind) triple, or ‘nil` when no built-in override is registered.
-
.permitted_class_nominals(value) ⇒ Object
Extract Singleton-class elements from a Tuple or Array-shape carrier, mapping each to its Nominal counterpart.
-
.registry ⇒ Rigor::Inference::HktRegistry
Frozen registry pre-seeded with all bundled HKT registrations + bodies.
Class Method Details
.apply_post_reduce(kind, reduced, arg_types) ⇒ Object
Slice 2c-bis — post-reduce hook. Receives the already- reduced ‘Type` and the call-site’s ‘arg_types`; returns a (possibly augmented) `Type`. `kind = nil` is the identity (passes the reduced type through unchanged). Only `:yaml_permitted_classes` is implemented today; plugin / Rigor-bundled callers wanting their own post-reduce hooks add a branch here.
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 278 def apply_post_reduce(kind, reduced, arg_types) case kind when :yaml_permitted_classes augment_with_yaml_permitted_classes(reduced, arg_types) else # `nil` (no post-reduce declared) and any future # unrecognised kind both pass the reduced type # through unchanged. Unknown kinds are silently # tolerated rather than raised because adding a # new kind on a Rigor upgrade should not crash a # stale METHOD_RETURN_OVERRIDES entry on the # caller side. reduced end end |
.augment_with_yaml_permitted_classes(reduced, arg_types) ⇒ Object
Inspects arg_types for a ‘permitted_classes: [<Class>, …]` literal Array in the options Hash and unions each named class into the reduced result. Non-literal `permitted_classes:` values (a variable, a constant reference, a concat) silently no-op and the caller observes the base json::value envelope only. Defensive against the various ways Ruby literal arrays surface as Rigor types: `Tuple` for a single element, `Tuple[Singleton<Date>, Singleton<Symbol>]` for multiple, `Nominal[Array, [Singleton<…>]]` if the analyzer widened (rare for literal arrays).
305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 305 def augment_with_yaml_permitted_classes(reduced, arg_types) return reduced unless arg_types.is_a?(Array) && arg_types.size >= 2 opts = arg_types[1] return reduced unless opts.is_a?(Rigor::Type::HashShape) value = opts.pairs[:permitted_classes] || opts.pairs["permitted_classes"] return reduced if value.nil? extras = permitted_class_nominals(value) return reduced if extras.empty? Rigor::Type::Combinator.union(reduced, *extras) end |
.csv_parsed_body_tree ⇒ Object
58 59 60 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 58 def csv_parsed_body_tree Rigor::Inference::HktBodyParser.parse(CSV_PARSED_BODY, params: [:K]) end |
.csv_parsed_definition ⇒ Object
90 91 92 93 94 95 96 97 98 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 90 def csv_parsed_definition Rigor::Inference::HktRegistry.definition_with_body_tree( uri: :"csv::parsed", params: [:K], body_tree: csv_parsed_body_tree, source_path: __FILE__, source_line: __LINE__ - 5 ) end |
.csv_parsed_registration ⇒ Object
81 82 83 84 85 86 87 88 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 81 def csv_parsed_registration Rigor::Inference::HktRegistry::Registration.new( uri: :"csv::parsed", arity: 1, variance: [:out], bound: Rigor::Type::Combinator.untyped ) end |
.discriminated_args(spec, arg_types) ⇒ Object
Per-spec discriminator dispatch. Slice 3 ships one built-in discriminator (‘json_symbolize_names`) that observes the optional 2nd argument’s ‘HashShape` for a literal `symbolize_names: true` entry. Plugin / Rigor- bundled callers wanting their own discriminators add a branch here.
246 247 248 249 250 251 252 253 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 246 def discriminated_args(spec, arg_types) default_args = spec[:args].map { |n| Rigor::Type::Nominal.new(n) } return default_args if arg_types.nil? return default_args unless spec[:discriminator] == :json_symbolize_names return default_args unless json_symbolize_names?(arg_types) [Rigor::Type::Nominal.new("Symbol")] end |
.json_symbolize_names?(arg_types) ⇒ Boolean
Returns true iff the call-site’s 2nd argument is a ‘Type::HashShape` carrying a literal `symbolize_names: true` entry. Anything else (no second arg, non-HashShape, missing key, non-literal `true`) returns false so the default `K = String` branch wins.
261 262 263 264 265 266 267 268 269 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 261 def json_symbolize_names?(arg_types) return false unless arg_types.is_a?(Array) && arg_types.size >= 2 opts = arg_types[1] return false unless opts.is_a?(Rigor::Type::HashShape) value = opts.pairs[:symbolize_names] || opts.pairs["symbolize_names"] value.is_a?(Rigor::Type::Constant) && value.value == true end |
.json_value_body_tree ⇒ Object
43 44 45 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 43 def json_value_body_tree Rigor::Inference::HktBodyParser.parse(JSON_VALUE_BODY, params: [:K]) end |
.json_value_definition ⇒ Object
71 72 73 74 75 76 77 78 79 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 71 def json_value_definition Rigor::Inference::HktRegistry.definition_with_body_tree( uri: :"json::value", params: [:K], body_tree: json_value_body_tree, source_path: __FILE__, source_line: __LINE__ - 5 ) end |
.json_value_registration ⇒ Object
62 63 64 65 66 67 68 69 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 62 def json_value_registration Rigor::Inference::HktRegistry::Registration.new( uri: :"json::value", arity: 1, variance: [:out], bound: Rigor::Type::Combinator.untyped ) end |
.method_return_override(class_name:, method_name:, kind:, arg_types: nil, hkt_registry: nil) ⇒ Rigor::Type?
Returns the reduced HKT type for the given (class_name, method_name, kind) triple, or ‘nil` when no built-in override is registered. When `arg_types` is supplied AND the entry carries a `:discriminator` symbol, the discriminator may swap the spec’s default args for an alternate (e.g. ‘JSON.parse(str, symbolize_names: true)` discriminates `K = Symbol` instead of the default `K = String`).
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 221 def method_return_override(class_name:, method_name:, kind:, arg_types: nil, hkt_registry: nil) spec = METHOD_RETURN_OVERRIDES[[class_name, method_name.to_sym, kind]] return nil unless spec args = discriminated_args(spec, arg_types) registration = hkt_registry&.registration(spec[:uri]) bound = registration&.bound || Rigor::Type::Combinator.untyped app = Rigor::Type::App.new(spec[:uri], args, bound: bound) reduced = if hkt_registry.nil? || !hkt_registry.defined?(spec[:uri]) app else hkt_registry.reduce(app) || app end apply_post_reduce(spec[:post_reduce], reduced, arg_types) end |
.permitted_class_nominals(value) ⇒ Object
Extract Singleton-class elements from a Tuple or Array-shape carrier, mapping each to its Nominal counterpart. Returns an empty array when no static Singletons are reachable (e.g. value is ‘Dynamic`, element types are non-Singleton, etc.).
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 325 def permitted_class_nominals(value) candidates = if value.is_a?(Rigor::Type::Tuple) value.elements elsif value.is_a?(Rigor::Type::Nominal) && value.class_name == "Array" && value.type_args.size == 1 element = value.type_args.first element.is_a?(Rigor::Type::Union) ? element.members : [element] else [] end candidates.filter_map do |c| c.is_a?(Rigor::Type::Singleton) ? Rigor::Type::Nominal.new(c.class_name) : nil end end |
.registry ⇒ Rigor::Inference::HktRegistry
Returns frozen registry pre-seeded with all bundled HKT registrations + bodies. Allocated fresh each call rather than memoised — memoisation through a module-level ‘@registry` ivar surfaces a `Ractor::IsolationError` in pool workers (the ivar’s contents include ‘HktBody::AppRef` Symbol-keyed structures that the current Ractor shareability audit hasn’t yet been walked through). The registry is small enough that per-Environment construction is acceptable; an eager-frozen constant is a future optimisation once ADR-15 phase 4b.x covers the dependency graph.
112 113 114 115 116 117 |
# File 'lib/rigor/builtins/hkt_builtins.rb', line 112 def registry Rigor::Inference::HktRegistry.new( registrations: [json_value_registration, csv_parsed_registration], definitions: [json_value_definition, csv_parsed_definition] ) end |