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

Maintain expanded attrs/lists/attr params/inherits #224

Merged
merged 11 commits into from
Aug 8, 2024

Conversation

infinisil
Copy link
Member

@infinisil infinisil commented Jul 18, 2024

This is the alternative suggested in #222 (comment).

This PR changes attribute sets, lists, attribute parameters and inherits that are already on multiple lines to never be contracted to a single line. This is desirable because if a user writes these over multiple lines, there's most likely an intention to add more attributes, which would become harder when formatted.

Examples that would've been contracted to a single line before:

options.foo = lib.mkOption {
  type = types.str;
}
buildInputs = [
  libfoo
];
{
  stdenv,
}:
inherit (lib)
  foo
  ;

This change conforms to the standard due to

- The formatter may take the input formatting into account in some cases in order to preserve multi-line syntax elements (which would otherwise have been contracted by the rules).

This PR is best review commit-by-commit. The first bunch of commits are just code refactorings to make the functional change in later commits as simple as possible.

This work is sponsored by Antithesis

Copy link

github-actions bot commented Jul 18, 2024

Nixpkgs diff

@infinisil
Copy link
Member Author

infinisil commented Jul 18, 2024

Note that the Nixpkgs diff will be misleading for the same reasons as explained in #201 (comment)

@infinisil infinisil changed the title Maintain expanded attrs Maintain expanded attrs/lists/attr parameters Jul 19, 2024
@infinisil infinisil changed the title Maintain expanded attrs/lists/attr parameters Maintain expanded attrs/lists/attr params Jul 19, 2024
@infinisil infinisil force-pushed the maintain-expanded-attrs branch 5 times, most recently from 5082aec to d2a63a2 Compare July 19, 2024 03:56
@infinisil infinisil marked this pull request as ready for review July 19, 2024 04:04
@infinisil infinisil force-pushed the maintain-expanded-attrs branch 2 times, most recently from de25dc0 to 2c0d0d1 Compare July 19, 2024 05:18
@infinisil infinisil changed the title Maintain expanded attrs/lists/attr params Maintain expanded attrs/lists/attr params/inherits Jul 19, 2024
Copy link
Member

@0x4A6F 0x4A6F left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

Copy link
Member

@piegamesde piegamesde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main point of concern is to make the input preserving features optional for testing purposes. I admit that this is non-trivial to implement though. While we could push these settings through the pretty logic, this would probably require wrapping the entire code in a Reader or something. The alternative would be to encode such conditions in the IR and delay their decision until layoutGreedy. We already do this for optional trailing commas (which depend on the actual layout) for example. Another approach would be to push the option into the parser (which is already wrapped in State) by faking all source lines to 0 if --pure is given.

I haven't looked at the Nixpkgs wide diff but from looking at the test diff already I feel like we may want to introduce a couple of special cases where an expansion shouldn't be preserved: This may happen in situations where expanding made sense in the original format, but looks better collapsed in the RFC format.

