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
Implement #863: vertical-multiline for type params #864
Conversation
Implements enhancement scalameta#863 by extending verticalMultilineAtDefinitionSite to handle type parameters as well as data parameters. The type parameter group behaves slightly different than the data parameters. While data parameters are an all-or-nothing (either it all fits in one line or it goes vertical), type parameters are allowed to go in one line if they can fit regardless of whether the data parameters fit too.
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 to run out right now so I will have to review later.
case _: Decl.Def | _: Defn.Def | _: Defn.Macro | _: Defn.Class | | ||
_: Defn.Trait | _: Ctor.Secondary => | ||
true | ||
case x: Ctor.Primary if x.parent.exists(_.isInstanceOf[Defn.Class]) => |
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 seems like a reasonable workaround for the owners
issue.
* | ||
* For classes this includes primary and secondary Ctors. | ||
*/ | ||
def isClassOrDef(tree: Tree): Boolean = tree match { |
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 created this because I wasn't aware that isDefnSite
includes things like type applications.
I'm not sure whether that's correct (type applications are definitions
?) but I opted for a more explicit function in order to not break old functionality (and since I might not have complete understanding on what a defn
actually means).
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.
Type applications should probably not be included in isDefnSite, I opened #867 to remind myself to investigate that and fix isDefnSite
. For now, I think it's OK for you to create your own utility for vertical multiline.
isClassOrDefn
is not so accurate here since this also includes Defn.Trait
. A more appropriate name could be isDefnSiteWithParams
, since it excludes Defn.Val
which is a definition but has no parameters. It's up to you what name you prefer.
val dataParamsOwner = owners(lastParen) | ||
@inline def ownerCheck(rp: Token): Boolean = { | ||
val owner = owners(rp) | ||
owner == leftOwner || owner == dataParamsOwner |
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 way this works is:
- When dealing with defs, then the
owners(rp)
will always equals to the leftOwner, for both type params and data params - When dealing with classes though, this isn't the case. For classes
owners(rp)
will only equalleftOwners
for type params. This means the second test will kick in for data params (who share the same owner as thelastParen
).
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 could compare against for both the owner(rp)
and the owner(rp).parent
. That may work too.
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.
Well that didn't work (messes with local things like def f(parser: Parser[R])
). It might work if I put more restrictions on it but I feel my current restriction is pretty solid (no relative checks. We only have one lastParen
).
Keeping as is.
This style currently breaks if we have a modified Ctor. case class SomeClass[A] private (name: String) |
Also fix a bug in `findFirst`
val isClassTrait = | ||
leftOwner.is[meta.Ctor.Primary] || | ||
leftOwner.is[meta.Defn.Class] || | ||
leftOwner.is[meta.Defn.Trait] |
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.
Since I have the holistic information about the defn (ie: I can ask things like "does this have any data params? Any type params?") I feel like this can all be cleaner (at the cost of, maybe, some code duplication).
Better defined the concepts of `shouldNotDangle` and `mixedParam`. Removed unnecessary checks when catching traits.
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.
Wow! I'm really happy to see this PR, it demonstrates a good understanding of Policies and Splits.
A few comments, otherwise great job 👍
def unapply(tok: Token): Boolean = is(tok) | ||
} | ||
|
||
object LeftParenOrBracket { |
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.
def unapply(tok: Token): Boolean = is(tok) | ||
} | ||
|
||
object RightParenOrBracket { |
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.
Same, TokenClasses.
@@ -108,6 +108,23 @@ class FormatOps(val tree: Tree, val initStyle: ScalafmtConfig) { | |||
(packages.result(), imports.result(), arguments.toMap, optional) | |||
} | |||
|
|||
object CtorModifier { |
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 we move this to https://github.com/scalameta/scalafmt/blob/master/core/src/main/scala/org/scalafmt/util/TreeClasses.scala ?
The @classifier
macro annotation creates a Classifiable
type-class instance, which makes it possible to use the Token/Tree.is[T]
syntax. You can probably avoid the macro annotation by writing some boilerplate yourself, but I haven't looked at the expanded code. It's up to yourself.
The (undocumented) general rule where I keep utilities is
- FormatOps: anything that requires access to the fields in FormatOps, which is custom for each source file.
- Token/TreeOps: anything that is not require fields in FormatOps, agnostic to the source file being formatted.
- Token/TreeClasses: classifiers like this.
I should probably add this to the contributing docs.
@@ -165,7 +182,7 @@ class FormatOps(val tree: Tree, val initStyle: ScalafmtConfig) { | |||
@tailrec | |||
final def findFirst(start: FormatToken, end: Token)( | |||
f: FormatToken => Boolean): Option[FormatToken] = { | |||
if (start.left.start < end.start) 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.
Whoah, good catch! Was this method used somewhere before? Logic bugs like this tend to pop up in dead code.
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 wasn't used anywhere.
@@ -16,6 +16,18 @@ case class Policy( | |||
noDequeue: Boolean = false, | |||
isSingleLine: Boolean = false)(implicit val line: sourcecode.Line) { | |||
|
|||
def merge(other: PartialFunction[Decision, Decision], newExpire: Int): Policy = |
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.
Nice 👍 I've written this too many times.
] protected ( | ||
value: Long, | ||
name: String | ||
)(implicit ex: Executor) |
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.
So I understand correctly, will the last closing parens never dangle? If so, why?
There seems to be missing a test case where the last param list of a class has >1 parameters.
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 should not dangle because it creates an ugly result when combined with extends
and with
(unless we do some manipulation with those, but then it would need to ignore other settings and make the overall style more complex). We do dangle for defs though.
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.
Aha, makes sense. I just remembered now that IntelliJ style does the same, exclude dangling parentheses in defn site (although both def
and class
)
} | ||
|
||
/** | ||
* Whether [[tree]] is a defintion site |
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 try structure docstrings like this:
"Returns X if Y(, else Z)?"
* | ||
* For classes this includes primary and secondary Ctors. | ||
*/ | ||
def isClassOrDef(tree: Tree): Boolean = tree match { |
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.
Type applications should probably not be included in isDefnSite, I opened #867 to remind myself to investigate that and fix isDefnSite
. For now, I think it's OK for you to create your own utility for vertical multiline.
isClassOrDefn
is not so accurate here since this also includes Defn.Trait
. A more appropriate name could be isDefnSiteWithParams
, since it excludes Defn.Val
which is a definition but has no parameters. It's up to you what name you prefer.
@@ -229,6 +229,25 @@ object TreeOps { | |||
case _ => None | |||
} | |||
|
|||
/** | |||
* Whether the [[tree]] is a class, trait or def |
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.
See comment below.
|
||
val firstIndent = | ||
if (r.is[RightParen]) // An empty param group | ||
indentSep | ||
else | ||
indentParam | ||
|
||
val singleLineSplit = |
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:
val singleLineExpire =
if (isBracket) close // comment
else lastParen // comment
val singleLineSplit = Split(NoSplit, 0)...
``
This should help with the ever-growing compilation times.
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 👍 Merging once CI is green.
I'm working on the release infrastructure now. Hoping to get a setup so that a release is published to bintray on merge into master and stable releases are published to sonatype on tag push.
…a#864) * Implement scalameta#863: vertical-multiline for type params Implements enhancement scalameta#863 by extending verticalMultilineAtDefinitionSite to handle type parameters as well as data parameters. The type parameter group behaves slightly different than the data parameters. While data parameters are an all-or-nothing (either it all fits in one line or it goes vertical), type parameters are allowed to go in one line if they can fit regardless of whether the data parameters fit too. * Add trait tests * Fix case: Class with just type params Also fix a bug in `findFirst` * Fix trait inconsistency * Simplify verticalMultiline Better defined the concepts of `shouldNotDangle` and `mixedParam`. Removed unnecessary checks when catching traits. * Fixed some docs and move classifiers to their respective places * Extract verticalMultiline impl from Router This should help with the ever-growing compilation times. * Ran Scalafmt * More doc and style changes
Implements enhancement #863 by extending verticalMultilineAtDefinitionSite
to handle type parameters as well as data parameters.
The type parameter group behaves slightly different than the data
parameters. While data parameters are an all-or-nothing (either it all
fits in one line or it goes vertical), type parameters are allowed to go
in one line if they can fit regardless of whether the data parameters
fit too.
This PR does NOT fix #862. I plan on working on that one next but I found implementing this "fix" in vertical-multiline to be simpler. With the knowledge I gained here I can hopefully fix #862 now.