Skip to content

Commit 1cc6480

Browse files
committed
Ruby 3.2 struct compatibility
1 parent 2fdd257 commit 1cc6480

8 files changed

+1876
-47
lines changed

Gemfile

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ gemspec
88
gem "rake", "~> 13.0"
99

1010
gem "rspec", "~> 3.0"
11+
12+
gem "test-unit"

Gemfile.lock

+20-16
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,34 @@ PATH
77
GEM
88
remote: https://rubygems.org/
99
specs:
10-
diff-lcs (1.4.4)
11-
rake (13.0.3)
12-
rbs (1.5.1)
13-
rspec (3.10.0)
14-
rspec-core (~> 3.10.0)
15-
rspec-expectations (~> 3.10.0)
16-
rspec-mocks (~> 3.10.0)
17-
rspec-core (3.10.1)
18-
rspec-support (~> 3.10.0)
19-
rspec-expectations (3.10.1)
10+
diff-lcs (1.5.0)
11+
power_assert (2.0.1)
12+
rake (13.0.6)
13+
rbs (1.8.1)
14+
rspec (3.11.0)
15+
rspec-core (~> 3.11.0)
16+
rspec-expectations (~> 3.11.0)
17+
rspec-mocks (~> 3.11.0)
18+
rspec-core (3.11.0)
19+
rspec-support (~> 3.11.0)
20+
rspec-expectations (3.11.1)
2021
diff-lcs (>= 1.2.0, < 2.0)
21-
rspec-support (~> 3.10.0)
22-
rspec-mocks (3.10.2)
22+
rspec-support (~> 3.11.0)
23+
rspec-mocks (3.11.1)
2324
diff-lcs (>= 1.2.0, < 2.0)
24-
rspec-support (~> 3.10.0)
25-
rspec-support (3.10.2)
25+
rspec-support (~> 3.11.0)
26+
rspec-support (3.11.1)
27+
test-unit (3.5.3)
28+
power_assert
2629

2730
PLATFORMS
28-
ruby
31+
x86_64-linux
2932

3033
DEPENDENCIES
3134
rake (~> 13.0)
3235
rspec (~> 3.0)
36+
test-unit
3337
typed_struct!
3438

3539
BUNDLED WITH
36-
2.1.4
40+
2.4.0.dev

lib/typed_struct.rb

+60-22
Original file line numberDiff line numberDiff line change
@@ -21,50 +21,88 @@ class << self
2121
def new(opts = Options.new, **properties)
2222
properties.each_key do |prop|
2323
if method_defined?(prop)
24-
$stdout.puts OVERRIDING_NATIVE_METHOD_MSG % [prop.inspect, caller(3).first]
24+
warn OVERRIDING_NATIVE_METHOD_MSG % [prop.inspect, caller(3).first]
2525
end
2626
end
2727

28-
super(*properties.keys, keyword_init: true).tap do |klass|
29-
klass.class.instance_eval do
30-
include TypeChecking
31-
attr_reader :options
32-
end
28+
klass = if opts[:class_name]
29+
super(opts[:class_name], *properties.keys, keyword_init: opts[:keyword_init])
30+
else
31+
super(*properties.keys, keyword_init: opts[:keyword_init])
32+
end
3333

34-
klass.instance_eval do
35-
@options = { types: properties, options: opts }
34+
klass.class.instance_eval do
35+
include TypeChecking
36+
attr_reader :options
37+
end
3638

37-
define_method :[]= do |key, val|
38-
prop = properties[key]
39-
unless val_is_type? val, prop
40-
raise "Unexpected type #{val.class} for #{key.inspect} (expected #{prop})"
41-
end
39+
klass.instance_eval do
40+
@options = { types: properties, options: opts }
4241

43-
super key, val
42+
define_method :[]= do |key, val|
43+
if key.is_a?(Integer)
44+
if key.negative?
45+
raise IndexError, "offset #{key} too small for struct(size:#{members.size})"
46+
elsif key >= members.size
47+
raise IndexError, "offset #{key} too large for struct(size:#{members.size})"
48+
end
49+
key = self.members[key]
50+
end
51+
unless properties.key?(key)
52+
raise NameError, "no member '#{key}' in struct"
53+
end
54+
prop = properties[key]
55+
unless val_is_type? val, prop
56+
raise TypeError, "unexpected type #{val.class} for #{key.inspect} (expected #{prop})"
4457
end
4558

46-
properties.each_key do |k|
47-
define_method :"#{k}=" do |val|
48-
self[k] = val
49-
end
59+
super key, val
60+
end
61+
62+
properties.each_key do |k|
63+
define_method :"#{k}=" do |val|
64+
self[k] = val
5065
end
5166
end
5267
end
68+
69+
klass
5370
end
5471
end
5572

56-
def initialize(**attrs)
73+
def initialize(*positional_attrs, **attrs)
5774
opts = self.__class__.options
75+
if opts[:options][:keyword_init] == true && !positional_attrs.empty?
76+
raise ArgumentError, "wrong number of arguments (given #{positional_attrs.size}, expected 0)"
77+
elsif (opts[:options][:keyword_init] == false && !attrs.empty?) ||
78+
(opts[:options][:keyword_init] != true && !positional_attrs.empty?)
79+
positional_attrs << attrs unless attrs.empty?
80+
attrs = positional_attrs.zip(self.members).to_h(&:reverse)
81+
end
82+
83+
if attrs.size > members.size
84+
raise ArgumentError, "struct size differs"
85+
end
86+
5887
vals = opts[:types].to_h do |prop, expected_type|
5988
value = attrs.fetch(prop, opts[:options][:default])
6089
unless val_is_type? value, expected_type
61-
raise "Unexpected type #{value.class} for #{prop.inspect} (expected #{expected_type})"
90+
raise TypeError, "unexpected type #{value.class} for #{prop.inspect} (expected #{expected_type})"
6291
end
6392
[prop, value]
6493
end
6594

66-
super **vals
95+
if opts[:options][:keyword_init] == true
96+
super **vals
97+
else
98+
super *vals.values
99+
end
67100
end
68101

69-
Options = TypedStruct.new({ default: nil }, default: Rbs("untyped"))
102+
Options = TypedStruct.new(
103+
{ default: nil },
104+
default: Rbs("untyped"),
105+
keyword_init: Rbs("bool?"),
106+
class_name: Rbs("String? | Symbol?")
107+
)
70108
end

0 commit comments

Comments
 (0)