about summary refs log tree commit diff
diff options
context:
space:
mode:
authorCamille GILLOT <gillot.camille@gmail.com>2021-07-10 23:34:41 +0200
committerCamille GILLOT <gillot.camille@gmail.com>2021-07-17 19:35:33 +0200
commit6e78d6c9d697217998322ab626cbeee24cdfe228 (patch)
tree0f6242e82240bc69adb48880344da2fcd555c5ab
parentc2d43e132975126dda1364346db8498fbc3917b0 (diff)
downloadrust-6e78d6c9d697217998322ab626cbeee24cdfe228.tar.gz
rust-6e78d6c9d697217998322ab626cbeee24cdfe228.zip
Make the CrateNum part of the ExpnId.
-rw-r--r--compiler/rustc_ast/src/node_id.rs6
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder.rs9
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs19
-rw-r--r--compiler/rustc_metadata/src/rmeta/encoder.rs14
-rw-r--r--compiler/rustc_metadata/src/rmeta/mod.rs6
-rw-r--r--compiler/rustc_middle/src/middle/cstore.rs6
-rw-r--r--compiler/rustc_middle/src/ty/query/on_disk_cache.rs12
-rw-r--r--compiler/rustc_span/src/hygiene.rs345
-rw-r--r--compiler/rustc_span/src/lib.rs2
9 files changed, 307 insertions, 112 deletions
diff --git a/compiler/rustc_ast/src/node_id.rs b/compiler/rustc_ast/src/node_id.rs
index d20bace6088..e1e7d757d7e 100644
--- a/compiler/rustc_ast/src/node_id.rs
+++ b/compiler/rustc_ast/src/node_id.rs
@@ -1,4 +1,4 @@
-use rustc_span::ExpnId;
+use rustc_span::{ExpnId, LocalExpnId};
 use std::fmt;
 
 rustc_index::newtype_index! {
@@ -25,11 +25,11 @@ pub const DUMMY_NODE_ID: NodeId = NodeId::MAX;
 
 impl NodeId {
     pub fn placeholder_from_expn_id(expn_id: ExpnId) -> Self {
-        NodeId::from_u32(expn_id.as_u32())
+        NodeId::from_u32(expn_id.expect_local().as_u32())
     }
 
     pub fn placeholder_to_expn_id(self) -> ExpnId {
-        ExpnId::from_u32(self.as_u32())
+        LocalExpnId::from_u32(self.as_u32()).to_expn_id()
     }
 }
 
diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index e2203fffd45..4b72ac86957 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -30,9 +30,10 @@ use rustc_middle::ty::codec::TyDecoder;
 use rustc_middle::ty::{self, Ty, TyCtxt, Visibility};
 use rustc_serialize::{opaque, Decodable, Decoder};
 use rustc_session::Session;
+use rustc_span::hygiene::{ExpnIndex, MacroKind};
 use rustc_span::source_map::{respan, Spanned};
 use rustc_span::symbol::{sym, Ident, Symbol};
-use rustc_span::{self, hygiene::MacroKind, BytePos, ExpnId, Pos, Span, SyntaxContext, DUMMY_SP};
+use rustc_span::{self, BytePos, ExpnId, Pos, Span, SyntaxContext, DUMMY_SP};
 
 use proc_macro::bridge::client::ProcMacro;
 use std::io;
@@ -348,6 +349,12 @@ impl<'a, 'tcx> Decodable<DecodeContext<'a, 'tcx>> for DefIndex {
     }
 }
 
