low_type 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5862d1077a767f4d1df0df6f535a3b8c5d0de646a23088ea9679316a11a57f9e
4
- data.tar.gz: ae4b3a7e0d57ab5e088e8d666de25df1b121d248b832a343a13565f1cb44d6b5
3
+ metadata.gz: d845917e9ed4afc79db302fe7f5a2955a4e299d7bd415e82e2f26af3b14cd25f
4
+ data.tar.gz: f9213b5731f3805ebf1e99efa0ccdd741b955464403842aeaa171d0b41fff146
5
5
  SHA512:
6
- metadata.gz: ddf2c76d224b5dc51a5503f7635d9c6f0e2de8ee3c9dccdf9cc1e7eb6105fe88cb1045438a2a40322ceb78128c6a8a5604cc2db5d1f8bb71eeaf23d28f0ccc99
7
- data.tar.gz: ceb481d5488ce8b217b5715f43991280359b846acee3d7f0fa205ab485f17e4c1da48c914db3b293a37a2ee21b5516ab5334b8240c69a9ab69730bbbfdc7cca7
6
+ metadata.gz: 6066f8c29073720201a83438c58067ef37d2b02b493f108e31bee624069b68a7049e3a64aebe20da851f7b8e13e7d599c02deb1adbb9050ccd271a895933c6c2
7
+ data.tar.gz: 16c144045ae7666c1ab42610e641d9b4946b14cc9adf22023239a55dbfcbc5a8da714b53c94972a883249008512bcc56a60b465ce9ba9da1908a0151856afa7c
data/lib/errors.rb ADDED
@@ -0,0 +1,3 @@
1
+ class ArgumentTypeError < TypeError; end;
2
+ class LocalTypeError < TypeError; end;
3
+ class ReturnTypeError < TypeError; end;
@@ -0,0 +1,11 @@
1
+ module LowType
2
+ class ProxyInterface
3
+ def error_type(value:)
4
+ raise NotImplementedError
5
+ end
6
+
7
+ def error_message(value:, line: nil)
8
+ raise NotImplementedError
9
+ end
10
+ end
11
+ end
data/lib/low_type.rb CHANGED
@@ -29,19 +29,6 @@ module LowType
29
29
  end
30
30
 
31
31
  class << klass
32
- # Public API.
33
- def type(expression)
34
- # TODO: Runtime type expression for the supplied variable.
35
- end
36
- alias_method :low_type, :type
37
-
38
- # Public API.
39
- def value(expression)
40
- ::LowType.value(expression)
41
- end
42
- alias_method :low_value, :value
43
-
44
- # Internal API.
45
32
  def low_methods
46
33
  @low_methods ||= {}
47
34
  end
@@ -57,21 +44,38 @@ module LowType
57
44
  Hash.define_singleton_method('[]', hash_class_method)
58
45
  end
59
46
 
60
- # Internal API.
61
47
  class << self
48
+ # Public API.
49
+
50
+ def config
51
+ config = Struct.new(:type_assignment, :deep_type_check)
52
+ @config ||= config.new(false, false)
53
+ end
54
+
55
+ def configure
56
+ yield(config)
57
+
58
+ if config.type_assignment
59
+ require_relative 'type_assignment'
60
+ include TypeAssignment
61
+ end
62
+ end
63
+
64
+ # Internal API.
65
+
62
66
  def file_path(klass:)
63
67
  caller.find { |callee| callee.end_with?("<class:#{klass}>'") }.split(':').first
64
68
  end
65
69
 
66
- def type?(expression)
67
- expression.respond_to?(:new) || expression == Integer || (expression.is_a?(Hash) && expression.keys.first.respond_to?(:new) && expression.values.first.respond_to?(:new))
70
+ def type?(type)
71
+ type.respond_to?(:new) || type == Integer || (type.is_a?(::Hash) && type.keys.first.respond_to?(:new) && type.values.first.respond_to?(:new))
68
72
  end
69
73
 
70
74
  def value?(expression)
71
75
  !expression.respond_to?(:new) && expression != Integer
72
76
  end
73
77
 
