diff --git a/crates/hir_def/src/db.rs b/crates/hir_def/src/db.rs
index 068b2ee3858..9b7a213a12e 100644
--- a/crates/hir_def/src/db.rs
+++ b/crates/hir_def/src/db.rs
@@ -13,6 +13,7 @@ use crate::{
     data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData},
     generics::GenericParams,
     import_map::ImportMap,
+    intern::Interned,
     item_tree::ItemTree,
     lang_item::{LangItemTarget, LangItems},
     nameres::DefMap,
@@ -113,7 +114,7 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
     fn expr_scopes(&self, def: DefWithBodyId) -> Arc<ExprScopes>;
 
     #[salsa::invoke(GenericParams::generic_params_query)]
-    fn generic_params(&self, def: GenericDefId) -> Arc<GenericParams>;
+    fn generic_params(&self, def: GenericDefId) -> Interned<GenericParams>;
 
     #[salsa::invoke(Attrs::variants_attrs_query)]
     fn variants_attrs(&self, def: EnumId) -> Arc<ArenaMap<LocalEnumVariantId, Attrs>>;
diff --git a/crates/hir_def/src/generics.rs b/crates/hir_def/src/generics.rs
index d55c189d4e3..de5acced8cf 100644
--- a/crates/hir_def/src/generics.rs
+++ b/crates/hir_def/src/generics.rs
@@ -2,7 +2,6 @@
 //! structs, impls, traits, etc. This module provides a common HIR for these
 //! generic parameters. See also the `Generics` type and the `generics_of` query
 //! in rustc.
-use std::sync::Arc;
 
 use base_db::FileId;
 use either::Either;
@@ -27,7 +26,7 @@ use crate::{
 };
 
 /// Data about a generic type parameter (to a function, struct, impl, ...).
