low_type 0.4.0 → 0.5.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: efc284f18360245938c679ce476f0588ea31560da0b75a1076d080d8d687e07c
4
- data.tar.gz: 7839a82ee075a6436203aaf991be31267a180e9b35f94a4a444365506f4700d8
3
+ metadata.gz: 5862d1077a767f4d1df0df6f535a3b8c5d0de646a23088ea9679316a11a57f9e
4
+ data.tar.gz: ae4b3a7e0d57ab5e088e8d666de25df1b121d248b832a343a13565f1cb44d6b5
5
5
  SHA512:
6
- metadata.gz: b2260394d54e93340450d2d4cfccd93e3d462f91c39fec4778205c52699590f4467639ac847cf9faa697b5e468308ee1d917c077670b30a4e9d41ca3413bc7b7
7
- data.tar.gz: 521ee18436fac4d04caa4a2787e93b65367ccbe0fdd69eee7b7479316e2d7642e0a99628fedc8fc93996c28f2620a91d51407143c4ec1baa02040a0478d5a4e8
6
+ metadata.gz: ddf2c76d224b5dc51a5503f7635d9c6f0e2de8ee3c9dccdf9cc1e7eb6105fe88cb1045438a2a40322ceb78128c6a8a5604cc2db5d1f8bb71eeaf23d28f0ccc99
7
+ data.tar.gz: ceb481d5488ce8b217b5715f43991280359b846acee3d7f0fa205ab485f17e4c1da48c914db3b293a37a2ee21b5516ab5334b8240c69a9ab69730bbbfdc7cca7
data/lib/low_type.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'redefiner'
4
4
  require_relative 'type_expression'
5
+ require_relative 'value_expression'
5
6
 
6
7
  module LowType
7
8
  # We do as much as possible on class load rather than on instantiation to be thread-safe and efficient.
@@ -28,19 +29,22 @@ module LowType
28
29
  end
29
30
 
30
31
  class << klass
31
- def low_methods
32
- @low_methods ||= {}
33
- end
34
-
32
+ # Public API.
35
33
  def type(expression)
36
34
  # TODO: Runtime type expression for the supplied variable.
37
35
  end
38
36
  alias_method :low_type, :type
39
37
 
38
+ # Public API.
40
39
  def value(expression)
41
- # TODO: Cancel out a type expression.
40
+ ::LowType.value(expression)
42
41
  end
43
42
  alias_method :low_value, :value
43
+
44
+ # Internal API.
45
+ def low_methods
46
+ @low_methods ||= {}
47
+ end
44
48
  end
45
49
 
46
50
  parser = Parser.new(file_path: LowType.file_path(klass:))
@@ -53,6 +57,7 @@ module LowType
53
57
  Hash.define_singleton_method('[]', hash_class_method)
54
58
  end
55
59
 
60
+ # Internal API.
56
61
  class << self
57
62
  def file_path(klass:)
58
63
  caller.find { |callee| callee.end_with?("<class:#{klass}>'") }.split(':').first
@@ -65,8 +70,11 @@ module LowType
65
70
  def value?(expression)
66
71
  !expression.respond_to?(:new) && expression != Integer
67
72
  end
73
+
74
+ def value(type)
75
+ TypeExpression.new(default_value: ValueExpression.new(value: type))
76
+ end
68
77
  end
69
78
 
70
- class ValueExpression; end
71
79
  class Boolean; end # TrueClass or FalseClass
72
80
  end
@@ -0,0 +1,11 @@
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/parser.rb CHANGED
@@ -21,8 +21,10 @@ module LowType
21
21
 
22
22
  def self.return_node(method_node:)
23
23
  # Only a lambda defined immediately after a method's parameters is considered a return type expression.
24
- node = method_node.compact_child_nodes.find { |node| node.is_a?(Prism::StatementsNode) }.body.first
24
+ statements_node = method_node.compact_child_nodes.find { |node| node.is_a?(Prism::StatementsNode) }
25
+ return nil if statements_node.nil? # Sometimes developers define methods without code inside them.
25
26
 
27
+ node = statements_node.body.first
26
28
  return node if node.is_a?(Prism::LambdaNode)
27
29
 
28
30
  nil
data/lib/redefiner.rb CHANGED
@@ -4,6 +4,8 @@ require_relative 'parser'
4
4
  require_relative 'type_expression'
5
5
 
6
6
  module LowType
7
+ class ReturnError < StandardError; end
8
+
7
9
  class Redefiner
8
10
  class << self
9
11
  def redefine_methods(method_nodes:, private_start_line:, klass:)
@@ -17,15 +19,20 @@ module LowType
17
19
 
18
20
  define_method(name) do |*args, **kwargs|
19
21
  klass.low_methods[name].params.each do |param_proxy|
22
+ # Get argument value or default value.
20
23
  value = param_proxy.position ? args[param_proxy.position] : kwargs[param_proxy.name]
21
24
  value = param_proxy.type_expression.default_value if value.nil? && param_proxy.type_expression.default_value != :LOW_TYPE_UNDEFINED
22
- param_proxy.type_expression.validate!(value:, name: param_proxy.name)
25
+ # Validate argument type.
26
+ param_proxy.type_expression.validate!(value:, name: param_proxy.name, error_type: ArgumentError, error_keyword: 'required')
27
+ # Handle value(type) special case.
28
+ value = value.value if value.is_a?(ValueExpression)
29
+ # Redefine argument value.
23
30
  param_proxy.position ? args[param_proxy.position] = value : kwargs[param_proxy.name] = value
24
31
  end
25
32
 
26
33
  if return_expression
27
34
  return_value = super(*args, **kwargs)
28
- return_expression.validate!(value: return_value, name:)
35
+ return_expression.validate!(value: return_value, name:, error_type: ReturnError, error_keyword: 'return')
29
36
  return return_value
30
37
  end
31
38
 
@@ -39,22 +46,12 @@ module LowType
39
46
  end
40
47
  end
41
48
 
42
- def return_type_expression(method_node:)
43
- return_node = Parser.return_node(method_node:)
44
- return nil if return_node.nil?
45
-
46
- expression = eval(return_node.slice).call
47
-
48
- return expression if expression.class == TypeExpression
49
- TypeExpression.new(type: expression)
50
- end
51
-
52
49
  def params_with_type_expressions(method_node:)
53
50
  return [] if method_node.parameters.nil?
54
51
 
55
52
  params = method_node.parameters.slice
56
53
  proxy_method = eval("-> (#{params}) {}")
57
- required_args, required_kwargs = Redefiner.required_args(proxy_method:)
54
+ required_args, required_kwargs = required_args(proxy_method:)
58
55
 
59
56
  typed_method = eval(
60
57
  <<~RUBY
@@ -84,9 +81,21 @@ module LowType
84
81
 
85
82
  # TODO: Write spec for this.
86
83
  rescue ArgumentError => e
87
- raise ArgumentError, "Incorrect param syntax"
84
+ raise ArgumentError, "Incorrect param syntax: #{e.message}"
85
+ end
86
+
87
+ def return_type_expression(method_node:)
88
+ return_node = Parser.return_node(method_node:)
89
+ return nil if return_node.nil?
90
+
91
+ expression = eval(return_node.slice).call
92
+
93
+ return expression if expression.class == TypeExpression
94
+ TypeExpression.new(type: expression)
88
95
  end
89
96
 
97
+ private
98
+
90
99
  def required_args(proxy_method:)
91
100
  required_args = []
92
101
  required_kwargs = {}
@@ -104,6 +113,11 @@ module LowType
104
113
 
105
114
  [required_args, required_kwargs]
106
115
  end
116
+
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)
120
+ end
107
121
  end
108
122
  end
109
123
  end
@@ -2,8 +2,9 @@ module LowType
2
2
  class TypeExpression
3
3
  attr_reader :types, :default_value
4
4
 
5
- def initialize(type:, default_value: :LOW_TYPE_UNDEFINED)
6
- @types = [type]
5
+ def initialize(type: nil, default_value: :LOW_TYPE_UNDEFINED)
6
+ @types = []
7
+ @types << type unless type.nil?
7
8
  @default_value = default_value
8
9
  end
9
10
 
@@ -24,10 +25,10 @@ module LowType
24
25
  @default_value == :LOW_TYPE_UNDEFINED
25
26
  end
26
27
 
27
- def validate!(value:, name:)
28
+ def validate!(value:, name:, error_type:, error_keyword:)
28
29
  if value.nil?
29
30
  return true if @default_value.nil?
30
- raise ArgumentError, "Missing required argument of type '#{@types.join(', ')}' for '#{name}'" if required?
31
+ raise error_type, "Missing #{error_keyword} value of type '#{@types.join(', ')}' for '#{name}'" if required?
31
32
  end
32
33
 
33
34
  @types.each do |type|
@@ -39,20 +40,24 @@ module LowType
39
40
  end
40
41
  end
41
42
 
42
- raise TypeError, "Invalid type '#{value.class}' for '#{name}'"
43
+ raise TypeError, "Invalid type '#{value.class}' for '#{name}'. Valid types: [#{@types.join(', ')}]"
43
44
  end
44
45
  end
45
46
  end
46
47
 
47
48
  class Object
48
49
  class << self
50
+ # For "Type | [type_expression/type/value]" situations, redirecting to or generating a type expression from types.
49
51
  # "|" is not defined on Object class and this is the most compute-efficient way to achieve our goal (world peace).
52
+ # "|" is overridable by any child object. While we could def/undef this method, this approach is actually lighter.
50
53
  # "|" bitwise operator on Integer is not defined when the receiver is an Integer class, so we are not in conflict.
51
54
  def |(expression)
52
55
  if expression.class == ::LowType::TypeExpression
56
+ # We pass our type into their type expression.
53
57
  expression | self
54
58
  expression
55
59
  else
60
+ # We turn our type into a type expression and pass in their [type_expression/type/value].
56
61
  type_expression = ::LowType::TypeExpression.new(type: self)
57
62
  type_expression | expression
58
63
  end
@@ -0,0 +1,11 @@
1
+ class ValueExpression
2
+ attr_reader :value
3
+
4
+ def initialize(value:)
5
+ @value = value
6
+ end
7
+
8
+ def class
9
+ @value
10
+ end
11
+ 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.4.0'
4
+ VERSION = '0.5.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.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -18,10 +18,12 @@ extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - lib/low_type.rb
21
+ - lib/method_proxy.rb
21
22
  - lib/param_proxy.rb
22
23
  - lib/parser.rb
23
24
  - lib/redefiner.rb
24
25
  - lib/type_expression.rb
26
+ - lib/value_expression.rb
25
27
  - lib/version.rb
26
28
  homepage: https://codeberg.org/low_ruby/low_type
27
29
  licenses: []