diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a117407def..c13685cb287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Compatibility: * Fix `Kernel#raise` and don't override `cause` at exception re-raising (#3831, @andrykonchin). * Return a pointer with `#type_size` of 1 for `Pointer#read_pointer` (@eregon). * Fix `rb_str_locktmp()` and `rb_str_unlocktmp()` to raise `FrozenError` when string argument is frozen (#3752, @andrykonchin). +* Fix Struct setters to raise `FrozenError` when a struct is frozen (#3850, @andrykonchin). Performance: diff --git a/spec/ruby/core/struct/element_set_spec.rb b/spec/ruby/core/struct/element_set_spec.rb index 6ba7b081a92..0a0e34a5eee 100644 --- a/spec/ruby/core/struct/element_set_spec.rb +++ b/spec/ruby/core/struct/element_set_spec.rb @@ -26,4 +26,11 @@ -> { car[-4] = true }.should raise_error(IndexError) -> { car[Object.new] = true }.should raise_error(TypeError) end + + it "raises a FrozenError on a frozen struct" do + car = StructClasses::Car.new('Ford', 'Ranger') + car.freeze + + -> { car[:model] = 'Escape' }.should raise_error(FrozenError) + end end diff --git a/spec/ruby/core/struct/struct_spec.rb b/spec/ruby/core/struct/struct_spec.rb index 8817dc1a58c..1b6a4488ce9 100644 --- a/spec/ruby/core/struct/struct_spec.rb +++ b/spec/ruby/core/struct/struct_spec.rb @@ -33,6 +33,13 @@ car['model'].should == 'F150' car[1].should == 'F150' end + + it "writer methods raise a FrozenError on a frozen struct" do + car = StructClasses::Car.new('Ford', 'Ranger') + car.freeze + + -> { car.model = 'Escape' }.should raise_error(FrozenError) + end end describe "Struct subclasses" do diff --git a/src/main/ruby/truffleruby/core/struct.rb b/src/main/ruby/truffleruby/core/struct.rb index 815738b7f7a..a776b1de4f9 100644 --- a/src/main/ruby/truffleruby/core/struct.rb +++ b/src/main/ruby/truffleruby/core/struct.rb @@ -58,7 +58,10 @@ def self.new(*attrs, keyword_init: nil, &block) attrs.each do |a| define_method(a) { Primitive.object_hidden_var_get(self, a) } - define_method(:"#{a}=") { |value| Primitive.object_hidden_var_set(self, a, value) } + define_method(:"#{a}=") do |value| + Primitive.check_frozen self + Primitive.object_hidden_var_set(self, a, value) + end end def self.new(...)