Skip to content

lib/generators: uninline recursion depth tracking#168327

Closed
K900 wants to merge 2 commits intoNixOS:masterfrom
K900:fix-recursive-insanity
Closed

lib/generators: uninline recursion depth tracking#168327
K900 wants to merge 2 commits intoNixOS:masterfrom
K900:fix-recursive-insanity

Conversation

@K900
Copy link
Contributor

@K900 K900 commented Apr 11, 2022

As cute as it is, it breaks in "interesting" ways when combined with nixpkgs' magic __functor attribute and friends.

This has notably caused pretty-printing pkgs.rust to fail, making at least two users (me included) extremely confused.

There's probably a better approach here involving more nasty self-recursion and a wrapper, but let's cross that bridge when we come to it.

To reproduce: pkgs.lib.options.showDefs [ { file = "foo"; value = pkgs.rust.packages.stable.buildRustPackages; } ].

Description of changes

Removed the withRecursive wrapper, moved all the step-counting into toPretty, lowered the default depth to a more reasonable amount.

Ping @Profpatsch and @Ma27 as originators of the function.

Things done
  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandbox = true set in nix.conf? (See Nix manual)
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 22.05 Release Notes (or backporting 21.11 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
    • (Release notes changes) Ran nixos/doc/manual/md-to-db.sh to update generated release notes
  • Fits CONTRIBUTING.md.

@K900
Copy link
Contributor Author

K900 commented Apr 12, 2022

Fixed the tests.

@ckiee
Copy link
Member

ckiee commented Apr 12, 2022

Perhaps relevant for #167388?

lib/strings.nix Outdated
Copy link
Member

Choose a reason for hiding this comment

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

I think this might be more useful with the arguments round the other way? I could imagine myself currying repeat 3 more than repeat "str".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. Done.

@ofborg ofborg bot added 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. labels Apr 12, 2022
@Ma27
Copy link
Member

Ma27 commented Apr 12, 2022

The problem is quite simple, if { __functor = ... } is the last value to be evaluated, __functor will be replaced by <unevaluated> thus breaking isFunction.

Currently preparing a patch to fix this.

@K900
Copy link
Contributor Author

K900 commented Apr 12, 2022

The same can also happen with __pretty and any other "magic" type that gets added later. I'm not sure trying to special case these is worth it.

@Ma27
Copy link
Member

Ma27 commented Apr 12, 2022

Well, this issue only applies to "magic" attributes that are used within toPretty, right? I.e. __functionArgs, __functor, __pretty and __toString.

@K900
Copy link
Contributor Author

K900 commented Apr 12, 2022

Yes, but more of those could be added at the same time.

Ma27 added a commit to Ma27/nixpkgs that referenced this pull request Apr 12, 2022
Closes NixOS#168327

The issue reported there can be demonstrated with the following
expression:

    → nix-instantiate --eval -E "with import ./. {}; pkgs.lib.options.showDefs [ { file = \"foo\"; value = pkgs.rust.packages.stable.buildRustPackages; } ]"
    error: attempt to call something which is not a function but a string

           at /home/ma27/Projects/nixpkgs/lib/trivial.nix:442:35:

              441|   isFunction = f: builtins.isFunction f ||
              442|     (f ? __functor && isFunction (f.__functor f));
                 |                                   ^
              443|

Basically, if a `__functor` is in an attribute-set at depth-limit,
`__functor` will be set to `"<unevaluated>"`. This however breaks
`lib.isFunction` which checks for a `__functor` by invoking `__functor`
with `f` itself.

The same issue - "magic" attributes being shadowed by `withRecursion` -
also applies to others such as
`__pretty`/`__functionArgs`/`__toString`.

Since these attributes have a low-risk of causing a stack overflow
(because these are flat attr-sets or even functions), ignoring them in
`withRecursion` seems like a valid solution.
@Ma27
Copy link
Member

Ma27 commented Apr 12, 2022

First of all, people should be careful with that, there's no convention that prevents people from using __ on their own and thus we'd potentially break existing Nix code.

Second, if the new magic attribute affects pretty logic, you'd have to touch lib.generators.toPretty already, so I don't see that much of an issue here.

However don't get me wrong, I understand your point! I'll file a PR with my change anyways as an alternative, but it'd be understandable from my PoV if your's gets merged in favor of mine :)