+impl<'a, 'tcx> Decodable<DecodeContext<'a, 'tcx>> for ExpnIndex {
+    fn decode(d: &mut DecodeContext<'a, 'tcx>) -> Result<ExpnIndex, String> {
+        Ok(ExpnIndex::from_u32(d.read_u32()?))
+    }
+}
+
 impl<'a, 'tcx> Decodable<DecodeContext<'a, 'tcx>> for SyntaxContext {
     fn decode(decoder: &mut DecodeContext<'a, 'tcx>) -> Result<SyntaxContext, String> {
         let cdata = decoder.cdata();
diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
index ce8dfeae076..67023e9e84e 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
@@ -18,11 +18,11 @@ use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::{self, TyCtxt, Visibility};
 use rustc_session::utils::NativeLibKind;
 use rustc_session::{Session, StableCrateId};
+use rustc_span::hygiene::{ExpnData, ExpnHash, ExpnId};
 use rustc_span::source_map::{Span, Spanned};
 use rustc_span::symbol::Symbol;
 
 use rustc_data_structures::sync::Lrc;
-use rustc_span::ExpnId;
 use smallvec::SmallVec;
 use std::any::Any;
 
@@ -494,6 +494,23 @@ impl CrateStore for CStore {
     fn as_any(&self) -> &dyn Any {
         self
     }
+    fn decode_expn_data(&self, sess: &Session, expn_id: ExpnId) -> (ExpnData, ExpnHash) {
+        let crate_data = self.get_crate_data(expn_id.krate);
+        (
+            crate_data
+                .root
+                .expn_data
+                .get(&crate_data, expn_id.local_id)
+                .unwrap()
+                .decode((&crate_data, sess)),
+            crate_data
+                .root
+                .expn_hashes
+                .get(&crate_data, expn_id.local_id)
+                .unwrap()
+                .decode((&crate_data, sess)),
+        )
+    }
 
     fn crate_name(&self, cnum: CrateNum) -> Symbol {
         self.get_crate_data(cnum).root.name
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index ba6d2d74aa7..4684daef4a1 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -31,7 +31,7 @@ use rustc_session::config::CrateType;
 use rustc_span::symbol::{sym, Ident, Symbol};
 use rustc_span::{self, ExternalSource, FileName, SourceFile, Span, SyntaxContext};
 use rustc_span::{
-    hygiene::{HygieneEncodeContext, MacroKind},
+    hygiene::{ExpnIndex, HygieneEncodeContext, MacroKind},
     RealFileName,
 };
 use rustc_target::abi::VariantIdx;
@@ -168,6 +168,12 @@ impl<'a, 'tcx> Encodable<EncodeContext<'a, 'tcx>> for DefIndex {
     }
 }
 
+impl<'a, 'tcx> Encodable<EncodeContext<'a, 'tcx>> for ExpnIndex {
+    fn encode(&self, s: &mut EncodeContext<'a, 'tcx>) -> opaque::EncodeResult {
+        s.emit_u32(self.as_u32())
+    }
+}
+
 impl<'a, 'tcx> Encodable<EncodeContext<'a, 'tcx>> for SyntaxContext {
     fn encode(&self, s: &mut EncodeContext<'a, 'tcx>) -> opaque::EncodeResult {
         rustc_span::hygiene::raw_encode_syntax_context(*self, &s.hygiene_ctxt, s)
@@ -1588,8 +1594,10 @@ impl EncodeContext<'a, 'tcx> {
                 Ok(())
             },
             |(this, _, expn_data_table, expn_hash_table), index, expn_data, hash| {
-                expn_data_table.set(index, this.lazy(expn_data));
-                expn_hash_table.set(index, this.lazy(hash));
+                if let Some(index) = index.as_local() {
+                    expn_data_table.set(index.as_raw(), this.lazy(expn_data));
+                    expn_hash_table.set(index.as_raw(), this.lazy(hash));
+                }
                 Ok(())
             },
         );
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index f2ebfb9b725..a487753f462 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -19,7 +19,7 @@ use rustc_middle::ty::{self, ReprOptions, Ty};
 use rustc_serialize::opaque::Encoder;
 use rustc_session::config::SymbolManglingVersion;
 use rustc_span::edition::Edition;
-use rustc_span::hygiene::MacroKind;
+use rustc_span::hygiene::{ExpnIndex, MacroKind};
 use rustc_span::symbol::{Ident, Symbol};
 use rustc_span::{self, ExpnData, ExpnHash, ExpnId, Span};
 use rustc_target::spec::{PanicStrategy, TargetTriple};
@@ -170,8 +170,8 @@ macro_rules! Lazy {
 }
 
 type SyntaxContextTable = Lazy<Table<u32, Lazy<SyntaxContextData>>>;
-type ExpnDataTable = Lazy<Table<u32, Lazy<ExpnData>>>;
-type ExpnHashTable = Lazy<Table<u32, Lazy<ExpnHash>>>;
+type ExpnDataTable = Lazy<Table<ExpnIndex, Lazy<ExpnData>>>;
+type ExpnHashTable = Lazy<Table<ExpnIndex, Lazy<ExpnHash>>>;
 
 #[derive(MetadataEncodable, MetadataDecodable)]
 crate struct ProcMacroData {
diff --git a/compiler/rustc_middle/src/middle/cstore.rs b/compiler/rustc_middle/src/middle/cstore.rs
index 7efe8e061e8..c10fcc2e90c 100644
--- a/compiler/rustc_middle/src/middle/cstore.rs
+++ b/compiler/rustc_middle/src/middle/cstore.rs
@@ -6,12 +6,13 @@ use crate::ty::TyCtxt;
 
 use rustc_ast as ast;
 use rustc_data_structures::sync::{self, MetadataRef};
-use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE};
+use rustc_hir::def_id::{CrateNum, DefId, StableCrateId, LOCAL_CRATE};
 use rustc_hir::definitions::{DefKey, DefPath, DefPathHash};
 use rustc_macros::HashStable;
 use rustc_session::search_paths::PathKind;
 use rustc_session::utils::NativeLibKind;
-use rustc_session::StableCrateId;
+use rustc_session::Session;
+use rustc_span::hygiene::{ExpnData, ExpnHash, ExpnId};
 use rustc_span::symbol::Symbol;
 use rustc_span::Span;
 use rustc_target::spec::Target;
@@ -187,6 +188,7 @@ pub type MetadataLoaderDyn = dyn MetadataLoader + Sync;
 /// during resolve)
 pub trait CrateStore: std::fmt::Debug {
     fn as_any(&self) -> &dyn Any;
+    fn decode_expn_data(&self, sess: &Session, expn_id: ExpnId) -> (ExpnData, ExpnHash);
 
     // Foreign definitions.
     // This information is safe to access, since it's hashed as part of the DefPathHash, which incr.
diff --git a/compiler/rustc_middle/src/ty/query/on_disk_cache.rs b/compiler/rustc_middle/src/ty/query/on_disk_cache.rs
index 85e84d6a0f4..358d016368e 100644
--- a/compiler/rustc_middle/src/ty/query/on_disk_cache.rs
+++ b/compiler/rustc_middle/src/ty/query/on_disk_cache.rs
@@ -364,9 +364,12 @@ impl<'sess> OnDiskCache<'sess> {
                     Ok(())
                 },
                 |encoder, index, expn_data, hash| -> FileEncodeResult {
-                    let pos = AbsoluteBytePos::new(encoder.position());
-                    encoder.encode_tagged(TAG_EXPN_DATA, &(expn_data, hash))?;
-                    expn_ids.insert(index, pos);
+                    if index.krate == LOCAL_CRATE {
+                        let pos = AbsoluteBytePos::new(encoder.position());
+                        encoder.encode_tagged(TAG_EXPN_DATA, &(expn_data, hash))?;
+                        expn_ids.insert(index.local_id.as_u32(), pos);
+                    }
+                    // TODO Handle foreign expansions.
                     Ok(())
                 },
             )?;
@@ -807,6 +810,9 @@ impl<'a, 'tcx> Decodable<CacheDecoder<'a, 'tcx>> for ExpnId {
                     Ok(data)
                 })
             },
+            |this, expn_id| {
+                Ok(this.tcx.untracked_resolutions.cstore.decode_expn_data(this.tcx.sess, expn_id))
+            },
         )
     }
 }
diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs
index e3174b47f8d..14b68112982 100644
--- a/compiler/rustc_span/src/hygiene.rs
+++ b/compiler/rustc_span/src/hygiene.rs
@@ -29,12 +29,13 @@ use crate::symbol::{kw, sym, Symbol};
 use crate::with_session_globals;
 use crate::{HashStableContext, Span, DUMMY_SP};
 
-use crate::def_id::{CrateNum, DefId, CRATE_DEF_INDEX, LOCAL_CRATE};
+use crate::def_id::{CrateNum, DefId, CRATE_DEF_ID, LOCAL_CRATE};
 use rustc_data_structures::fingerprint::Fingerprint;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_data_structures::sync::{Lock, Lrc};
 use rustc_data_structures::unhash::UnhashMap;
+use rustc_index::vec::IndexVec;
 use rustc_macros::HashStable_Generic;
 use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
 use std::fmt;
@@ -58,9 +59,34 @@ pub struct SyntaxContextData {
     dollar_crate_name: Symbol,
 }
 
+rustc_index::newtype_index! {
+    /// A unique ID associated with a macro invocation and expansion.
+    pub struct ExpnIndex {
+        ENCODABLE = custom
+    }
+}
+
 /// A unique ID associated with a macro invocation and expansion.
-#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
-pub struct ExpnId(u32);
+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ExpnId {
+    pub krate: CrateNum,
+    pub local_id: ExpnIndex,
+}
+
+impl fmt::Debug for ExpnId {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        // Generate crate_::{{expn_}}.
+        write!(f, "{:?}::{{{{expn{}}}}}", self.krate, self.local_id.private)
+    }
+}
+
+rustc_index::newtype_index! {
+    /// A unique ID associated with a macro invocation and expansion.
+    pub struct LocalExpnId {
+        ENCODABLE = custom
+        DEBUG_FORMAT = "expn{}"
+    }
+}
 
 /// A unique hash value associated to an expansion.
 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encodable, Decodable, HashStable_Generic)]
@@ -86,31 +112,103 @@ pub enum Transparency {
     Opaque,
 }
 
