Add IDEA support for the longevity annotations
sullivan- opened this issue · 5 comments
IDEA can't expand the longevity macro annotations, and consequently shows error messages for primaryKey
and props
in an example like this:
@persistent[DomainModel]
case class User(username: Username, email: Email, fullName: FullName)
object User {
implicit val usernameKey = primaryKey(props.username)
}
For references to User in other source files, while implicit resolution of the generated PEv
s and the keys seems to work fine, we still get compiler errors on things like User.props
and User.queryDsl
(inherited from PType
).
It looks like the right way to handle this is to use the "IntelliJ API to build scala macros support": https://blog.jetbrains.com/scala/2015/10/14/intellij-api-to-build-scala-macros-support/
Another potential alternative is to make modifications/enhancements to longevity API to allow users to specify things a little differently so that IDEA can follow along. I'm thinking along the lines of:
- Allow users to say something like this:
@persistent[DomainModel]
case class User(username: Username, email: Email, fullName: FullName)
object User extends PType[Domain, User] {
implicit val usernameKey = primaryKey(props.username)
}
So the @persistent
annotation already extends the User
companion object with PType
, but we could allow this redundancy easily enough.
- Add a
PType
method something like:
def dprop[A](propName: String): Prop[User, A]
This method finds the right property in the User.props
object generated by the @persistent
macro. In this case, we could say dprop[Username]("username")
instead of props.username
. Of course we would lose a lot of type safety guarantees here: this method could throw runtime exception on misnamed or mistyped properties.
It looks like these two changes would allow IDEA users to have workarounds for the build errors. The non-workaround, IDEA API for scala macros approach would be much more desirable, but probably a good deal more work.
It's an open question here whether IDEA's SyntheticMembersInjector
will be flexible enough to do what we need. Assuming it is, I'm going to say that the IDEA API approach is best here. I don't really have the time to look into it now gratis - looks like a good bit of work.
If there is interest in the workaround solution described above, I may have time to address these. I don't think it would be much work. Of course, a PR would be most welcome, so let me know if you are interested in giving it a try, and I can help you find the places in the code where you will want to look!
Rough game plan:
- Allow for combination of
@persistent
andextends PType
As in this:
@persistent[DomainModel]
case class User(username: Username, email: Email, fullName: FullName)
object User extends PType[Domain, User] {
implicit val usernameKey = primaryKey(props.username)
}
Currently this will fail because the macro ends up extending User
with PType
twice. Rework AbstractPersistedImpl.augmentedCompanion
to allow for this, by removing the duplicated type:
https://github.com/longevityframework/longevity/blob/master/longevity/src/main/scala/longevity/model/annotations/AbstractPersistentImpl.scala#L28
- Unit tests for this change in
longevity.unit.model.annotations
. Should be pretty easy to add based on existing tests. - Consider updating scaladocs for
@persistent
,@polyPersistent
,@derivedPersistent
to reflect this change- Basically, just take a look and make sure the scaladocs have not become inaccurate somehow
- Add non-typesafe prop method described above
def dprop[A](propName: String): Prop[User, A]
- Maybe we should think more carefully about how to name this method. I am prefixing this method. I have "d" here for "dynamic" (suggestive of non-typesafe).
- In
PType
you will find apropSet
, and eachProp
has apath
, so we should be able to create aprivate lazy val
map from path to prop from this. Thedprop
method above can use this map to do the lookup.
-
dprop
method should fail fast on type errors- As an initial pass, we can just ignore the type parameter
A
, and useasInstanceOf
to produce a property of the right type. Once this is working, we should probably check the types are right within thedprop
method. To do this, we will need to add an implicitTypeTag
parameter todprop
method. Inside our method, we can convert this to aTypeKey
, and compare it for equality againstProp.propTypeKey
.
- As an initial pass, we can just ignore the type parameter
- Exceptions for two failure conditions for method
dprop
- Use
NoSuchPropPathException
andPropTypeException
inlongevity.exceptions.model
. You can replace theTypeKey
parameters for these methods with String representations of the type names if you like. In general I am trying to remove instances ofTypeKey
from the longevity API. removing them from the constructor signatures of exception classes is not exactly high priority :-)
- Use
- Unit test for one happy case, two error cases for
drop
method inlongevity.unit.model.PTypeSpec
- ScalaDoc for
dprop
- Should mention that it is intended as temporary workaround for IDEA until we get around to implementing with scalameta. Include link to the longevity scalameta issue here.
- Should document the exceptions thrown.
- User manual updates
- Create a new chapter "addenda" at bottom of manual
- Put existing chapter on logging inside
- Add a section there on IDEA support that describes the two workarounds, talks about migrating to scalameta as the real solution, and links to the scalameta issue
@mardo has generously offered to take this on. Work to proceed on feat/idea-workaround
branch
Quick note to say that we've dropped the idea of putting in a PType.dprops
method. I'm going to close out this ticket because it's pretty messy, and I want readers to be able to get a clear picture of the status of this issue when looking at the ticket. I recreated the ticket cleanly here: