diff --git ext/ripper/eventids2.c ext/ripper/eventids2.c
index e876e95ab3..cbf5c30d98 100644
--- ext/ripper/eventids2.c
+++ ext/ripper/eventids2.c
@@ -42,6 +42,7 @@ typedef struct {
     ID ripper_id_words_beg;
     ID ripper_id_qwords_beg;
     ID ripper_id_qsymbols_beg;
+    ID ripper_id_hashexpand_beg;
     ID ripper_id_symbols_beg;
     ID ripper_id_words_sep;
     ID ripper_id_rational;
@@ -104,6 +105,7 @@ ripper_init_eventids2(void)
     set_id2(words_beg);
     set_id2(qwords_beg);
     set_id2(qsymbols_beg);
+    set_id2(hashexpand_beg);
     set_id2(symbols_beg);
     set_id2(words_sep);
     set_id2(rational);
@@ -250,6 +252,7 @@ static const struct token_assoc {
     {tPOW,			O(op)},
     {tQWORDS_BEG,		O(qwords_beg)},
     {tQSYMBOLS_BEG,		O(qsymbols_beg)},
+    {tHASHEXPAND_BEG,		O(hashexpand_beg)},
     {tSYMBOLS_BEG,		O(symbols_beg)},
     {tRATIONAL,			O(rational)},
     {tREGEXP_BEG,		O(regexp_beg)},
diff --git parse.y parse.y
index 37d50f7a8b..8180e23a56 100644
--- parse.y
+++ parse.y
@@ -848,7 +848,7 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in
 
 %type <node> singleton strings string string1 xstring regexp
 %type <node> string_contents xstring_contents regexp_contents string_content
-%type <node> words symbols symbol_list qwords qsymbols word_list qword_list qsym_list word
+%type <node> words symbols symbol_list qwords qsymbols word_list qword_list qsym_list word  hashexpand hashexpand_list
 %type <node> literal numeric simple_numeric dsym cpath
 %type <node> top_compstmt top_stmts top_stmt begin_block
 %type <node> bodystmt compstmt stmts stmt_or_begin stmt expr arg primary command command_call method_call
@@ -932,7 +932,7 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in
 %token tDSTAR		"**arg"
 %token tAMPER		"&"
 %token tLAMBDA		"->"
-%token tSYMBEG tSTRING_BEG tXSTRING_BEG tREGEXP_BEG tWORDS_BEG tQWORDS_BEG tSYMBOLS_BEG tQSYMBOLS_BEG
+%token tSYMBEG tSTRING_BEG tXSTRING_BEG tREGEXP_BEG tWORDS_BEG tQWORDS_BEG tSYMBOLS_BEG tQSYMBOLS_BEG tHASHEXPAND_BEG
 %token tSTRING_DBEG tSTRING_DEND tSTRING_DVAR tSTRING_END tLAMBEG tLABEL_END
 
 /*
@@ -2359,6 +2359,7 @@ primary		: literal
 		| qwords
 		| symbols
 		| qsymbols
+		| hashexpand
 		| var_ref
 		| backref
 		| tFID
@@ -3719,6 +3720,7 @@ p_primitive	: literal
 		| qwords
 		| symbols
 		| qsymbols
+		| hashexpand
 		| keyword_variable
 		    {
 		    /*%%%*/
@@ -3955,6 +3957,36 @@ qsym_list	: /* none */
 		    }
 		;
 
+hashexpand	: tHASHEXPAND_BEG ' ' hashexpand_list tSTRING_END
+		    {
+		    /*%%%*/
+			$$ = new_hash(p, $3, &@$);
+		    /*% %*/
+		    /*% ripper: hash!($3) %*/
+		    }
+		;
+
+hashexpand_list	: /* none */
+		    {
+		    /*%%%*/
+			$$ = 0;
+		    /*% %*/
+		    /*% ripper: hashexpand_new! %*/
+		    }
+		| hashexpand_list string_content ' '
+		    {
+		    /*%%%*/
+			NODE *key, *val, *estr;
+			estr = evstr2dstr(p, $2);
+			estr = $2;
+			key = NEW_CALL(estr, rb_intern("to_sym"), 0, &@$);
+			val = NEW_FCALL(rb_intern("eval"), NEW_LIST(estr, &@$), &@$);
+			$$ = list_append(p, list_append(p, $1, key), val);
+		    /*% %*/
+		    /*% ripper: hashexpand_add!($1, $2) %*/
+		    }
+		;
+
 string_contents : /* none */
 		    {
 		    /*%%%*/
@@ -7695,6 +7727,10 @@ parse_percent(struct parser_params *p, const int space_seen, const enum lex_stat
 	    SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);
 	    return tSYMBEG;
 
+	  case 'h':
+	    p->lex.strterm = NEW_STRTERM(str_dword, term, paren);
+	    return tHASHEXPAND_BEG;
+
 	  default:
 	    yyerror0("unknown type of %string");
 	    return 0;
diff --git test/ripper/dummyparser.rb test/ripper/dummyparser.rb
index ca36985893..27c4b2db99 100644
--- test/ripper/dummyparser.rb
+++ test/ripper/dummyparser.rb
@@ -244,6 +244,17 @@ def on_qsymbols_add(symbols, symbol)
     symbols.push Node::Sym.new(symbol)
   end
 
+  def on_hashexpand_new
+#     NodeList.new
+    Node.new('assoc')
+  end
+
+  def on_hashexpand_add(hashexpand, str)
+    pp hashexpand
+#     hashexpand.push Node::Sym.new(str)
+#     hashexpand.push str
+  end
+
   def on_mlhs_new
     NodeList.new
   end
diff --git test/ripper/test_parser_events.rb test/ripper/test_parser_events.rb
index 2fca61e1d1..7215a23744 100644
--- test/ripper/test_parser_events.rb
+++ test/ripper/test_parser_events.rb
@@ -1045,6 +1045,16 @@ def test_symbols_add
     assert_equal '[array([:a])]', tree
   end
 
+  def test_hashexpand_add
+    thru_hasexpand_add = false
+    a = 1
+    b = 2
+    tree = parse('%h[a,b]', :on_hashexpand_add) {thru_hasexpand_add = true}
+    pp tree
+#     assert_equal true, thru_hasexpand_add
+#     assert_equal '[hash([:a,a,:b,b])]', tree
+  end
+
   def test_qwords_new
     thru_qwords_new = false
     parse('%w[]', :on_qwords_new) {thru_qwords_new = true}
@@ -1063,6 +1073,12 @@ def test_symbols_new
     assert_equal true, thru_symbols_new
   end
 
+  def test_hashexpand_new
+    thru_hashexpand_new = false
+    parse('%h[]', :on_hashexpand_new) {thru_hashexpand_new = true}
+    assert_equal true, thru_hashexpand_new
+  end
+
   def test_redo
     thru_redo = false
     parse('redo', :on_redo) {thru_redo = true}
diff --git test/ripper/test_scanner_events.rb test/ripper/test_scanner_events.rb
index c9fce34a77..49107d766c 100644
--- test/ripper/test_scanner_events.rb
+++ test/ripper/test_scanner_events.rb
@@ -682,6 +682,19 @@ def test_symbols_beg
                  scan('symbols_beg', "%I(\nw)")
   end
 
+  def test_hashexpand_beg
+    assert_equal [],
+                 scan('hashexpand_beg', '')
+    assert_equal ['%h('],
+                 scan('hashexpand_beg', '%h()')
+    assert_equal ['%h('],
+                 scan('hashexpand_beg', '%h(w w w)')
+    assert_equal ['%h('],
+                 scan('hashexpand_beg', '%h( w w w )')
+    assert_equal ['%h('],
+                 scan('hashexpand_beg', "%h(\nw)")
+  end
+
   def test_words_sep
     assert_equal [],
                  scan('words_sep', '')
diff --git test/ruby/test_parse.rb test/ruby/test_parse.rb
index 6160aa1452..69afbf03ce 100644
--- test/ruby/test_parse.rb
+++ test/ruby/test_parse.rb
@@ -1200,6 +1200,47 @@ def test_cdmarg_after_command_args_and_tlbrace_arg
     assert_valid_syntax('let () { m(a) do; end }')
   end
 
+  def test_hashexpand_meth
+    "test_hashexpand_meth"
+  end
+
+  def test_hashexpand
+    hoge = 1
+    foo = "str"
+    bar = nil
+    @test_hashexpand_value = { a: 1 }
+    @@test_hashexpand_value = [1, 2]
+    hash = %h(hoge foo bar @test_hashexpand_value @@test_hashexpand_value test_hashexpand_meth)
+
+    assert_equal(
+      hash,
+      { hoge: 1, foo: "str", bar: nil, "@test_hashexpand_value": { a: 1}, "@@test_hashexpand_value": [1, 2], test_hashexpand_meth: "test_hashexpand_meth" }
+    )
+
+    assert_equal(
+      hash.keys,
+      %i(hoge foo bar @test_hashexpand_value @@test_hashexpand_value test_hashexpand_meth)
+    )
+
+    str = 42
+    pp %h(#{foo})
+    assert_equal(%h(#{foo}), { str: 42 })
+
+    assert_operator(Hash, :===, hash)
+
+    proc { |value|
+      hoge = -1
+      assert_equal(%h(hoge foo value), { hoge: -1, foo: "str", value: 42 })
+    }.call 42
+
+    assert_equal(%h(hoge foo hoge), { hoge: hoge, foo: foo })
+    assert_equal(%h(hoge foo hoge).keys, %i(hoge foo))
+
+    assert_raise(NameError) do
+      %h(not_found)
+    end
+  end
+
 =begin
   def test_past_scope_variable
     assert_warning(/past scope/) {catch {|tag| eval("BEGIN{throw tag}; tap {a = 1}; a")}}
