From d72479b8654a2941a0ebbde23b052b4e24eee366 Mon Sep 17 00:00:00 2001 From: Joel Drapper Date: Thu, 28 Nov 2024 15:13:03 +0000 Subject: [PATCH] Make `_Callable` and `Procable` powered by `_Interface` --- lib/literal/types.rb | 9 +++++---- lib/literal/types/callable_type.rb | 31 ----------------------------- lib/literal/types/interface_type.rb | 28 +++++++++++++++++++++++++- lib/literal/types/procable_type.rb | 12 ----------- test/types.test.rb | 9 +++++++++ 5 files changed, 41 insertions(+), 48 deletions(-) delete mode 100644 lib/literal/types/callable_type.rb delete mode 100644 lib/literal/types/procable_type.rb diff --git a/lib/literal/types.rb b/lib/literal/types.rb index 4c093729..dfd1e249 100644 --- a/lib/literal/types.rb +++ b/lib/literal/types.rb @@ -4,7 +4,6 @@ module Literal::Types autoload :AnyType, "literal/types/any_type" autoload :ArrayType, "literal/types/array_type" autoload :BooleanType, "literal/types/boolean_type" - autoload :CallableType, "literal/types/callable_type" autoload :ClassType, "literal/types/class_type" autoload :ConstraintType, "literal/types/constraint_type" autoload :DescendantType, "literal/types/descendant_type" @@ -20,7 +19,6 @@ module Literal::Types autoload :NeverType, "literal/types/never_type" autoload :NilableType, "literal/types/nilable_type" autoload :NotType, "literal/types/not_type" - autoload :ProcableType, "literal/types/procable_type" autoload :RangeType, "literal/types/range_type" autoload :SetType, "literal/types/set_type" autoload :TruthyType, "literal/types/truthy_type" @@ -28,8 +26,11 @@ module Literal::Types autoload :UnionType, "literal/types/union_type" autoload :VoidType, "literal/types/void_type" + ProcableType = InterfaceType.new(:to_proc).freeze + CallableType = InterfaceType.new(:call).freeze + NilableBooleanType = NilableType.new(BooleanType::Instance).freeze - NilableCallableType = NilableType.new(CallableType::Instance).freeze + NilableCallableType = NilableType.new(CallableType).freeze NilableJSONDataType = NilableType.new(JSONDataType::Instance).freeze NilableLambdaType = NilableType.new(LambdaType).freeze NilableProcableType = NilableType.new(ProcableType).freeze @@ -67,7 +68,7 @@ def _Boolean? # Matches if the value responds to `#call`. def _Callable - CallableType::Instance + CallableType end # Nilabl version of `_Callable` diff --git a/lib/literal/types/callable_type.rb b/lib/literal/types/callable_type.rb deleted file mode 100644 index 019d8199..00000000 --- a/lib/literal/types/callable_type.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -# @api private -class Literal::Types::CallableType - include Literal::Type - - def inspect - "_Callable" - end - - def ===(value) - value.respond_to?(:call) - end - - def >=(other) - (self == other) || (Proc == other) || (Method == other) || case other - when Literal::Types::IntersectionType - other.types.any? { |type| Literal.subtype?(type, of: self) } - when Literal::Types::ConstraintType - other.object_constraints.any? { |type| Literal.subtype?(type, of: self) } - when Literal::Types::InterfaceType - other.methods.include?(:call) - else - false - end - end - - Instance = new.freeze - - freeze -end diff --git a/lib/literal/types/interface_type.rb b/lib/literal/types/interface_type.rb index be2283e0..c5a38498 100644 --- a/lib/literal/types/interface_type.rb +++ b/lib/literal/types/interface_type.rb @@ -2,6 +2,15 @@ # @api private class Literal::Types::InterfaceType + # TODO: We can generate this and make it much more extensive. + METHOD_TYPE_MAPPINGS = { + :call => Set[Proc, Method], + :to_proc => Set[Proc, Method], + :to_s => Set[String], + }.freeze + + include Literal::Type + def initialize(*methods) raise Literal::ArgumentError.new("_Interface type must have at least one method.") if methods.size < 1 @methods = methods @@ -9,9 +18,26 @@ def initialize(*methods) attr_reader :methods - def inspect = "_Interface(#{@methods.map(&:inspect).join(', ')})" + def inspect + "_Interface(#{@methods.map(&:inspect).join(', ')})" + end def ===(value) @methods.all? { |m| value.respond_to?(m) } end + + def >=(other) + case other + when Literal::Types::InterfaceType + @methods.all? { |m| other.methods.include?(m) } + when Module + @methods.map { |m| METHOD_TYPE_MAPPINGS[m] }.all? { |types| types&.include?(other) } + when Literal::Types::IntersectionType + other.types.any? { |type| Literal.subtype?(type, of: self) } + when Literal::Types::ConstraintType + other.object_constraints.any? { |type| Literal.subtype?(type, of: self) } + else + false + end + end end diff --git a/lib/literal/types/procable_type.rb b/lib/literal/types/procable_type.rb deleted file mode 100644 index 7860c353..00000000 --- a/lib/literal/types/procable_type.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -# @api private -module Literal::Types::ProcableType - def self.inspect = "_Procable" - - def self.===(value) - Proc === value || value.respond_to?(:to_proc) - end - - freeze -end diff --git a/test/types.test.rb b/test/types.test.rb index 209efa83..05fc7aa5 100644 --- a/test/types.test.rb +++ b/test/types.test.rb @@ -274,6 +274,15 @@ def expect_type_error(expected:, actual:, message:) Expected: _Interface(:each, :map, :select) Actual (NilClass): nil MSG + + assert _Interface(:a) >= _Interface(:a) + assert _Interface(:a) >= _Interface(:a, :b) + assert _Interface(:a, :b) >= _Interface(:a, :b) + refute _Interface(:a, :b) >= _Interface(:a) + refute _Interface(:a) >= _Interface(:b) + assert _Interface(:call) >= _Callable + assert _Interface(:to_proc) >= _Procable + refute _Interface(:to_proc, :random_method) >= Proc end test "_Intersection" do