Skip to content

Make case class hashCode take class into account #2159

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

Merged
merged 4 commits into from
Apr 3, 2017

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Mar 30, 2017

Previously, elements of the following classes had the same hash code:

case class A()
case class B()

Now they are distinguished.

Previously, elements of the following classes had the same hash code:

    case class A()
    case class B()

Now they are distinguished.
@smarter
Copy link
Member

smarter commented Mar 30, 2017

What's the usecase for that?

* acc = Statics.mix(acc, x);
* acc = Statics.mix(acc, Statics.this.anyHash(y));
* Statics.finalizeHash(acc, 2)
* }
*/
def caseHashCodeBody(implicit ctx: Context): Tree = {
val acc = ctx.newSymbol(ctx.owner, "acc".toTermName, Mutable | Synthetic, defn.IntType, coord = ctx.owner.pos)
val accDef = ValDef(acc, Literal(Constant(0xcafebabe)))
val accDef = ValDef(acc, This(clazz).select(defn.Product_productPrefix).select(defn.Any_hashCode).ensureApplied)
Copy link
Contributor

Choose a reason for hiding this comment

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

Given that this code generates also productPrefix itself, why not inline constant ownName here instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, given that hashcodes on strings are fixed by the spec, we can actually compute the runtime value and inline it: Literal(ownName.toString.hashcode).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@DarkDimius Neat! I'll change it.

@DarkDimius
Copy link
Contributor

What's the usecase for that?

Less hash collisions for unrelated things.

case class A(val a: Int)
case class B(val b: Int)
case class C(val c: Int)
...
...
case class Z(val z: Int)

new HashSet(new A(0), new B(0), new C(0), ..., new Z(0)) <-- I love O(n^2) hashmaps :-)

@odersky
Copy link
Contributor Author

odersky commented Mar 31, 2017

That failure looks like a deadlock. /cc @felixmulder

@DarkDimius DarkDimius merged commit 5021c76 into scala:master Apr 3, 2017
* acc = Statics.mix(acc, x);
* acc = Statics.mix(acc, Statics.this.anyHash(y));
* Statics.finalizeHash(acc, 2)
* }
*/
def caseHashCodeBody(implicit ctx: Context): Tree = {
val acc = ctx.newSymbol(ctx.owner, "acc".toTermName, Mutable | Synthetic, defn.IntType, coord = ctx.owner.pos)
val accDef = ValDef(acc, Literal(Constant(0xcafebabe)))
val accDef = ValDef(acc, Literal(Constant(clazz.fullName.toString.hashCode)))
Copy link
Member

Choose a reason for hiding this comment

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

scalac uses productPrefix (which is user definable), rather than the class name.

What is the definition of fullName for non-member classes?

Copy link
Contributor

Choose a reason for hiding this comment

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

scalac uses productPrefix (which is user definable), rather than the class name.

What's the motivation behind this?
Product prefix isn't required to even return the same value, so one can break hash-code by returning different values there.

Copy link
Member

@retronym retronym Apr 3, 2017

Choose a reason for hiding this comment

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

Neither are the case accessors (they could be vars).

I guess a motivation would be to have more stable hash codes for a local case class so that it wasn't volatile wrt fresh name suffixes. Perhaps this implementation acheives the same by inlining fullName, rather than calling this.getClass.toString).

I'm just pointing out the deviation.

Copy link
Contributor

Choose a reason for hiding this comment

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

AFAIK, The full name here won't include any fresh name suffices. They will be only added after Flatten. Two local classes with the same name will get the same constant.

Copy link
Member

Choose a reason for hiding this comment

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

Neither are the case accessors (they could be vars).

I see now, though, that a mutable productPrefix would be worse that a var accessor, as equals does not check that this.productPrefix == that.productPrefix.

@allanrenucci allanrenucci deleted the fix-hashcode branch December 14, 2017 19:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants