From 51ea7c12e70244dc89893888f3bfd6cead59d4c5 Mon Sep 17 00:00:00 2001
From: Alona Enraght-Moony <code@alona.page>
Date: Sun, 1 Dec 2024 21:37:18 +0000
Subject: [PATCH 1/2] rustdoc-json: Add tests for `static`s

---
 tests/rustdoc-json/statics/statics.rs | 10 ++++++++++
 1 file changed, 10 insertions(+)
 create mode 100644 tests/rustdoc-json/statics/statics.rs

diff --git a/tests/rustdoc-json/statics/statics.rs b/tests/rustdoc-json/statics/statics.rs
new file mode 100644
index 0000000000000..e3ed936992563
--- /dev/null
+++ b/tests/rustdoc-json/statics/statics.rs
@@ -0,0 +1,10 @@
+//@ is '$.index[*][?(@.name=="A")].inner.static.type.primitive' '"i32"'
+//@ is '$.index[*][?(@.name=="A")].inner.static.is_mutable' false
+//@ is '$.index[*][?(@.name=="A")].inner.static.expr' '"5"'
+pub static A: i32 = 5;
+
+//@ is '$.index[*][?(@.name=="B")].inner.static.type.primitive' '"u32"'
+//@ is '$.index[*][?(@.name=="B")].inner.static.is_mutable' true
+// Expr value isn't gaurenteed, it'd be fine to change it.
+//@ is '$.index[*][?(@.name=="B")].inner.static.expr' '"_"'
+pub static mut B: u32 = 2 + 3;

From f33dba028704d108497b8c06943b9bbc3d14c42b Mon Sep 17 00:00:00 2001
From: Alona Enraght-Moony <code@alona.page>
Date: Sun, 1 Dec 2024 21:39:58 +0000
Subject: [PATCH 2/2] rustdoc-json: Include safety of `static`s

---
 src/librustdoc/json/conversions.rs    | 29 +++++++++++---------
 src/rustdoc-json-types/lib.rs         | 18 ++++++++++++-
 tests/rustdoc-json/statics/extern.rs  | 39 +++++++++++++++++++++++++++
 tests/rustdoc-json/statics/statics.rs |  2 ++
 4 files changed, 74 insertions(+), 14 deletions(-)
 create mode 100644 tests/rustdoc-json/statics/extern.rs

diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 1c8303d4c2087..bb967b7f163ed 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -321,8 +321,8 @@ fn from_clean_item(item: clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum {
         MethodItem(m, _) => ItemEnum::Function(from_function(m, true, header.unwrap(), renderer)),
         TyMethodItem(m) => ItemEnum::Function(from_function(m, false, header.unwrap(), renderer)),
         ImplItem(i) => ItemEnum::Impl((*i).into_json(renderer)),
-        StaticItem(s) => ItemEnum::Static(s.into_json(renderer)),
-        ForeignStaticItem(s, _) => ItemEnum::Static(s.into_json(renderer)),
+        StaticItem(s) => ItemEnum::Static(convert_static(s, rustc_hir::Safety::Safe, renderer)),
+        ForeignStaticItem(s, safety) => ItemEnum::Static(convert_static(s, safety, renderer)),
         ForeignTypeItem => ItemEnum::ExternType,
         TypeAliasItem(t) => ItemEnum::TypeAlias(t.into_json(renderer)),
         // FIXME(generic_const_items): Add support for generic free consts
@@ -831,17 +831,20 @@ impl FromClean<Box<clean::TypeAlias>> for TypeAlias {
     }
 }
 
-impl FromClean<clean::Static> for Static {
-    fn from_clean(stat: clean::Static, renderer: &JsonRenderer<'_>) -> Self {
-        let tcx = renderer.tcx;
-        Static {
-            type_: (*stat.type_).into_json(renderer),
-            is_mutable: stat.mutability == ast::Mutability::Mut,
-            expr: stat
-                .expr
-                .map(|e| rendered_const(tcx, tcx.hir().body(e), tcx.hir().body_owner_def_id(e)))
-                .unwrap_or_default(),
-        }
+fn convert_static(
+    stat: clean::Static,
+    safety: rustc_hir::Safety,
+    renderer: &JsonRenderer<'_>,
+) -> Static {
+    let tcx = renderer.tcx;
+    Static {
+        type_: (*stat.type_).into_json(renderer),
+        is_mutable: stat.mutability == ast::Mutability::Mut,
+        is_unsafe: safety == rustc_hir::Safety::Unsafe,
+        expr: stat
+            .expr
+            .map(|e| rendered_const(tcx, tcx.hir().body(e), tcx.hir().body_owner_def_id(e)))
+            .unwrap_or_default(),
     }
 }
 
diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs
index f553a78d766e5..84b33e3d86068 100644
--- a/src/rustdoc-json-types/lib.rs
+++ b/src/rustdoc-json-types/lib.rs
@@ -30,7 +30,7 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
 /// This integer is incremented with every breaking change to the API,
 /// and is returned along with the JSON blob as [`Crate::format_version`].
 /// Consuming code should assert that this value matches the format version(s) that it supports.
-pub const FORMAT_VERSION: u32 = 36;
+pub const FORMAT_VERSION: u32 = 37;
 
 /// The root of the emitted JSON blob.
 ///
@@ -1238,6 +1238,22 @@ pub struct Static {
     ///
     /// It's not guaranteed that it'll match the actual source code for the initial value.
     pub expr: String,
+
+    /// Is the static `unsafe`?
+    ///
+    /// This is only true if it's in an `extern` block, and not explicity marked
+    /// as `safe`.
+    ///
+    /// ```rust
+    /// unsafe extern {
+    ///     static A: i32;      // unsafe
+    ///     safe static B: i32; // safe
+    /// }
+    ///
+    /// static C: i32 = 0;     // safe
+    /// static mut D: i32 = 0; // safe
+    /// ```
+    pub is_unsafe: bool,
 }
 
 /// A primitive type declaration. Declarations of this kind can only come from the core library.
diff --git a/tests/rustdoc-json/statics/extern.rs b/tests/rustdoc-json/statics/extern.rs
new file mode 100644
index 0000000000000..d38fdf1cd1cdd
--- /dev/null
+++ b/tests/rustdoc-json/statics/extern.rs
@@ -0,0 +1,39 @@
+// ignore-tidy-linelength
+//@ edition: 2021
+
+extern "C" {
+    //@ is '$.index[*][?(@.name=="A")].inner.static.is_unsafe'  true
+    //@ is '$.index[*][?(@.name=="A")].inner.static.is_mutable' false
+    pub static A: i32;
+    //@ is '$.index[*][?(@.name=="B")].inner.static.is_unsafe'  true
+    //@ is '$.index[*][?(@.name=="B")].inner.static.is_mutable' true
+    pub static mut B: i32;
+
+    // items in unadorned `extern` blocks cannot have safety qualifiers
+}
+
+unsafe extern "C" {
+    //@ is '$.index[*][?(@.name=="C")].inner.static.is_unsafe'  true
+    //@ is '$.index[*][?(@.name=="C")].inner.static.is_mutable' false
+    pub static C: i32;
+    //@ is '$.index[*][?(@.name=="D")].inner.static.is_unsafe'  true
+    //@ is '$.index[*][?(@.name=="D")].inner.static.is_mutable' true
+    pub static mut D: i32;
+
+    //@ is '$.index[*][?(@.name=="E")].inner.static.is_unsafe'  false
+    //@ is '$.index[*][?(@.name=="E")].inner.static.is_mutable' false
+    pub safe static E: i32;
+    //@ is '$.index[*][?(@.name=="F")].inner.static.is_unsafe'  false
+    //@ is '$.index[*][?(@.name=="F")].inner.static.is_mutable' true
+    pub safe static mut F: i32;
+
+    //@ is '$.index[*][?(@.name=="G")].inner.static.is_unsafe'  true
+    //@ is '$.index[*][?(@.name=="G")].inner.static.is_mutable' false
+    pub unsafe static G: i32;
+    //@ is '$.index[*][?(@.name=="H")].inner.static.is_unsafe'  true
+    //@ is '$.index[*][?(@.name=="H")].inner.static.is_mutable' true
+    pub unsafe static mut H: i32;
+}
+
+//@ ismany '$.index[*][?(@.inner.static)].inner.static.expr' '""' '""' '""' '""' '""' '""' '""' '""'
+//@ ismany '$.index[*][?(@.inner.static)].inner.static.type.primitive' '"i32"' '"i32"' '"i32"' '"i32"' '"i32"' '"i32"' '"i32"' '"i32"'
diff --git a/tests/rustdoc-json/statics/statics.rs b/tests/rustdoc-json/statics/statics.rs
index e3ed936992563..a8af23cc87dcc 100644
--- a/tests/rustdoc-json/statics/statics.rs
+++ b/tests/rustdoc-json/statics/statics.rs
@@ -1,10 +1,12 @@
 //@ is '$.index[*][?(@.name=="A")].inner.static.type.primitive' '"i32"'
 //@ is '$.index[*][?(@.name=="A")].inner.static.is_mutable' false
 //@ is '$.index[*][?(@.name=="A")].inner.static.expr' '"5"'
+//@ is '$.index[*][?(@.name=="A")].inner.static.is_unsafe' false
 pub static A: i32 = 5;
 
 //@ is '$.index[*][?(@.name=="B")].inner.static.type.primitive' '"u32"'
 //@ is '$.index[*][?(@.name=="B")].inner.static.is_mutable' true
 // Expr value isn't gaurenteed, it'd be fine to change it.
 //@ is '$.index[*][?(@.name=="B")].inner.static.expr' '"_"'
+//@ is '$.index[*][?(@.name=="B")].inner.static.is_unsafe' false
 pub static mut B: u32 = 2 + 3;