UniqueConstantType(value: Constant) => private lazy val _tpe: Type = value.tpe. Why?

27 views
Skip to first unread message

Eugene Burmako

unread,
Apr 9, 2012, 9:35:03 AM4/9/12
to scala-internals
Say, we have a Literal(Constant(StringTpe)), which represents
classOf[String].

After typechecking this tree will be assigned
UniqueConstantType(Constant(StringTpe)). If someone inspects that
type, it will dereference _tpe and will cache Class[String] as its
type.

Now if we inspect the same tree after erasure, we will have a funny
situation. tpe method of Constant(StringTpe) will return Class (which
is a correct erased version of Class[String]). However, tpe method of
Literal(Constant(StringTpe)) will return the cached type, which will
crash CleanUp.

Another piece of fanciness is hashcoding UniqueConstantTypes. Say, we
want to remedy situation and assign our Literal correct type, which is
ConstantType(Constant(StringTpe)).

However, ConstantTypes are created with a factory. This factory is
smart. After creating a UniqueConstantType it will looks into a
hashmap, and if this hashmap already has this type, it will discard
its creation and return an existing type.

Now the fun begins. Hashcode of UniqueConstantType is equal to the
hashcode of the underlying Constant. Constant, as a case class,
calculates its hashcode from its fields - namely, its only field,
value. However, there's already a constant for StringTpe in the
hashmap, and it corresponds to a broken UniqueConstantType, which will
be happily returned by the factory.

***

There is so much weirdness going on (constants having bogus hashcodes,
constant types having bogus underlying types), that I'm unsure where
exactly I start fixing the stuff. Hence I'd like to hear the original
motivation behind making _tpe a lazy val.

Paul Phillips

unread,
Apr 9, 2012, 10:59:28 AM4/9/12
to scala-i...@googlegroups.com
On Mon, Apr 9, 2012 at 6:35 AM, Eugene Burmako <eugene....@epfl.ch> wrote:
> After typechecking this tree will be assigned
> UniqueConstantType(Constant(StringTpe)). If someone inspects that
> type, it will dereference _tpe and will cache Class[String] as its
> type.
>
> Now if we inspect the same tree after erasure, we will have a funny
> situation. tpe method of Constant(StringTpe) will return Class (which
> is a correct erased version of Class[String]). However, tpe method of
> Literal(Constant(StringTpe)) will return the cached type, which will
> crash CleanUp.

You have seen this comment I trust, especially "I would be very
surprised if there aren't more."

/** Apparently we smuggle a Type around as a Literal(Constant(tp))
* and the implementation of Constant#tpe is such that x.tpe becomes
* ClassType(value.asInstanceOf[Type]), i.e. java.lang.Class[Type].
* Can't find any docs on how/why it's done this way. See ticket
* SI-490 for some interesting comments from lauri alanko suggesting
* that the type given by classOf[T] is too strong and should be
* weakened so as not to suggest that classOf[List[String]] is any
* different from classOf[List[Int]].
*
* !!! See deconstMap in Erasure for one bug this encoding has induced:
* I would be very surprised if there aren't more.
*/
def mkClassOf(tp: Type): Tree =
Literal(Constant(tp)) setType ConstantType(Constant(tp))

And

val deconstMap = new TypeMap {
// For some reason classOf[Foo] creates
ConstantType(Constant(tpe)) with an actual Type for tpe,
// which is later translated to a Class. Unfortunately that means
we have bugs like the erasure
// of Class[Foo] and classOf[Bar] not being seen as equivalent,
leading to duplicate method
// generation and failing bytecode. See ticket #4753.
def apply(tp: Type): Type = tp match {
case PolyType(_, _) => mapOver(tp)
case MethodType(_, _) => mapOver(tp) //
nullarymethod was eliminated during uncurry
case ConstantType(Constant(_: Type)) => ClassClass.tpe // all
classOfs erase to Class
case _ => tp.deconst
}
}

> Hence I'd like to hear the original
> motivation behind making _tpe a lazy val.

There's a comment right above it which sounds like a motivation to me:

/** Save the type of `value`. For Java enums, it depends on
finding the linked class,
* which might not be found after `flatten`. */

Eugene Burmako

unread,
Apr 9, 2012, 11:11:07 AM4/9/12
to scala-internals
Well, sure I saw the comment, but my message was rather an invitation
for a chat with someone knowledgeable in constants.

For example, as far as I can guess, enums are smuggled in symbols.
Does this mean that if I make _tpe lazy only for enum constants, I
will not break anything?

Also, while we're at it, the thought of constants being inconsistent
terrifies me. For example, GenJVM happily uses both typeValue (which
is the original tpe passed to the constant) and tpe (which is the tpe
recalculated w.r.t erasure). The only reason why it doesn't blow up is
that normally erasure recreates typerefs even when tpe == tpe.erasure,
so constants get different hashcodes and constant types get different
hashcodes as well. I hope this is something that the author of the
mentioned change thought of, and I'd love to discuss that.

On Apr 9, 4:59 pm, Paul Phillips <pa...@improving.org> wrote:

Paul Phillips

unread,
Apr 9, 2012, 2:33:33 PM4/9/12
to scala-i...@googlegroups.com
On Mon, Apr 9, 2012 at 8:11 AM, Eugene Burmako <eugene....@epfl.ch> wrote:
> I hope this is something that the author of the
> mentioned change thought of, and I'd love to discuss that.

It's safest to assume nothing was thought of.

> For example, as far as I can guess, enums are smuggled in symbols.
> Does this mean that if I make _tpe lazy only for enum constants, I
> will not break anything?

Most of what I know about what doesn't work is derived from just
making a change like this and ant all.clean test. If everything
passes, that doesn't necessarily mean it's okay - but since something
usually breaks, it's useful for avoiding too much actual thinking.

https://github.com/scala/scala/commit/f36d200b
https://issues.scala-lang.org/browse/SI-1329

Reply all
Reply to author
Forward
0 new messages