Repository: 7mind/izumi
Pull request #832 (2020-01-16 21:16:54)

fundamentals-reflection: precompute LightTypeTag hashCode at compile-time

codecov-io: # [Codecov](https://codecov.io/gh/7mind/izumi/pull/832?src=pr&el=h1) Report
> Merging [#832](https://codecov.io/gh/7mind/izumi/pull/832?src=pr&el=desc) into [develop](https://codecov.io/gh/7mind/izumi/commit/18e70f956764c95f1c09a891273891fa9a0dd79f?src=pr&el=desc) will **decrease** coverage by `7.37%`.
> The diff coverage is `50%`.

[![Impacted file tree graph](https://codecov.io/gh/7mind/izumi/pull/832/graphs/tree.svg?width=650&token=77pa3UU1Je&height=150&src=pr)](https://codecov.io/gh/7mind/izumi/pull/832?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##           develop     #832      +/-   ##
===========================================
- Coverage     70.2%   62.82%   -7.38%     
===========================================
  Files          552      545       -7     
  Lines        14074    12321    -1753     
  Branches       726      740      +14     
===========================================
- Hits          9881     7741    -2140     
- Misses        4193     4580     +387
```


| [Impacted Files](https://codecov.io/gh/7mind/izumi/pull/832?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...scala/izumi/fundamentals/reflection/TagMacro.scala](https://codecov.io/gh/7mind/izumi/pull/832/diff?src=pr&el=tree#diff-ZnVuZGFtZW50YWxzL2Z1bmRhbWVudGFscy1yZWZsZWN0aW9uL3NyYy9tYWluL3NjYWxhL2l6dW1pL2Z1bmRhbWVudGFscy9yZWZsZWN0aW9uL1RhZ01hY3JvLnNjYWxh) | `93.47% <ø> (+8.86%)` | :arrow_up: |
| [...ndamentals/reflection/macrortti/LightTypeTag.scala](https://codecov.io/gh/7mind/izumi/pull/832/diff?src=pr&el=tree#diff-ZnVuZGFtZW50YWxzL2Z1bmRhbWVudGFscy1yZWZsZWN0aW9uL3NyYy9tYWluL3NjYWxhL2l6dW1pL2Z1bmRhbWVudGFscy9yZWZsZWN0aW9uL21hY3JvcnR0aS9MaWdodFR5cGVUYWcuc2NhbGE=) | `93.33% <100%> (-0.42%)` | :arrow_down: |
| [...ntals/reflection/macrortti/LightTypeTagMacro.scala](https://codecov.io/gh/7mind/izumi/pull/832/diff?src=pr&el=tree#diff-ZnVuZGFtZW50YWxzL2Z1bmRhbWVudGFscy1yZWZsZWN0aW9uL3NyYy9tYWluL3NjYWxhL2l6dW1pL2Z1bmRhbWVudGFscy9yZWZsZWN0aW9uL21hY3JvcnR0aS9MaWdodFR5cGVUYWdNYWNyby5zY2FsYQ==) | `57.14% <40%> (ø)` | :arrow_up: |
| [...la/izumi/fundamentals/collections/IzIterable.scala](https://codecov.io/gh/7mind/izumi/pull/832/diff?src=pr&el=tree#diff-ZnVuZGFtZW50YWxzL2Z1bmRhbWVudGFscy1jb2xsZWN0aW9ucy9zcmMvbWFpbi9zY2FsYS9penVtaS9mdW5kYW1lbnRhbHMvY29sbGVjdGlvbnMvSXpJdGVyYWJsZS5zY2FsYQ==) | `0% <0%> (-100%)` | :arrow_down: |
| [...alingua/translator/toscala/CompositeRenderer.scala](https://codecov.io/gh/7mind/izumi/pull/832/diff?src=pr&el=tree#diff-aWRlYWxpbmd1YS12MS9pZGVhbGluZ3VhLXYxLXRyYW5zcGlsZXJzL3NyYy9tYWluL3NjYWxhL2l6dW1pL2lkZWFsaW5ndWEvdHJhbnNsYXRvci90b3NjYWxhL0NvbXBvc2l0ZVJlbmRlcmVyLnNjYWxh) | `0% <0%> (-100%)` | :arrow_down: |
| [...slator/toscala/types/runtime/IDLRuntimeTypes.scala](https://codecov.io/gh/7mind/izumi/pull/832/diff?src=pr&el=tree#diff-aWRlYWxpbmd1YS12MS9pZGVhbGluZ3VhLXYxLXRyYW5zcGlsZXJzL3NyYy9tYWluL3NjYWxhL2l6dW1pL2lkZWFsaW5ndWEvdHJhbnNsYXRvci90b3NjYWxhL3R5cGVzL3J1bnRpbWUvSURMUnVudGltZVR5cGVzLnNjYWxh) | `0% <0%> (-100%)` | :arrow_down: |
| [...a/izumi/idealingua/model/common/PackageTools.scala](https://codecov.io/gh/7mind/izumi/pull/832/diff?src=pr&el=tree#diff-aWRlYWxpbmd1YS12MS9pZGVhbGluZ3VhLXYxLW1vZGVsL3NyYy9tYWluL3NjYWxhL2l6dW1pL2lkZWFsaW5ndWEvbW9kZWwvY29tbW9uL1BhY2thZ2VUb29scy5zY2FsYQ==) | `0% <0%> (-100%)` | :arrow_down: |
| [...i/idealingua/translator/toscala/EnumRenderer.scala](https://codecov.io/gh/7mind/izumi/pull/832/diff?src=pr&el=tree#diff-aWRlYWxpbmd1YS12MS9pZGVhbGluZ3VhLXYxLXRyYW5zcGlsZXJzL3NyYy9tYWluL3NjYWxhL2l6dW1pL2lkZWFsaW5ndWEvdHJhbnNsYXRvci90b3NjYWxhL0VudW1SZW5kZXJlci5zY2FsYQ==) | `0% <0%> (-100%)` | :arrow_down: |
| [...toscala/extensions/ScalaTranslatorExtensions.scala](https://codecov.io/gh/7mind/izumi/pull/832/diff?src=pr&el=tree#diff-aWRlYWxpbmd1YS12MS9pZGVhbGluZ3VhLXYxLXRyYW5zcGlsZXJzL3NyYy9tYWluL3NjYWxhL2l6dW1pL2lkZWFsaW5ndWEvdHJhbnNsYXRvci90b3NjYWxhL2V4dGVuc2lvbnMvU2NhbGFUcmFuc2xhdG9yRXh0ZW5zaW9ucy5zY2FsYQ==) | `0% <0%> (-100%)` | :arrow_down: |
| [...ngua/translator/toscala/types/ServiceContext.scala](https://codecov.io/gh/7mind/izumi/pull/832/diff?src=pr&el=tree#diff-aWRlYWxpbmd1YS12MS9pZGVhbGluZ3VhLXYxLXRyYW5zcGlsZXJzL3NyYy9tYWluL3NjYWxhL2l6dW1pL2lkZWFsaW5ndWEvdHJhbnNsYXRvci90b3NjYWxhL3R5cGVzL1NlcnZpY2VDb250ZXh0LnNjYWxh) | `0% <0%> (-100%)` | :arrow_down: |
| ... and [180 more](https://codecov.io/gh/7mind/izumi/pull/832/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/7mind/izumi/pull/832?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/7mind/izumi/pull/832?src=pr&el=footer). Last update [18e70f9...a8727f5](https://codecov.io/gh/7mind/izumi/pull/832?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/load/PluginLoader.scala b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/load/PluginLoader.scala
index 8149d2a2b..0f13cf99a 100644
--- a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/load/PluginLoader.scala
+++ b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/load/PluginLoader.scala
@@ -1,7 +1,6 @@
 package izumi.distage.plugins.load
 
-import distage.plugins.PluginConfig
-import izumi.distage.plugins.PluginBase
+import izumi.distage.plugins.{PluginBase, PluginConfig}
 
 trait PluginLoader {
   def load(): Seq[PluginBase]
diff --git a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/load/PluginLoaderDefaultImpl.scala b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/load/PluginLoaderDefaultImpl.scala
index 259b24bc5..7f0a710ad 100644
--- a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/load/PluginLoaderDefaultImpl.scala
+++ b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/load/PluginLoaderDefaultImpl.scala
@@ -42,8 +42,6 @@ class PluginLoaderDefaultImpl
 object PluginLoaderDefaultImpl {
   private lazy val cache = new SyncCache[String, Seq[PluginBase]]()
 
-
-
   def doLoad[T](base: String, whitelistClasses: Seq[String], enabledPackages: Seq[String], disabledPackages: Seq[String], debug: Boolean): Seq[T] = {
     val scanResult = Value(new ClassGraph())
       .map(_.whitelistPackages(enabledPackages: _*))
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala b/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala
index 19fa38092..dee1a4685 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala
@@ -129,14 +129,16 @@ abstract class RoleAppLauncherImpl[F[_]: TagK: DIEffect] extends RoleAppLauncher
   }
 
   protected def loadRoles(parameters: RawAppArgs, logger: IzLogger, plugins: AllLoadedPlugins): RolesInfo = {
-    val activeRoleNames = parameters.roles.map(_.role).toSet
-    val roleProvider: RoleProvider[F] = new RoleProvider.Impl(logger, activeRoleNames)
     val bindings = plugins.app.flatMap(_.bindings)
-    val bsBindings = plugins.app.flatMap(_.bindings)
-    logger.info(s"Available ${plugins.app.size -> "app plugins"} with ${bindings.size -> "app bindings"} and ${plugins.bootstrap.size -> "bootstrap plugins"} with ${bsBindings.size -> "bootstrap bindings"} ...")
-    val roles = roleProvider.getInfo(bindings)
+    val bsBindings = plugins.bootstrap.flatMap(_.bindings)
+    logger.info(s"Available ${plugins.app.size -> "app plugins"} with ${bindings.size -> "app bindings"} and ${
+      plugins.bootstrap.size -> "bootstrap plugins"} with ${bsBindings.size -> "bootstrap bindings"} ...")
 
+    val activeRoleNames = parameters.roles.map(_.role).toSet
+    val roleProvider = new RoleProvider.Impl(logger, activeRoleNames)
+    val roles = roleProvider.getInfo(bindings)
     printRoleInfo(logger, roles)
+
     val missing = parameters.roles.map(_.role).toSet.diff(roles.availableRoleBindings.map(_.descriptor.id).toSet)
     if (missing.nonEmpty) {
       logger.crit(s"Missing ${missing.niceList() -> "roles"}")
diff --git a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/TagMacro.scala b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/TagMacro.scala
index 3ca266cce..83abb0ff7 100644
--- a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/TagMacro.scala
+++ b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/TagMacro.scala
@@ -98,7 +98,7 @@ class TagMacro(val c: blackbox.Context) {
 
     val res = {
       if (isSimplePartialApplication) {
-        // FIXME: unused in complex tl ???
+        // FIXME: unused in complex type lambda ???
         val argTags = {
           // FIXME: fast-path optimize if typeArg is Strong
           val args = embeddedMaybeNonParamTypeArgs.map(_.map(t => ReflectionUtil.norm(c.universe: c.universe.type)(t.dealias)))
diff --git a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTag.scala b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTag.scala
index 686a64ed1..74106cf05 100644
--- a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTag.scala
+++ b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTag.scala
@@ -144,7 +144,7 @@ abstract class LightTypeTag
 
   override def hashCode(): Int = hashcode
 
-  private lazy val hashcode: Int = {
+  private[this] lazy val hashcode: Int = {
     ref.hashCode() * 31
   }
 }
@@ -173,15 +173,16 @@ object LightTypeTag {
     LightTypeTag(ref, mergedBasesDB, mergedInheritanceDb)
   }
 
-  def parse[T](refString: String, basesString: String): LightTypeTag = {
+  def parse[T](hashCode: Int, refString: String, basesString: String): LightTypeTag = {
     lazy val shared = {
       subtypeDBsSerializer.unpickle(ByteBuffer.wrap(basesString.getBytes("ISO-8859-1")))
     }
 
-    new ParsedLightTypeTag(refString, () => shared.bases, () => shared.idb)
+    new ParsedLightTypeTag(hashCode, refString, () => shared.bases, () => shared.idb)
   }
 
   final class ParsedLightTypeTag(
+                                  override val hashCode: Int,
                                   private val refString: String,
                                   bases: () => Map[AbstractReference, Set[AbstractReference]],
                                   db: () => Map[NameReference, Set[NameReference]],
diff --git a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTagMacro.scala b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTagMacro.scala
index f6c114646..0fde1a034 100644
--- a/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTagMacro.scala
+++ b/fundamentals/fundamentals-reflection/src/main/scala/izumi/fundamentals/reflection/macrortti/LightTypeTagMacro.scala
@@ -18,16 +18,16 @@ private[reflection] class LightTypeTagMacro0[C <: blackbox.Context](val c: C)(lo
   protected final val impl = new LightTypeTagImpl[c.universe.type](c.universe, withCache = cacheEnabled, logger)
 
   final def makeStrongHKTag[ArgStruct: c.WeakTypeTag]: c.Expr[LTag.StrongHK[ArgStruct]] = {
-    val tpe = weakTypeOf[ArgStruct]
-    if (ReflectionUtil.allPartsStrong(tpe.dealias)) {
-      c.Expr[LTag.StrongHK[ArgStruct]](q"new ${weakTypeOf[LTag.StrongHK[ArgStruct]]}(${makeParsedHKTagLightTypeTagImpl(tpe)})")
+    val tpe = unpackArgStruct(weakTypeOf[ArgStruct])
+    if (ReflectionUtil.allPartsStrong(tpe)) {
+      c.Expr[LTag.StrongHK[ArgStruct]](q"new ${weakTypeOf[LTag.StrongHK[ArgStruct]]}(${makeParsedLightTypeTagImpl(tpe)})")
     } else {
       c.abort(c.enclosingPosition, s"Can't materialize LTag.StrongHKTag[$tpe]: found unresolved type parameters in $tpe")
     }
   }
 
   final def makeWeakHKTag[ArgStruct: c.WeakTypeTag]: c.Expr[LTag.WeakHK[ArgStruct]] = {
-    c.Expr[LTag.WeakHK[ArgStruct]](q"new ${weakTypeOf[LTag.WeakHK[ArgStruct]]}(${makeParsedHKTagLightTypeTagImpl(weakTypeOf[ArgStruct])})")
+    c.Expr[LTag.WeakHK[ArgStruct]](q"new ${weakTypeOf[LTag.WeakHK[ArgStruct]]}(${makeParsedLightTypeTagImpl(unpackArgStruct(weakTypeOf[ArgStruct]))})")
   }
 
   final def makeStrongTag[T: c.WeakTypeTag]: c.Expr[LTag[T]] = {
@@ -49,34 +49,6 @@ private[reflection] class LightTypeTagMacro0[C <: blackbox.Context](val c: C)(lo
     makeParsedLightTypeTagImpl(weakTypeOf[T])
   }
 
-  final def makeParsedHKTagLightTypeTagImpl(argStruct: Type): c.Expr[LightTypeTag] = {
-    def badShapeError(t: TypeApi) = {
-      c.abort(c.enclosingPosition, s"Expected type shape RefinedType `{ type Arg[A] = X[A] }` for summoning `LTag.StrongHK/WeakHK[X]`, but got $t (raw: ${showRaw(t)} ${t.getClass})")
-    }
-
-    argStruct match {
-      case r: RefinedTypeApi =>
-        r.decl(TypeName("Arg")) match {
-          case sym: TypeSymbolApi =>
-            makeParsedLightTypeTagImpl(sym.info.typeConstructor)
-          case _ => badShapeError(r)
-        }
-      case other => badShapeError(other)
-    }
-  }
-
-  @inline final def unpackArgStruct(t: Type): Type = {
-    def badShapeError() = c.abort(c.enclosingPosition, s"Expected type shape RefinedType `{ type Arg[A] = X[A] }` for summoning `LTag.StrongHK/WeakHK[X]`, but got $t (raw: ${showRaw(t)} ${t.getClass})")
-    t match {
-      case r: RefinedTypeApi =>
-        r.decl(TypeName("Arg")) match {
-          case sym: TypeSymbolApi => sym.info.typeConstructor
-          case _ => badShapeError()
-        }
-      case _ => badShapeError()
-    }
-  }
-
   final def makeParsedLightTypeTagImpl(tpe: Type): c.Expr[LightTypeTag] = {
     val res = impl.makeFullTagImpl(tpe)
 
@@ -87,10 +59,28 @@ private[reflection] class LightTypeTagMacro0[C <: blackbox.Context](val c: C)(lo
       new String(bytes, 0, bytes.length, "ISO-8859-1")
     }
 
+    val hashCodeRef = res.hashCode()
     val strRef = serialize(res.ref)(LightTypeTag.lttRefSerializer)
     val strDBs = serialize(SubtypeDBs(res.basesdb, res.idb))(LightTypeTag.subtypeDBsSerializer)
 
-    c.Expr[LightTypeTag](q"_root_.izumi.fundamentals.reflection.macrortti.LightTypeTag.parse($strRef : _root_.java.lang.String, $strDBs : _root_.java.lang.String)")
+    c.Expr[LightTypeTag](q"_root_.izumi.fundamentals.reflection.macrortti.LightTypeTag.parse($hashCodeRef: _root_.scala.Int, $strRef : _root_.java.lang.String, $strDBs : _root_.java.lang.String)")
+  }
+
+  @inline final def unpackArgStruct(t: Type): Type = {
+    def badShapeError() = {
+      c.abort(c.enclosingPosition,
+        s"Expected type shape RefinedType `{ type Arg[A] = X[A] }` for summoning `LTag.StrongHK/WeakHK[X]`, but got $t (raw: ${showRaw(t)} ${t.getClass})"
+      )
+    }
+    t match {
+      case r: RefinedTypeApi =>
+        r.decl(TypeName("Arg")) match {
+          case sym: TypeSymbolApi =>
+            sym.info.typeConstructor
+          case _ => badShapeError()
+        }
+      case _ => badShapeError()
+    }
   }
 
 }


Test patch:
diff --git a/fundamentals/fundamentals-reflection/src/test/scala/izumi.fundamentals.reflection.test/LightTypeTagTest.scala b/fundamentals/fundamentals-reflection/src/test/scala/izumi.fundamentals.reflection.test/LightTypeTagTest.scala
index de2b0bf33..42f179c71 100644
--- a/fundamentals/fundamentals-reflection/src/test/scala/izumi.fundamentals.reflection.test/LightTypeTagTest.scala
+++ b/fundamentals/fundamentals-reflection/src/test/scala/izumi.fundamentals.reflection.test/LightTypeTagTest.scala
@@ -362,7 +362,10 @@ class LightTypeTagTest extends AnyWordSpec {
     }
 
     "support TagK* family summoners" in {
-      assertSame(LTagK[List].tag, `LTT[_]`[List])
+      val tag = LTagK[List].tag
+      val tag1 = LTagKK[Either].tag
+      assertSame(tag, `LTT[_]`[List])
+      assertSame(tag1, `LTT[_,_]`[Either])
     }
 
     "support intersection type equality" in {