Skip to content
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

Weird formatting of Scala3-style intersection types #4705

Open
satorg opened this issue Dec 30, 2024 · 4 comments · May be fixed by #4710
Open

Weird formatting of Scala3-style intersection types #4705

satorg opened this issue Dec 30, 2024 · 4 comments · May be fixed by #4710

Comments

@satorg
Copy link

satorg commented Dec 30, 2024

The issue confirmed to exist in all Scalafmt versions at least since v3.8.2 through v3.8.4-RC4.
The issue first discovered in typelevel/cats#4673.

Configuration

version = 3.8.4-RC4
maxColumn = 120
runner.dialect = scala213source3

(or version = 3.8.3, or version = 3.8.2, etc.)

Command-line parameters

> scalafmt -c .scalafmt.conf Foo.scala

Steps

Given code like this:

object Foo {
  def oldStyle: Traverse[Option] with MonadError[Option, Unit] with Alternative[Option] with CommutativeMonad[Option] with CoflatMap[Option] with Align[Option] =
    ???

  def newStyle: Traverse[Option] & MonadError[Option, Unit] & Alternative[Option] & CommutativeMonad[Option] & CoflatMap[Option] & Align[Option] =
    ???
}

Problem

Scalafmt formats code like this:

object Foo {
  def oldStyle: Traverse[Option]
    with MonadError[Option, Unit]
    with Alternative[Option]
    with CommutativeMonad[Option]
    with CoflatMap[Option]
    with Align[Option] =
    ???

  def newStyle: Traverse[Option] & MonadError[Option, Unit] & Alternative[Option] & CommutativeMonad[
    Option
  ] & CoflatMap[Option] & Align[Option] =
    ???
}

Note the newStyle statement gets formatted in a very awkward and quite difficult-to-read way.
The oldStyle statement looks fine though.

Expectation

I would like the newStyle part of the formatted output to look like this:

  def newStyle: Traverse[Option] &
    MonadError[Option, Unit] &
    Alternative[Option] &
    CommutativeMonad[Option] &
    CoflatMap[Option] &
    Align[Option] =
    ???

or something like this would work even better:

  def newStyle:
    Traverse[Option] &
    MonadError[Option, Unit] &
    Alternative[Option] &
    CommutativeMonad[Option] &
    CoflatMap[Option] &
    Align[Option] =
    ???

Workaround

Reformatting the above snippet manually seems helping keep the desired format in place.

@kitbellew
Copy link
Collaborator

@satorg just to clarify: there's no change in formatting of with between different formatter versions, nor of &? are you simply observing that with and & are formatted differently?

Type.With and Type.ApplyInfix are two different scalameta trees, and & is not the only type infix operator. we can't change how & is formatted since it might cause others to complain of "lots of uncalled-for changes".

all i can suggest is use newlines.afterInfix = many, although it applies to both Term.ApplyInfix and Type.ApplyInfix.

@satorg
Copy link
Author

satorg commented Dec 30, 2024

@kitbellew the issue emerged when Cats started switching from with to Scala3-style & syntax. Types with with looked formatted pretty nicely. However, with the & syntax those types become quite difficult to read.

Perhaps, there are some cases when the current way of formatting types with & makes sense, but honestly I'm struggling to come up with an example where breaking a line after the left bracket [ could be justified:

... PrecedingTypes & CommutativeMonad[
    Option
  ] & FollowingTypes ...

i.e. what could be benefits in splitting the type definition itself if it seems possible to split the line a little in advance and keep the type in one line? E.g.:

... PrecedingTypes &
  CommutativeMonad[Option] & FollowingTypes ...

I may not be aware of all the plumbings in Scalafmt/Scalameta, but the default behavior seems pretty weird to me.

@kitbellew
Copy link
Collaborator

i.e. what could be benefits in splitting the type definition itself if it seems possible to split the line a little in advance and keep the type in one line? E.g.:

i don't disagree but & is an infix, and infixes are treated in a very special way.

keep in mind that by default newlines.afterInfix is set to keep, meaning that if you want a newline after &, you should insert it yourself, and it will be preserved (and since you don't have a newline, that is also preserved, hence the formatter has no other choice but to break within brackets).

or use some or many, and it will be done automatically.

@satorg
Copy link
Author

satorg commented Dec 31, 2024

@kitbellew , understood, thank you for the clarification.
Feel free to close this issue if you find it unfeasible to fix.
I think I'll be fine with that since there seems to be a way to work it around.
Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants