Skip to content

Conversation

@dyegoaurelio
Copy link
Contributor

@dyegoaurelio dyegoaurelio commented Oct 30, 2025

Previously, single-line list formatting was limited to lists within function arguments. This change extends the behavior to all lists, applying the same rules:

  • Lists with >6 items always: expand
  • Lists with >1 items and at least one non-simple item: expand
  • Otherwise: try to fit on one line

nixpkgs diff is empty because if the brackets are on different lines, we keep them like that (unless we're on--strict mode)

The diff on the test cases were positive IMO.

closes #206

@github-actions
Copy link

github-actions bot commented Oct 30, 2025

Nixpkgs diff

Previously, single-line list formatting was limited to lists within function arguments. This change extends the behavior to all lists, applying the same rules:
- Lists with >6 items always: expand
- Lists with >1 items and at least one non-simple item: expand
- Otherwise: try to fit on one line
@dyegoaurelio dyegoaurelio force-pushed the extend-single-line-lists branch from 16690f0 to 4dfc134 Compare October 30, 2025 14:27
Copy link
Contributor

@MattSturgeon MattSturgeon left a comment

Choose a reason for hiding this comment

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

I agree, most of the diff to our testsuite is good. However our testsuite is biased toward trivial examples.

For context, the reason nixfmt usually prefers expanding to multiline lists is to avoid diff noise when those lists are modified in future PRs.

This is most obvious in lists passed to mkDerivation, such as meta.maintainers, buildInputs, etc. These inputs should almost-always be multiline lists, even if they currently only contain a few items.

On the other hand more "general" non-package code, such as library functions, may be using more trivial lists that benefit from being single line and where diff noise is less of a concern. The challenge is implementing a way to distinguish between lists that benefit from being multiline vs list that benefit from being singleline.

You could argue that this is on the author to decide, however that then opens the door for nit-pick PR feedback, where reviewers waste time commenting on singleline lists that should really be multiline (and vice versa).

This is why nixfmt has (so far) had a firmer stance of any list with multiple items must be expanded.

All that said, I'm personally in favour of finding a way to allow more singleline lists. I'm just conscious of doing it in a way that still enforces lists that are likely to change over time remain expanded to multiline formatting.

Maybe one heuristic could be "any token in the list is more than X chars long"? Or "any element in the list references a variable binding"? Not sure, just throwing ideas out there.

Another heuristic may be that if the list is itself the value of an attribute binding, then it is likely to change over time. Although, this won't catch foo = [ x y z ] ++ optionals bar [ a b c ], since in that example [ a b c ] is not being bound directly to foo.

pkgs.xorg.fontadobe100dpi
pkgs.xorg.fontadobe75dpi
];
[ pkgs.xorg.fontadobe100dpi pkgs.xorg.fontadobe75dpi ];
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a case where the expanded formatting is preferred:

Suggested change
[ pkgs.xorg.fontadobe100dpi pkgs.xorg.fontadobe75dpi ];
[
pkgs.xorg.fontadobe100dpi
pkgs.xorg.fontadobe75dpi
];

"vga=0x317"
"nomodeset"
];
++ optionals config.boot.vesa [ "vga=0x317" "nomodeset" ];
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally, I'd prefer that we retain multiline/expanded formatting when concatenating as part of a chain of lists.

Suggested change
++ optionals config.boot.vesa [ "vga=0x317" "nomodeset" ];
++ optionals config.boot.vesa [
"vga=0x317"
"nomodeset"
];

pam_krb5
pam_ccreds
];
++ lib.optionals config.security.pam.krb5.enable [ pam_krb5 pam_ccreds ];
Copy link
Contributor

Choose a reason for hiding this comment

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

I could live with this more trivial list being contracted, but again I'd prefer it to remain expanded:

Suggested change
++ lib.optionals config.security.pam.krb5.enable [ pam_krb5 pam_ccreds ];
++ lib.optionals config.security.pam.krb5.enable [
pam_krb5
pam_ccreds
];

"__toString"
"__pretty"
];
specialAttrs = [ "__functor" "__functionArgs" "__toString" "__pretty" ];
Copy link
Contributor

Choose a reason for hiding this comment

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

Another example that benefits from being expanded:

Suggested change
specialAttrs = [ "__functor" "__functionArgs" "__toString" "__pretty" ];
specialAttrs = [
"__functor"
"__functionArgs"
"__toString"
"__pretty"
];

]
[ 1 2 3 ]
);
e = (if null then true else [ 1 2 3 ]);
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree, trivial cases like this benefit from being allowed to contract.

@dyegoaurelio
Copy link
Contributor Author

Another heuristic may be that if the list is itself the value of an attribute binding, then it is likely to change over time. Although, this won't catch "foo = [ x y z ] ++ optionals bar [ a b c ], since in that example [ a b c ]is not being bound _directly_ tofoo`.

We're already using the isSimple check, which removes things like sets and functions with a lot arguments.

I think we could do something like this

allowedToContract (Token (LoneAnn (Identifier txt))) = txt  == "false" || txt == "true" -- allow only boolean literals.
allowedToContract (Selection {}) = False -- e.g pkgs.xorg
allowedToContract t = isSimple (Term t)

this prevents changes such as this one

index 063dc21..fd242fb 100644
--- a/test/diff/monsters_3/out-pure.nix
+++ b/test/diff/monsters_3/out-pure.nix
@@ -45,13 +45,7 @@ stdenv.mkDerivation rec {
     wrapGAppsHook4
     glib # for glib-compile-resources
   ];
-  buildInputs = [
-    cairo
-    glib
-    gtk4
-    libadwaita
-    pango
-  ];
+  buildInputs = [ cairo glib gtk4 libadwaita pango ];

while allowing changes such as this one

-  (map (
-    buildAllowCommand "allow" [
-      "snapshot"
-      "mount"
-      "destroy"
-    ]
-  ))
+  (map (buildAllowCommand "allow" [ "snapshot" "mount" "destroy" ]))

@jfly
Copy link
Collaborator

jfly commented Nov 11, 2025

We discussed this in today's team meeting, and agree with @MattSturgeon's comments above. We'd like to see either heuristic(s) that handle those cases, or a cli option (as requested in #206).

It would also be great to discuss this in real time at our next team meeting!

@jfly jfly moved this to Approved in Nix formatting Nov 11, 2025
@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-2025-11-11/72019/1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Approved

Development

Successfully merging this pull request may close these issues.

CLI option to format non-singleton lists on one line

4 participants