Another option is btw to special-case each __ attribute, but I think that would cause too much confusion since e.g. stdenv also uses these attributes on derivations.

@K900
Copy link
Contributor Author

K900 commented Apr 12, 2022

Am I correct in assuming that you want to keep withRecursive separate for reusability? I think we can get the best of both worlds here if we do some combinator crimes. I'll try and write up a prototype later tonight.

@Ma27
Copy link
Member

Ma27 commented Apr 13, 2022

I think we can get the best of both worlds here if we do some combinator crimes

Can you elaborate what you exactly mean with "combinator crimes", please?

Am I correct in assuming that you want to keep withRecursive separate for reusability

tl;dr yes, recursion limits and pretty-printing are actually two different things and thus were separated, see also the discussion in #131205 (and the linked tickets).

@K900
Copy link
Contributor Author

K900 commented Apr 13, 2022

We can make withRecursionLimit a "decorator" of sorts that wraps the serializer function and passes the wrapped copy to itself to allow for recursion. It will make writing serializers slightly more awkward, but the depth tracking will be actually correct.

@K900
Copy link
Contributor Author

K900 commented Apr 13, 2022

@Ma27 try the latest commit

@Profpatsch
Copy link
Member

I think @infinisil added that originally?

lib/strings.nix Outdated
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn’t introduce a new stdlib function in an unrelated commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could always inline this, but it sounds like something that's both simple, slightly non-trivial and generally useful. Maybe I can split this off into a separate commit or even PR instead?

Copy link
Member

Choose a reason for hiding this comment

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

A separate PR sounds reasonable. But even just having a separate commit is better than bunching a new library function with some other changes.

Comment on lines 245 to 280
Copy link
Member

Choose a reason for hiding this comment

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

This is a breaking API change of an exposed library function, we can’t do that.

Copy link
Member

Choose a reason for hiding this comment

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

You need to keep the API the same; e.g. making some of the options no-ops should also work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's not really viable here unfortunately - the old version took an object to be inspected, and the new one takes a function to be wrapped. I guess we can make a withRecursionLimit', or even a _withRecursionLimit'?

Copy link
Member

Choose a reason for hiding this comment

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

As I said, I don’t think we should break the API without good cause.

Copy link
Member

Choose a reason for hiding this comment

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

Or rather we shouldn’t break the API, period :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, what I'm saying is, keep the old function as it is, introduce the new one without exposing it.

lib/strings.nix Outdated
Copy link
Member

Choose a reason for hiding this comment

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

The example should probably contain a string that is longer than one character.

@K900
Copy link
Contributor Author

K900 commented Apr 23, 2022

Updated. Split strings.repeat into its own commit (along with the tests), restored the old withRecursion function and added new withRecursionLimit (name subject to bikeshedding); this keeps everything API compatible, except the output of toPretty, which I sincerely hope no one actually relies on.

@K900 K900 requested review from Profpatsch and alyssais April 23, 2022 10:59
Avoids weird issues when recursing into magic attributes
like __functor and __pretty by counting recursions only
when actually necessary.
@Ma27
Copy link
Member

Ma27 commented Apr 23, 2022

I think @infinisil added that originally?

@Profpatsch I added it originally it in #131205 and also filed a PR #168374 to fix it using withRecursion.

However, @K900 brought some valid concerns up regarding my solution, so I'd leave it up to you to decide :)

(sorry for my late replies, was sick recently and had a lot of other stuff to take care of after that)

@K900
Copy link
Contributor Author

K900 commented Apr 23, 2022

Force-pushed again to fix some weird mismerge - my local copy of nixpkgs was behind the laptop I wrote the "generator crimes" PoC on.

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

Labels

10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants