diff --git a/CHANGELOG.md b/CHANGELOG.md index 687a77b9c64f..74fe2c50306f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Compatibility: * Implement `Time#deconstruct_keys` from Ruby 3.2 (#3039, @rwstauner). * Do not autosplat a proc that accepts a single positional argument and keywords (#3039, @andrykonchin). * Support passing anonymous * and ** parameters as method call arguments (#3039, @andrykonchin). +* Handle either positional or keywords arguments by default in `Struct.new` (#3039, @rwstauner). Performance: diff --git a/spec/tags/core/struct/initialize_tags.txt b/spec/tags/core/struct/initialize_tags.txt deleted file mode 100644 index 8bbd19c4d0e0..000000000000 --- a/spec/tags/core/struct/initialize_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Struct#initialize warns about passing only keyword arguments -fails:Struct#initialize can be initialized with keyword arguments diff --git a/spec/tags/core/struct/new_tags.txt b/spec/tags/core/struct/new_tags.txt deleted file mode 100644 index 22a7dfffab58..000000000000 --- a/spec/tags/core/struct/new_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Struct.new on subclasses accepts keyword arguments to initialize diff --git a/src/main/ruby/truffleruby/core/struct.rb b/src/main/ruby/truffleruby/core/struct.rb index 0d220cf0045d..19f8447620cb 100644 --- a/src/main/ruby/truffleruby/core/struct.rb +++ b/src/main/ruby/truffleruby/core/struct.rb @@ -56,7 +56,8 @@ def self.new(klass_name, *attrs, keyword_init: nil, &block) end klass = Class.new self do - _specialize attrs unless keyword_init + # _specialize doesn't support keyword arguments + _specialize attrs if Primitive.false?(keyword_init) attrs.each do |a| define_method(a) { Primitive.object_hidden_var_get(self, a) } @@ -71,6 +72,7 @@ def self.[](*args) new(*args) end + # This doesn't apply when keyword_init is nil. if keyword_init def self.inspect super + '(keyword_init: true)' @@ -156,7 +158,13 @@ def initialize(*args, **kwargs) raise ArgumentError, "Expected #{attrs.size}, got #{args.size}" end - if Primitive.class(self)::KEYWORD_INIT + keyword_init = Primitive.class(self)::KEYWORD_INIT + + # When keyword_init is nil: + # If there are any positional args we treat them all as positional. + # If there are no args at all we also want to run the positional handling code. + + if keyword_init || (Primitive.nil?(keyword_init) && args.empty? && !kwargs.empty?) # Accept a single positional hash for https://bugs.ruby-lang.org/issues/18632 and spec if kwargs.empty? && args.size == 1 && Primitive.is_a?(args.first, Hash) kwargs = args.first