74
- def value(type)
78
+ def value(type:)
75
79
  TypeExpression.new(default_value: ValueExpression.new(value: type))
76
80
  end
77
81
  end
@@ -0,0 +1,21 @@
1
+ require_relative '../interfaces/proxy_interface'
2
+ require_relative '../errors'
3
+
4
+ module LowType
5
+ class LocalProxy < ProxyInterface
6
+ attr_reader :type_expression, :name
7
+
8
+ def initialize(type_expression:, name:)
9
+ @type_expression = type_expression
10
+ @name = name
11
+ end
12
+
13
+ def error_type(value:)
14
+ LocalTypeError
15
+ end
16
+
17
+ def error_message(value:, line:)
18
+ "Invalid variable type #{value.class} in '#{@name.class}' on line #{line}. Valid types: '#{@type_expression.valid_types.join(', ')}'"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module LowType
2
+ class MethodProxy
3
+ attr_reader :name, :params, :return_proxy
4
+
5
+ def initialize(name:, params: [], return_proxy: nil)
6
+ @name = name
7
+ @params = params
8
+ @return_proxy = return_proxy
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ require_relative '../interfaces/proxy_interface'
2
+ require_relative '../errors'
3
+
4
+ module LowType
5
+ class ParamProxy < ProxyInterface
6
+ attr_reader :type_expression, :name, :type, :position
7
+
8
+ def initialize(type_expression:, name:, type:, position: nil)
9
+ @type_expression = type_expression
10
+ @name = name
11
+ @type = type
12
+ @position = position
13
+ end
14
+
15
+ def error_type(value:)
16
+ return ArgumentError if value.nil?
17
+ ArgumentTypeError
18
+ end
19
+
20
+ def error_message(value:, line: nil)
21
+ return "Missing argument for parameter '#{@name}'. Position: #{@position}" if value.nil?
22
+
23
+ "Invalid argument type '#{value.class}' for parameter '#{@name}'. Valid types: '#{@type_expression.valid_types.join(', ')}'"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ require_relative '../interfaces/proxy_interface'
2
+ require_relative '../errors'
3
+
4
+ module LowType
5
+ class ReturnProxy < ProxyInterface
6
+ attr_reader :type_expression, :name
7
+
8
+ def initialize(type_expression:, name:)
9
+ @type_expression = type_expression
10
+ @name = name
11
+ end
12
+
13
+ def error_type(value:)
14
+ ReturnTypeError
15
+ end
16
+
17
+ def error_message(value:, line: nil)
18
+ "Invalid return type '#{value.class}' for method '#{@name}'. Valid types: '#{@type_expression.valid_types.join(', ')}'"
19
+ end
20
+ end
21
+ end
data/lib/redefiner.rb CHANGED
@@ -1,11 +1,10 @@
1
- require_relative 'method_proxy'
2
- require_relative 'param_proxy'
1
+ require_relative 'proxies/method_proxy'
2
+ require_relative 'proxies/param_proxy'
3
+ require_relative 'proxies/return_proxy'
3
4
  require_relative 'parser'
4
5
  require_relative 'type_expression'
5
6
 
6
7
  module LowType
7
- class ReturnError < StandardError; end
8
-
9
8
  class Redefiner
10
9
  class << self
11
10
  def redefine_methods(method_nodes:, private_start_line:, klass:)
@@ -13,9 +12,9 @@ module LowType
13
12
  method_nodes.each do |method_node|
14
13
  name = method_node.name
15
14
  params = Redefiner.params_with_type_expressions(method_node:)
16
- return_expression = Redefiner.return_type_expression(method_node:)
15
+ return_proxy = Redefiner.return_proxy(method_node:)
17
16
 
18
- klass.low_methods[name] = MethodProxy.new(name:, params:, return_expression:)
17
+ klass.low_methods[name] = MethodProxy.new(name:, params:, return_proxy:)
19
18
 
20
19
  define_method(name) do |*args, **kwargs|
21
20
  klass.low_methods[name].params.each do |param_proxy|
@@ -23,16 +22,16 @@ module LowType
23
22
  value = param_proxy.position ? args[param_proxy.position] : kwargs[param_proxy.name]
