diff --git a/Cargo.lock b/Cargo.lock
index ca0febcfd04..fa7c97d5a7c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1763,9 +1763,9 @@ dependencies = [
 
 [[package]]
 name = "ungrammar"
-version = "1.14.8"
+version = "1.14.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "403c1892ad46cacffb28c73550172999c6c75f70ca9c97bcafc8ce99d6421529"
+checksum = "66be59c2fd880e3d76d1a6cf6d34114008f1d8af2748d4ad9d39ea712f14fda9"
 
 [[package]]
 name = "unicase"
diff --git a/Cargo.toml b/Cargo.toml
index 4d6908fa93e..84da50b9f6c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,8 @@ miniz_oxide.opt-level = 3
 
 [profile.release]
 incremental = true
-debug = 0 # Set this to 1 or 2 to get more useful backtraces in debugger.
+# Set this to 1 or 2 to get more useful backtraces in debugger.
+debug = 0
 
 [patch.'crates-io']
 # rowan = { path = "../rowan" }
diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs
index 00ccbf5010e..1009c67ffa1 100644
--- a/crates/parser/src/grammar/generic_params.rs
+++ b/crates/parser/src/grammar/generic_params.rs
@@ -100,7 +100,7 @@ fn lifetime_bounds(p: &mut Parser) {
 }
 
 // test type_param_bounds
-// struct S<T: 'a + ?Sized + (Copy)>;
+// struct S<T: 'a + ?Sized + (Copy) + ~const Drop>;
 pub(super) fn bounds(p: &mut Parser) {
     assert!(p.at(T![:]));
     p.bump(T![:]);
@@ -124,14 +124,24 @@ pub(super) fn bounds_without_colon_m(p: &mut Parser, marker: Marker) -> Complete
 fn type_bound(p: &mut Parser) -> bool {
     let m = p.start();
     let has_paren = p.eat(T!['(']);
-    p.eat(T![?]);
     match p.current() {
         LIFETIME_IDENT => lifetime(p),
         T![for] => types::for_type(p, false),
-        _ if paths::is_use_path_start(p) => types::path_type_(p, false),
-        _ => {
-            m.abandon(p);
-            return false;
+        current => {
+            match current {
+                T![?] => p.bump_any(),
+                T![~] => {
+                    p.bump_any();
+                    p.expect(T![const]);
+                }
+                _ => (),
+            }
+            if paths::is_use_path_start(p) {
+                types::path_type_(p, false);
+            } else {
+                m.abandon(p);
+                return false;
+            }
         }
     }
     if has_paren {
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index a34522435a2..d5fc7893cfe 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -29,7 +29,7 @@ rayon = "1"
 expect-test = "1.2.0-pre.1"
 proc-macro2 = "1.0.8"
 quote = "1.0.2"
-ungrammar = "=1.14.8"
+ungrammar = "=1.14.9"
 
 test_utils = { path = "../test_utils" }
 sourcegen = { path = "../sourcegen" }
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index 15b8371c3eb..005e78c48a1 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -1271,6 +1271,8 @@ pub struct TypeBound {
 impl TypeBound {
     pub fn lifetime(&self) -> Option<Lifetime> { support::child(&self.syntax) }
     pub fn question_mark_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![?]) }
+    pub fn tilde_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![~]) }
+    pub fn const_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![const]) }
     pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
 }
 
diff --git a/crates/syntax/src/tests/sourcegen_ast.rs b/crates/syntax/src/tests/sourcegen_ast.rs
index d2b58774903..a1a96581b4a 100644
--- a/crates/syntax/src/tests/sourcegen_ast.rs
+++ b/crates/syntax/src/tests/sourcegen_ast.rs
@@ -536,6 +536,7 @@ impl Field {
                     "?" => "question_mark",
                     "," => "comma",
                     "|" => "pipe",
+                    "~" => "tilde",
                     _ => name,
                 };
                 format_ident!("{}_token", name)
diff --git a/crates/syntax/test_data/parser/inline/ok/0007_type_param_bounds.rast b/crates/syntax/test_data/parser/inline/ok/0007_type_param_bounds.rast
index 121c3966ac9..4cd03485f42 100644
--- a/crates/syntax/test_data/parser/inline/ok/0007_type_param_bounds.rast
+++ b/crates/syntax/test_data/parser/inline/ok/0007_type_param_bounds.rast
@@ -1,17 +1,17 @@
-SOURCE_FILE@0..35
-  STRUCT@0..34
+SOURCE_FILE@0..49
+  STRUCT@0..48
     STRUCT_KW@0..6 "struct"
     WHITESPACE@6..7 " "
     NAME@7..8
       IDENT@7..8 "S"
-    GENERIC_PARAM_LIST@8..33
+    GENERIC_PARAM_LIST@8..47
       L_ANGLE@8..9 "<"
-      TYPE_PARAM@9..32
+      TYPE_PARAM@9..46
         NAME@9..10
           IDENT@9..10 "T"
         COLON@10..11 ":"
         WHITESPACE@11..12 " "
-        TYPE_BOUND_LIST@12..32
+        TYPE_BOUND_LIST@12..46
           TYPE_BOUND@12..14
             LIFETIME@12..14
               LIFETIME_IDENT@12..14 "'a"
@@ -36,6 +36,18 @@ SOURCE_FILE@0..35
                   NAME_REF@27..31
                     IDENT@27..31 "Copy"
             R_PAREN@31..32 ")"
-      R_ANGLE@32..33 ">"
-    SEMICOLON@33..34 ";"
-  WHITESPACE@34..35 "\n"
+          WHITESPACE@32..33 " "
+          PLUS@33..34 "+"
+          WHITESPACE@34..35 " "
+          TYPE_BOUND@35..46
+            TILDE@35..36 "~"
+            CONST_KW@36..41 "const"
+            WHITESPACE@41..42 " "
+            PATH_TYPE@42..46
+              PATH@42..46
+                PATH_SEGMENT@42..46
+                  NAME_REF@42..46
+                    IDENT@42..46 "Drop"
+      R_ANGLE@46..47 ">"
+    SEMICOLON@47..48 ";"
+  WHITESPACE@48..49 "\n"
diff --git a/crates/syntax/test_data/parser/inline/ok/0007_type_param_bounds.rs b/crates/syntax/test_data/parser/inline/ok/0007_type_param_bounds.rs
index 919bde0ee92..5da3083b9c5 100644
--- a/crates/syntax/test_data/parser/inline/ok/0007_type_param_bounds.rs
+++ b/crates/syntax/test_data/parser/inline/ok/0007_type_param_bounds.rs
@@ -1 +1 @@
-struct S<T: 'a + ?Sized + (Copy)>;
+struct S<T: 'a + ?Sized + (Copy) + ~const Drop>;