@@ -822,78 +822,105 @@ private static boolean shouldHandleKwargs(IRubyObject[] args, int callInfo) {
822822 return (callInfo & CALL_KEYWORD_EMPTY ) == 0 && args .length >= 1 && args [args .length - 1 ] instanceof RubyHash ;
823823 }
824824
825+ enum KwargsAction {
826+ REPLACE_WITH_DUP_RETURN_UNDEFINED ,
827+ REPLACE_WITH_DUP_WITH_RUBY2_RETURN_UNDEFINED ,
828+ RETURN_DUP ,
829+ RETURN_UNDEFINED ,
830+ RETURN_DUP_WITHOUT_RUBY2 ,
831+ RETURN_DUP_WITH_RUBY2 ,
832+ RETURN_WITHOUT_RUBY2 ,
833+ RETURN_HASH
834+ }
835+
825836 private static IRubyObject receiveKeywordsHash (ThreadContext context , IRubyObject [] args , boolean hasRestArgs ,
826837 boolean acceptsKeywords , boolean ruby2_keywords_method , int callInfo ) {
827838 RubyHash hash = (RubyHash ) args [args .length - 1 ];
828839
829- if (ruby2_keywords_method ) {
830- return receiveKeywordsHashRuby2KeywordsMethod (context , args , acceptsKeywords , hash , callInfo );
831- } else if (hash .isRuby2KeywordHash ()) {
832- return receiveKeywordsHashRubyKeywordsHash (context , args , hasRestArgs , acceptsKeywords , callInfo , hash );
833- } else if ((callInfo & CALL_KEYWORD ) == 0 ) {
834- return UNDEFINED ; // we did not pass keywords to process
835- } else if (acceptsKeywords && !hash .isEmpty ()) { // normal case kwargs passed and not empty
836- return hash .dupFast (context );
837- }
838-
839- args [args .length - 1 ] = hash .dup (context );
840-
841- // All other situations no-op
842- return UNDEFINED ;
843- }
844-
845- private static IRubyObject receiveKeywordsHashRubyKeywordsHash (ThreadContext context , IRubyObject [] args ,
846- boolean hasRestArgs , boolean acceptsKeywords ,
847- int callInfo , RubyHash hash ) {
848- // ruby2_keywords only get unmarked if it enters a method which accepts keywords.
849- // This means methods which don't just keep that marked hash around in case it is passed
850- // onto another method which accepts keywords.
851- if (acceptsKeywords ) {
852- if (!hash .isEmpty ()) hash = hash .dupFast (context );
853- hash .setRuby2KeywordHash (false );
854- return hash ;
855- }
856-
857- if ((callInfo & CALL_SPLATS ) != 0 ) {
858- // somehow we splatted an array with an empty keyword argument attached (ruby2_keywords_hash can lead to this)
859- if (hash .isEmpty ()) return hash ;
860-
861- args [args .length - 1 ] = hash .dup (context );
862- return UNDEFINED ;
863- }
864-
865- boolean explicitKeywords = (callInfo & CALL_KEYWORD ) != 0 ;
866- if (explicitKeywords ) {
867- if (!hash .isEmpty ()) return hash .dupFast (context );
868-
869- // FIXME: This is a bit gross. a real kwarg callsite if passed to a non-kwarg method but it
870- // has a rest arg will dup the original kwarg (presumably so you cannot modify the original
871- // kwarg hash). This should be handled during recv_rest_arg but we no longer have the info so
872- // it happening here.
873- if (hasRestArgs ) args [args .length - 1 ] = hash .dup (context );
840+ switch (kwargsAction (hash , hasRestArgs , acceptsKeywords , ruby2_keywords_method , callInfo )) {
841+ case REPLACE_WITH_DUP_RETURN_UNDEFINED -> {
842+ args [args .length - 1 ] = hash .dupFast (context );
843+ return UNDEFINED ;
844+ }
845+ case REPLACE_WITH_DUP_WITH_RUBY2_RETURN_UNDEFINED -> {
846+ args [args .length - 1 ] = hash .dupFast (context ).withRuby2Keywords (true );
847+ return UNDEFINED ;
848+ }
849+ case RETURN_DUP -> {
850+ return hash .dupFast (context );
851+ }
852+ case RETURN_DUP_WITHOUT_RUBY2 -> {
853+ return hash .dupFast (context ).withRuby2Keywords (false );
854+ }
855+ case RETURN_DUP_WITH_RUBY2 -> {
856+ return hash .dupFast (context ).withRuby2Keywords (true );
857+ }
858+ case RETURN_WITHOUT_RUBY2 -> {
859+ return hash .withRuby2Keywords (false );
860+ }
861+ case RETURN_HASH -> {
862+ return hash ;
863+ }
864+ case RETURN_UNDEFINED -> {
865+ return UNDEFINED ;
866+ }
867+ case null -> throw new IllegalArgumentException ("null kwargs action" );
874868 }
875-
876- // All other situations no-op
877- return UNDEFINED ;
878869 }
879870
880- private static IRubyObject receiveKeywordsHashRuby2KeywordsMethod (ThreadContext context , IRubyObject [] args ,
881- boolean acceptsKeywords , RubyHash hash ,
882- int callInfo ) {
883- if ((callInfo & CALL_KEYWORD ) != 0 ) { // We have passed in kwargs
884- setTrailingHashRuby2Keywords (context , args , hash );
885- } else if (hash .isRuby2KeywordHash () && (callInfo & CALL_SPLATS ) != 0 && hash .isEmpty ()) { // splatted empty hash...just return it
886- return hash ;
887- } else if (acceptsKeywords && !hash .isEmpty ()) { // keywords method which has keywords
888- return hash .dupFast (context );
871+ private static KwargsAction kwargsAction (RubyHash hash , boolean hasRestArgs ,
872+ boolean acceptsKeywords , boolean ruby2_keywords_method , int callInfo ) {
873+ if (ruby2_keywords_method ) {
874+ // no keywords to use in this method
875+ if ((callInfo & CALL_KEYWORD ) != 0 ) {
876+ return KwargsAction .REPLACE_WITH_DUP_WITH_RUBY2_RETURN_UNDEFINED ;
877+ } else if (hash .isRuby2KeywordHash () && (callInfo & CALL_SPLATS ) != 0 && hash .isEmpty ()) { // splatted empty hash...just return it
878+ return KwargsAction .RETURN_HASH ;
879+ } else if (acceptsKeywords && !hash .isEmpty ()) { // keywords method which has keywords
880+ return KwargsAction .RETURN_DUP ;
881+ } else {
882+ return KwargsAction .RETURN_UNDEFINED ;
883+ }
884+ } else if (hash .isRuby2KeywordHash ()) {
885+ RubyHash hash1 = hash ;
886+ // ruby2_keywords only get unmarked if it enters a method which accepts keywords.
887+ // This means methods which don't just keep that marked hash around in case it is passed
888+ // onto another method which accepts keywords.
889+ if (acceptsKeywords ) {
890+ if (!hash1 .isEmpty ()) {
891+ return KwargsAction .RETURN_DUP_WITHOUT_RUBY2 ;
892+ } else {
893+ return KwargsAction .RETURN_WITHOUT_RUBY2 ;
894+ }
895+ } else if ((callInfo & CALL_SPLATS ) != 0 ) {
896+ // somehow we splatted an array with an empty keyword argument attached (ruby2_keywords_hash can lead to this)
897+ if (hash1 .isEmpty ()) {
898+ return KwargsAction .RETURN_HASH ;
899+ } else {
900+ return KwargsAction .REPLACE_WITH_DUP_RETURN_UNDEFINED ;
901+ }
902+ } else if (hasKeywords (callInfo )) {
903+ if (!hash1 .isEmpty ()) {
904+ return KwargsAction .RETURN_DUP ;
905+ } else if (hasRestArgs ) {
906+ // FIXME: This is a bit gross. a real kwarg callsite if passed to a non-kwarg method but it
907+ // has a rest arg will dup the original kwarg (presumably so you cannot modify the original
908+ // kwarg hash). This should be handled during recv_rest_arg but we no longer have the info so
909+ // it happening here.
910+ return KwargsAction .REPLACE_WITH_DUP_RETURN_UNDEFINED ;
911+ } else {
912+ return KwargsAction .RETURN_UNDEFINED ;
913+ }
914+ } else {
915+ return KwargsAction .RETURN_UNDEFINED ;
916+ }
917+ } else if (!hasKeywords (callInfo )) {
918+ return KwargsAction .RETURN_UNDEFINED ;
919+ } else if (acceptsKeywords && !hash .isEmpty ()) {
920+ return KwargsAction .RETURN_DUP ;
921+ } else {
922+ return KwargsAction .REPLACE_WITH_DUP_RETURN_UNDEFINED ;
889923 }
890- return UNDEFINED ; // no keywords to use in this method
891- }
892-
893- private static void setTrailingHashRuby2Keywords (ThreadContext context , IRubyObject [] args , RubyHash hash ) {
894- hash = hash .dupFast (context );
895- hash .setRuby2KeywordHash (true );
896- args [args .length - 1 ] = hash ;
897924 }
898925
899926 /**
0 commit comments