Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libsyntax: Do not derive Hash for Ident #28823

Merged
merged 2 commits into from
Oct 6, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions src/libsyntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ use ptr::P;
use std::fmt;
use std::rc::Rc;
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use serialize::{Encodable, Decodable, Encoder, Decoder};

/// A name is a part of an identifier, representing a string or gensym. It's
Expand All @@ -84,7 +85,7 @@ pub struct SyntaxContext(pub u32);
/// An identifier contains a Name (index into the interner
/// table) and a SyntaxContext to track renaming and
/// macro expansion per Flatt et al., "Macros That Work Together"
#[derive(Clone, Copy, Eq, Hash)]
#[derive(Clone, Copy, Eq)]
pub struct Ident {
pub name: Name,
pub ctxt: SyntaxContext
Expand Down Expand Up @@ -133,22 +134,35 @@ impl Ident {

impl PartialEq for Ident {
fn eq(&self, other: &Ident) -> bool {
if self.ctxt == other.ctxt {
self.name == other.name
} else {
// IF YOU SEE ONE OF THESE FAILS: it means that you're comparing
// idents that have different contexts. You can't fix this without
// knowing whether the comparison should be hygienic or non-hygienic.
// if it should be non-hygienic (most things are), just compare the
// 'name' fields of the idents.
if self.ctxt != other.ctxt {
// There's no one true way to compare Idents. They can be compared
// non-hygienically `id1.name == id2.name`, hygienically
// `mtwt::resolve(id1) == mtwt::resolve(id2)`, or even member-wise
// `(id1.name, id1.ctxt) == (id2.name, id2.ctxt)` depending on the situation.
// Ideally, PartialEq should not be implemented for Ident at all, but that
// would be too impractical, because many larger structures (Token, in particular)
// including Idents as their parts derive PartialEq and use it for non-hygienic
// comparisons. That's why PartialEq is implemented and defaults to non-hygienic
// comparison. Hash is implemented too and is consistent with PartialEq, i.e. only
// the name of Ident is hashed. Still try to avoid comparing idents in your code
// (especially as keys in hash maps), use one of the three methods listed above
// explicitly.
//
// On the other hand, if the comparison does need to be hygienic,
// one example and its non-hygienic counterpart would be:
// syntax::parse::token::Token::mtwt_eq
// syntax::ext::tt::macro_parser::token_name_eq
// If you see this panic, then some idents from different contexts were compared
// non-hygienically. It's likely a bug. Use one of the three comparison methods
// listed above explicitly.

panic!("idents with different contexts are compared with operator `==`: \
{:?}, {:?}.", self, other);
}

self.name == other.name
}
}

impl Hash for Ident {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state)
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/libsyntax/ext/mtwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ use std::collections::HashMap;
pub struct SCTable {
table: RefCell<Vec<SyntaxContext_>>,
mark_memo: RefCell<HashMap<(SyntaxContext,Mrk),SyntaxContext>>,
rename_memo: RefCell<HashMap<(SyntaxContext,Name,SyntaxContext,Name),SyntaxContext>>,
// The pair (Name,SyntaxContext) is actually one Ident, but it needs to be hashed and
// compared as pair (name, ctxt) and not as an Ident
rename_memo: RefCell<HashMap<(SyntaxContext,(Name,SyntaxContext),Name),SyntaxContext>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why make this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for clarity. It's actually an Ident (and it was literally Ident before #28642), but it should be hashed and compared as pair and not as an Ident.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment explaining this please?

}

#[derive(PartialEq, RustcEncodable, RustcDecodable, Hash, Debug, Copy, Clone)]
Expand Down Expand Up @@ -82,7 +84,7 @@ fn apply_rename_internal(id: Ident,
to: Name,
ctxt: SyntaxContext,
table: &SCTable) -> SyntaxContext {
let key = (ctxt, id.name, id.ctxt, to);
let key = (ctxt, (id.name, id.ctxt), to);

*table.rename_memo.borrow_mut().entry(key).or_insert_with(|| {
SyntaxContext(idx_push(&mut *table.table.borrow_mut(), Rename(id, to, ctxt)))
Expand Down