softwaremill / quicklens

Modify deeply nested case class fields
https://softwaremill.com/open-source/
Apache License 2.0
825 stars 53 forks source link

Support for IntelliJ IDEA? #33

Open saamalik opened 7 years ago

saamalik commented 7 years ago

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 7 years ago

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?

aneeshde commented 7 years ago

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 7 years ago

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

DaniRey commented 6 years ago

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}

ninadvps commented 5 years ago

was this ever implemented in intellij?

soujiro32167 commented 3 years ago

Came here with that question too...

DaniRey commented 3 years ago

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.

soujiro32167 commented 3 years ago

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"
}