-#[derive(Clone, PartialEq, Eq, Debug)]
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
 pub struct TypeParamData {
     pub name: Option<Name>,
     pub default: Option<Interned<TypeRef>>,
@@ -35,19 +34,19 @@ pub struct TypeParamData {
 }
 
 /// Data about a generic lifetime parameter (to a function, struct, impl, ...).
-#[derive(Clone, PartialEq, Eq, Debug)]
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
 pub struct LifetimeParamData {
     pub name: Name,
 }
 
 /// Data about a generic const parameter (to a function, struct, impl, ...).
-#[derive(Clone, PartialEq, Eq, Debug)]
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
 pub struct ConstParamData {
     pub name: Name,
     pub ty: Interned<TypeRef>,
 }
 
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
 pub enum TypeParamProvenance {
     TypeParamList,
     TraitSelf,
@@ -55,7 +54,7 @@ pub enum TypeParamProvenance {
 }
 
 /// Data about the generic parameters of a function, struct, impl, etc.
-#[derive(Clone, PartialEq, Eq, Debug, Default)]
+#[derive(Clone, PartialEq, Eq, Debug, Default, Hash)]
 pub struct GenericParams {
     pub types: Arena<TypeParamData>,
     pub lifetimes: Arena<LifetimeParamData>,
@@ -67,14 +66,14 @@ pub struct GenericParams {
 /// where clauses like `where T: Foo + Bar` are turned into multiple of these.
 /// It might still result in multiple actual predicates though, because of
 /// associated type bindings like `Iterator<Item = u32>`.
-#[derive(Clone, PartialEq, Eq, Debug)]
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
 pub enum WherePredicate {
     TypeBound { target: WherePredicateTypeTarget, bound: TypeBound },
     Lifetime { target: LifetimeRef, bound: LifetimeRef },
     ForLifetime { lifetimes: Box<[Name]>, target: WherePredicateTypeTarget, bound: TypeBound },
 }
 
-#[derive(Clone, PartialEq, Eq, Debug)]
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
 pub enum WherePredicateTypeTarget {
     TypeRef(Interned<TypeRef>),
     /// For desugared where predicates that can directly refer to a type param.
@@ -92,7 +91,7 @@ impl GenericParams {
     pub(crate) fn generic_params_query(
         db: &dyn DefDatabase,
         def: GenericDefId,
-    ) -> Arc<GenericParams> {
+    ) -> Interned<GenericParams> {
         let _p = profile::span("generic_params_query");
 
         let generics = match def {
@@ -100,47 +99,49 @@ impl GenericParams {
                 let id = id.lookup(db).id;
                 let tree = id.item_tree(db);
                 let item = &tree[id.value];
-                tree[item.generic_params].clone()
+                item.generic_params.clone()
             }
             GenericDefId::AdtId(AdtId::StructId(id)) => {
                 let id = id.lookup(db).id;
                 let tree = id.item_tree(db);
                 let item = &tree[id.value];
-                tree[item.generic_params].clone()
+                item.generic_params.clone()
             }
             GenericDefId::AdtId(AdtId::EnumId(id)) => {
                 let id = id.lookup(db).id;
                 let tree = id.item_tree(db);
                 let item = &tree[id.value];
-                tree[item.generic_params].clone()
+                item.generic_params.clone()
             }
             GenericDefId::AdtId(AdtId::UnionId(id)) => {
                 let id = id.lookup(db).id;
                 let tree = id.item_tree(db);
                 let item = &tree[id.value];
-                tree[item.generic_params].clone()
+                item.generic_params.clone()
             }
             GenericDefId::TraitId(id) => {
                 let id = id.lookup(db).id;
                 let tree = id.item_tree(db);
                 let item = &tree[id.value];
-                tree[item.generic_params].clone()
+                item.generic_params.clone()
             }
             GenericDefId::TypeAliasId(id) => {
                 let id = id.lookup(db).id;
                 let tree = id.item_tree(db);
                 let item = &tree[id.value];
-                tree[item.generic_params].clone()
+                item.generic_params.clone()
             }
             GenericDefId::ImplId(id) => {
                 let id = id.lookup(db).id;
                 let tree = id.item_tree(db);
                 let item = &tree[id.value];
-                tree[item.generic_params].clone()
+                item.generic_params.clone()
+            }
+            GenericDefId::EnumVariantId(_) | GenericDefId::ConstId(_) => {
+                Interned::new(GenericParams::default())
             }
-            GenericDefId::EnumVariantId(_) | GenericDefId::ConstId(_) => GenericParams::default(),
         };
-        Arc::new(generics)
+        generics
     }
 
     fn new(db: &dyn DefDatabase, def: GenericDefId) -> (GenericParams, InFile<SourceMap>) {
diff --git a/crates/hir_def/src/intern.rs b/crates/hir_def/src/intern.rs
index d163f633f30..2467e9299f0 100644
--- a/crates/hir_def/src/intern.rs
+++ b/crates/hir_def/src/intern.rs
@@ -14,6 +14,8 @@ use dashmap::{lock::RwLockWriteGuard, DashMap, SharedValue};
 use once_cell::sync::OnceCell;
 use rustc_hash::FxHasher;
 
+use crate::generics::GenericParams;
+
 type InternMap<T> = DashMap<Arc<T>, (), BuildHasherDefault<FxHasher>>;
 type Guard<T> =
     RwLockWriteGuard<'static, HashMap<Arc<T>, SharedValue<()>, BuildHasherDefault<FxHasher>>>;
@@ -194,4 +196,10 @@ macro_rules! impl_internable {
     )+ };
 }
 
-impl_internable!(crate::type_ref::TypeRef, crate::type_ref::TraitRef, crate::path::ModPath, str);
+impl_internable!(
+    crate::type_ref::TypeRef,
+    crate::type_ref::TraitRef,
+    crate::path::ModPath,
+    GenericParams,
+    str
+);
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index 739906778f9..2406624862d 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -58,13 +58,6 @@ impl fmt::Debug for RawVisibilityId {
     }
 }
 
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub struct GenericParamsId(u32);
-
-impl GenericParamsId {
-    pub const EMPTY: Self = GenericParamsId(u32::max_value());
-}
-
 /// The item tree of a source file.
 #[derive(Debug, Default, Eq, PartialEq)]
 pub struct ItemTree {
@@ -146,7 +139,6 @@ impl ItemTree {
                 macro_rules,
                 macro_defs,
                 vis,
-                generics,
                 inner_items,
             } = &mut **data;
 
@@ -170,7 +162,6 @@ impl ItemTree {
             macro_defs.shrink_to_fit();
 
             vis.arena.shrink_to_fit();
-            generics.arena.shrink_to_fit();
 
             inner_items.shrink_to_fit();
         }
@@ -241,32 +232,6 @@ static VIS_PUB: RawVisibility = RawVisibility::Public;
 static VIS_PRIV: RawVisibility = RawVisibility::Module(ModPath::from_kind(PathKind::Super(0)));
 static VIS_PUB_CRATE: RawVisibility = RawVisibility::Module(ModPath::from_kind(PathKind::Crate));
 
-#[derive(Default, Debug, Eq, PartialEq)]
-struct GenericParamsStorage {
-    arena: Arena<GenericParams>,
-}
-
-impl GenericParamsStorage {
-    fn alloc(&mut self, params: GenericParams) -> GenericParamsId {
-        if params.types.is_empty()
-            && params.lifetimes.is_empty()
-            && params.consts.is_empty()
-            && params.where_predicates.is_empty()
-        {
-            return GenericParamsId::EMPTY;
-        }
-
-        GenericParamsId(self.arena.alloc(params).into_raw().into())
-    }
-}
-
-static EMPTY_GENERICS: GenericParams = GenericParams {
-    types: Arena::new(),
-    lifetimes: Arena::new(),
-    consts: Arena::new(),
-    where_predicates: Vec::new(),
-};
-
 #[derive(Default, Debug, Eq, PartialEq)]
 struct ItemTreeData {
     imports: Arena<Import>,
@@ -289,7 +254,6 @@ struct ItemTreeData {
     macro_defs: Arena<MacroDef>,
 
     vis: ItemVisibilities,
-    generics: GenericParamsStorage,
 
     inner_items: FxHashMap<FileAstId<ast::BlockExpr>, SmallVec<[ModItem; 1]>>,
 }
@@ -508,17 +472,6 @@ impl Index<RawVisibilityId> for ItemTree {
     }
 }
 
-impl Index<GenericParamsId> for ItemTree {
-    type Output = GenericParams;
-
-    fn index(&self, index: GenericParamsId) -> &Self::Output {
-        match index {
-            GenericParamsId::EMPTY => &EMPTY_GENERICS,
-            _ => &self.data().generics.arena[Idx::from_raw(index.0.into())],
-        }
-    }
-}
-
 impl<N: ItemTreeNode> Index<FileItemTreeId<N>> for ItemTree {
     type Output = N;
     fn index(&self, id: FileItemTreeId<N>) -> &N {
@@ -555,7 +508,7 @@ pub struct ExternCrate {
 pub struct Function {
     pub name: Name,
     pub visibility: RawVisibilityId,
-    pub generic_params: GenericParamsId,
+    pub generic_params: Interned<GenericParams>,
     pub abi: Option<Interned<str>>,
     pub params: IdRange<Param>,
     pub ret_type: Interned<TypeRef>,
@@ -590,7 +543,7 @@ impl FnFlags {
 pub struct Struct {
     pub name: Name,
     pub visibility: RawVisibilityId,
-    pub generic_params: GenericParamsId,
+    pub generic_params: Interned<GenericParams>,
     pub fields: Fields,
     pub ast_id: FileAstId<ast::Struct>,
     pub kind: StructDefKind,
@@ -610,7 +563,7 @@ pub enum StructDefKind {
 pub struct Union {
     pub name: Name,
     pub visibility: RawVisibilityId,
-    pub generic_params: GenericParamsId,
+    pub generic_params: Interned<GenericParams>,
     pub fields: Fields,
     pub ast_id: FileAstId<ast::Union>,
 }
@@ -619,7 +572,7 @@ pub struct Union {
 pub struct Enum {
     pub name: Name,
     pub visibility: RawVisibilityId,
-    pub generic_params: GenericParamsId,
+    pub generic_params: Interned<GenericParams>,
     pub variants: IdRange<Variant>,
     pub ast_id: FileAstId<ast::Enum>,
 }
@@ -648,7 +601,7 @@ pub struct Static {
 pub struct Trait {
     pub name: Name,
     pub visibility: RawVisibilityId,
-    pub generic_params: GenericParamsId,
+    pub generic_params: Interned<GenericParams>,
     pub is_auto: bool,
     pub is_unsafe: bool,
     pub bounds: Box<[TypeBound]>,
@@ -658,7 +611,7 @@ pub struct Trait {
 
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct Impl {
-    pub generic_params: GenericParamsId,
+    pub generic_params: Interned<GenericParams>,
     pub target_trait: Option<Interned<TraitRef>>,
     pub self_ty: Interned<TypeRef>,
     pub is_negative: bool,
@@ -672,7 +625,7 @@ pub struct TypeAlias {
     pub visibility: RawVisibilityId,
     /// Bounds on the type alias itself. Only valid in trait declarations, eg. `type Assoc: Copy;`.
     pub bounds: Box<[TypeBound]>,
-    pub generic_params: GenericParamsId,
+    pub generic_params: Interned<GenericParams>,
     pub type_ref: Option<Interned<TypeRef>>,
     pub is_extern: bool,
     pub ast_id: FileAstId<ast::TypeAlias>,
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index 0c8112ddabd..c5629af2401 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -434,7 +434,7 @@ impl Ctx {
         let mut res = Function {
             name,
             visibility,
-            generic_params: GenericParamsId::EMPTY,
+            generic_params: Interned::new(GenericParams::default()),
             abi,
             params,
             ret_type: Interned::new(ret_type),
@@ -682,7 +682,7 @@ impl Ctx {
         &mut self,
         owner: GenericsOwner<'_>,
         node: &impl ast::GenericParamsOwner,
-    ) -> GenericParamsId {
+    ) -> Interned<GenericParams> {
         // Generics are part of item headers and may contain inner items we need to collect.
         if let Some(params) = node.generic_param_list() {
             self.collect_inner_items(params.syntax());
@@ -698,7 +698,7 @@ impl Ctx {
         &mut self,
         owner: GenericsOwner<'_>,
         node: &impl ast::GenericParamsOwner,
-    ) -> GenericParamsId {
+    ) -> Interned<GenericParams> {
         let mut sm = &mut Default::default();
         let mut generics = GenericParams::default();
         match owner {
@@ -740,7 +740,7 @@ impl Ctx {
         }
 
         generics.shrink_to_fit();
-        self.data().generics.alloc(generics)
+        Interned::new(generics)
     }
 
     fn lower_type_bounds(&mut self, node: &impl ast::TypeBoundsOwner) -> Vec<TypeBound> {
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index f408e510ad2..be9a5e1a0e7 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -27,6 +27,7 @@ pub mod dyn_map;
 pub mod keys;
 
 pub mod item_tree;
+pub mod intern;
 
 pub mod adt;
 pub mod data;
@@ -49,7 +50,6 @@ pub mod import_map;
 
 #[cfg(test)]
 mod test_db;
-mod intern;
 
 use std::{
     hash::{Hash, Hasher},
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs
index a73585ee798..0391cc49b64 100644
--- a/crates/hir_def/src/resolver.rs
+++ b/crates/hir_def/src/resolver.rs
@@ -14,6 +14,7 @@ use crate::{
     db::DefDatabase,
     expr::{ExprId, LabelId, PatId},
     generics::GenericParams,
+    intern::Interned,
     item_scope::{BuiltinShadowMode, BUILTIN_SCOPE},
     nameres::DefMap,
     path::{ModPath, PathKind},
@@ -50,7 +51,7 @@ enum Scope {
     /// All the items and imported names of a module
     ModuleScope(ModuleItemMap),
     /// Brings the generic parameters of an item into scope
-    GenericParams { def: GenericDefId, params: Arc<GenericParams> },
+    GenericParams { def: GenericDefId, params: Interned<GenericParams> },
     /// Brings `Self` in `impl` block into scope
     ImplDefScope(ImplId),
     /// Brings `Self` in enum, struct and union definitions into scope
diff --git a/crates/hir_ty/src/utils.rs b/crates/hir_ty/src/utils.rs
index c85c328af00..df0ea4368af 100644
--- a/crates/hir_ty/src/utils.rs
+++ b/crates/hir_ty/src/utils.rs
@@ -9,6 +9,7 @@ use hir_def::{
     generics::{
         GenericParams, TypeParamData, TypeParamProvenance, WherePredicate, WherePredicateTypeTarget,
     },
+    intern::Interned,
     path::Path,
     resolver::{HasResolver, TypeNs},
     type_ref::TypeRef,
@@ -158,7 +159,7 @@ pub(crate) fn generics(db: &dyn DefDatabase, def: GenericDefId) -> Generics {
 #[derive(Debug)]
 pub(crate) struct Generics {
     def: GenericDefId,
-    pub(crate) params: Arc<GenericParams>,
+    pub(crate) params: Interned<GenericParams>,
     parent_generics: Option<Box<Generics>>,
 }
 
diff --git a/lib/arena/src/lib.rs b/lib/arena/src/lib.rs
index bce15c8679f..1720537cb6e 100644
--- a/lib/arena/src/lib.rs
+++ b/lib/arena/src/lib.rs
@@ -90,7 +90,7 @@ impl<T> Idx<T> {
 }
 
 /// Yet another index-based arena.
-#[derive(Clone, PartialEq, Eq)]
+#[derive(Clone, PartialEq, Eq, Hash)]
 pub struct Arena<T> {
     data: Vec<T>,
 }