Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 0c2ebbd

Browse files
committed
Rename PtrKey as Interned and improve it.
In particular, there's now more protection against incorrect usage, because you can only create one via `Interned::new_unchecked`, which makes it more obvious that you must be careful. There are also some tests.
1 parent 028e57b commit 0c2ebbd

File tree

7 files changed

+177
-50
lines changed

7 files changed

+177
-50
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use std::cmp::Ordering;
2+
use std::hash::{Hash, Hasher};
3+
use std::ops::Deref;
4+
use std::ptr;
5+
6+
mod private {
7+
#[derive(Clone, Copy, Debug)]
8+
pub struct PrivateZst;
9+
}
10+
11+
/// A reference to a value that is interned, and is known to be unique.
12+
///
13+
/// Note that it is possible to have a `T` and a `Interned<T>` that are (or
14+
/// refer to) equal but different values. But if you have two different
15+
/// `Interned<T>`s, they both refer to the same value, at a single location in
16+
/// memory. This means that equality and hashing can be done on the value's
17+
/// address rather than the value's contents, which can improve performance.
18+
///
19+
/// The `PrivateZst` field means you can pattern match with `Interned(v, _)`
20+
/// but you can only construct a `Interned` with `new_unchecked`, and not
21+
/// directly.
22+
#[derive(Debug)]
23+
#[cfg_attr(not(bootstrap), rustc_pass_by_value)]
24+
pub struct Interned<'a, T>(pub &'a T, pub private::PrivateZst);
25+
26+
impl<'a, T> Interned<'a, T> {
27+
/// Create a new `Interned` value. The value referred to *must* be interned
28+
/// and thus be unique, and it *must* remain unique in the future. This
29+
/// function has `_unchecked` in the name but is not `unsafe`, because if
30+
/// the uniqueness condition is violated condition it will cause incorrect
31+
/// behaviour but will not affect memory safety.
32+
#[inline]
33+
pub const fn new_unchecked(t: &'a T) -> Self {
34+
Interned(t, private::PrivateZst)
35+
}
36+
}
37+
38+
impl<'a, T> Clone for Interned<'a, T> {
39+
fn clone(&self) -> Self {
40+
*self
41+
}
42+
}
43+
44+
impl<'a, T> Copy for Interned<'a, T> {}
45+
46+
impl<'a, T> Deref for Interned<'a, T> {
47+
type Target = T;
48+
49+
#[inline]
50+
fn deref(&self) -> &T {
51+
self.0
52+
}
53+
}
54+
55+
impl<'a, T> PartialEq for Interned<'a, T> {
56+
#[inline]
57+
fn eq(&self, other: &Self) -> bool {
58+
// Pointer equality implies equality, due to the uniqueness constraint.
59+
ptr::eq(self.0, other.0)
60+
}
61+
}
62+
63+
impl<'a, T> Eq for Interned<'a, T> {}
64+
65+
impl<'a, T: PartialOrd> PartialOrd for Interned<'a, T> {
66+
fn partial_cmp(&self, other: &Interned<'a, T>) -> Option<Ordering> {
67+
// Pointer equality implies equality, due to the uniqueness constraint,
68+
// but the contents must be compared otherwise.
69+
if ptr::eq(self.0, other.0) {
70+
Some(Ordering::Equal)
71+
} else {
72+
let res = self.0.partial_cmp(&other.0);
73+
debug_assert!(res != Some(Ordering::Equal));
74+
res
75+
}
76+
}
77+
}
78+
79+
impl<'a, T: Ord> Ord for Interned<'a, T> {
80+
fn cmp(&self, other: &Interned<'a, T>) -> Ordering {
81+
// Pointer equality implies equality, due to the uniqueness constraint,
82+
// but the contents must be compared otherwise.
83+
if ptr::eq(self.0, other.0) {
84+
Ordering::Equal
85+
} else {
86+
let res = self.0.cmp(&other.0);
87+
debug_assert!(res != Ordering::Equal);
88+
res
89+
}
90+
}
91+
}
92+
93+
impl<'a, T> Hash for Interned<'a, T> {
94+
#[inline]
95+
fn hash<H: Hasher>(&self, s: &mut H) {
96+
// Pointer hashing is sufficient, due to the uniqueness constraint.
97+
ptr::hash(self.0, s)
98+
}
99+
}
100+
101+
#[cfg(test)]
102+
mod tests;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use super::*;
2+
use std::cmp::Ordering;
3+
4+
#[derive(Debug)]
5+
struct S(u32);
6+
7+
impl PartialEq for S {
8+
fn eq(&self, _other: &Self) -> bool {
9+
panic!("shouldn't be called");
10+
}
11+
}
12+
13+
impl Eq for S {}
14+
15+
impl PartialOrd for S {
16+
fn partial_cmp(&self, other: &S) -> Option<Ordering> {
17+
// The `==` case should be handled by `Interned`.
18+
assert_ne!(self.0, other.0);
19+
self.0.partial_cmp(&other.0)
20+
}
21+
}
22+
23+
impl Ord for S {
24+
fn cmp(&self, other: &S) -> Ordering {
25+
// The `==` case should be handled by `Interned`.
26+
assert_ne!(self.0, other.0);
27+
self.0.cmp(&other.0)
28+
}
29+
}
30+
31+
#[test]
32+
fn test_uniq() {
33+
let s1 = S(1);
34+
let s2 = S(2);
35+
let s3 = S(3);
36+
let s4 = S(1); // violates uniqueness
37+
38+
let v1 = Interned::new_unchecked(&s1);
39+
let v2 = Interned::new_unchecked(&s2);
40+
let v3a = Interned::new_unchecked(&s3);
41+
let v3b = Interned::new_unchecked(&s3);
42+
let v4 = Interned::new_unchecked(&s4); // violates uniqueness
43+
44+
assert_ne!(v1, v2);
45+
assert_ne!(v2, v3a);
46+
assert_eq!(v1, v1);
47+
assert_eq!(v3a, v3b);
48+
assert_ne!(v1, v4); // same content but different addresses: not equal
49+
50+
assert_eq!(v1.cmp(&v2), Ordering::Less);
51+
assert_eq!(v3a.cmp(&v2), Ordering::Greater);
52+
assert_eq!(v1.cmp(&v1), Ordering::Equal); // only uses Interned::eq, not S::cmp
53+
assert_eq!(v3a.cmp(&v3b), Ordering::Equal); // only uses Interned::eq, not S::cmp
54+
55+
assert_eq!(v1.partial_cmp(&v2), Some(Ordering::Less));
56+
assert_eq!(v3a.partial_cmp(&v2), Some(Ordering::Greater));
57+
assert_eq!(v1.partial_cmp(&v1), Some(Ordering::Equal)); // only uses Interned::eq, not S::cmp
58+
assert_eq!(v3a.partial_cmp(&v3b), Some(Ordering::Equal)); // only uses Interned::eq, not S::cmp
59+
}

