diff --git a/grammar.ron b/grammar.ron
index 53248519f64..0717b7a7694 100644
--- a/grammar.ron
+++ b/grammar.ron
@@ -25,6 +25,7 @@ Grammar(
         "const",
         "static",
         "mut",
+        "unsafe",
     ],
     tokens: [
         "ERROR",
@@ -89,6 +90,7 @@ Grammar(
         "MOD_ITEM",
         "USE_ITEM",
         "STATIC_ITEM",
+        "CONST_ITEM",
 
         "EXTERN_BLOCK",
         "ENUM_VARIANT",
diff --git a/src/parser/event_parser/grammar/items/consts.rs b/src/parser/event_parser/grammar/items/consts.rs
new file mode 100644
index 00000000000..c9881d681aa
--- /dev/null
+++ b/src/parser/event_parser/grammar/items/consts.rs
@@ -0,0 +1,21 @@
+use super::*;
+
+pub(super) fn static_item(p: &mut Parser) {
+    const_or_static(p, STATIC_KW)
+}
+
+pub(super) fn const_item(p: &mut Parser) {
+    const_or_static(p, CONST_KW)
+}
+
+fn const_or_static(p: &mut Parser, kw: SyntaxKind) {
+    assert!(p.at(kw));
+    p.bump();
+    p.eat(MUT_KW); // TODO: validator to forbid const mut
+    p.expect(IDENT);
+    p.expect(COLON);
+    types::type_ref(p);
+    p.expect(EQ);
+    expressions::expr(p);
+    p.expect(SEMI);
+}
diff --git a/src/parser/event_parser/grammar/items/mod.rs b/src/parser/event_parser/grammar/items/mod.rs
index 9930de34783..8ccf8f90f9a 100644
--- a/src/parser/event_parser/grammar/items/mod.rs
+++ b/src/parser/event_parser/grammar/items/mod.rs
@@ -2,6 +2,7 @@ use super::*;
 
 mod structs;
 mod use_item;
+mod consts;
 
 pub(super) fn mod_contents(p: &mut Parser, stop_on_r_curly: bool) {
     attributes::inner_attributes(p);
@@ -47,9 +48,26 @@ fn item(p: &mut Parser) {
             }
         }
         STATIC_KW => {
-            static_item(p);
+            consts::static_item(p);
             STATIC_ITEM
         }
+        CONST_KW => match p.nth(1) {
+            FN_KW => {
+                p.bump();
+                fn_item(p);
+                FN_ITEM
+            }
+            UNSAFE_KW if p.nth(2) == FN_KW => {
+                p.bump();
+                p.bump();
+                fn_item(p);
+                FN_ITEM
+            }
+            _ => {
+                consts::const_item(p);
+                CONST_ITEM
+            }
+        },
         MOD_KW => {
             mod_item(p);
             MOD_ITEM
@@ -101,19 +119,6 @@ fn extern_block(p: &mut Parser) {
     p.bump();
     p.expect(R_CURLY);
 }
-
-fn static_item(p: &mut Parser) {
-    assert!(p.at(STATIC_KW));
-    p.bump();
-    p.eat(MUT_KW);
-    p.expect(IDENT);
-    p.expect(COLON);
-    types::type_ref(p);
-    p.expect(EQ);
-    expressions::expr(p);
-    p.expect(SEMI);
-}
-
 fn mod_item(p: &mut Parser) {
     assert!(p.at(MOD_KW));
     p.bump();
diff --git a/src/syntax_kinds.rs b/src/syntax_kinds.rs
index 8d21d3fb7bb..c182aea78fd 100644
--- a/src/syntax_kinds.rs
+++ b/src/syntax_kinds.rs
@@ -31,6 +31,7 @@ pub enum SyntaxKind {
     CONST_KW,
     STATIC_KW,
     MUT_KW,
+    UNSAFE_KW,
     ERROR,
     IDENT,
     UNDERSCORE,
@@ -90,6 +91,7 @@ pub enum SyntaxKind {
     MOD_ITEM,
     USE_ITEM,
     STATIC_ITEM,
+    CONST_ITEM,
     EXTERN_BLOCK,
     ENUM_VARIANT,
     NAMED_FIELD,
@@ -144,6 +146,7 @@ impl SyntaxKind {
             CONST_KW => &SyntaxInfo { name: "CONST_KW" },
             STATIC_KW => &SyntaxInfo { name: "STATIC_KW" },
             MUT_KW => &SyntaxInfo { name: "MUT_KW" },
+            UNSAFE_KW => &SyntaxInfo { name: "UNSAFE_KW" },
             ERROR => &SyntaxInfo { name: "ERROR" },
             IDENT => &SyntaxInfo { name: "IDENT" },
             UNDERSCORE => &SyntaxInfo { name: "UNDERSCORE" },
@@ -203,6 +206,7 @@ impl SyntaxKind {
             MOD_ITEM => &SyntaxInfo { name: "MOD_ITEM" },
             USE_ITEM => &SyntaxInfo { name: "USE_ITEM" },
             STATIC_ITEM => &SyntaxInfo { name: "STATIC_ITEM" },
+            CONST_ITEM => &SyntaxInfo { name: "CONST_ITEM" },
             EXTERN_BLOCK => &SyntaxInfo { name: "EXTERN_BLOCK" },
             ENUM_VARIANT => &SyntaxInfo { name: "ENUM_VARIANT" },
             NAMED_FIELD => &SyntaxInfo { name: "NAMED_FIELD" },
@@ -253,6 +257,7 @@ pub(crate) fn ident_to_keyword(ident: &str) -> Option<SyntaxKind> {
         "const" => Some(CONST_KW),
         "static" => Some(STATIC_KW),
         "mut" => Some(MUT_KW),
+        "unsafe" => Some(UNSAFE_KW),
         _ => None,
     }
 }
diff --git a/tests/data/parser/ok/0024_const_fn.rs b/tests/data/parser/ok/0024_const_fn.rs
new file mode 100644
index 00000000000..eba9322a1ca
--- /dev/null
+++ b/tests/data/parser/ok/0024_const_fn.rs
@@ -0,0 +1,5 @@
+const fn foo() {
+}
+
+const unsafe fn foo() {
+}
diff --git a/tests/data/parser/ok/0024_const_fn.txt b/tests/data/parser/ok/0024_const_fn.txt
new file mode 100644
index 00000000000..0fd48599729
--- /dev/null
+++ b/tests/data/parser/ok/0024_const_fn.txt
@@ -0,0 +1,29 @@
+FILE@[0; 46)
+  FN_ITEM@[0; 20)
+    CONST_KW@[0; 5)
+    WHITESPACE@[5; 6)
+    FN_KW@[6; 8)
+    WHITESPACE@[8; 9)
+    IDENT@[9; 12) "foo"
+    L_PAREN@[12; 13)
+    R_PAREN@[13; 14)
+    WHITESPACE@[14; 15)
+    L_CURLY@[15; 16)
+    WHITESPACE@[16; 17)
+    R_CURLY@[17; 18)
+    WHITESPACE@[18; 20)
+  FN_ITEM@[20; 46)
+    CONST_KW@[20; 25)
+    WHITESPACE@[25; 26)
+    UNSAFE_KW@[26; 32)
+    WHITESPACE@[32; 33)
+    FN_KW@[33; 35)
+    WHITESPACE@[35; 36)
+    IDENT@[36; 39) "foo"
+    L_PAREN@[39; 40)
+    R_PAREN@[40; 41)
+    WHITESPACE@[41; 42)
+    L_CURLY@[42; 43)
+    WHITESPACE@[43; 44)
+    R_CURLY@[44; 45)
+    WHITESPACE@[45; 46)
diff --git a/tests/data/parser/ok/0025_const_item.rs b/tests/data/parser/ok/0025_const_item.rs
new file mode 100644
index 00000000000..7446859b5a4
--- /dev/null
+++ b/tests/data/parser/ok/0025_const_item.rs
@@ -0,0 +1,2 @@
+const FOO: u32 = 92;
+const mut BAR: u32 = 62;
diff --git a/tests/data/parser/ok/0025_const_item.txt b/tests/data/parser/ok/0025_const_item.txt
new file mode 100644
index 00000000000..588e001f59a
--- /dev/null
+++ b/tests/data/parser/ok/0025_const_item.txt
@@ -0,0 +1,31 @@
+FILE@[0; 46)
+  CONST_ITEM@[0; 21)
+    CONST_KW@[0; 5)
+    WHITESPACE@[5; 6)
+    IDENT@[6; 9) "FOO"
+    COLON@[9; 10)
+    WHITESPACE@[10; 11)
+    IDENT@[11; 14) "u32"
+    WHITESPACE@[14; 15)
+    EQ@[15; 16)
+    LITERAL@[16; 19)
+      WHITESPACE@[16; 17)
+      INT_NUMBER@[17; 19)
+    SEMI@[19; 20)
+    WHITESPACE@[20; 21)
+  CONST_ITEM@[21; 46)
+    CONST_KW@[21; 26)
+    WHITESPACE@[26; 27)
+    MUT_KW@[27; 30)
+    WHITESPACE@[30; 31)
+    IDENT@[31; 34) "BAR"
+    COLON@[34; 35)
+    WHITESPACE@[35; 36)
+    IDENT@[36; 39) "u32"
+    WHITESPACE@[39; 40)
+    EQ@[40; 41)
+    LITERAL@[41; 44)
+      WHITESPACE@[41; 42)
+      INT_NUMBER@[42; 44)
+    SEMI@[44; 45)
+    WHITESPACE@[45; 46)