From 85effadebf5e54101ad8dde09358d970b3758741 Mon Sep 17 00:00:00 2001 From: Yueh-Shun Li Date: Fri, 16 Oct 2026 03:54:05 +0800 Subject: [PATCH] lib.isFunction: fix correctness when taking nested functors Restore the correct behaviour prior to commit 77334757d7e3 ("lib.isFunction: save an attribute lookup") while preserving its performance gain. --- lib/tests/misc.nix | 55 ++++++++++++++++++++++++++++++++++++++++++++++ lib/trivial.nix | 16 ++++++++++++-- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 643c3b0b8a25..bef3a4923a49 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -986,6 +986,61 @@ runTests { expr = isFunction { __functor = null; }; expected = false; }; + testIsFunctionAttrsWithNestedFunctors = { + expr = { + a = isFunction { __functor = self: { __functor = self: x: null; }; }; + b = isFunction { + __functor = { + __functor = + self: outer: x: + null; + }; + }; + c = isFunction { + __functor = { + __functor = self: outer: { __functor = self: x: null; }; + }; + }; + }; + expected = { + a = true; + b = true; + c = true; + }; + }; + testIsFunctionNestedSetFunctionArgs = + let + faOrig = { + x = false; + }; + fa1 = faOrig // { + y1 = true; + }; + fa2 = faOrig // { + y2 = true; + }; + fa3 = faOrig // { + y3 = true; + }; + fa4 = faOrig // { + y4 = true; + }; + f0 = { x, ... }: x; + f1 = setFunctionArgs f0 fa1; + f2 = setFunctionArgs f1 fa2; + f3 = setFunctionArgs f2 fa3; + f4 = setFunctionArgs f3 fa4; + in + { + expr = map isFunction [ + f0 + f1 + f2 + f3 + f4 + ]; + expected = replicate 5 true; + }; testIsStorePath = { expr = diff --git a/lib/trivial.nix b/lib/trivial.nix index f333440ee384..ffe8eab1b28b 100644 --- a/lib/trivial.nix +++ b/lib/trivial.nix @@ -1126,9 +1126,21 @@ in */ isFunction = let - isFunction = builtins.isFunction; + isLambda = builtins.isFunction; in - f: isFunction f || (f ? __functor && isFunction f.__functor && isFunction (f.__functor f)); + f: + # Equivalent to `isLambda f || (f ? __functor && isFunction f.__functor && isFunction (f.__functor f))` + # but faster when `f.__functor` and `f.__functor f` are both lambdas. + isLambda f + || ( + f ? __functor + && ( + let + fFunctor = f.__functor; + in + (isLambda fFunctor || isFunction fFunctor) && (isLambda (fFunctor f) || isFunction (fFunctor f)) + ) + ); /** `mirrorFunctionArgs f g` creates a new function `g'` with the same behavior as `g` (`g' x == g x`)