literal 0.2.0 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -156
- data/lib/literal/data_structure.rb +24 -15
- data/lib/literal/enum.rb +20 -15
- data/lib/literal/errors/type_error.rb +76 -3
- data/lib/literal/properties/data_schema.rb +3 -4
- data/lib/literal/properties/schema.rb +57 -7
- data/lib/literal/properties.rb +18 -4
- data/lib/literal/property.rb +47 -20
- data/lib/literal/rails/enum_serializer.rb +20 -0
- data/lib/literal/rails/enum_type.rb +41 -0
- data/lib/literal/rails/patches/active_record.rb +31 -0
- data/lib/literal/rails.rb +9 -0
- data/lib/literal/railtie.rb +13 -0
- data/lib/literal/types/array_type.rb +13 -1
- data/lib/literal/types/boolean_type.rb +0 -6
- data/lib/literal/types/constraint_type.rb +32 -3
- data/lib/literal/types/enumerable_type.rb +1 -1
- data/lib/literal/types/float_type.rb +1 -1
- data/lib/literal/types/hash_type.rb +7 -1
- data/lib/literal/types/integer_type.rb +1 -1
- data/lib/literal/types/json_data_type.rb +3 -1
- data/lib/literal/types/map_type.rb +15 -1
- data/lib/literal/types/set_type.rb +7 -1
- data/lib/literal/types/string_type.rb +1 -1
- data/lib/literal/types/symbol_type.rb +1 -1
- data/lib/literal/types/tuple_type.rb +11 -1
- data/lib/literal/types/union_type.rb +8 -2
- data/lib/literal/types.rb +202 -32
- data/lib/literal/version.rb +1 -1
- data/lib/literal.rb +8 -3
- metadata +11 -7
- data/lib/literal.test.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75c7cc8853143bd8f4ac74b7fc4a5b5d2812bd39c0b4f696a87c2ffc0fb31046
|
4
|
+
data.tar.gz: 7781d6362e9ae8f75d0156809f95b0c1f8a37df855356173c9b6f09428419bc0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6acc2552c11ff3c5f12877b5597f9a6a138bf4901f5fce3801004e286b522c6263fa2454621ac4f5511f553af073d5113d96cd1af1045bc9e44c014c15ab7420
|
7
|
+
data.tar.gz: 6f9b205b3343426385185ebd0ea0e04164f6a06f9d42d6cb34c542c8d12a98ab327e274b336fb13d389e7380f5bd79831eb2b508f2d019cd6143658db40f3195
|
data/README.md
CHANGED
@@ -1,158 +1,3 @@
|
|
1
1
|
# A Literal Ruby Gem [WIP]
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
Literal uses Ruby-native types. Any method that responds to `===(value)` is considered a type. Note, this is how Ruby’s case statements and pattern matching work. It’s also how `Array#any?` and `Array#all?` work. Essentially all Ruby objects are types and just work the way you’d expect them to. A few examples:
|
6
|
-
|
7
|
-
- On a `Range`, `===(value)` checks if the value is within the range.
|
8
|
-
- On a `Regexp`, `===(value)` checks if the value matches the pattern.
|
9
|
-
- On a `Class`, `===(value)` checks if the value is an instance of the class.
|
10
|
-
- On a `Proc`, `===(value)` calls the proc with the value.
|
11
|
-
- On a `String`, `===(value)` checks if the value is equal to the string.
|
12
|
-
- On the class `String`, `===(value)` checks if the value is a string.
|
13
|
-
|
14
|
-
Literal extends this idea with the concept of generics or _parameterised_ types. A generic is a method that returns an object that respond to `===(value)`.
|
15
|
-
|
16
|
-
If we want to check that a given value is an `Array`, we could do this:
|
17
|
-
|
18
|
-
```ruby
|
19
|
-
Array === [1, 2, 3]
|
20
|
-
```
|
21
|
-
|
22
|
-
But what if we want to check that it’s an Array of Integers? Literal provides a library of special types that can be composed for this purpose. In this case, we can use the type `_Array(Integer)`.
|
23
|
-
|
24
|
-
```ruby
|
25
|
-
_Array(Integer) === [1, 2, 3]
|
26
|
-
```
|
27
|
-
|
28
|
-
These special types are defined on the `Literal::Types` module. To access them in a class, you can `extend` this module. To access them on an instance, you can `include` the module. If you want to use them globally, you can `extend` the module at the root.
|
29
|
-
|
30
|
-
```ruby
|
31
|
-
extend Literal::Types
|
32
|
-
```
|
33
|
-
|
34
|
-
This is recommended for applications, but not for libraries, as we don’t want to pollute the global namespace from library code.
|
35
|
-
|
36
|
-
`Literal::Properties`, `Literal::Object`, `Literal::Struct` and `Literal::Data` already extend `Literal::Types`, so you don’t need to extend `Literal::Types` yourself if you’re only using literal types for literal properties.
|
37
|
-
|
38
|
-
## Properties
|
39
|
-
|
40
|
-
`Literal::Properties` is a mixin that allows you to define the structure of an object. Properties are defined using the `prop` method.
|
41
|
-
|
42
|
-
The first argument is the name of the property as a `Symbol`. The second argument is the _type_ of the property. Remember, the type can be any object that responds to `===(value)`.
|
43
|
-
|
44
|
-
The third argument is optional. You can set this to `:*`, `:**`, `:&`, or `:positional` to change the kind of initializer parameter.
|
45
|
-
|
46
|
-
```ruby
|
47
|
-
class Person
|
48
|
-
extend Literal::Properties
|
49
|
-
|
50
|
-
prop :name, String
|
51
|
-
prop :age, Integer
|
52
|
-
end
|
53
|
-
```
|
54
|
-
|
55
|
-
You can also use keyword arguments to define _readers_ and _writers_. These can be set to `false`, `:public`, `:protected`, or `:private` and default to `false`.
|
56
|
-
|
57
|
-
```ruby
|
58
|
-
class Person
|
59
|
-
extend Literal::Properties
|
60
|
-
|
61
|
-
prop :name, String, reader: :public
|
62
|
-
prop :age, Integer, writer: :protected
|
63
|
-
end
|
64
|
-
```
|
65
|
-
|
66
|
-
Properties are required by deafult. To make them optional, set the type to a that responds to `===(nil)` with `true`. `Literal::Types` provides a special types for this purpose. Let’s make the age optional by setting its type to a `_Nilable(Integer)`:
|
67
|
-
|
68
|
-
```ruby
|
69
|
-
class Person
|
70
|
-
extend Literal::Properties
|
71
|
-
|
72
|
-
prop :name, String
|
73
|
-
prop :age, _Nilable(Integer)
|
74
|
-
end
|
75
|
-
```
|
76
|
-
|
77
|
-
Alternatively, you can give the property a default value. This default value must match the type of the property.
|
78
|
-
|
79
|
-
```ruby
|
80
|
-
class Person
|
81
|
-
extend Literal::Properties
|
82
|
-
|
83
|
-
prop :name, String, default: "John Doe"
|
84
|
-
prop :age, _Nilable(Integer)
|
85
|
-
end
|
86
|
-
```
|
87
|
-
|
88
|
-
Note, the above example will fail unless you have frozen string literals enabled. (Which, honestly, you should.) Default values must be frozen. If you can’t use a frozen value, you can pass a proc instead.
|
89
|
-
|
90
|
-
```ruby
|
91
|
-
class Person
|
92
|
-
extend Literal::Properties
|
93
|
-
|
94
|
-
prop :name, String, default: -> { "John Doe" }
|
95
|
-
prop :age, _Nilable(Integer)
|
96
|
-
end
|
97
|
-
```
|
98
|
-
|
99
|
-
The proc will be called to generate the default value.
|
100
|
-
|
101
|
-
You can also pass a block to the `prop` method. This block will be called with the value of the property when it’s set, which is useful for coercion.
|
102
|
-
|
103
|
-
```ruby
|
104
|
-
class Person
|
105
|
-
extend Literal::Properties
|
106
|
-
|
107
|
-
prop :name, String
|
108
|
-
prop :age, Integer do |value|
|
109
|
-
value.to_i
|
110
|
-
end
|
111
|
-
end
|
112
|
-
```
|
113
|
-
|
114
|
-
Coercion takes place prior to type-checking, so you can safely coerce a value to a different type in the block.
|
115
|
-
|
116
|
-
You can use properties that conflict with ruby keywords. Literal will handle everything for you automatically.
|
117
|
-
|
118
|
-
```ruby
|
119
|
-
class Person
|
120
|
-
extend Literal::Properties
|
121
|
-
|
122
|
-
prop :class, String, :positional
|
123
|
-
prop :end, Integer
|
124
|
-
end
|
125
|
-
```
|
126
|
-
|
127
|
-
If you’d prefer to subclass than extend a module, you can use the `Literal::Object` class instead. `Literal::Object` literally extends `Literal::Properties`.
|
128
|
-
|
129
|
-
```ruby
|
130
|
-
class Person < Literal::Object
|
131
|
-
prop :name, String
|
132
|
-
prop :age, Integer
|
133
|
-
end
|
134
|
-
```
|
135
|
-
|
136
|
-
## Structs
|
137
|
-
|
138
|
-
`Literal::Struct` is like `Literal::Object`, but it also provides a few extras.
|
139
|
-
|
140
|
-
Structs implement `==` so you can compare one struct to another. They also implement `hash`. Structs also have public _readers_ and _writers_ by default.
|
141
|
-
|
142
|
-
```ruby
|
143
|
-
class Person < Literal::Struct
|
144
|
-
prop :name, String
|
145
|
-
prop :age, Integer
|
146
|
-
end
|
147
|
-
```
|
148
|
-
|
149
|
-
## Data
|
150
|
-
|
151
|
-
`Literal::Data` is like `Literal::Struct`, but you can’t define _writers_. Additionally, objects are _frozen_ after initialization. Additionally any non-frozen properties are duplicated and frozen.
|
152
|
-
|
153
|
-
```ruby
|
154
|
-
class Person < Literal::Data
|
155
|
-
prop :name, String
|
156
|
-
prop :age, Integer
|
157
|
-
end
|
158
|
-
```
|
3
|
+
See the website for [documentation](https://literal.fun/docs).
|
@@ -10,25 +10,18 @@ class Literal::DataStructure
|
|
10
10
|
object
|
11
11
|
end
|
12
12
|
|
13
|
-
def ==(other)
|
14
|
-
if Literal::DataStructure === other
|
15
|
-
to_h == other.to_h
|
16
|
-
else
|
17
|
-
false
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def hash
|
22
|
-
[self.class, to_h].hash
|
23
|
-
end
|
24
|
-
|
25
13
|
def [](key)
|
26
|
-
instance_variable_get("@#{key}")
|
14
|
+
instance_variable_get(:"@#{key}")
|
27
15
|
end
|
28
16
|
|
29
17
|
def []=(key, value)
|
30
|
-
|
31
|
-
|
18
|
+
# TODO: Sync error array w/ generated setter
|
19
|
+
@literal_properties[key].check(value) { |c| raise NotImplementedError }
|
20
|
+
instance_variable_set(:"@#{key}", value)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
{}
|
32
25
|
end
|
33
26
|
|
34
27
|
def deconstruct
|
@@ -57,4 +50,20 @@ class Literal::DataStructure
|
|
57
50
|
def marshal_dump
|
58
51
|
[1, to_h, frozen?]
|
59
52
|
end
|
53
|
+
|
54
|
+
def hash
|
55
|
+
self.class.hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def ==(other)
|
59
|
+
other.is_a?(self.class) && other.class.literal_properties.empty?
|
60
|
+
end
|
61
|
+
alias eql? ==
|
62
|
+
|
63
|
+
def self.__generate_literal_methods__(new_property, buffer = +"")
|
64
|
+
super
|
65
|
+
literal_properties.generate_hash(buffer)
|
66
|
+
literal_properties.generate_eq(buffer)
|
67
|
+
buffer
|
68
|
+
end
|
60
69
|
end
|
data/lib/literal/enum.rb
CHANGED
@@ -21,6 +21,15 @@ class Literal::Enum
|
|
21
21
|
@indexes = {}
|
22
22
|
@index = {}
|
23
23
|
end
|
24
|
+
|
25
|
+
if RUBY_ENGINE != "truffleruby"
|
26
|
+
TracePoint.trace(:end) do |tp|
|
27
|
+
if tp.self == subclass
|
28
|
+
tp.self.__after_defined__
|
29
|
+
tp.disable
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
24
33
|
end
|
25
34
|
|
26
35
|
def index(name, type, unique: true, &block)
|
@@ -34,9 +43,9 @@ class Literal::Enum
|
|
34
43
|
|
35
44
|
key, value = kwargs.first
|
36
45
|
|
37
|
-
|
38
|
-
|
39
|
-
|
46
|
+
types = @indexes.fetch(key)
|
47
|
+
type = types.first
|
48
|
+
Literal.check(actual: value, expected: type) { |c| raise NotImplementedError }
|
40
49
|
|
41
50
|
@index.fetch(key)[value]
|
42
51
|
end
|
@@ -76,9 +85,9 @@ class Literal::Enum
|
|
76
85
|
end
|
77
86
|
end
|
78
87
|
|
79
|
-
def new(
|
88
|
+
def new(*args, **kwargs, &block)
|
80
89
|
raise ArgumentError if frozen?
|
81
|
-
new_object = super(
|
90
|
+
new_object = super(*args, **kwargs, &nil)
|
82
91
|
|
83
92
|
if block
|
84
93
|
new_object.instance_exec(&block)
|
@@ -90,6 +99,10 @@ class Literal::Enum
|
|
90
99
|
def __after_defined__
|
91
100
|
raise ArgumentError if frozen?
|
92
101
|
|
102
|
+
if RUBY_VERSION < "3.2"
|
103
|
+
constants(false).each { |name| const_added(name) }
|
104
|
+
end
|
105
|
+
|
93
106
|
@indexes.each do |name, (type, unique, block)|
|
94
107
|
index = @members.group_by(&block).freeze
|
95
108
|
|
@@ -111,8 +124,8 @@ class Literal::Enum
|
|
111
124
|
freeze
|
112
125
|
end
|
113
126
|
|
114
|
-
def each
|
115
|
-
@members.each(
|
127
|
+
def each
|
128
|
+
@members.each { |member| yield(member) }
|
116
129
|
end
|
117
130
|
|
118
131
|
def each_value(&)
|
@@ -166,11 +179,3 @@ class Literal::Enum
|
|
166
179
|
Marshal.dump(@value)
|
167
180
|
end
|
168
181
|
end
|
169
|
-
|
170
|
-
TracePoint.trace(:end) do |tp|
|
171
|
-
it = tp.self
|
172
|
-
|
173
|
-
if Class === it && it < Literal::Enum
|
174
|
-
it.__after_defined__
|
175
|
-
end
|
176
|
-
end
|
@@ -1,10 +1,83 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Literal::TypeError < TypeError
|
4
|
+
INDENT = " "
|
5
|
+
|
4
6
|
include Literal::Error
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
class Context
|
9
|
+
attr_reader :receiver, :method, :label, :expected, :actual, :children
|
10
|
+
|
11
|
+
def initialize(
|
12
|
+
receiver: nil, # _Nilable(Object)
|
13
|
+
method: nil, # _Nilable(String)
|
14
|
+
label: nil, # _Nilable(String)
|
15
|
+
expected: nil, # _Nilable(_Any)
|
16
|
+
actual: nil, # _Nilable(_Any)
|
17
|
+
parent: nil # _Nilable(Context)
|
18
|
+
)
|
19
|
+
@receiver = receiver
|
20
|
+
@method = method
|
21
|
+
@label = label
|
22
|
+
@expected = expected
|
23
|
+
@actual = actual
|
24
|
+
@children = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def descend(level = 0, &blk)
|
28
|
+
yield self, level
|
29
|
+
@children.each { |child| child.descend(level + 1, &blk) }
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def fill_receiver(receiver:, method:, label: nil)
|
34
|
+
@receiver = receiver
|
35
|
+
@method = method
|
36
|
+
@label = label
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_child(expected: nil, **kwargs)
|
40
|
+
child = self.class.new(expected:, **kwargs)
|
41
|
+
expected.record_literal_type_errors(child) if expected.respond_to?(:record_literal_type_errors)
|
42
|
+
@children << child
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(context:)
|
47
|
+
@context = context
|
48
|
+
|
49
|
+
super()
|
50
|
+
end
|
51
|
+
|
52
|
+
def message
|
53
|
+
message = +"Type mismatch\n\n"
|
54
|
+
|
55
|
+
@context.descend do |c, level|
|
56
|
+
idt = INDENT * level
|
57
|
+
if c.receiver || c.method
|
58
|
+
message << idt
|
59
|
+
message << c.receiver.class.inspect if c.receiver
|
60
|
+
message << c.method if c.method
|
61
|
+
message << " (from #{backtrace[1]})" if level.zero?
|
62
|
+
message << "\n"
|
63
|
+
end
|
64
|
+
if c.label
|
65
|
+
idt << INDENT
|
66
|
+
message << idt << c.label << "\n"
|
67
|
+
end
|
68
|
+
if c.expected && c.children.empty?
|
69
|
+
message << idt << " Expected: #{c.expected.inspect}\n"
|
70
|
+
message << idt << " Actual (#{c.actual.class}): #{c.actual.inspect}\n"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
message
|
74
|
+
end
|
75
|
+
|
76
|
+
def deconstruct_keys(keys)
|
77
|
+
to_h.slice(*keys)
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_h
|
81
|
+
@context.to_h
|
9
82
|
end
|
10
83
|
end
|
@@ -1,9 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Literal::Properties::DataSchema < Literal::Properties::Schema
|
4
|
-
def
|
5
|
-
|
6
|
-
|
7
|
-
buffer << "after_initialize if respond_to?(:after_initialize)\nfreeze\n"
|
4
|
+
def generate_after_initializer(buffer = +"")
|
5
|
+
super
|
6
|
+
buffer << " freeze\n"
|
8
7
|
end
|
9
8
|
end
|
@@ -19,7 +19,9 @@ class Literal::Properties::Schema
|
|
19
19
|
def <<(value)
|
20
20
|
@mutex.synchronize do
|
21
21
|
@properties_index[value.name] = value
|
22
|
-
|
22
|
+
# ruby's sort is unstable, this trick makes it stable
|
23
|
+
n = 0
|
24
|
+
@sorted_properties = @properties_index.values.sort_by! { |it| n += 1; [it, n] }
|
23
25
|
end
|
24
26
|
|
25
27
|
self
|
@@ -40,24 +42,71 @@ class Literal::Properties::Schema
|
|
40
42
|
@sorted_properties.size
|
41
43
|
end
|
42
44
|
|
45
|
+
def empty?
|
46
|
+
@sorted_properties.empty?
|
47
|
+
end
|
48
|
+
|
43
49
|
def generate_initializer(buffer = +"")
|
50
|
+
buffer << "alias initialize initialize\n"
|
44
51
|
buffer << "def initialize(#{generate_initializer_params})\n"
|
45
52
|
generate_initializer_body(buffer)
|
53
|
+
buffer << "" \
|
54
|
+
"rescue Literal::TypeError => error\n" \
|
55
|
+
" error.set_backtrace(caller(2))\n" \
|
56
|
+
" raise\n" \
|
57
|
+
"else\n"
|
58
|
+
generate_after_initializer(buffer)
|
46
59
|
buffer << "end\n"
|
47
60
|
end
|
48
61
|
|
62
|
+
def generate_after_initializer(buffer = +"")
|
63
|
+
buffer << " after_initialize if respond_to?(:after_initialize)\n"
|
64
|
+
end
|
65
|
+
|
49
66
|
def generate_to_h(buffer = +"")
|
50
|
-
buffer << "
|
67
|
+
buffer << "alias to_h to_h\n"
|
68
|
+
buffer << "def to_h\n" << " {\n"
|
51
69
|
|
52
70
|
sorted_properties = @sorted_properties
|
53
71
|
i, n = 0, sorted_properties.size
|
54
72
|
while i < n
|
55
73
|
property = sorted_properties[i]
|
56
|
-
buffer << property.name.name << ": @" << property.name.name << ",\n"
|
74
|
+
buffer << " " << property.name.name << ": @" << property.name.name << ",\n"
|
57
75
|
i += 1
|
58
76
|
end
|
59
77
|
|
60
|
-
buffer << "}\n" << "end\n"
|
78
|
+
buffer << " }\n" << "end\n"
|
79
|
+
end
|
80
|
+
|
81
|
+
def generate_hash(buffer = +"")
|
82
|
+
buffer << "def hash\n [self.class,\n"
|
83
|
+
|
84
|
+
sorted_properties = @sorted_properties
|
85
|
+
i, n = 0, sorted_properties.size
|
86
|
+
while i < n
|
87
|
+
property = sorted_properties[i]
|
88
|
+
buffer << " @" << property.name.name << ",\n"
|
89
|
+
i += 1
|
90
|
+
end
|
91
|
+
|
92
|
+
buffer << " ].hash\n" << "end\n"
|
93
|
+
end
|
94
|
+
|
95
|
+
def generate_eq(buffer = +"")
|
96
|
+
buffer << "def ==(other)\n"
|
97
|
+
buffer << " return false unless other.is_a?(self.class) && other.class.literal_properties.size == self.class.literal_properties.size\n"
|
98
|
+
|
99
|
+
sorted_properties = @sorted_properties
|
100
|
+
i, n = 0, sorted_properties.size
|
101
|
+
while i < n
|
102
|
+
property = sorted_properties[i]
|
103
|
+
buffer << " @" << property.name.name << " == other.#{property.escaped_name}"
|
104
|
+
buffer << " &&\n " if i < n - 1
|
105
|
+
i += 1
|
106
|
+
end
|
107
|
+
buffer << " true" if n.zero?
|
108
|
+
buffer << "\nend\n"
|
109
|
+
buffer << "alias eql? ==\n"
|
61
110
|
end
|
62
111
|
|
63
112
|
private
|
@@ -83,7 +132,7 @@ class Literal::Properties::Schema
|
|
83
132
|
else # required
|
84
133
|
buffer << property.escaped_name
|
85
134
|
end
|
86
|
-
|
135
|
+
when :keyword
|
87
136
|
if property.default?
|
88
137
|
buffer << property.name.name << ": Literal::Null"
|
89
138
|
elsif property.type === nil
|
@@ -91,6 +140,8 @@ class Literal::Properties::Schema
|
|
91
140
|
else # required
|
92
141
|
buffer << property.name.name << ":"
|
93
142
|
end
|
143
|
+
else
|
144
|
+
raise "You should never see this error."
|
94
145
|
end
|
95
146
|
|
96
147
|
i += 1
|
@@ -101,9 +152,8 @@ class Literal::Properties::Schema
|
|
101
152
|
end
|
102
153
|
|
103
154
|
def generate_initializer_body(buffer = +"")
|
104
|
-
buffer << "properties = self.class.literal_properties.properties_index\n"
|
155
|
+
buffer << " properties = self.class.literal_properties.properties_index\n"
|
105
156
|
generate_initializer_handle_properties(@sorted_properties, buffer)
|
106
|
-
buffer << "after_initialize if respond_to?(:after_initialize)\n"
|
107
157
|
end
|
108
158
|
|
109
159
|
def generate_initializer_handle_properties(properties, buffer = +"")
|
data/lib/literal/properties.rb
CHANGED
@@ -6,6 +6,11 @@ module Literal::Properties
|
|
6
6
|
|
7
7
|
include Literal::Types
|
8
8
|
|
9
|
+
def self.extended(base)
|
10
|
+
super
|
11
|
+
base.include(base.__send__(:__literal_extension__))
|
12
|
+
end
|
13
|
+
|
9
14
|
def prop(name, type, kind = :keyword, reader: false, writer: false, predicate: false, default: nil, &coercion)
|
10
15
|
if default && !(Proc === default || default.frozen?)
|
11
16
|
raise Literal::ArgumentError.new("The default must be a frozen object or a Proc.")
|
@@ -66,16 +71,25 @@ module Literal::Properties
|
|
66
71
|
end
|
67
72
|
|
68
73
|
def __define_literal_methods__(new_property)
|
69
|
-
|
70
|
-
|
71
|
-
)
|
74
|
+
code = __generate_literal_methods__(new_property)
|
75
|
+
__literal_extension__.module_eval(code)
|
72
76
|
end
|
73
77
|
|
74
78
|
def __literal_extension__
|
75
79
|
if defined?(@__literal_extension__)
|
76
80
|
@__literal_extension__
|
77
81
|
else
|
78
|
-
@__literal_extension__ = Module.new
|
82
|
+
@__literal_extension__ = Module.new do
|
83
|
+
def initialize
|
84
|
+
after_initialize if respond_to?(:after_initialize)
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_h
|
88
|
+
{}
|
89
|
+
end
|
90
|
+
|
91
|
+
set_temporary_name "Literal::Properties(Extension)" if respond_to?(:set_temporary_name)
|
92
|
+
end
|
79
93
|
end
|
80
94
|
end
|
81
95
|
|
data/lib/literal/property.rb
CHANGED
@@ -51,7 +51,24 @@ class Literal::Property
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def default?
|
54
|
-
|
54
|
+
nil != @default
|
55
|
+
end
|
56
|
+
|
57
|
+
def param
|
58
|
+
case @kind
|
59
|
+
when :*
|
60
|
+
"*#{escaped_name}"
|
61
|
+
when :**
|
62
|
+
"**#{escaped_name}"
|
63
|
+
when :&
|
64
|
+
"&#{escaped_name}"
|
65
|
+
when :positional
|
66
|
+
escaped_name
|
67
|
+
when :keyword
|
68
|
+
"#{@name.name}:"
|
69
|
+
else
|
70
|
+
raise "You should never see this error."
|
71
|
+
end
|
55
72
|
end
|
56
73
|
|
57
74
|
def <=>(other)
|
@@ -77,18 +94,28 @@ class Literal::Property
|
|
77
94
|
end
|
78
95
|
end
|
79
96
|
|
80
|
-
def check(value)
|
81
|
-
|
97
|
+
def check(value, &)
|
98
|
+
raise ArgumentError.new("Cannot check type without a block") unless block_given?
|
99
|
+
|
100
|
+
Literal.check(actual: value, expected: @type, &)
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_writer(receiver, value)
|
104
|
+
Literal.check(actual: value, expected: @type) { |c| c.fill_receiver(receiver:, method: "##{@name.name}=(value)") }
|
105
|
+
end
|
106
|
+
|
107
|
+
def check_initializer(receiver, value)
|
108
|
+
Literal.check(actual: value, expected: @type) { |c| c.fill_receiver(receiver:, method: "#initialize", label: param) }
|
82
109
|
end
|
83
110
|
|
84
111
|
def generate_reader_method(buffer = +"")
|
85
112
|
buffer <<
|
86
113
|
(@reader ? @reader.name : "public") <<
|
87
|
-
"
|
114
|
+
"\ndef " <<
|
88
115
|
@name.name <<
|
89
|
-
"\
|
116
|
+
"\n value = @" <<
|
90
117
|
@name.name <<
|
91
|
-
"\
|
118
|
+
"\n value\nend\n"
|
92
119
|
end
|
93
120
|
|
94
121
|
if Literal::TYPE_CHECKS_DISABLED
|
@@ -98,7 +125,7 @@ class Literal::Property
|
|
98
125
|
" def " <<
|
99
126
|
@name.name <<
|
100
127
|
"=(value)\n" <<
|
101
|
-
"@#{@name.name} = value\nend\n"
|
128
|
+
" @#{@name.name} = value\nend\n"
|
102
129
|
end
|
103
130
|
else # type checks are enabled
|
104
131
|
def generate_writer_method(buffer = +"")
|
@@ -107,10 +134,12 @@ class Literal::Property
|
|
107
134
|
" def " <<
|
108
135
|
@name.name <<
|
109
136
|
"=(value)\n" <<
|
110
|
-
"self.class.literal_properties[:" <<
|
137
|
+
" self.class.literal_properties[:" <<
|
111
138
|
@name.name <<
|
112
|
-
"].
|
113
|
-
"
|
139
|
+
"].check_writer(self, value)\n" <<
|
140
|
+
" @" << @name.name << " = value\n" <<
|
141
|
+
"rescue Literal::TypeError => error\n error.set_backtrace(caller(1))\n raise\n" <<
|
142
|
+
"end\n"
|
114
143
|
end
|
115
144
|
end
|
116
145
|
|
@@ -120,15 +149,15 @@ class Literal::Property
|
|
120
149
|
" def " <<
|
121
150
|
@name.name <<
|
122
151
|
"?\n" <<
|
123
|
-
"!!@" <<
|
152
|
+
" !!@" <<
|
124
153
|
@name.name <<
|
125
154
|
"\n" <<
|
126
155
|
"end\n"
|
127
156
|
end
|
128
157
|
|
129
158
|
def generate_initializer_handle_property(buffer = +"")
|
130
|
-
buffer << "# " << @name.name << "\n" <<
|
131
|
-
"property = properties[:" << @name.name << "]\n"
|
159
|
+
buffer << " # " << @name.name << "\n" <<
|
160
|
+
" property = properties[:" << @name.name << "]\n"
|
132
161
|
|
133
162
|
if @kind == :keyword && ruby_keyword?
|
134
163
|
generate_initializer_escape_keyword(buffer)
|
@@ -169,25 +198,23 @@ class Literal::Property
|
|
169
198
|
|
170
199
|
def generate_initializer_assign_default(buffer = +"")
|
171
200
|
buffer <<
|
172
|
-
"if " <<
|
201
|
+
" if " <<
|
173
202
|
((@kind == :&) ? "nil" : "Literal::Null") <<
|
174
203
|
" == " <<
|
175
204
|
escaped_name <<
|
176
|
-
"\n" <<
|
205
|
+
"\n " <<
|
177
206
|
escaped_name <<
|
178
|
-
" = property.default_value\
|
207
|
+
" = property.default_value\n end\n"
|
179
208
|
end
|
180
209
|
|
181
210
|
def generate_initializer_check_type(buffer = +"")
|
182
211
|
buffer <<
|
183
|
-
"
|
184
|
-
"raise Literal::TypeError.expected(" << escaped_name << ", to_be_a: property.type)\n" <<
|
185
|
-
"end\n"
|
212
|
+
" property.check_initializer(self, " << escaped_name << ")\n"
|
186
213
|
end
|
187
214
|
|
188
215
|
def generate_initializer_assign_value(buffer = +"")
|
189
216
|
buffer <<
|
190
|
-
"@" <<
|
217
|
+
" @" <<
|
191
218
|
@name.name <<
|
192
219
|
" = " <<
|
193
220
|
escaped_name <<
|