24
23
  value = param_proxy.type_expression.default_value if value.nil? && param_proxy.type_expression.default_value != :LOW_TYPE_UNDEFINED
25
24
  # Validate argument type.
26
- param_proxy.type_expression.validate!(value:, name: param_proxy.name, error_type: ArgumentError, error_keyword: 'required')
25
+ param_proxy.type_expression.validate!(value:, proxy: param_proxy)
27
26
  # Handle value(type) special case.
28
27
  value = value.value if value.is_a?(ValueExpression)
29
28
  # Redefine argument value.
30
29
  param_proxy.position ? args[param_proxy.position] = value : kwargs[param_proxy.name] = value
31
30
  end
32
31
 
33
- if return_expression
32
+ if return_proxy
34
33
  return_value = super(*args, **kwargs)
35
- return_expression.validate!(value: return_value, name:, error_type: ReturnError, error_keyword: 'return')
34
+ return_proxy.type_expression.validate!(value: return_value, proxy: klass.low_methods[name].return_proxy)
36
35
  return return_value
37
36
  end
38
37
 
@@ -84,14 +83,14 @@ module LowType
84
83
  raise ArgumentError, "Incorrect param syntax: #{e.message}"
85
84
  end
86
85
 
87
- def return_type_expression(method_node:)
86
+ def return_proxy(method_node:)
88
87
  return_node = Parser.return_node(method_node:)
89
88
  return nil if return_node.nil?
90
89
 
91
90
  expression = eval(return_node.slice).call
91
+ expression = TypeExpression.new(type: expression) unless expression.is_a?(TypeExpression)
92
92
 
93
- return expression if expression.class == TypeExpression
94
- TypeExpression.new(type: expression)
93
+ ReturnProxy.new(type_expression: expression, name: method_node.name)
95
94
  end
96
95
 
97
96
  private
@@ -114,9 +113,9 @@ module LowType
114
113
  [required_args, required_kwargs]
115
114
  end
116
115
 
117
- # Type expressions are eval()'d in the context of this module class (the instance doesn't exist yet) so alias API.
118
- def value(expression)
119
- ::LowType.value(expression)
116
+ # Value expressions are eval()'d in the context of this module class (the instance doesn't exist yet) so alias API.
117
+ def value(type)
118
+ LowType.value(type:)
120
119
  end
121
120
  end
122
121
  end
@@ -0,0 +1,59 @@
1
+ require_relative 'proxies/local_proxy'
2
+ require_relative 'type_expression'
3
+ require_relative 'value_expression'
4
+
5
+ module TypeAssignment
6
+ class AssignmentError < StandardError; end
7
+
8
+ def type(type_expression)
9
+ object = type_expression.default_value
10
+
11
+ if !LowType.value?(object)
12
+ raise AssignmentError, "Single-instance objects like #{object} are not supported"
13
+ end
14
+
15
+ local_proxy = LowType::LocalProxy.new(type_expression:, name: self)
16
+ object.instance_variable_set('@local_proxy', local_proxy)
17
+
18
+ type_expression.validate!(value: object, proxy: local_proxy, line: caller_locations(1, 1).first.lineno)
19
+
20
+ def object.with_type=(value)
21
+ local_proxy = self.instance_variable_get('@local_proxy')
22
+ type_expression = local_proxy.type_expression
23
+ type_expression.validate!(value:, proxy: local_proxy, line: caller_locations(1, 1).first.lineno)
24
+
25
+ # We can't reassign self in Ruby so we reassign instance variables instead.
26
+ value.instance_variables.each do |variable|
27
+ self.instance_variable_set(variable, value.instance_variable_get(variable))
28
+ end
29
+
30
+ self
31
+ end
32
+
33
+ return object.value if object.is_a?(ValueExpression)
34
+
35
+ object
36
+ end
37
+ alias_method :low_type, :type
38
+
39
+ def value(type)
40
+ LowType.value(type:)
41
+ end
42
+ alias_method :low_value, :value
43
+ end
44
+
45
+ module LowType
46
+ class Array < ::Array
47
+ def self.[](type)
48
+ return TypeExpression.new(type: [type]) if LowType.type?(type)
49
+ super
50
+ end
51
+ end
52
+
53
+ class Hash < ::Hash
54
+ def self.[](type)
55
+ return TypeExpression.new(type:) if LowType.type?(type)
56
+ super
57
+ end
58
+ end
59
+ end
@@ -1,3 +1,5 @@
1
+ require_relative 'proxies/param_proxy'
2
+
1
3
  module LowType