-- Map the last token of that expression, no matter how deep it sits
-- in the AST. This is useful for modifying comments
mapLastToken :: (forall b. Ann b -> Ann b) -> a -> a
mapLastToken f a = fst (mapLastToken' (\x -> (f x, ())) a)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it should be removed. We've had use for this in the past on and off depending on implementation details of the layout

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also just add it back if/when we need it

src/Nixfmt/Lexer.hs Show resolved Hide resolved
pretty (fmap (,hardspace) krec) <> pretty paropen <> sep <> pretty parclose
where
-- If the braces are on different lines, keep them like that
sep = if sourceLine paropen /= sourceLine parclose then hardline else hardspace
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I generally see the necessity of preserving parts of the input, it makes reviewing the diff for changes a lot harder. Because now I'll never know if the current change looks the way it does because of the intended output format or because some aspect of the input got preserved.

I'd like to see this feature optional, and have our diff tests run once with and without. This will also ensure that all relevant code paths are more thoroughly tested.

@infinisil
Copy link
Member Author

infinisil commented Jul 21, 2024

@piegamesde In another branch I just experimented with an idea that seems to work pretty well: fb16a35. I don't think this should be blocking for this PR though

@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/formatting-team-meeting-2024-07-23/49510/1

@piegamesde
Copy link
Member

What do we do about things like the formatting of pypkgs, which now have a lot of unfortunately contracted attrsets? I think –and not only because of that– my proposed tweaks to the expansion rules should still be implemented.

While I don't consider that a blocker, I'd still like to block on at least the local tests additionally running in pure mode. Especially since a lot of future tweaks to the format may get shadowed by this. I'm not 100% fond of your approach to do that since a pure mode will require running the formatter twice (first with shrinkwrap and then again normally) despite a more straightforward way existing, but that can still be worked on later.

@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/enforcing-nix-formatting-in-nixpkgs/49506/8

@infinisil
Copy link
Member Author

What do we do about things like the formatting of pypkgs, which now have a lot of unfortunately contracted attrsets? I think –and not only because of that– my proposed tweaks to the expansion rules should still be implemented.

I agree that some tweaks might be good, but this can be done entirely independently. This PR solves one problem, not all related problems.

While I don't consider that a blocker, I'd still like to block on at least the local tests additionally running in pure mode. Especially since a lot of future tweaks to the format may get shadowed by this. I'm not 100% fond of your approach to do that since a pure mode will require running the formatter twice (first with shrinkwrap and then again normally) despite a more straightforward way existing, but that can still be worked on later.

I honestly don't have the time to work on finishing such a pure mode. As implemented in fb16a35 it's quite unrefined and has some oddities, such as shifting around of trailing comments, which are just confusing. And other approaches are very tricky to implement.

Changing the format really isn't a critical failure that would endanger the stability downstream projects, so I think the tests that I added in this PR are enough.

@infinisil infinisil marked this pull request as draft August 6, 2024 18:43
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/formatting-team-meeting-2024-08-06/50222/1

And remove an unused class method
This is a part of the preparation for a future commit that changes the
number of fields of the Ann type
This is a part of the preparation for a future commit that changes the
number of fields of the Ann type
By making it a record and not relying on the non-record syntax, we can
easily reorder or add new fields to it in the future without breaking
any existing patterns.

This commit also uses the record update syntax where before an entirely
new Ann was constructed, saving a bunch of variables (and implicitly
passing through any extra fields added to Ann in the future).
This is finally the new field of Ann that past commits have been working
up to.

Because it's not an optional field, the ann function is extended to
take an extra argument for its initialisation, which then automatically
prompts the connection of source locations from the original to the newly
created value.
@piegamesde piegamesde marked this pull request as ready for review August 7, 2024 08:49
Copy link
Member Author

@infinisil infinisil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review for @piegamesde's code

main/Main.hs Outdated Show resolved Hide resolved
main/Main.hs Outdated Show resolved Hide resolved
src/Nixfmt/Types.hs Outdated Show resolved Hide resolved
src/Nixfmt/Types.hs Outdated Show resolved Hide resolved
-- Same as mapLastToken, but the mapping function also yields a value that may be
-- returned. This is useful for getting/extracting values
mapLastToken' :: (forall b. Ann b -> (Ann b, c)) -> a -> (a, c)

mapAllTokens :: (forall b. Ann b -> Ann b) -> a -> a

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate that this needs to be so manual. I've played around with some changes to improve that, very WIP though, just for future reference: 3b61d8a

@piegamesde piegamesde force-pushed the maintain-expanded-attrs branch 2 times, most recently from 7fca012 to 9f54daa Compare August 8, 2024 09:05
piegamesde and others added 6 commits August 8, 2024 19:01
This changes the formatting to take into account whether attribute sets
are expanded onto multiple lines already, and if so, preserves that.

This is desirable because if a user writes an attribute set over
multiple lines, there's most likely an intention to add more attributes,
which would become harder when formatted.

An example of this is module system options, which usually start out
with just a type (or even no attributes), but usually get more
attributes later. This would've been contracted onto a single line
before this change:

    options.foo = lib.mkOption {
      type = types.str;
    };

This change conforms to the standard due to this line:

> The formatter may take the input formatting into account in some cases in order to preserve multi-line syntax elements (which would otherwise have been contracted by the rules).
This changes the formatting to take into account whether lists
are expanded onto multiple lines already, and if so, preserves that.

This is desirable because if a user writes an list over
multiple lines, there's most likely an intention to add more elements,
which would become harder when formatted.

An example of this is build inputs, which usually start out with just a
single element, but usually get more elements later. This would've been
contracted onto a single line before this change:

    buildInputs = [
      libfoo
    ];

This change conforms to the standard due to this line:

> The formatter may take the input formatting into account in some cases in order to preserve multi-line syntax elements (which would otherwise have been contracted by the rules).
This changes the formatting to take into account whether attribute
parameters are expanded onto multiple lines already,
and if so, preserves that.

This is desirable because if a user writes an attribute parameter over
multiple lines, there's most likely an intention to add more attributes,
which would become harder when formatted.

An example of this is package recipes, which usually start out with just a
single attribute argument, but usually get more later. This would've been
contracted onto a single line before this change:

    {
      stdenv,
    }:

This change conforms to the standard due to this line:

> The formatter may take the input formatting into account in some cases in order to preserve multi-line syntax elements (which would otherwise have been contracted by the rules).
This changes the formatting to take into account whether inherits
are expanded onto multiple lines already, and if so, preserves that.

This is desirable because if a user writes an inherit over
multiple lines, there's most likely an intention to add more,
which would become harder when formatted.

An example of this is inheriting from `lib`, which usually starts out
with just a single value, but usually gets more later. This would've been
contracted onto a single line before this change:

    inherit (lib)
      foo
      ;

This change conforms to the standard due to this line:

> The formatter may take the input formatting into account in some cases in order to preserve multi-line syntax elements (which would otherwise have been contracted by the rules).
This might happen if you temporarily remove all inherits, in which case
the formatter shouldn't remove the spacing that was there.

E.g. I might have this initially

    inherit a b;

    inherit
      c
      d
      ;

removing all the bindings gives me

    inherit ; # I already have the space that I'll need

    inherit # I can just press enter here to add one
      ;

which shouldn't be changed, because it's easier like this to insert new
entries again, compared to

    inherit;

This also makes it more consistent with the parent commit's change
@infinisil infinisil dismissed piegamesde’s stale review August 8, 2024 17:01

A strict mode is now implemented

@infinisil
Copy link
Member Author

Did a minor push to rename a left-over mention of pure mode. @piegamesde Please merge if it looks good to you, I think it's good now :D

@piegamesde piegamesde merged commit 2d79e0f into master Aug 8, 2024
2 checks passed
@piegamesde piegamesde deleted the maintain-expanded-attrs branch August 8, 2024 18:07
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/satisfaction-survey-from-the-new-rfc-166-formatting/49758/16

@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/satisfaction-survey-from-the-new-rfc-166-formatting/49758/18

@doronbehar
Copy link

Could we get this into Nixpkgs' CI please 🙏 ?

@infinisil
Copy link
Member Author

infinisil commented Aug 21, 2024

@doronbehar NixOS/nixpkgs#336322, but after this, the pinned Nixpkgs needs to be updated once nixpkgs-unstable updates with the new nixfmt version

Edit: Done

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

Successfully merging this pull request may close these issues.

5 participants