-
Notifications
You must be signed in to change notification settings - Fork 396
Cleanups after the big Name refactoring #3833
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
Conversation
GitHub is partially down at the moment, so the CI is not triggered. I will try to re-close and re-open later. |
Since that object defines Name classes and constant Names, it makes more sense to call it `Names`.
a6fc468
to
fa23839
Compare
@@ -548,18 +548,18 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { | |||
|
|||
case Assign(select, rhs) => | |||
select match { | |||
case Select(This(), ClassRef(cls), FieldIdent(_, _)) | |||
case Select(This(), cls, FieldIdent(_, _)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems kind of inconsistent that we are not renaming cls
-> className
here. Intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not intended, I forgot it. Fixed.
|
||
/** Returns `true` iff this is the name of a static initializer. */ | ||
def isStaticInitializer: Boolean = this == StaticInitializerSimpleName | ||
def isStaticInitializer: Boolean = encoded(0) == '<' && encoded(1) == 'c' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change? Is it actually faster?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's clearly faster because:
- in the
true
case, testing againstStaticInitializerSimpleName
requires at least the same comparisons to happen, but loading the rhs from memory instead of as immediates, plus more comparisons for the subsequent characters. - in the
false
case, we trade looking upthis._hashCode
andthat._hashCode
for looking up justthis.encoded(0)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also this avoids defining some methods of SimpleMethodName
in terms of constant instances of SimpleMethodName
itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've reverted that change in that commit, and instead I've added another commit later that uses an even better mechanism, using reference equality.
throw new IllegalArgumentException( | ||
"A constructor or static initializer must not have a result type") | ||
"A reflective proxy must have a result type of java.lang.Object") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit strange that the check is against a constant but the name is inlined... It's a detail though.
} | ||
buildInner(scope.withEnv(scope.env.withLocalDefs(newMappings)), cont1) | ||
buildInner(scope.withEnv(newEnv), cont1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why the old code wouldn't work here...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
withLocalDefs
wants a List[(LocalName, LocalDef)]
, but now newMappings
is a List[(Binding.Name, LocalDef)]
.
* `equals()` and `hashCode()`, along with `==` and `##`, are just as | ||
* broken for `EncodedName` as for `Array`s. Use the methods in the | ||
* companion object instead. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why put them in the companion? Is there a disadvantage to simply overriding them? (IIUC for an AnyVal
the byte code will look pretty much the same than a method in the companion when not boxed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, overriding equals
and hashCode
is prohibited in an AnyVal
. The compiler rejects it. I never understood why that restriction exists, but there's nothing I can do about it now.
scala.util.hashing.MurmurHash3.bytesHash(encoded.bytes) | ||
|
||
private[Names] def concat(encoded1: EncodedName, | ||
encoded2: EncodedName): EncodedName = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Define +
on EncodedName
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I called it ++
instead. +
only means concatenation for String
s, and it has the problem of allowing String + Anything
without any check. ++
consistently means concatenation for collections in Scala.
* broken for `EncodedName` as for `Array`s. Use the methods in the | ||
* companion object instead. | ||
*/ | ||
final class EncodedName private (private[ir] val bytes: Array[Byte]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how this gives a neat separation between base encoding and higher level name guarantees. In this line: Have you considered putting this into a separate file (so the utf8 encoding methods would get closer together without increasing noise in this file).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, consider simply calling this UTF8String
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -106,14 +106,16 @@ object Serializers { | |||
final val FormatNoPositionValue = -1 | |||
} | |||
|
|||
private final class ByteString(val bytes: Array[Byte]) { | |||
private final class EncodedNameKey(val encoded: EncodedName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If EncodedName
gets the equals and hash code directly, this class can simply disappear, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but as I said above, EncodedName
cannot get equals
and hashCode
:(
It made sense to use `ClassRef`s when they wrapped bare `String`s, as it provided better type safety. However, now that we have type-safe `ClassName`s, the wrapping `ClassRef` are just noise and useless indirections. We also more consistently use `className` as a variable of type `ClassName`, instead of `cls` which was used for `ClassRef`s. After this commit, `ClassRef`s are only used as one case of `TypeRef`, as they should.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
@@ -548,18 +548,18 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { | |||
|
|||
case Assign(select, rhs) => | |||
select match { | |||
case Select(This(), ClassRef(cls), FieldIdent(_, _)) | |||
case Select(This(), cls, FieldIdent(_, _)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not intended, I forgot it. Fixed.
|
||
/** Returns `true` iff this is the name of a static initializer. */ | ||
def isStaticInitializer: Boolean = this == StaticInitializerSimpleName | ||
def isStaticInitializer: Boolean = encoded(0) == '<' && encoded(1) == 'c' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's clearly faster because:
- in the
true
case, testing againstStaticInitializerSimpleName
requires at least the same comparisons to happen, but loading the rhs from memory instead of as immediates, plus more comparisons for the subsequent characters. - in the
false
case, we trade looking upthis._hashCode
andthat._hashCode
for looking up justthis.encoded(0)
} | ||
buildInner(scope.withEnv(scope.env.withLocalDefs(newMappings)), cont1) | ||
buildInner(scope.withEnv(newEnv), cont1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
withLocalDefs
wants a List[(LocalName, LocalDef)]
, but now newMappings
is a List[(Binding.Name, LocalDef)]
.
* broken for `EncodedName` as for `Array`s. Use the methods in the | ||
* companion object instead. | ||
*/ | ||
final class EncodedName private (private[ir] val bytes: Array[Byte]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
scala.util.hashing.MurmurHash3.bytesHash(encoded.bytes) | ||
|
||
private[Names] def concat(encoded1: EncodedName, | ||
encoded2: EncodedName): EncodedName = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I called it ++
instead. +
only means concatenation for String
s, and it has the problem of allowing String + Anything
without any check. ++
consistently means concatenation for collections in Scala.
|
||
/** Returns `true` iff this is the name of a static initializer. */ | ||
def isStaticInitializer: Boolean = this == StaticInitializerSimpleName | ||
def isStaticInitializer: Boolean = encoded(0) == '<' && encoded(1) == 'c' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also this avoids defining some methods of SimpleMethodName
in terms of constant instances of SimpleMethodName
itself.
…ame. In doing so, we can turn `resultTypeRef` from being optional to being mandatory. For constructors and static initializers, it must be set to `void`, whereas for reflective proxies, it must be set to `java.lang.Object`. This is consistent with how they are typechecked both at definition site and call site.
Instead of using a fake `LocalName` for `this`, we now use a dedicated ADT for the `name` of a `Binding`, which be either `Binding.This` or `Binding.Local(name, originalName)`. In `OptEnv`, we put the `LocalDef` for `this` in a separate field.
Instead of being a raw `Array[Byte]`, we now use a dedicated class `UTF8String` which is an immutable valid UTF-8 sequence, validated as such when constructed. This allows to expose it to users of `Name`s in a safe way. We also remove or hide all the constructors that by-pass name validation, so that it is not possible to create invalid names from outside the `ir` package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider dropping the "the" in the message of the new commit so it's not too long.
Since we need to validate `<init>` and `<clinit>` separately anyway, using the result of this validation to ensure the uniqueness of the resulting `SimpleMethodName`s is free. This allows to implement `isConstructor` and `isStaticInitializer` with `eq`.
After the recent refactorings, we were left with a number of fields and variables named `encodedName`, even though they have the type `ClassName` or `MethodName`. We have now renamed all of them to `className` and `methodName`, respectively.
Good point :) |
There were several changes to the IR in 1.0.0-RC1 that have an impact on this codebase: * Primitive types now have dedicated `PrimRef`s when used as `TypeRef`s, so for example `IntRef` instead of `ClassRef("I")`. See scala-js/scala-js#3802 * Names now have dedicated types instead of `String`s, and depend on the kind of symbol: `LocalName`, `LabelName`, `FieldName`, `MethodName` and `ClassName`. There are different kinds of `js.Ident`s to go with each kind of `Name`. See scala-js/scala-js#3804, scala-js/scala-js#3814 and scala-js/scala-js#3833 * Original names are now represented by a dedicated `OriginalName` type, and they are stored only at definition site, not at use site. See scala-js/scala-js#3845
There were several changes to the IR in 1.0.0-RC1 that have an impact on this codebase: * Primitive types now have dedicated `PrimRef`s when used as `TypeRef`s, so for example `IntRef` instead of `ClassRef("I")`. See scala-js/scala-js#3802 * Names now have dedicated types instead of `String`s, and depend on the kind of symbol: `LocalName`, `LabelName`, `FieldName`, `MethodName` and `ClassName`. There are different kinds of `js.Ident`s to go with each kind of `Name`. See scala-js/scala-js#3804, scala-js/scala-js#3814 and scala-js/scala-js#3833 * Original names are now represented by a dedicated `OriginalName` type, and they are stored only at definition site, not at use site. See scala-js/scala-js#3845
No description provided.