@@ -19,13 +19,17 @@ class TypedStruct < Struct
19
19
20
20
class << self
21
21
def new ( opts = Options . new , **properties )
22
+ if const_defined? ( "RSpec" ) && RUBY_VERSION < "3.2" && opts [ :keyword_init ] . nil?
23
+ opts [ :keyword_init ] = false
24
+ end
25
+
22
26
properties . each_key do |prop |
23
27
if method_defined? ( prop )
24
- $stdout . puts OVERRIDING_NATIVE_METHOD_MSG % [ prop . inspect , caller ( 3 ) . first ]
28
+ warn OVERRIDING_NATIVE_METHOD_MSG % [ prop . inspect , caller ( 3 ) . first ]
25
29
end
26
30
end
27
31
28
- super ( *properties . keys , keyword_init : true ) . tap do |klass |
32
+ super ( opts [ :class_name ] , *properties . keys , keyword_init : opts [ :keyword_init ] ) . tap do |klass |
29
33
klass . class . instance_eval do
30
34
include TypeChecking
31
35
attr_reader :options
@@ -35,9 +39,25 @@ def new(opts = Options.new, **properties)
35
39
@options = { types : properties , options : opts }
36
40
37
41
define_method :[]= do |key , val |
42
+ if key . is_a? ( Integer )
43
+ key = if key . negative?
44
+ offset = self . members . size + key
45
+ if offset . negative?
46
+ raise IndexError , "offset #{ key } too small for struct(size:#{ self . members . size } )"
47
+ end
48
+ self . members [ offset ]
49
+ elsif key >= self . members . size
50
+ raise IndexError , "offset #{ key } too large for struct(size:#{ self . members . size } )"
51
+ else
52
+ self . members [ key ]
53
+ end
54
+ end
55
+ unless properties . key? ( key )
56
+ raise NameError , "no member '#{ key } ' in struct"
57
+ end
38
58
prop = properties [ key ]
39
59
unless val_is_type? val , prop
40
- raise "Unexpected type #{ val . class } for #{ key . inspect } (expected #{ prop } )"
60
+ raise TypeError , "unexpected type #{ val . class } for #{ key . inspect } (expected #{ prop } )"
41
61
end
42
62
43
63
super key , val
@@ -53,18 +73,41 @@ def new(opts = Options.new, **properties)
53
73
end
54
74
end
55
75
56
- def initialize ( **attrs )
76
+ def initialize ( *positional_attrs , * *attrs )
57
77
opts = self . __class__ . options
78
+ if opts [ :options ] [ :keyword_init ] == true && !positional_attrs . empty?
79
+ raise ArgumentError , "wrong number of arguments (given #{ positional_attrs . size } , expected 0)"
80
+ elsif ( opts [ :options ] [ :keyword_init ] == false && !attrs . empty? ) ||
81
+ ( opts [ :options ] [ :keyword_init ] != true && !positional_attrs . empty? )
82
+ positional_attrs << attrs unless attrs . empty?
83
+ attrs = positional_attrs . zip ( self . members ) . to_h ( &:reverse )
84
+ end
85
+
86
+ if !positional_attrs . empty? && attrs . size > self . members . size
87
+ raise ArgumentError , "struct size differs"
88
+ elsif !( attrs . keys - self . members ) . empty?
89
+ raise ArgumentError , "unknown keywords: #{ ( attrs . keys - self . members ) . join ( ', ' ) } "
90
+ end
91
+
58
92
vals = opts [ :types ] . to_h do |prop , expected_type |
59
93
value = attrs . fetch ( prop , opts [ :options ] [ :default ] )
60
94
unless val_is_type? value , expected_type
61
- raise "Unexpected type #{ value . class } for #{ prop . inspect } (expected #{ expected_type } )"
95
+ raise TypeError , "unexpected type #{ value . class } for #{ prop . inspect } (expected #{ expected_type } )"
62
96
end
63
97
[ prop , value ]
64
98
end
65
99
66
- super **vals
100
+ if opts [ :options ] [ :keyword_init ]
101
+ super **vals
102
+ else
103
+ super *vals . values
104
+ end
67
105
end
68
106
69
- Options = TypedStruct . new ( { default : nil } , default : Rbs ( "untyped" ) )
107
+ Options = TypedStruct . new (
108
+ { default : nil , keyword_init : true } ,
109
+ default : Rbs ( "untyped" ) ,
110
+ keyword_init : Rbs ( "bool?" ) ,
111
+ class_name : Rbs ( "String? | Symbol?" )
112
+ )
70
113
end
0 commit comments