Skip to content

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)