lib/dry/schema/predicate_inferrer.rb in dry-schema-1.4.3 vs lib/dry/schema/predicate_inferrer.rb in dry-schema-1.5.0

- old
+ new

@@ -1,196 +1,16 @@ # frozen_string_literal: true -require 'dry/core/cache' +require "dry/types/predicate_inferrer" module Dry module Schema - # PredicateInferrer is used internally by `Macros::Value` - # for inferring type-check predicates from type specs. - # # @api private - class PredicateInferrer - extend Dry::Core::Cache + class PredicateInferrer < ::Dry::Types::PredicateInferrer + Compiler = ::Class.new(superclass::Compiler) - TYPE_TO_PREDICATE = { - DateTime => :date_time?, - FalseClass => :false?, - Integer => :int?, - NilClass => :nil?, - String => :str?, - TrueClass => :true?, - BigDecimal => :decimal? - }.freeze - - REDUCED_TYPES = { - [[[:true?], [:false?]]] => %i[bool?] - }.freeze - - HASH = %i[hash?].freeze - - ARRAY = %i[array?].freeze - - NIL = %i[nil?].freeze - - # Compiler reduces type AST into a list of predicates - # - # @api private - class Compiler - # @return [PredicateRegistry] - # @api private - attr_reader :registry - - # @api private - def initialize(registry) - @registry = registry - end - - # @api private - def infer_predicate(type) - [TYPE_TO_PREDICATE.fetch(type) { :"#{type.name.split('::').last.downcase}?" }] - end - - # @api private - def visit(node) - meth, rest = node - public_send(:"visit_#{meth}", rest) - end - - # @api private - def visit_nominal(node) - type = node[0] - predicate = infer_predicate(type) - - if registry.key?(predicate[0]) - predicate - else - [type?: type] - end - end - - # @api private - def visit_hash(_) - HASH - end - - # @api private - def visit_array(_) - ARRAY - end - - # @api private - def visit_lax(node) - visit(node) - end - - # @api private - def visit_constructor(node) - other, * = node - visit(other) - end - - # @api private - def visit_enum(node) - other, * = node - visit(other) - end - - # @api private - def visit_sum(node) - left_node, right_node, = node - left = visit(left_node) - right = visit(right_node) - - if left.eql?(NIL) - right - else - [[left, right]] - end - end - - # @api private - def visit_constrained(node) - other, rules = node - predicates = visit(rules) - - if predicates.empty? - visit(other) - else - [*visit(other), *merge_predicates(predicates)] - end - end - - # @api private - def visit_any(_) - EMPTY_ARRAY - end - - # @api private - def visit_and(node) - left, right = node - visit(left) + visit(right) - end - - # @api private - def visit_predicate(node) - pred, args = node - - if pred.equal?(:type?) - EMPTY_ARRAY - elsif registry.key?(pred) - *curried, _ = args - values = curried.map { |_, v| v } - - if values.empty? - [pred] - else - [pred => values[0]] - end - else - EMPTY_ARRAY - end - end - - private - - # @api private - def merge_predicates(nodes) - preds, merged = nodes.each_with_object([[], {}]) do |predicate, (ps, h)| - if predicate.is_a?(::Hash) - h.update(predicate) - else - ps << predicate - end - end - - merged.empty? ? preds : [*preds, merged] - end - end - - # @return [Compiler] - # @api private - attr_reader :compiler - - # @api private - def initialize(registry) + def initialize(registry = PredicateRegistry.new) @compiler = Compiler.new(registry) - end - - # Infer predicate identifier from the provided type - # - # @return [Symbol] - # - # @api private - def [](type) - self.class.fetch_or_store(type.hash) do - predicates = compiler.visit(type.to_ast) - - if predicates.is_a?(Hash) - predicates - else - REDUCED_TYPES[predicates] || predicates - end - end end end end end