@@ -19,13 +19,17 @@ class TypedStruct < Struct
1919
2020 class << self
2121 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+
2226 properties . each_key do |prop |
2327 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 ]
2529 end
2630 end
2731
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 |
2933 klass . class . instance_eval do
3034 include TypeChecking
3135 attr_reader :options
@@ -35,9 +39,25 @@ def new(opts = Options.new, **properties)
3539 @options = { types : properties , options : opts }
3640
3741 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
3858 prop = properties [ key ]
3959 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 } )"
4161 end
4262
4363 super key , val
@@ -53,18 +73,41 @@ def new(opts = Options.new, **properties)
5373 end
5474 end
5575
56- def initialize ( **attrs )
76+ def initialize ( *positional_attrs , * *attrs )
5777 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+
5892 vals = opts [ :types ] . to_h do |prop , expected_type |
5993 value = attrs . fetch ( prop , opts [ :options ] [ :default ] )
6094 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 } )"
6296 end
6397 [ prop , value ]
6498 end
6599
66- super **vals
100+ if opts [ :options ] [ :keyword_init ]
101+ super **vals
102+ else
103+ super *vals . values
104+ end
67105 end
68106
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+ )
70113end
0 commit comments