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
Generalize with-chain wrapping for defs and type definitions #1132
Conversation
@@ -1021,7 +1021,7 @@ class Router(formatOps: FormatOps) { | |||
Split(Space, 0), | |||
Split(Newline, 1).withPolicy(policy) | |||
) | |||
case t @ SelfAnnotation(top) => | |||
case t @ WithChain(top) => |
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 don't love this name but I wanted to change it to something more general than SelfAnnotation
. Any suggestions?
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 name is good 👍
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 this contribution! New name looks good, we should make this work for all result types.
case (top: Type.With) `:parent:` (_: Term.Param) `:parent:` (_: Template) => | ||
Some(top) | ||
// type definitions | ||
case (top: Type.With) `:parent:` (_: Defn.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.
I suspect this pattern can be extended to any Defn or Decl, that will cover val/var/def regardless if they're abstract or not.
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 when we don't want this to match for Type.With, there are other tree nodes like Term.ApplyType where this is equally relevant.
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.
Yeah I agree for the most part.
If I open it up to match all Type.With
s then one test fails (with good reason IMO):
// Before:
{ def getAnnotationOwner(
annotationOwnerLookUp: ScLiteral => Option[
PsiAnnotationOwner with PsiElement]): Option[PsiAnnotationOwner] = 1 }
// After -- breaks in generic type:
{
def getAnnotationOwner(
annotationOwnerLookUp: ScLiteral => Option[PsiAnnotationOwner
with PsiElement]): Option[PsiAnnotationOwner] = 1
}
// Expected:
{
def getAnnotationOwner(
annotationOwnerLookUp: ScLiteral => Option[
PsiAnnotationOwner with PsiElement]): Option[PsiAnnotationOwner] = 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.
But even in that scenario, if the type were long enough we probably want a newline instead of violating the column limit. Maybe we can make this split a low priority or high cost etc?
This looks like a great change! I'm adding some examples here based on @olafurpg's request in #1133. In Cats when we have code like: implicit val catsDataInstancesForNonEmptyList: SemigroupK[NonEmptyList]
with Reducible[NonEmptyList] with Bimonad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] = ...
implicit val catsDataInstancesForNonEmptyVector: SemigroupK[NonEmptyVector]
with Reducible[NonEmptyVector] with Bimonad[NonEmptyVector]
with NonEmptyTraverse[NonEmptyVector] = ...
implicit val catsStdInstancesForOption
: Traverse[Option] with MonadError[Option, Unit] with Alternative[Option]
with CommutativeMonad[Option] with CoflatMap[Option] = ... After formatting it ends up looking like: implicit val catsDataInstancesForNonEmptyList: SemigroupK[NonEmptyList] with Reducible[
NonEmptyList] with Bimonad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] = ...
implicit val catsDataInstancesForNonEmptyVector: SemigroupK[NonEmptyVector] with Reducible[
NonEmptyVector] with Bimonad[NonEmptyVector] with NonEmptyTraverse[NonEmptyVector] = ...
implicit val catsStdInstancesForOption
: Traverse[Option] with MonadError[Option, Unit] with Alternative[Option] with CommutativeMonad[
Option] with CoflatMap[Option] = ... It would be nice to see the style changes exhibited in this PR to extend to the return types of |
@ceedubs Thanks for the input! I'll add these as test cases. I ran the examples through with this change and it ended up with implicit val catsDataInstancesForNonEmptyList: SemigroupK[NonEmptyList]
with Reducible[NonEmptyList]
with Bimonad[NonEmptyList]
with NonEmptyTraverse[NonEmptyList] = ???
implicit val catsDataInstancesForNonEmptyVector: SemigroupK[NonEmptyVector]
with Reducible[NonEmptyVector]
with Bimonad[NonEmptyVector]
with NonEmptyTraverse[NonEmptyVector] = ???
implicit val catsStdInstancesForOption: Traverse[Option] with MonadError[Option, Unit]
with Alternative[Option]
with CommutativeMonad[Option]
with CoflatMap[Option] = ??? Does that seem about right? I'm not sure why the last one didn't drop Edit: Figured it out. I was running with |
@iantabolt nice! On the last one, maybe it has to do with inlining until you hit the column limit? I'm not super familiar with the particulars of behavior. And does it depend on |
with HasThrottles | ||
with HasThrowableNotifier | ||
<<< abstract def #1125 | ||
trait NeedsServices { |
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 probably shouldn't be in Type.stat
since there is no type
. Maybe all these tests should be in TypeWith.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.
TypeWith.stat
would be good, this is important enough to merit a separate suite.
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.
A couple more minor comments.
@@ -1021,7 +1021,7 @@ class Router(formatOps: FormatOps) { | |||
Split(Space, 0), | |||
Split(Newline, 1).withPolicy(policy) | |||
) | |||
case t @ SelfAnnotation(top) => | |||
case t @ WithChain(top) => |
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 name is good 👍
: Traverse[Option] with MonadError[Option, Unit] with Alternative[Option] | ||
with CommutativeMonad[Option] with CoflatMap[Option] = ??? | ||
>>> | ||
implicit val catsStdInstancesForOption: Traverse[Option] |
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 may be more ambitious that you were planning for (so "no" is an acceptable answer), but while were fixing with chains, could we add a test case for?
new A with B with C with D ...
I've seen surprising behavior in these scenarios and I suspect fixing it likewise is a matter of adding another case to WithChain
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 no problem (I think)
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 might need some help with this. I don't think this falls into the Type.With
case and instead hits the Template
case.
My thought is to treat this like the KwExtends
case above and do
case tok @ FormatToken(_, right @ KwWith(), _) =>
rightOwner match {
+ // Anonymous template (new A with B { ... })
+ case template: Template if template.parent.exists(_.is[Term.New]) =>
+ val lastToken = template.tokens.last
+ binPackParentConstructorSplits(
+ Set(template),
+ lastToken,
+ 2
+ )
case template: Template =>
val hasSelfAnnotation = template.self.tokens.nonEmpty
This almost works but it indents after every with
new A
with B
with C
with D
Any ideas?
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 pushed the non-working change mentioned above. I'm assuming the fix is something analogous to the isFirstWith
logic but I'm not sure how to achieve it with the template
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.
Hmm, the changes seem unrelated enough to merit a separate PR. Your change already improves on the state-of-the-art so I propose you drop the last commit, we merge and move this case to another ticket.
I'll need to locally play around with this case to see how would be best to solve it.
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.
SGTM 👍. I rolled it back
with HasThrottles | ||
with HasThrowableNotifier | ||
<<< abstract def #1125 | ||
trait NeedsServices { |
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.
TypeWith.stat
would be good, this is important enough to merit a separate suite.
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, you can see what type nodes are for a given syntax with the ammonite repl like this
@ import $ivy.`org.scalameta:testkit_2.12:1.7.0`, scala.meta._, testkit._
import $ivy.$ , scala.meta._, testkit._
@ q"new A with B with C"
res1: Term.New = Term.New(
Template(
List(),
List(Ctor.Ref.Name("A"), Ctor.Ref.Name("B"), Ctor.Ref.Name("C")),
Term.Param(List(), _, None, None),
None
)
)
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.
LGTM 👍 Thanks for this contribution @iantabolt !
I'll be attending a conference for the next week, but I hope to have v1.5.0 out this April. Until then, you can already use the pre-releases to Bintray as documented on the website but note those don't include the IntelliJ plugin. |
What configuration setting do you use to wrap with-chains? @olafurpg @iantabolt |
This fixes #1125 to handle wrapping of "with-chains".
For example, a long chain of
can be wrapped as
This was already implemented for self-types (
trait Xyz { self: Foo with Bar with Baz =>
). This PR expands the behavior to handle type definitions (type MyType = Foo with Bar with Baz
) and function return types (def xyz: Foo with Bar with Baz
).