-
Notifications
You must be signed in to change notification settings - Fork 185
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
Add symbol-based type shortener #42
Conversation
token.input.eq(from.input) && | ||
token.end <= to.end && | ||
token.start >= from.start | ||
sealed abstract class Patch { |
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.
These changes were imported from previous @olafurpg's commits in other branch.
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 have bigger pending changes on imports. But I'm hoping to get this in first if that's possible.
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.
Are these changes necessary now? If not remove
case _ => | ||
} | ||
/** The compiler sets different symbols for `PackageDef`s | ||
* and term names pointing to that package. Get the |
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.
For some reason this is not aligned, sorry about that.
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.
what?! can you fix it manually?
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.
Reword suggestion: returns canonical symbol of a package symbol for equality comparison.
It's typically good to start docstrings with "Returns ..."
* we need to check both names to guarantee whether a name is in scope. | ||
*/ | ||
@inline | ||
def lookupBothNames(name: String, |
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'm extending this method to return both the term symbol and type names if both exist. This can happen if types with same names are defined in a trait and its companion object, for example.
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.
Explain that in docstring instead of here?
} | ||
|
||
// Disambiguate overloaded symbols caused by name shadowing | ||
if (symbol.isOverloaded) { |
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.
We try to disambiguate the overloaded symbol with the owner. If there's not luck, we fallback to a string-based approach: get the symbol whose full name is the longest common subexpression of our tpe
.
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.
mention this in code comment?
val wholeScope = mixScopes(inScope, realRootScope) | ||
val disambiguatingSyntax = ref.syntax | ||
val (_, reversedSymbols) = { | ||
names.iterator.foldLeft(wholeScope -> List.empty[g.Symbol]) { |
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.
The iterator
here can be removed.
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 do you have it?
// Return shortened type for names already in scope | ||
val onlyNames = onlyPaths.map(_._1) | ||
val removed = removePrefixes(refNoThis, onlyNames) | ||
val shortenedAndRenamedType = removed.transform { |
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.
The renaming logic can go into an independent method for easing readability.
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 think that is a good idea. I'm not religious about it, but I think the rule of 30 is nice to keep in mind https://github.com/databricks/scala-style-guide#rule_of_30
else paths.tail.map(_._1) | ||
} | ||
|
||
// Shortened names must be just one if no PDT |
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.
What's the idiomatic way of failing in scalafix? Here we're failing fast since these cases should never happen. I wonder if this is ok or we should go for another solution.
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.
Are these exceptional/unexpected cases? If so, then throw exception with clear error message about what assumption was broken. If not exceptional or unexpected, then encode in type signature of shortenType
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.
btw, I suspect this will eventually become a part of the scala.meta semantic api, once we have gained some experience with it in scalafix.
So far, imports inside blocks are not recognised (and we should): val foo = {
import scala.concurrent.mutable.List
List.empty[Int]
} It could be done by changing the |
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.
} | ||
} | ||
/** Get the last sequential top level package from a source file. */ | ||
def getLastTopLevelPkg(potentialPkg: Stat): Stat = { |
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.
Is this an equivalent implementation?
def getLastTopLevelPkg(potPkg: Stat): Stat = potPkg match {
case Pkg(_, head +: Nil) => getLastTopLevelPkg(head)
case Pkg(_, head +: _) => head
case _ => potPkg
}
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.
In what test is this method needed?
@@ -18,4 +17,7 @@ trait SemanticApi { | |||
|
|||
/** Returns the type annotation for given val/def. */ | |||
def typeSignature(defn: Defn): Option[Type] | |||
|
|||
/** */ |
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.
Suggestion: Returns the same type with fully qualified names shortened and missing imports.
What do you think about returning a case class instead of tuple?
tpe
should be named toShorten
and t
should be atLocation
.
What happens on failure? This is method is used for aestetics reasons and can safely be skipped so it could either return an Option[(a, b)] if shortening failed or default to toShorten -> Nil
. WDYT?
token.input.eq(from.input) && | ||
token.end <= to.end && | ||
token.start >= from.start | ||
sealed abstract class Patch { |
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 have bigger pending changes on imports. But I'm hoping to get this in first if that's possible.
>>> | ||
import scala.collection.immutable.{Map => MyMap} | ||
class A { | ||
implicit val x: MyMap[Int, String] = |
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.
Impressive! 😍
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.
Indeed :)
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.
Have a look at the other two tests checking renames.
MyMap(1 -> "STRING") | ||
} | ||
<<< removed map | ||
import scala.collection.immutable.{Map => _} |
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.
This isn't testing anything, since Map
is a val in Predef. Maybe use a different collection that's not in Predef?
else paths.tail.map(_._1) | ||
} | ||
|
||
// Shortened names must be just one if no PDT |
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.
btw, I suspect this will eventually become a part of the scala.meta semantic api, once we have gained some experience with it in scalafix.
} | ||
|
||
// Shortened names must be just one if no PDT | ||
assert(shortenedNames.size == 1) |
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.
Include at least a clarifying message.
new SemanticApi { | ||
override def shortenType(tpe: m.Type, | ||
owner: m.Tree): (m.Type, Seq[m.Ref]) = { | ||
logger.elem(tpe) |
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.
Remove
val typeRefsAndRewrites = typeRefs.zip(typeRewrites) | ||
val mappedRewrites = typeRefsAndRewrites.map { | ||
case (typeRef, missingOrHit) => | ||
typeRef -> missingOrHit.fold(_._2, identity) |
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.
._.2, identity
is a sign that a case class might be a better encoding.
@@ -190,7 +190,7 @@ class ScalaJs | |||
Command("examples/test:compile") | |||
) | |||
), | |||
skip = true // GenJsCode is hard: import renames + dependent types | |||
skip = false // GenJsCode is hard: import renames + dependent types |
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.
Did you run clean/compile after disabling scalafix? I think I disabled it for some reason while debugging.
Diff from cats: diff --git a/.jvmopts b/.jvmopts
deleted file mode 100644
index 83ed6fb4..00000000
--- a/.jvmopts
+++ /dev/null
@@ -1,12 +0,0 @@
-# see https://weblogs.java.net/blog/kcpeppe/archive/2013/12/11/case-study-jvm-hotspot-flags
--Dfile.encoding=UTF8
--Xms1G
--Xmx6G
--XX:MaxPermSize=512M
--XX:ReservedCodeCacheSize=250M
--XX:+TieredCompilation
--XX:-UseGCOverheadLimit
-# effectively adds GC to Perm space
--XX:+CMSClassUnloadingEnabled
-# must be enabled for CMSClassUnloadingEnabled to work
--XX:+UseConcMarkSweepGC
diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala
index e248189d..d81a4746 100644
--- a/js/src/test/scala/cats/tests/FutureTests.scala
+++ b/js/src/test/scala/cats/tests/FutureTests.scala
@@ -2,6 +2,7 @@ package cats
package js
package tests
+import scala.concurrent.ExecutionContextExecutor
import cats.laws.discipline._
import cats.js.instances.Await
import cats.js.instances.future.futureComonad
@@ -17,7 +18,7 @@ import cats.laws.discipline.arbitrary._
// https://issues.scala-lang.org/browse/SI-7934
@deprecated("", "")
class DeprecatedForwarder {
- implicit def runNow = scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow
+ implicit def runNow: ExecutionContextExecutor = scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow
}
object DeprecatedForwarder extends DeprecatedForwarder
import DeprecatedForwarder.runNow
diff --git a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala
index 769970c6..addac01e 100644
--- a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala
+++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala
@@ -228,7 +228,7 @@ class LawTests extends FunSuite with Discipline {
.forall { case (x, y) => a.eqv(x, y) == b.eqv(x, y) }
}
- implicit val monoidOrderN = Order.whenEqualMonoid[N]
+ implicit val monoidOrderN: Monoid[Order[N]] with Band[Order[N]] = Order.whenEqualMonoid[N]
laws[GroupLaws, Order[N]].check(_.monoid)
laws[GroupLaws, Order[N]].check(_.band)
diff --git a/kernel/src/main/scala/cats/kernel/instances/char.scala b/kernel/src/main/scala/cats/kernel/instances/char.scala
index 4238fa84..456afbe4 100644
--- a/kernel/src/main/scala/cats/kernel/instances/char.scala
+++ b/kernel/src/main/scala/cats/kernel/instances/char.scala
@@ -4,7 +4,7 @@ package instances
package object char extends CharInstances
trait CharInstances {
- implicit val catsKernelStdOrderForChar = new CharOrder
+ implicit val catsKernelStdOrderForChar: CharOrder = new CharOrder
}
class CharOrder extends Order[Char] {
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 10bb5da4..e8db97b3 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -11,3 +11,5 @@ addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.13")
addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.4.1")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1")
addSbtPlugin("com.fortysevendeg" % "sbt-microsites" % "0.3.2")
+
+addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.2.2")
diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala
index d7c41002..9a9af542 100644
--- a/tests/src/test/scala/cats/tests/EvalTests.scala
+++ b/tests/src/test/scala/cats/tests/EvalTests.scala
@@ -1,6 +1,11 @@
package cats
package tests
+import cats.Monoid
+import cats.Semigroup
+import cats.Order
+import cats.PartialOrder
+import cats.Eq
import scala.math.min
import cats.laws.ComonadLaws
import cats.laws.discipline.{BimonadTests, CartesianTests, MonadTests, SerializableTests}
@@ -92,7 +97,7 @@ class EvalTests extends CatsSuite {
}
{
- implicit val iso = CartesianTests.Isomorphisms.invariant[Eval]
+ implicit val iso: CartesianTests.Isomorphisms[Eval] = CartesianTests.Isomorphisms.invariant[Eval]
checkAll("Eval[Int]", BimonadTests[Eval].bimonad[Int, Int, Int])
checkAll("Eval[Int]", MonadTests[Eval].monad[Int, Int, Int])
}
@@ -102,27 +107,27 @@ class EvalTests extends CatsSuite {
checkAll("Eval[Int]", GroupLaws[Eval[Int]].group)
{
- implicit val A = ListWrapper.monoid[Int]
+ implicit val A: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int]
checkAll("Eval[ListWrapper[Int]]", GroupLaws[Eval[ListWrapper[Int]]].monoid)
}
{
- implicit val A = ListWrapper.semigroup[Int]
+ implicit val A: Semigroup[ListWrapper[Int]] = ListWrapper.semigroup[Int]
checkAll("Eval[ListWrapper[Int]]", GroupLaws[Eval[ListWrapper[Int]]].semigroup)
}
{
- implicit val A = ListWrapper.order[Int]
+ implicit val A: Order[ListWrapper[Int]] = ListWrapper.order[Int]
checkAll("Eval[ListWrapper[Int]]", OrderLaws[Eval[ListWrapper[Int]]].order)
}
{
- implicit val A = ListWrapper.partialOrder[Int]
+ implicit val A: PartialOrder[ListWrapper[Int]] = ListWrapper.partialOrder[Int]
checkAll("Eval[ListWrapper[Int]]", OrderLaws[Eval[ListWrapper[Int]]].partialOrder)
}
{
- implicit val A = ListWrapper.eqv[Int]
+ implicit val A: Eq[ListWrapper[Int]] = ListWrapper.eqv[Int]
checkAll("Eval[ListWrapper[Int]]", OrderLaws[Eval[ListWrapper[Int]]].eqv)
}
diff --git a/tests/src/test/scala/cats/tests/IdTests.scala b/tests/src/test/scala/cats/tests/IdTests.scala
index 0465dd34..a7b12751 100644
--- a/tests/src/test/scala/cats/tests/IdTests.scala
+++ b/tests/src/test/scala/cats/tests/IdTests.scala
@@ -1,10 +1,11 @@
package cats
package tests
+import cats.Id
import cats.laws.discipline._
class IdTests extends CatsSuite {
- implicit val iso = CartesianTests.Isomorphisms.invariant[Id]
+ implicit val iso: CartesianTests.Isomorphisms[Id] = CartesianTests.Isomorphisms.invariant[Id]
checkAll("Id[Int]", BimonadTests[Id].bimonad[Int, Int, Int])
checkAll("Bimonad[Id]", SerializableTests.serializable(Bimonad[Id]))
diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala
index c913a8f8..c59a86e5 100644
--- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala
+++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala
@@ -1,6 +1,8 @@
package cats
package tests
+import cats.PartialOrder
+import cats.Order
import cats.kernel.laws.{GroupLaws, OrderLaws}
import cats.data.NonEmptyList
@@ -36,7 +38,7 @@ class NonEmptyListTests extends CatsSuite {
checkAll("Eq[NonEmptyList[ListWrapper[Int]]]", SerializableTests.serializable(Eq[NonEmptyList[ListWrapper[Int]]]))
{
- implicit val A = ListWrapper.partialOrder[Int]
+ implicit val A: PartialOrder[ListWrapper[Int]] = ListWrapper.partialOrder[Int]
checkAll("NonEmptyList[ListWrapper[Int]]", OrderLaws[NonEmptyList[ListWrapper[Int]]].partialOrder)
checkAll("PartialOrder[NonEmptyList[ListWrapper[Int]]]", SerializableTests.serializable(PartialOrder[NonEmptyList[ListWrapper[Int]]]))
@@ -44,7 +46,7 @@ class NonEmptyListTests extends CatsSuite {
}
{
- implicit val A = ListWrapper.order[Int]
+ implicit val A: Order[ListWrapper[Int]] = ListWrapper.order[Int]
checkAll("NonEmptyList[ListWrapper[Int]]", OrderLaws[NonEmptyList[ListWrapper[Int]]].order)
checkAll("Order[NonEmptyList[ListWrapper[Int]]]", SerializableTests.serializable(Order[NonEmptyList[ListWrapper[Int]]])) |
traverser.traverse(unit.body) | ||
|
||
def toMetaType(tp: g.Tree) = | ||
config.dialect(tp.toString).parse[m.Type].get |
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.
This is causing the errors.
@@ -13,6 +15,14 @@ case object ExplicitImplicit extends Rewrite { | |||
case m.Term.ApplyType(m.Term.Name("implicitly"), _) => true | |||
case _ => false | |||
} | |||
@scala.annotation.tailrec | |||
final def parents(tree: Tree, |
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.
remove
@@ -8,14 +8,37 @@ class A { | |||
implicit val x: List[Int] = List(1) | |||
} | |||
<<< map | |||
import scala.concurrent.Future |
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.
remove
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.
Removed.
token.input.eq(from.input) && | ||
token.end <= to.end && | ||
token.start >= from.start | ||
sealed abstract class Patch { |
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.
Are these changes necessary now? If not remove
@@ -44,14 +67,124 @@ class A { | |||
class A { | |||
class B { class C } | |||
implicit val x = new B | |||
implicit val y = new x.C | |||
implicit val y: x.C = new x.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.
This is the input, remove?
Adding and organizing imports is delegated to a future rewrite. `shortenType` will only perform a type shortening at a given location. This greatly simplifies the implementation, which has to be still cleaned up for readability purposes.
Given the ambiguity of term selects in Scala meta, we have to try luck with every name and look it up with both type and term names. The previous traversal assumed that once we found a symbol, we took the right path, but this is not true: ```scala object A class A { type B } ``` Looking up `A.B` would fail if the algorithm start looking term names. `object A` would be found, but `type B` wouldn't since it's not defined in the companion object but the type. The new algorithm allow us to backtrack once we find these lookup failures (as we're browsing through the RootPackage members, we know that we will find at least one solution). Therefore, we perform a full DFS pre-order traversal in the binary tree that represents all the possibilities.
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 gone through the remaining set of comments. Current version has more documentation, a simplified implementation product of several iterations, and higher test covering (with added tests).
I have one question:
- Where should I define exceptions for scalafix? Do you have the infrastructure for error handling yet? Perhaps it's better to use
sys.error
until you get to it?
And some remarks on the current implementation:
- The errors in cats cannot be fixed with
-Yrangepos
. The type in the implicit val is inferred and cannot be fetched from the sources. Therefore, implicit val types whose types depend on the Kind projector cannot be fixed in a principled way. I suggest that we think about a low-level fix to still print the string representation of the types (remember that being an inferred type, itsoriginal
tree is also an emptyTypeTree
). - We're still not handling imports inside
DefTree
s. I'll have a look at that tomorrow. - Implicit vals that depend on type alias will get the dealiased type.
type Foo = String
implicit val foo = new Foo
will be rewritten as:
implicit val foo: String = new Foo
I'm still thinking of a solution to this (if there's one, it's not clear to me we can be consistent with type aliases without introducing misbehaviours--scalac only give us the expanded type).
@@ -8,14 +8,37 @@ class A { | |||
implicit val x: List[Int] = List(1) | |||
} | |||
<<< map | |||
import scala.concurrent.Future |
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.
Removed.
>>> | ||
import scala.collection.immutable.{Map => MyMap} | ||
class A { | ||
implicit val x: MyMap[Int, String] = |
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.
Have a look at the other two tests checking renames.
} | ||
>>> | ||
class A { | ||
class B { class C } | ||
implicit val x: B = new B | ||
implicit val y: x.C = new x.C | ||
} | ||
<<< NOWRAP higher kinded cats | ||
package cats { |
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.
This is just a reproduction of an intricate scenario. Even though it has the names cats, I would leave it here. If you disagree, please point to concrete file you'd like to have it.
} | ||
} | ||
<<< higher kinded | ||
import scala.concurrent.Future |
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.
No, this line is needed. If you have a look at the shortened Future
in the implicit val type result, you'll see that the import oracle correctly shortens the type because it finds it in the context.
trait C { | ||
class B | ||
} | ||
implicit val x: D.c.B = new D.c.B |
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.
💃
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 didn't have time to look at the algorithm in details, but went through the rest of the code.
>>> | ||
import scala.collection.immutable.{Map => MyMap} | ||
class A { | ||
implicit val x: MyMap[Int, String] = |
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.
Indeed :)
object slick { | ||
case class Supplier(id: Int, name: String) | ||
implicit val supplierGetter = (arg: (Int, String)) => Supplier(arg._1, arg._2) | ||
} | ||
>>> | ||
object slick { | ||
case class Supplier(id: Int, name: String) | ||
implicit val supplierGetter: ((Int, String)) => slick.Supplier = (arg: (Int, String)) => Supplier(arg._1, arg._2) | ||
implicit val supplierGetter: ((Int, String)) => Supplier = (arg: (Int, String)) => Supplier(arg._1, arg._2) | ||
} | ||
<<< NOWRAP package import | ||
package scala.concurrent { |
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.
Some tricky examples that come to mind:
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.
package a {
class C
}
package b {
class C
}
package c {
object M {
import a._
import b._
implicit val c = new b.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.
trait T {
class C
}
package object a extends T {
}
object M {
import a._
implicit val c = new 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.
package a {
class C
}
package b {
class C
}
package c {
object M {
import a._
locally {
import b._
implicit val c = new 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.
Thanks for the code snippets, will add to tests. Forgot about our beloved locally
😄.
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.
Awesome tests! Very nice 👍
/** The compiler sets different symbols for `PackageDef`s | ||
* and term names pointing to that package. Get the | ||
* underlying symbol of `moduleClass` for them to be equal. */ | ||
@inline |
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 @inline
here and below?
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.
They can be removed.
val mixedScope = sc.cloneScope | ||
sc2.foreach(s => mixedScope.enterIfNew(s)) | ||
mixedScope | ||
} |
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'd probably use a dedicated data structure to manipulate scopes. Things like enterIfNew
may have hidden performance costs.
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.
There is indeed a performance cost attached to it. I'll study a better data structure to manipulate scopes in the future, right now we're focusing more on correctness. I've also realised that I should not be using enterIfNew
in some concrete parts of the code. Fixing tomorrow.
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.
Anything trivial, e.g. case class Scope(syms: List[Symbol])
, would do. The implementation of Scope
is quite intricate, so it'll be just an unnecessary complication.
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.
We should optimize for correctness and simplicity for now. We can worry about performance later.
// Handle packages to import from user-defined compiler flags | ||
val packagesToImportFrom = { | ||
if (g.settings.noimports) Nil | ||
else if (unit.isJava) RootImports.javaImports |
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.
Can unit.isJava
ever be true?
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 got this logic from nsc Context
just in case it is sometimes true. I would expect it to be false almost always, but better safe than sorry?
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.
scalafix-nsc guards against unit.isJava
, so this will always be false. I don't mind keeping it though for consistency with the compiler impl.
traverser.traverse(unit.body) | ||
|
||
def toMetaType(tp: g.Tree) = { | ||
config.dialect(tp.toString).parse[m.Type].get |
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.
There's no guarantee that parse
is going to succeed here. Do you plan to make this more robust?
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.
This is actually the cause of a problem with cats. We have no general solution so far, tp
may not have a tree representation (inferred) and it's not fetchable from sources. Any suggestion @xeno-by?
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.
Wait for a Type converter that is coming to 2.x along with nirvana and friends.
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'm not sure if the type converter will solve the problem either. Kind-projector synthesizes unique type names for each appearance of Type.Name(?)
. The synthesized types look like this
cats.laws.discipline.CartesianTests.Isomorphisms[[β$0$]cats.free.Free[Option,β$0$]]
I'm not 100% sure, but I think those should be pretty-printed as ?
. Maybe we'll need to special case kind-projector since it's a very popular compiler plugin.
The best solution for now is to gracefully handle the parse error and not shorten that type signature. The type signature of shortenType
indicates that it's always a safe operation, and indeed, you can always choose not to shorten the type.
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.
We can specialcase kind-projector, yeah.
The [T]Foo[T]
type doesn't have surface syntax in Scala, so it'll have to be emulated - either with kind-projector or with the { type λ[T] = Foo[T] }#λ
trick. This can be done automatically by the type converter.
new SemanticApi { | ||
override def shortenType(tpe: m.Type, owner: m.Tree): m.Type = { |
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.
Should owner
be renamed to atLocation
to be consistent with the API?
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 owner is only used for its position, why not pass a position here in the first place?
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 and yes. We decided to pass owner
just in case we could need it afterwards, but it's unnecessary IIRC.
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.
This signature is tricky because the current impl only works for defn.{val,var,def}, so the owner
should really be a Defn
, if anything. With a position, it wouldn't be clear which position you should pass in e.g. the defn's name or the defn itself.
IIRC, the owner
needs to map to some tree with an ownerchain.
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.
Can the disambiguation between defn and defn's name be done automatically?
val bottomUpScope = interestingOwners.iterator | ||
.map(_.info.members.filter(keepRelevantMembers)) | ||
.reduce(mixScopes _) | ||
val globalScope = mixScopes(bottomUpScope, userImportsScope) |
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.
Does this take import priority into account?
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 doesn't yet, thanks for pointing out.
inScope: g.Scope, | ||
enclosingTerm: g.Symbol): m.Ref = { | ||
|
||
val refNoThis = stripThis(toShorten) |
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.
Will this strip things like C.this.foo
or only some.pkg.this.foo
?
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.
Both, actually. They will be converted to C.foo
and some.pkg.foo
.
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.
What if C
is an enclosing class? Then C.foo
doesn't make sense.
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 I understand correctly, Foo.this.bar
can be stripped to bar
in all cases except the following?
trait Foo {
def bar
trait Kas {
def bar
Foo.this.bar
}
}
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 think so, yes.
BTW. Maybe move the new test cases to a |
I'll rebase this on top of the semantic API soon. |
With the introduction of scalahost and the scala.meta semantic API, I believe context aware scope would be a great feature to have in scala.meta instead of scalafix. Also, scalafix-nsc is being phased out in favor of the offline Mirror so I think that is the best way forward. |
@xeno-by I'm planning to follow up on this next weekend. Will you accept a PR fully implementing a symbol-based type shortener with the new semantic API? |
I think a good start would be to open a ticket in scalameta listing the details of this feature, what methods/signatures it would have etc. |
This PR introduces a symbol-based type shortener to replace the string-based one. The benefits of this approach are:
I think that there are some small details to flesh out before we nail down the desired behaviour. I'm commenting on some lines to make it clearer. I ran this on cats and I received pretty diffs, but for some reasons I was not successful at running it in scalajs.