low_type 1.1.13 → 1.3.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: 6f0cac83a3d18258da13254f3d293d0630a718d7c2640bc56974d9973b78b330
4
- data.tar.gz: b6c8fda73a3ca4d6c6d2a5aa91015cd162641f42edce4c3b901e1c481f2152af
3
+ metadata.gz: a35463e7919196f86142b2120884cc3ddb70375569d14b9a5c48d734b002b942
4
+ data.tar.gz: 93f54ee40ee59d4bc95e81a05aacdd4c20a5c0b5c546e756f72337118d9d1f52
5
5
  SHA512:
6
- metadata.gz: b93f26a5ff5a22466aaa898201549205f3acef574488b887193a2c9fd0deef69894b679f4c966e01ac7fedc0326c69e694851bf0de9445f51922a7b6c12caa5f
7
- data.tar.gz: f377e9f4f5bc85cb1e51e1a0cc3c36c0d6ac1b057da181341924377fb208208fa35bc27a1e5070ced94270fa46351c56c6d93dda9f08a21c69d913ae43d9b5b4
6
+ metadata.gz: '00039a16689c9dedae5b0dde2af61e705d5959a354a58fe9321d46ef973d698da0339658fe51cee366051a15351ac6a3164b0a6f79ef2e14361e30f8058adb7b'
7
+ data.tar.gz: bc1e1ff7f780f51c5d902616c8593e955f7130a0e17ee0557038d1f31f6b948fd7d9306c3842cac149698ecfe932be3b08a526e4f93b73c262e4d995354712e8
@@ -40,26 +40,36 @@ module Low
40
40
  eval(proxy.value, binding, proxy.file_path, proxy.start_line) # rubocop:disable Security/Eval
41
41
  end
42
42
 
43
+ def class_evaluate(proxy:, class_binding:)
44
+ # Not a security risk because the code comes from a trusted source; the file that included lowtype.
45
+ eval(proxy.value, class_binding, proxy.file_path, proxy.start_line) # rubocop:disable Security/Eval
46
+ end
47
+
43
48
  class << self
44
- def evaluate(method_proxies:)
49
+ def evaluate(method_proxies:, class_binding: nil)
45
50
  require_relative '../syntax/union_types' if LowType.config.union_type_expressions
46
51
 
47
52
  method_proxies.each_value do |method_proxy|
48
- evaluate_param_proxy_expressions(method_proxy:)
53
+ evaluate_param_proxy_expressions(method_proxy:, class_binding:)
49
54
  evaluate_return_proxy_expression(return_proxy: method_proxy.return_proxy) if method_proxy.return_proxy
50
55
  end
51
56
  end
52
57
 
53
- def evaluate_param_proxy_expressions(method_proxy:)
58
+ def evaluate_param_proxy_expressions(method_proxy:, class_binding: nil)
54
59
  begin # rubocop:disable Style/RedundantBegin
55
60
  method_proxy.tagged_params(:value).each do |param_proxy|
56
- # TODO: Evaluate in the binding of the class that included LowType if not a type managed by LowType.
57
- expression = new.instance_evaluate(proxy: param_proxy)
61
+ expression = begin
62
+ new.instance_evaluate(proxy: param_proxy)
63
+ rescue NameError
64
+ raise unless class_binding
65
+
66
+ new.class_evaluate(proxy: param_proxy, class_binding:)
67
+ end
58
68
  param_proxy.expression = cast_type_expression(expression:, method_proxy:)
59
69
  end
60
- rescue NameError
70
+ rescue NameError => e
61
71
  mp = method_proxy
62
- raise NameError, "Unknown type '#{mp.value}' for #{mp.scope} at #{mp.file_path}:#{mp.start_line}"
72
+ raise NameError, "Unknown type '#{e.name}' for #{mp.scope} at #{mp.file_path}:#{mp.start_line}"
63
73
  end
64
74
  end
65
75
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'expressions'
4
4
 
5
+ require_relative 'value_expression'
5
6
  require_relative '../proxies/param_proxy'
6
7
  require_relative '../queries/type_query'
7
8
 
@@ -123,6 +124,10 @@ module Low
123
124
  end
124
125
 
125
126
  def hash_types_match_values?(types:, values:)
127
+ return true if values.empty? && empty_hash_default_value?
128
+ return values.empty? if types.empty?
129
+ return false if values.empty?
130
+
126
131
  # TODO: Shallow validation of hash could be made deeper with user config.