compiler/rustc_data_structures/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#![feature(type_alias_impl_trait)]
2222
#![feature(new_uninit)]
2323
#![feature(once_cell)]
24+
#![feature(rustc_attrs)]
2425
#![feature(test)]
2526
#![feature(thread_id_value)]
2627
#![feature(vec_into_raw_parts)]
@@ -68,12 +69,12 @@ pub mod flock;
6869
pub mod functor;
6970
pub mod fx;
7071
pub mod graph;
72+
pub mod intern;
7173
pub mod jobserver;
7274
pub mod macros;
7375
pub mod map_in_place;
7476
pub mod obligation_forest;
7577
pub mod owning_ref;
76-
pub mod ptr_key;
7778
pub mod sip128;
7879
pub mod small_c_str;
7980
pub mod snapshot_map;

compiler/rustc_data_structures/src/ptr_key.rs

Lines changed: 0 additions & 37 deletions
This file was deleted.

compiler/rustc_resolve/src/imports.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{NameBinding, NameBindingKind, PathResult, PrivacyError, ToNameBindin
1111

1212
use rustc_ast::NodeId;
1313
use rustc_data_structures::fx::FxHashSet;
14-
use rustc_data_structures::ptr_key::PtrKey;
14+
use rustc_data_structures::intern::Interned;
1515
use rustc_errors::{pluralize, struct_span_err, Applicability};
1616
use rustc_hir::def::{self, PartialRes};
1717
use rustc_hir::def_id::DefId;
@@ -134,7 +134,7 @@ impl<'a> Import<'a> {
134134
pub struct NameResolution<'a> {
135135
/// Single imports that may define the name in the namespace.
136136
/// Imports are arena-allocated, so it's ok to use pointers as keys.
137-
single_imports: FxHashSet<PtrKey<'a, Import<'a>>>,
137+
single_imports: FxHashSet<Interned<'a, Import<'a>>>,
138138
/// The least shadowable known binding for this name, or None if there are no known bindings.
139139
pub binding: Option<&'a NameBinding<'a>>,
140140
shadowed_glob: Option<&'a NameBinding<'a>>,
@@ -153,7 +153,7 @@ impl<'a> NameResolution<'a> {
153153
}
154154

155155
crate fn add_single_import(&mut self, import: &'a Import<'a>) {
156-
self.single_imports.insert(PtrKey(import));
156+
self.single_imports.insert(Interned::new_unchecked(import));
157157
}
158158
}
159159

@@ -850,7 +850,7 @@ impl<'a, 'b> ImportResolver<'a, 'b> {
850850
Err(Determined) => {
851851
let key = this.new_key(target, ns);
852852
this.update_resolution(parent, key, |_, resolution| {
853-
resolution.single_imports.remove(&PtrKey(import));
853+
resolution.single_imports.remove(&Interned::new_unchecked(import));
854854
});
855855
}
856856
Ok(binding) if !binding.is_importable() => {

compiler/rustc_resolve/src/lib.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use rustc_ast::{ItemKind, ModKind, Path};
3838
use rustc_ast_lowering::ResolverAstLowering;
3939
use rustc_ast_pretty::pprust;
4040
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
41-
use rustc_data_structures::ptr_key::PtrKey;
41+
use rustc_data_structures::intern::Interned;
4242
use rustc_data_structures::sync::Lrc;
4343
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
4444
use rustc_expand::base::{DeriveResolutions, SyntaxExtension, SyntaxExtensionKind};
@@ -964,7 +964,7 @@ pub struct Resolver<'a> {
964964
/// language items.
965965
empty_module: Module<'a>,
966966
module_map: FxHashMap<DefId, Module<'a>>,
967-
binding_parent_modules: FxHashMap<PtrKey<'a, NameBinding<'a>>, Module<'a>>,
967+
binding_parent_modules: FxHashMap<Interned<'a, NameBinding<'a>>, Module<'a>>,
968968
underscore_disambiguator: u32,
969969

970970
/// Maps glob imports to the names of items actually imported.
@@ -1115,7 +1115,7 @@ impl<'a> ResolverArenas<'a> {
11151115
self.name_resolutions.alloc(Default::default())
11161116
}
11171117
fn alloc_macro_rules_scope(&'a self, scope: MacroRulesScope<'a>) -> MacroRulesScopeRef<'a> {
1118-
PtrKey(self.dropless.alloc(Cell::new(scope)))
1118+
Interned::new_unchecked(self.dropless.alloc(Cell::new(scope)))
11191119
}
11201120
fn alloc_macro_rules_binding(
11211121
&'a self,
@@ -2938,7 +2938,9 @@ impl<'a> Resolver<'a> {
29382938
}
29392939

29402940
fn set_binding_parent_module(&mut self, binding: &'a NameBinding<'a>, module: Module<'a>) {
2941-
if let Some(old_module) = self.binding_parent_modules.insert(PtrKey(binding), module) {
2941+
if let Some(old_module) =
2942+
self.binding_parent_modules.insert(Interned::new_unchecked(binding), module)
2943+
{
29422944
if !ptr::eq(module, old_module) {
29432945
span_bug!(binding.span, "parent module is reset for binding");
29442946
}
@@ -2954,8 +2956,8 @@ impl<'a> Resolver<'a> {
29542956
// is disambiguated to mitigate regressions from macro modularization.
29552957
// Scoping for `macro_rules` behaves like scoping for `let` at module level, in general.
29562958
match (
2957-
self.binding_parent_modules.get(&PtrKey(macro_rules)),
2958-
self.binding_parent_modules.get(&PtrKey(modularized)),
2959+
self.binding_parent_modules.get(&Interned::new_unchecked(macro_rules)),
2960+
self.binding_parent_modules.get(&Interned::new_unchecked(modularized)),
29592961
) {
29602962
(Some(macro_rules), Some(modularized)) => {
29612963
macro_rules.nearest_parent_mod() == modularized.nearest_parent_mod()

compiler/rustc_resolve/src/macros.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_ast_lowering::ResolverAstLowering;
1111
use rustc_ast_pretty::pprust;
1212
use rustc_attr::StabilityLevel;
1313
use rustc_data_structures::fx::FxHashSet;
14-
use rustc_data_structures::ptr_key::PtrKey;
14+
use rustc_data_structures::intern::Interned;
1515
use rustc_data_structures::sync::Lrc;
1616
use rustc_errors::struct_span_err;
1717
use rustc_expand::base::{Annotatable, DeriveResolutions, Indeterminate, ResolverExpand};
@@ -71,7 +71,7 @@ pub enum MacroRulesScope<'a> {
7171
/// This helps to avoid uncontrollable growth of `macro_rules!` scope chains,
7272
/// which usually grow lineraly with the number of macro invocations
7373
/// in a module (including derives) and hurt performance.
74-
pub(crate) type MacroRulesScopeRef<'a> = PtrKey<'a, Cell<MacroRulesScope<'a>>>;
74+
pub(crate) type MacroRulesScopeRef<'a> = Interned<'a, Cell<MacroRulesScope<'a>>>;
7575

7676
// Macro namespace is separated into two sub-namespaces, one for bang macros and
7777
// one for attribute-like macros (attributes, derives).

0 commit comments

Comments
 (0)