softwaremill/quicklens

Support for IntelliJ IDEA?

saamalik opened this issue ยท 8 comments

Hi,
This truly is a fantastic library which enables some really removes a bunch of boilerplate code! I was wondering if there was any plan (now or later) to support IntellIJ IDEA support so we don't see those squiggly error lines (cannot resolve symbol)?

IntelliJ added support for plugins using Macros to write their own plugins: https://blog.jetbrains.com/scala/2015/10/14/intellij-api-to-build-scala-macros-support/

Thanks!

adamw commented

Quicklens macros are "blackbox", which means they return precise types, so this should be already IDE-friendly, with autocomplete etc., as long as there's the import import com.softwaremill.quicklens._.

Could you maybe give an example of what is incorrectly highlighted by IntelliJ?

Using your own examples (street, address, person),

import com.softwaremill.quicklens._

case class Street(name: String)
case class Address(street: Option[Street])
case class Person(addresses: List[Address])

val person = Person(List(
  Address(Some(Street("1 Functional Rd."))),
  Address(Some(Street("2 Imperative Dr.")))
))
person.modify(_.addresses.at(1).street.each.name).using(_.toUpperCase)

The last statement yields errors at .at ("Cannot resolve symbol at") and toUpperCase("Cannot resolve symbol toUpperCase")

It executes perfectly and without issue, but IntelliJ does NOT like the syntax.

The issue has also been added to IntelliJ's bug tracker.

adamw commented

Ah true, thanks! Though the issue is with IntelliJ's type checker, doesn't really involve macros.

The problem is not with the macro but rather with the implicit resolution. When we use

implicit def listQuickLensFunctor[A]: QuicklensFunctor[List, A] =
    new QuicklensFunctor[List, A] {
      override def map(fa: List[A])(f: A => A): List[A] = fa.map(f)
}

instead of

implicit def traversableQuicklensFunctor[F[_], A](implicit cbf: CanBuildFrom[F[A], A, F[A]], ev: F[A] => TraversableLike[A, F[A]]) =
    new QuicklensFunctor[F, A] {
      override def map(fa: F[A])(f: A => A) = fa.map(f)
}

then Intellij is happy to. I will file an according bug with JetBrains.

If a quicklens user adds my function to his code, the user has to be more specific with the imports. Because if the compiler sees two implicits which could be used, it cannot choose which one to use and fails.

For a basic example the following imports should be fine.

import com.softwaremill.quicklens.{ModifyPimp, QuicklensEach, QuicklensFunctor}

was this ever implemented in intellij?

Came here with that question too...

Here https://youtrack.jetbrains.com/issue/SCL-14526 you can find the bug which I opened. It wasn't resolved yet.

If you can upgrade to Scala 2.13 the problem will most probably go away. The reason is, that the collection implementation is more straight forward (i.e. CanBuildFrom isn't used anymore). At least we could remove our workaround in our code base after upgrading to 2.13.

Thank you, @DaniRey. I figured out what the problem was: these was another .each extension method coming from sttp.tapir.internal:

trait ModifyMacroFunctorSupport {
  implicit class ModifyEach[F[_], T](t: F[T])(implicit f: ModifyFunctor[F, T]) {
    @compileTimeOnly(canOnlyBeUsedInsideModify("each"))
    def each: T = sys.error("")
  }

  trait ModifyFunctor[F[_], A] {
    @compileTimeOnly(canOnlyBeUsedInsideModify("each"))
    def each(fa: F[A])(f: A => A): F[A] = sys.error("")
  }

  implicit def optionModifyFunctor[A]: ModifyFunctor[Option, A] =
    new ModifyFunctor[Option, A] {}

  private[tapir] def canOnlyBeUsedInsideModify(method: String) =
    s"$method can only be used inside ignore"
}