2
4
  class TypeExpression
3
5
  attr_reader :types, :default_value
@@ -25,22 +27,28 @@ module LowType
25
27
  @default_value == :LOW_TYPE_UNDEFINED
26
28
  end
27
29
 
28
- def validate!(value:, name:, error_type:, error_keyword:)
30
+ def validate!(value:, proxy:, line: nil)
29
31
  if value.nil?
30
32
  return true if @default_value.nil?
31
- raise error_type, "Missing #{error_keyword} value of type '#{@types.join(', ')}' for '#{name}'" if required?
33
+ raise proxy.error_type(value:), proxy.error_message(value:, line:) if required?
32
34
  end
33
35
 
34
36
  @types.each do |type|
35
37
  return true if LowType.type?(type) && type == value.class
36
38
  # TODO: Shallow validation of enumerables could be made deeper with user config.
37
- return true if type.class == Array && value.class == Array && type.first == value.first.class
38
- if type.class == Hash && value.class == Hash && type.keys[0] == value.keys[0].class && type.values[0] == value.values[0].class
39
+ return true if type.class == ::Array && value.class == ::Array && type.first == value.first.class
40
+ if type.class == ::Hash && value.class == ::Hash && type.keys[0] == value.keys[0].class && type.values[0] == value.values[0].class
39
41
  return true
40
42
  end
41
43
  end
42
44
 
43
- raise TypeError, "Invalid type '#{value.class}' for '#{name}'. Valid types: [#{@types.join(', ')}]"
45
+ raise proxy.error_type(value:), proxy.error_message(value:, line:)
46
+ end
47
+
48
+ def valid_types
49
+ types = @types.map { |type| type.inspect.to_s }
50
+ return types + ['nil'] if @default_value.nil?
51
+ types
44
52
  end
45
53
  end
46
54
  end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LowType
4
- VERSION = '0.5.0'
4
+ VERSION = '0.7.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: low_type
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -17,11 +17,16 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - lib/errors.rb
21
+ - lib/interfaces/proxy_interface.rb
20
22
  - lib/low_type.rb
21
- - lib/method_proxy.rb
22
- - lib/param_proxy.rb
23
23
  - lib/parser.rb
24
+ - lib/proxies/local_proxy.rb
25
+ - lib/proxies/method_proxy.rb
26
+ - lib/proxies/param_proxy.rb
27
+ - lib/proxies/return_proxy.rb
24
28
  - lib/redefiner.rb
29
+ - lib/type_assignment.rb
25
30
  - lib/type_expression.rb
26
31
  - lib/value_expression.rb
27
32
  - lib/version.rb
@@ -46,5 +51,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
46
51
  requirements: []
47
52
  rubygems_version: 3.7.2
48
53
  specification_version: 4
49
- summary: An elegant way to define types in Ruby
54
+ summary: Elegant types in Ruby
50
55
  test_files: []
data/lib/method_proxy.rb DELETED
@@ -1,11 +0,0 @@
1
- module LowType
2
- class MethodProxy
3
- attr_reader :name, :params, :return_expression
4
-
5
- def initialize(name:, params: [], return_expression: nil)
6
- @name = name
7
- @params = params
8
- @return_expression = return_expression
9
- end
10
- end
11
- end
data/lib/param_proxy.rb DELETED
@@ -1,12 +0,0 @@
1
- module LowType
2
- class ParamProxy
3
- attr_reader :type_expression, :name, :type, :position
4
-
5
- def initialize(type_expression:, name:, type:, position: nil)
6
- @type_expression = type_expression
7
- @name = name
8
- @type = type
9
- @position = position
10
- end
11
- end
12
- end