127
132
  types.keys[0] == values.keys[0].class && types.values[0] == values.values[0].class
128
133
  end
@@ -130,9 +135,10 @@ module Low
130
135
  def type_matches_value?(type:, value:, proxy:)
131
136
  if type.instance_of?(Class)
132
137
  return type.match?(value:) if Low::TypeQuery.complex_type?(expression: type)
138
+ return value.value <= type if value.instance_of?(ValueExpression)
133
139
 
134
- return type == value.class
135
- elsif type.instance_of?(::Low::TypeExpression)
140
+ return value.is_a?(type)
141
+ elsif type.instance_of?(Low::TypeExpression)
136
142
  type.validate!(value:, proxy:)
137
143
  return true
138
144
  end
@@ -145,5 +151,9 @@ module Low
145
151
 
146
152
  LowType.config.deep_type_check
147
153
  end
154
+
155
+ def empty_hash_default_value?
156
+ @default_value.is_a?(Hash) && @default_value.empty?
157
+ end
148
158
  end
149
159
  end
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # A value expression presents a type as a value:
4
- # 1. It is an instance
5
- # 2. It has a class method
6
- class ValueExpression
7
- attr_reader :value
3
+ module Low
4
+ # A value expression presents a type as a value:
5
+ # 1. It is an instance
6
+ # 2. It mimics the class method
7
+ class ValueExpression
8
+ attr_reader :value
8
9
 
9
- def initialize(value:)
10
- @value = value
11
- end
10
+ def initialize(value:)
11
+ @value = value
12
+ end
12
13
 
13
- def class
14
- @value
14
+ def class
15
+ @value
16
+ end
15
17
  end
16
18
  end
data/lib/low_type.rb CHANGED
@@ -31,23 +31,36 @@ require_relative 'types/complex_types'
31
31
  # │ │ │◄┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
32
32
  # │ │ │ │ │
33
33
  module LowType
34
- # We do as much as possible on class load rather than on object instantiation to be thread-safe and efficient.
34
+ # Defers evaluation and redefinition until after the class body finishes loading via TracePoint :end.
35
35
  def self.included(klass)
36
36
  file_path = Low::FileQuery.file_path(klass:)
37
37
  file_proxy = Lowkey.load(file_path)
38
38
  class_proxy = file_proxy[klass.name]
39
39
 
40
- Low::Evaluator.evaluate(method_proxies: class_proxy.keyed_methods)
41
-
42
40
  klass.include Low::ExpressionHelpers
43
41
  klass.extend Low::ExpressionHelpers
44
42
  klass.extend Low::TypeAccessors
45
43
  klass.extend Low::Types
46
44
 
47
- klass.prepend Low::Redefiner.redefine(method_proxies: class_proxy.instance_methods, class_proxy:)
48
- klass.singleton_class.prepend Low::Redefiner.redefine(method_proxies: class_proxy.class_methods, class_proxy:)
45
+ # Use TracePoint :end to capture the class binding after the class body finishes loading.
46
+ # At :end time, trace.self is the including class and trace.binding is the class body's binding,
47
+ # stored on class_proxy.class_binding for use by LowType and other consumers.
48
+ tp = TracePoint.new(:end) do |trace|
49
+ next unless trace.self == klass
50
+
51
+ class_proxy.class_binding = trace.binding
52
+
53
+ Low::Evaluator.evaluate(method_proxies: class_proxy.keyed_methods, class_binding: class_proxy.class_binding)
54
+
55
+ klass.prepend Low::Redefiner.redefine(method_proxies: class_proxy.instance_methods, class_proxy:)
56
+ klass.singleton_class.prepend Low::Redefiner.redefine(method_proxies: class_proxy.class_methods, class_proxy:)
57
+
58
+ Low::Adapter::Loader.load(klass:, class_proxy:)
59
+
60
+ tp.disable
61
+ end
49
62
 
50
- Low::Adapter::Loader.load(klass:, class_proxy:)
63
+ tp.enable
51
64
  end
52
65
 
53
66
  class << self
data/lib/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Low
4
4
  module Type
5
- VERSION = '1.1.13'
5
+ VERSION = '1.3.0'
6
6
  end
7
7
  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: 1.1.13
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -88,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  requirements: []
91
- rubygems_version: 3.7.2
91
+ rubygems_version: 4.0.6
92
92
  specification_version: 4
93
93
  summary: Elegant types in Ruby
94
94
  test_files: []