-impl ExpnId {
-    pub fn fresh_empty() -> Self {
+impl LocalExpnId {
+    /// The ID of the theoretical expansion that generates freshly parsed, unexpanded AST.
+    pub const ROOT: LocalExpnId = LocalExpnId::from_u32(0);
+
+    pub fn from_raw(idx: ExpnIndex) -> LocalExpnId {
+        LocalExpnId::from_u32(idx.as_u32())
+    }
+
+    pub fn as_raw(self) -> ExpnIndex {
+        ExpnIndex::from_u32(self.as_u32())
+    }
+
+    pub fn fresh_empty() -> LocalExpnId {
         HygieneData::with(|data| data.fresh_expn(None))
     }
 
-    pub fn fresh(expn_data: ExpnData, ctx: impl HashStableContext) -> Self {
+    pub fn fresh(expn_data: ExpnData, ctx: impl HashStableContext) -> LocalExpnId {
+        debug_assert_eq!(expn_data.parent.krate, LOCAL_CRATE);
         let expn_id = HygieneData::with(|data| data.fresh_expn(Some(expn_data)));
         update_disambiguator(expn_id, ctx);
         expn_id
     }
 
-    /// The ID of the theoretical expansion that generates freshly parsed, unexpanded AST.
     #[inline]
-    pub fn root() -> Self {
-        ExpnId(0)
+    pub fn expn_hash(self) -> ExpnHash {
+        HygieneData::with(|data| data.local_expn_hash(self))
     }
 
     #[inline]
-    pub fn as_u32(self) -> u32 {
-        self.0
+    pub fn expn_data(self) -> ExpnData {
+        HygieneData::with(|data| data.local_expn_data(self).clone())
+    }
+
+    #[inline]
+    pub fn to_expn_id(self) -> ExpnId {
+        ExpnId { krate: LOCAL_CRATE, local_id: self.as_raw() }
+    }
+
+    #[inline]
+    pub fn set_expn_data(self, mut expn_data: ExpnData, ctx: impl HashStableContext) {
+        debug_assert_eq!(expn_data.parent.krate, LOCAL_CRATE);
+        HygieneData::with(|data| {
+            let old_expn_data = &mut data.local_expn_data[self];
+            assert!(old_expn_data.is_none(), "expansion data is reset for an expansion ID");
+            assert_eq!(expn_data.orig_id, None);
+            debug_assert_eq!(expn_data.krate, LOCAL_CRATE);
+            expn_data.orig_id = Some(self.as_u32());
+            *old_expn_data = Some(expn_data);
+        });
+        update_disambiguator(self, ctx)
+    }
+
+    #[inline]
+    pub fn is_descendant_of(self, ancestor: LocalExpnId) -> bool {
+        self.to_expn_id().is_descendant_of(ancestor.to_expn_id())
     }
 
+    /// `expn_id.outer_expn_is_descendant_of(ctxt)` is equivalent to but faster than
+    /// `expn_id.is_descendant_of(ctxt.outer_expn())`.
     #[inline]
-    pub fn from_u32(raw: u32) -> ExpnId {
-        ExpnId(raw)
+    pub fn outer_expn_is_descendant_of(self, ctxt: SyntaxContext) -> bool {
+        self.to_expn_id().outer_expn_is_descendant_of(ctxt)
+    }
+
+    /// Returns span for the macro which originally caused this expansion to happen.
+    ///
+    /// Stops backtracing at include! boundary.
+    #[inline]
+    pub fn expansion_cause(self) -> Option<Span> {
+        self.to_expn_id().expansion_cause()
+    }
+
+    #[inline]
+    #[track_caller]
+    pub fn parent(self) -> LocalExpnId {
+        self.expn_data().parent.as_local().unwrap()
+    }
+}
+
+impl ExpnId {
+    /// The ID of the theoretical expansion that generates freshly parsed, unexpanded AST.
+    /// Invariant: we do not create any ExpnId with local_id == 0 and krate != 0.
+    pub const fn root() -> ExpnId {
+        ExpnId { krate: LOCAL_CRATE, local_id: ExpnIndex::from_u32(0) }
+    }
+
+    pub fn fresh_empty() -> ExpnId {
+        LocalExpnId::fresh_empty().to_expn_id()
+    }
+
+    pub fn fresh(expn_data: ExpnData, ctx: impl HashStableContext) -> ExpnId {
+        LocalExpnId::fresh(expn_data, ctx).to_expn_id()
+    }
+
+    #[inline]
+    pub fn set_expn_data(self, expn_data: ExpnData, ctx: impl HashStableContext) {
+        self.expect_local().set_expn_data(expn_data, ctx)
     }
 
     #[inline]
@@ -124,20 +222,19 @@ impl ExpnId {
     }
 
     #[inline]
-    pub fn expn_data(self) -> ExpnData {
-        HygieneData::with(|data| data.expn_data(self).clone())
+    pub fn as_local(self) -> Option<LocalExpnId> {
+        if self.krate == LOCAL_CRATE { Some(LocalExpnId::from_raw(self.local_id)) } else { None }
     }
 
     #[inline]
-    pub fn set_expn_data(self, mut expn_data: ExpnData, ctx: impl HashStableContext) {
-        HygieneData::with(|data| {
-            let old_expn_data = &mut data.expn_data[self.0 as usize];
-            assert!(old_expn_data.is_none(), "expansion data is reset for an expansion ID");
-            assert_eq!(expn_data.orig_id, None);
-            expn_data.orig_id = Some(self.as_u32());
-            *old_expn_data = Some(expn_data);
-        });
-        update_disambiguator(self, ctx)
+    #[track_caller]
+    pub fn expect_local(self) -> LocalExpnId {
+        self.as_local().unwrap()
+    }
+
+    #[inline]
+    pub fn expn_data(self) -> ExpnData {
+        HygieneData::with(|data| data.expn_data(self).clone())
     }
 
     pub fn is_descendant_of(self, ancestor: ExpnId) -> bool {
@@ -175,8 +272,10 @@ pub struct HygieneData {
     /// Each expansion should have an associated expansion data, but sometimes there's a delay
     /// between creation of an expansion ID and obtaining its data (e.g. macros are collected
     /// first and then resolved later), so we use an `Option` here.
-    expn_data: Vec<Option<ExpnData>>,
-    expn_hashes: Vec<ExpnHash>,
+    local_expn_data: IndexVec<LocalExpnId, Option<ExpnData>>,
+    local_expn_hashes: IndexVec<LocalExpnId, ExpnHash>,
+    foreign_expn_data: FxHashMap<ExpnId, ExpnData>,
+    foreign_expn_hashes: FxHashMap<ExpnId, ExpnHash>,
     expn_hash_to_expn_id: UnhashMap<ExpnHash, ExpnId>,
     syntax_context_data: Vec<SyntaxContextData>,
     syntax_context_map: FxHashMap<(SyntaxContext, ExpnId, Transparency), SyntaxContext>,
@@ -194,15 +293,17 @@ impl HygieneData {
             ExpnKind::Root,
             DUMMY_SP,
             edition,
-            Some(DefId::local(CRATE_DEF_INDEX)),
+            Some(CRATE_DEF_ID.to_def_id()),
             None,
         );
         root_data.orig_id = Some(0);
 
         HygieneData {
-            expn_data: vec![Some(root_data)],
-            expn_hashes: vec![ExpnHash(Fingerprint::ZERO)],
-            expn_hash_to_expn_id: std::iter::once((ExpnHash(Fingerprint::ZERO), ExpnId(0)))
+            local_expn_data: IndexVec::from_elem_n(Some(root_data), 1),
+            local_expn_hashes: IndexVec::from_elem_n(ExpnHash(Fingerprint::ZERO), 1),
+            foreign_expn_data: FxHashMap::default(),
+            foreign_expn_hashes: FxHashMap::default(),
+            expn_hash_to_expn_id: std::iter::once((ExpnHash(Fingerprint::ZERO), ExpnId::root()))
                 .collect(),
             syntax_context_data: vec![SyntaxContextData {
                 outer_expn: ExpnId::root(),
@@ -221,24 +322,42 @@ impl HygieneData {
         with_session_globals(|session_globals| f(&mut *session_globals.hygiene_data.borrow_mut()))
     }
 
-    fn fresh_expn(&mut self, mut expn_data: Option<ExpnData>) -> ExpnId {
-        let raw_id = self.expn_data.len() as u32;
+    fn fresh_expn(&mut self, mut expn_data: Option<ExpnData>) -> LocalExpnId {
+        let expn_id = self.local_expn_data.next_index();
         if let Some(data) = expn_data.as_mut() {
+            debug_assert_eq!(data.krate, LOCAL_CRATE);
             assert_eq!(data.orig_id, None);
-            data.orig_id = Some(raw_id);
+            data.orig_id = Some(expn_id.as_u32());
         }
-        self.expn_data.push(expn_data);
-        self.expn_hashes.push(ExpnHash(Fingerprint::ZERO));
-        ExpnId(raw_id)
+        self.local_expn_data.push(expn_data);
+        let _eid = self.local_expn_hashes.push(ExpnHash(Fingerprint::ZERO));
+        debug_assert_eq!(expn_id, _eid);
+        expn_id
+    }
+
+    #[inline]
+    fn local_expn_hash(&self, expn_id: LocalExpnId) -> ExpnHash {
+        self.local_expn_hashes[expn_id]
     }
 
     #[inline]
     fn expn_hash(&self, expn_id: ExpnId) -> ExpnHash {
-        self.expn_hashes[expn_id.0 as usize]
+        match expn_id.as_local() {
+            Some(expn_id) => self.local_expn_hashes[expn_id],
+            None => self.foreign_expn_hashes[&expn_id],
+        }
+    }
+
+    fn local_expn_data(&self, expn_id: LocalExpnId) -> &ExpnData {
+        self.local_expn_data[expn_id].as_ref().expect("no expansion data for an expansion ID")
     }
 
     fn expn_data(&self, expn_id: ExpnId) -> &ExpnData {
-        self.expn_data[expn_id.0 as usize].as_ref().expect("no expansion data for an expansion ID")
+        if let Some(expn_id) = expn_id.as_local() {
+            self.local_expn_data[expn_id].as_ref().expect("no expansion data for an expansion ID")
+        } else {
+            &self.foreign_expn_data[&expn_id]
+        }
     }
 
     fn is_descendant_of(&self, mut expn_id: ExpnId, ancestor: ExpnId) -> bool {
@@ -453,17 +572,21 @@ pub fn debug_hygiene_data(verbose: bool) -> String {
         } else {
             let mut s = String::from("");
             s.push_str("Expansions:");
-            data.expn_data.iter().enumerate().for_each(|(id, expn_info)| {
-                let expn_info = expn_info.as_ref().expect("no expansion data for an expansion ID");
+            let mut debug_expn_data = |(id, expn_info): (&ExpnId, &ExpnData)| {
                 s.push_str(&format!(
-                    "\n{}: parent: {:?}, call_site_ctxt: {:?}, def_site_ctxt: {:?}, kind: {:?}",
+                    "\n{:?}: parent: {:?}, call_site_ctxt: {:?}, def_site_ctxt: {:?}, kind: {:?}",
                     id,
                     expn_info.parent,
                     expn_info.call_site.ctxt(),
                     expn_info.def_site.ctxt(),
                     expn_info.kind,
-                ));
+                ))
+            };
+            data.local_expn_data.iter_enumerated().for_each(|(id, expn_info)| {
+                let expn_info = expn_info.as_ref().expect("no expansion data for an expansion ID");
+                debug_expn_data((&id.to_expn_id(), expn_info))
             });
+            data.foreign_expn_data.iter().for_each(debug_expn_data);
             s.push_str("\n\nSyntaxContexts:");
             data.syntax_context_data.iter().enumerate().for_each(|(id, ctxt)| {
                 s.push_str(&format!(
@@ -1024,7 +1147,7 @@ impl HygieneEncodeContext {
         &self,
         encoder: &mut T,
         mut encode_ctxt: impl FnMut(&mut T, u32, &SyntaxContextData) -> Result<(), R>,
-        mut encode_expn: impl FnMut(&mut T, u32, ExpnData, ExpnHash) -> Result<(), R>,
+        mut encode_expn: impl FnMut(&mut T, ExpnId, ExpnData, ExpnHash) -> Result<(), R>,
     ) -> Result<(), R> {
         // When we serialize a `SyntaxContextData`, we may end up serializing
         // a `SyntaxContext` that we haven't seen before
@@ -1051,9 +1174,9 @@ impl HygieneEncodeContext {
 
             let latest_expns = { std::mem::take(&mut *self.latest_expns.lock()) };
 
-            for_all_expns_in(latest_expns.into_iter(), |index, expn, data, hash| {
+            for_all_expns_in(latest_expns.into_iter(), |expn, data, hash| {
                 if self.serialized_expns.lock().insert(expn) {
-                    encode_expn(encoder, index, data, hash)?;
+                    encode_expn(encoder, expn, data, hash)?;
                 }
                 Ok(())
             })?;
@@ -1073,56 +1196,75 @@ pub struct HygieneDecodeContext {
     // `SyntaxContext`
     remapped_ctxts: Lock<Vec<Option<SyntaxContext>>>,
     // The same as `remapepd_ctxts`, but for `ExpnId`s
-    remapped_expns: Lock<Vec<Option<ExpnId>>>,
+    remapped_expns: Lock<Vec<Option<LocalExpnId>>>,
 }
 
 pub fn decode_expn_id_incrcomp<D: Decoder>(
     d: &mut D,
     context: &HygieneDecodeContext,
     decode_data: impl FnOnce(&mut D, u32) -> Result<(ExpnData, ExpnHash), D::Error>,
+    decode_foreign: impl FnOnce(&mut D, ExpnId) -> Result<(ExpnData, ExpnHash), D::Error>,
 ) -> Result<ExpnId, D::Error> {
+    let krate = CrateNum::decode(d)?;
     let index = u32::decode(d)?;
 
     // Do this after decoding, so that we decode a `CrateNum`
     // if necessary
-    if index == ExpnId::root().as_u32() {
+    if index == 0 {
         debug!("decode_expn_id: deserialized root");
         return Ok(ExpnId::root());
     }
 
+    if krate != LOCAL_CRATE {
+        let expn_id = ExpnId { krate, local_id: ExpnIndex::from_u32(index) };
+        if HygieneData::with(|hygiene_data| hygiene_data.foreign_expn_data.contains_key(&expn_id)) {
+            return Ok(expn_id);
+        }
+        let (expn_data, hash) = decode_foreign(d, expn_id)?;
+        debug_assert_eq!(krate, expn_data.krate);
+        debug_assert_eq!(expn_data.orig_id, Some(index));
+        let expn_id = HygieneData::with(|hygiene_data| {
+            debug_assert_eq!(expn_data.orig_id, Some(index));
+            let _old_data = hygiene_data.foreign_expn_data.insert(expn_id, expn_data);
+            debug_assert!(_old_data.is_none());
+            let _old_hash = hygiene_data.foreign_expn_hashes.insert(expn_id, hash);
+            debug_assert!(_old_hash.is_none());
+            let _old_id = hygiene_data.expn_hash_to_expn_id.insert(hash, expn_id);
+            debug_assert!(_old_id.is_none());
+            expn_id
+        });
+        return Ok(expn_id);
+    }
+
     let outer_expns = &context.remapped_expns;
 
     // Ensure that the lock() temporary is dropped early
     {
         if let Some(expn_id) = outer_expns.lock().get(index as usize).copied().flatten() {
-            return Ok(expn_id);
+            return Ok(expn_id.to_expn_id());
         }
     }
 
     // Don't decode the data inside `HygieneData::with`, since we need to recursively decode
     // other ExpnIds
     let (mut expn_data, hash) = decode_data(d, index)?;
+    debug_assert_eq!(krate, expn_data.krate);
 
     let expn_id = HygieneData::with(|hygiene_data| {
-        if let Some(&expn_id) = hygiene_data.expn_hash_to_expn_id.get(&hash) {
-            return expn_id;
+        if let Some(expn_id) = hygiene_data.expn_hash_to_expn_id.get(&hash) {
+            return *expn_id;
         }
 
-        let expn_id = ExpnId(hygiene_data.expn_data.len() as u32);
-
         // If we just deserialized an `ExpnData` owned by
         // the local crate, its `orig_id` will be stale,
         // so we need to update it to its own value.
         // This only happens when we deserialize the incremental cache,
         // since a crate will never decode its own metadata.
-        if expn_data.krate == LOCAL_CRATE {
-            expn_data.orig_id = Some(expn_id.0);
-        }
-
-        hygiene_data.expn_data.push(Some(expn_data));
-        hygiene_data.expn_hashes.push(hash);
-        let _old_id = hygiene_data.expn_hash_to_expn_id.insert(hash, expn_id);
-        debug_assert!(_old_id.is_none());
+        let expn_id = hygiene_data.local_expn_data.next_index();
+        expn_data.orig_id = Some(expn_id.as_u32());
+        hygiene_data.local_expn_data.push(Some(expn_data));
+        let _eid = hygiene_data.local_expn_hashes.push(hash);
+        debug_assert_eq!(expn_id, _eid);
 
         let mut expns = outer_expns.lock();
         let new_len = index as usize + 1;
@@ -1131,6 +1273,10 @@ pub fn decode_expn_id_incrcomp<D: Decoder>(
         }
         expns[index as usize] = Some(expn_id);
         drop(expns);
+        let expn_id = expn_id.to_expn_id();
+
+        let _old_id = hygiene_data.expn_hash_to_expn_id.insert(hash, expn_id);
+        debug_assert!(_old_id.is_none());
         expn_id
     });
     Ok(expn_id)
@@ -1138,39 +1284,42 @@ pub fn decode_expn_id_incrcomp<D: Decoder>(
 
 pub fn decode_expn_id<D: Decoder>(
     d: &mut D,
-    decode_data: impl FnOnce(CrateNum, u32) -> (ExpnData, ExpnHash),
+    decode_data: impl FnOnce(CrateNum, ExpnIndex) -> (ExpnData, ExpnHash),
 ) -> Result<ExpnId, D::Error> {
-    let index = u32::decode(d)?;
     let krate = CrateNum::decode(d)?;
+    let index = u32::decode(d)?;
 
     // Do this after decoding, so that we decode a `CrateNum`
     // if necessary
-    if index == ExpnId::root().as_u32() {
+    if index == 0 {
         debug!("decode_expn_id: deserialized root");
         return Ok(ExpnId::root());
     }
 
+    let index = ExpnIndex::from_u32(index);
+
     // This function is used to decode metadata, so it cannot decode information about LOCAL_CRATE.
     debug_assert_ne!(krate, LOCAL_CRATE);
+    let expn_id = ExpnId { krate, local_id: index };
+
+    // Fast path if the expansion has already been decoded.
+    if HygieneData::with(|hygiene_data| hygiene_data.foreign_expn_data.contains_key(&expn_id)) {
+        return Ok(expn_id);
+    }
 
     // Don't decode the data inside `HygieneData::with`, since we need to recursively decode
     // other ExpnIds
     let (expn_data, hash) = decode_data(krate, index);
     debug_assert_eq!(krate, expn_data.krate);
-    debug_assert_eq!(expn_data.orig_id, Some(index));
-
-    let expn_id = HygieneData::with(|hygiene_data| {
-        if let Some(&expn_id) = hygiene_data.expn_hash_to_expn_id.get(&hash) {
-            return expn_id;
-        }
+    debug_assert_eq!(Some(index.as_u32()), expn_data.orig_id);
 
-        let expn_id = ExpnId(hygiene_data.expn_data.len() as u32);
-        hygiene_data.expn_data.push(Some(expn_data));
-        hygiene_data.expn_hashes.push(hash);
+    HygieneData::with(|hygiene_data| {
+        let _old_data = hygiene_data.foreign_expn_data.insert(expn_id, expn_data);
+        debug_assert!(_old_data.is_none());
+        let _old_hash = hygiene_data.foreign_expn_hashes.insert(expn_id, hash);
+        debug_assert!(_old_hash.is_none());
         let _old_id = hygiene_data.expn_hash_to_expn_id.insert(hash, expn_id);
         debug_assert!(_old_id.is_none());
-
-        expn_id
     });
 
     Ok(expn_id)
@@ -1264,29 +1413,37 @@ fn for_all_ctxts_in<E, F: FnMut(u32, SyntaxContext, &SyntaxContextData) -> Resul
 
 fn for_all_expns_in<E>(
     expns: impl Iterator<Item = ExpnId>,
-    mut f: impl FnMut(u32, ExpnId, ExpnData, ExpnHash) -> Result<(), E>,
+    mut f: impl FnMut(ExpnId, ExpnData, ExpnHash) -> Result<(), E>,
 ) -> Result<(), E> {
     let all_data: Vec<_> = HygieneData::with(|data| {
         expns
-            .map(|expn| {
-                let idx = expn.0 as usize;
-                (expn, data.expn_data[idx].clone(), data.expn_hashes[idx].clone())
-            })
+            .map(|expn| (expn, data.expn_data(expn).clone(), data.expn_hash(expn).clone()))
             .collect()
     });
     for (expn, data, hash) in all_data.into_iter() {
-        let data = data.unwrap_or_else(|| panic!("Missing data for {:?}", expn));
-        f(expn.0, expn, data, hash)?;
+        f(expn, data, hash)?;
     }
     Ok(())
 }
 
+impl<E: Encoder> Encodable<E> for LocalExpnId {
+    fn encode(&self, e: &mut E) -> Result<(), E::Error> {
+        self.to_expn_id().encode(e)
+    }
+}
+
 impl<E: Encoder> Encodable<E> for ExpnId {
     default fn encode(&self, _: &mut E) -> Result<(), E::Error> {
         panic!("cannot encode `ExpnId` with `{}`", std::any::type_name::<E>());
     }
 }
 
+impl<D: Decoder> Decodable<D> for LocalExpnId {
+    fn decode(d: &mut D) -> Result<Self, D::Error> {
+        ExpnId::decode(d).map(ExpnId::expect_local)
+    }
+}
+
 impl<D: Decoder> Decodable<D> for ExpnId {
     default fn decode(_: &mut D) -> Result<Self, D::Error> {
         panic!("cannot decode `ExpnId` with `{}`", std::any::type_name::<D>());
@@ -1309,12 +1466,12 @@ pub fn raw_encode_expn_id_incrcomp<E: Encoder>(
     context: &HygieneEncodeContext,
     e: &mut E,
 ) -> Result<(), E::Error> {
-    // Record the fact that we need to serialize the corresponding
-    // `ExpnData`
+    // Record the fact that we need to serialize the corresponding `ExpnData`
     if !context.serialized_expns.lock().contains(&expn) {
         context.latest_expns.lock().insert(expn);
     }
-    expn.0.encode(e)
+    expn.krate.encode(e)?;
+    expn.local_id.as_u32().encode(e)
 }
 
 pub fn raw_encode_expn_id<E: Encoder>(
@@ -1322,21 +1479,19 @@ pub fn raw_encode_expn_id<E: Encoder>(
     context: &HygieneEncodeContext,
     e: &mut E,
 ) -> Result<(), E::Error> {
-    let data = expn.expn_data();
     // We only need to serialize the ExpnData
     // if it comes from this crate.
     // We currently don't serialize any hygiene information data for
     // proc-macro crates: see the `SpecializedEncoder<Span>` impl
     // for crate metadata.
-    if data.krate == LOCAL_CRATE {
-        // Record the fact that we need to serialize the corresponding
-        // `ExpnData`
+    // Record the fact that we need to serialize the corresponding `ExpnData`
+    if expn.krate == LOCAL_CRATE {
         if !context.serialized_expns.lock().contains(&expn) {
             context.latest_expns.lock().insert(expn);
         }
     }
-    data.orig_id.expect("Missing orig_id").encode(e)?;
-    data.krate.encode(e)
+    expn.krate.encode(e)?;
+    expn.local_id.as_u32().encode(e)
 }
 
 impl<E: Encoder> Encodable<E> for SyntaxContext {
@@ -1360,7 +1515,7 @@ impl<D: Decoder> Decodable<D> for SyntaxContext {
 /// `set_expn_data`). It is *not* called for foreign `ExpnId`s deserialized
 /// from another crate's metadata - since `ExpnData` includes a `krate` field,
 /// collisions are only possible between `ExpnId`s within the same crate.
-fn update_disambiguator(expn_id: ExpnId, mut ctx: impl HashStableContext) {
+fn update_disambiguator(expn_id: LocalExpnId, mut ctx: impl HashStableContext) {
     let mut expn_data = expn_id.expn_data();
     // This disambiguator should not have been set yet.
     assert_eq!(
@@ -1399,10 +1554,10 @@ fn update_disambiguator(expn_id: ExpnId, mut ctx: impl HashStableContext) {
 
     let expn_hash = ExpnHash(expn_hash);
     HygieneData::with(|data| {
-        data.expn_data[expn_id.0 as usize].as_mut().unwrap().disambiguator = disambiguator;
-        debug_assert_eq!(data.expn_hashes[expn_id.0 as usize].0, Fingerprint::ZERO);
-        data.expn_hashes[expn_id.0 as usize] = expn_hash;
-        let _old_id = data.expn_hash_to_expn_id.insert(expn_hash, expn_id);
+        data.local_expn_data[expn_id].as_mut().unwrap().disambiguator = disambiguator;
+        debug_assert_eq!(data.local_expn_hashes[expn_id].0, Fingerprint::ZERO);
+        data.local_expn_hashes[expn_id] = expn_hash;
+        let _old_id = data.expn_hash_to_expn_id.insert(expn_hash, expn_id.to_expn_id());
         debug_assert!(_old_id.is_none());
     });
 }
diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs
index 7a1ee20ee79..1c95cc91208 100644
--- a/compiler/rustc_span/src/lib.rs
+++ b/compiler/rustc_span/src/lib.rs
@@ -38,7 +38,7 @@ use edition::Edition;
 pub mod hygiene;
 use hygiene::Transparency;
 pub use hygiene::{DesugaringKind, ExpnKind, ForLoopLoc, MacroKind};
-pub use hygiene::{ExpnData, ExpnHash, ExpnId, SyntaxContext};
+pub use hygiene::{ExpnData, ExpnHash, ExpnId, LocalExpnId, SyntaxContext};
 pub mod def_id;
 use def_id::{CrateNum, DefId, DefPathHash, LOCAL_CRATE};
 pub mod lev_distance;