From 67cfc7a8f68776cbd7cf73e3c79b4cdd3ea102c4 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 28 Dec 2022 23:10:44 +0100 Subject: [PATCH 01/14] lib.strings: Add isSimpleCoercibleToString --- lib/strings.nix | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/strings.nix b/lib/strings.nix index 20f6db08cebc..039a9ed1db1c 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -805,6 +805,17 @@ rec { x ? outPath || x ? __toString; + /* Check whether a value can be coerced to a string, + The value must be a string, path, or attribute set. + + This follows Nix's internal coerceToString(coerceMore = false) logic, + except for external types, for which we return false. + */ + isSimpleCoercibleToString = x: + elem (typeOf x) [ "path" "string" ] || + x ? outPath || + x ? __toString; + /* Check whether a value is a store path. Example: From 2b4a8db032643ae61a3b05c24d121e124e0f8414 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 28 Dec 2022 23:17:29 +0100 Subject: [PATCH 02/14] lib.strings.isStorePath: Use isSimpleCoercibleToString Expecting no change in behavior. --- lib/strings.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/strings.nix b/lib/strings.nix index 039a9ed1db1c..5fba13822e3f 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -829,7 +829,7 @@ rec { => false */ isStorePath = x: - if !(isList x) && isCoercibleToString x then + if isSimpleCoercibleToString x then let str = toString x; in substring 0 1 str == "/" && dirOf str == storeDir From 03063f65a5d58fc0a72648d475101d86752725ba Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 28 Dec 2022 23:40:08 +0100 Subject: [PATCH 03/14] lib.strings.toShellVar: Use isSimpleCoercibleString Expecting no change in behavior. --- lib/strings.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/strings.nix b/lib/strings.nix index 5fba13822e3f..edc31b4e9e36 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -395,7 +395,7 @@ rec { */ toShellVar = name: value: lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" ( - if isAttrs value && ! isCoercibleToString value then + if isAttrs value && ! isSimpleCoercibleToString value then "declare -A ${name}=(${ concatStringsSep " " (lib.mapAttrsToList (n: v: "[${escapeShellArg n}]=${escapeShellArg v}" From 3a4c9bdbe619ed235aab005c5f162879c2ed6039 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 28 Dec 2022 23:18:29 +0100 Subject: [PATCH 04/14] lib.types.anything: Use isSimpleCoercibleToString Expecting no change in behavior. --- lib/types.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/types.nix b/lib/types.nix index e741bec6c29d..a99872d2f96b 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -55,6 +55,7 @@ let escapeNixString hasInfix isCoercibleToString + isSimpleCoercibleToString ; inherit (lib.trivial) boolToString @@ -227,7 +228,7 @@ rec { merge = loc: defs: let getType = value: - if isAttrs value && isCoercibleToString value + if isAttrs value && isSimpleCoercibleToString value then "stringCoercibleSet" else builtins.typeOf value; From 68b6443ed635a3e87a62a5a8dcfd05e952c93192 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 28 Dec 2022 23:34:15 +0100 Subject: [PATCH 05/14] lib.strings: Rename isCoercibleToString -> isMoreCoercibleToString --- lib/strings.nix | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/strings.nix b/lib/strings.nix index edc31b4e9e36..897fa3e3a204 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -798,8 +798,17 @@ rec { in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}" result; - /* Check whether a value can be coerced to a string */ - isCoercibleToString = x: + /* Soft-deprecated name for isMoreCoercibleToString */ + isCoercibleToString = lib.warnIf (lib.isInOldestRelease 2305) + "lib.strings.isCoercibleToString is deprecated in favor of either isSimpleCoercibleToString or isMoreCoercibleString. Only use the latter if it needs to return true for null, numbers, booleans and list of similarly coercibles." + isMoreCoercibleToString; + + /* Check whether a list or other value can be passed to toString. + + Many types of value are coercible to string this way, including int, float, + null, bool, list of similarly coercible values. + */ + isMoreCoercibleToString = x: elem (typeOf x) [ "path" "string" "null" "int" "float" "bool" ] || (isList x && lib.all isCoercibleToString x) || x ? outPath || From fed5dc66f80ae3a159a2449904ae067f60a04a67 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 28 Dec 2022 23:38:06 +0100 Subject: [PATCH 06/14] treewide: isCoercibleToString -> isMoreCoercibleToString No change in behavior. --- lib/strings.nix | 2 +- lib/types.nix | 4 ++-- nixos/modules/services/misc/nix-daemon.nix | 2 +- nixos/modules/services/system/self-deploy.nix | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/strings.nix b/lib/strings.nix index 897fa3e3a204..6d2462d03b2a 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -810,7 +810,7 @@ rec { */ isMoreCoercibleToString = x: elem (typeOf x) [ "path" "string" "null" "int" "float" "bool" ] || - (isList x && lib.all isCoercibleToString x) || + (isList x && lib.all isMoreCoercibleToString x) || x ? outPath || x ? __toString; diff --git a/lib/types.nix b/lib/types.nix index a99872d2f96b..b5ac0369c539 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -54,7 +54,7 @@ let concatStringsSep escapeNixString hasInfix - isCoercibleToString + isMoreCoercibleToString isSimpleCoercibleToString ; inherit (lib.trivial) @@ -480,7 +480,7 @@ rec { path = mkOptionType { name = "path"; descriptionClass = "noun"; - check = x: isCoercibleToString x && builtins.substring 0 1 (toString x) == "/"; + check = x: isMoreCoercibleToString x && builtins.substring 0 1 (toString x) == "/"; merge = mergeEqualOption; }; diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix index 10db7cdfb33c..98d5150f2436 100644 --- a/nixos/modules/services/misc/nix-daemon.nix +++ b/nixos/modules/services/misc/nix-daemon.nix @@ -42,7 +42,7 @@ let else if isDerivation v then toString v else if builtins.isPath v then toString v else if isString v then v - else if strings.isCoercibleToString v then toString v + else if strings.isMoreCoercibleToString v then toString v else abort "The nix conf value: ${toPretty {} v} can not be encoded"; mkKeyValue = k: v: "${escape [ "=" ] k} = ${mkValueString v}"; diff --git a/nixos/modules/services/system/self-deploy.nix b/nixos/modules/services/system/self-deploy.nix index 9b1ebfd37522..9f5f39970f26 100644 --- a/nixos/modules/services/system/self-deploy.nix +++ b/nixos/modules/services/system/self-deploy.nix @@ -18,7 +18,7 @@ let in lib.concatStrings (lib.mapAttrsToList toArg args); - isPathType = x: lib.strings.isCoercibleToString x && builtins.substring 0 1 (toString x) == "/"; + isPathType = x: lib.strings.isMoreCoercibleToString x && builtins.substring 0 1 (toString x) == "/"; in { From 29efb2c438ba2dd04d545c67a22d1e64576de9f4 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 28 Dec 2022 23:42:27 +0100 Subject: [PATCH 07/14] lib.types.path: Do not allow lists of strings --- lib/types.nix | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/types.nix b/lib/types.nix index b5ac0369c539..deeff36b4a4a 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -54,7 +54,6 @@ let concatStringsSep escapeNixString hasInfix - isMoreCoercibleToString isSimpleCoercibleToString ; inherit (lib.trivial) @@ -480,7 +479,7 @@ rec { path = mkOptionType { name = "path"; descriptionClass = "noun"; - check = x: isMoreCoercibleToString x && builtins.substring 0 1 (toString x) == "/"; + check = x: isSimpleCoercibleToString x && builtins.substring 0 1 (toString x) == "/"; merge = mergeEqualOption; }; From 5b8de3d9d85c121fd027608fe0c049356c6eea30 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 28 Dec 2022 23:47:51 +0100 Subject: [PATCH 08/14] nixos/self-deploy: Cleanup after types.path is not allowed to be a list anymore --- nixos/modules/services/system/self-deploy.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/modules/services/system/self-deploy.nix b/nixos/modules/services/system/self-deploy.nix index 9f5f39970f26..16a793a42253 100644 --- a/nixos/modules/services/system/self-deploy.nix +++ b/nixos/modules/services/system/self-deploy.nix @@ -18,7 +18,7 @@ let in lib.concatStrings (lib.mapAttrsToList toArg args); - isPathType = x: lib.strings.isMoreCoercibleToString x && builtins.substring 0 1 (toString x) == "/"; + isPathType = x: lib.types.path.check x; in { From 872a24ebbc8056142f7930cc0f775237c4ca83f7 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 31 Dec 2022 00:38:16 +0100 Subject: [PATCH 09/14] lib.strings: isSimpleCoercibleString -> isStringLike --- lib/strings.nix | 14 +++++++------- lib/types.nix | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/strings.nix b/lib/strings.nix index 6d2462d03b2a..0130ea37fb1b 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -395,7 +395,7 @@ rec { */ toShellVar = name: value: lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" ( - if isAttrs value && ! isSimpleCoercibleToString value then + if isAttrs value && ! isStringLike value then "declare -A ${name}=(${ concatStringsSep " " (lib.mapAttrsToList (n: v: "[${escapeShellArg n}]=${escapeShellArg v}" @@ -800,7 +800,7 @@ rec { /* Soft-deprecated name for isMoreCoercibleToString */ isCoercibleToString = lib.warnIf (lib.isInOldestRelease 2305) - "lib.strings.isCoercibleToString is deprecated in favor of either isSimpleCoercibleToString or isMoreCoercibleString. Only use the latter if it needs to return true for null, numbers, booleans and list of similarly coercibles." + "lib.strings.isCoercibleToString is deprecated in favor of either isStringLike or isMoreCoercibleString. Only use the latter if it needs to return true for null, numbers, booleans and list of similarly coercibles." isMoreCoercibleToString; /* Check whether a list or other value can be passed to toString. @@ -814,13 +814,13 @@ rec { x ? outPath || x ? __toString; - /* Check whether a value can be coerced to a string, + /* Check whether a value can be coerced to a string. The value must be a string, path, or attribute set. - This follows Nix's internal coerceToString(coerceMore = false) logic, - except for external types, for which we return false. + String-like values can be used without explicit conversion in + string interpolations and in most functions that expect a string. */ - isSimpleCoercibleToString = x: + isStringLike = x: elem (typeOf x) [ "path" "string" ] || x ? outPath || x ? __toString; @@ -838,7 +838,7 @@ rec { => false */ isStorePath = x: - if isSimpleCoercibleToString x then + if isStringLike x then let str = toString x; in substring 0 1 str == "/" && dirOf str == storeDir diff --git a/lib/types.nix b/lib/types.nix index deeff36b4a4a..666e6502d161 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -54,7 +54,7 @@ let concatStringsSep escapeNixString hasInfix - isSimpleCoercibleToString + isStringLike ; inherit (lib.trivial) boolToString @@ -227,7 +227,7 @@ rec { merge = loc: defs: let getType = value: - if isAttrs value && isSimpleCoercibleToString value + if isAttrs value && isStringLike value then "stringCoercibleSet" else builtins.typeOf value; @@ -479,7 +479,7 @@ rec { path = mkOptionType { name = "path"; descriptionClass = "noun"; - check = x: isSimpleCoercibleToString x && builtins.substring 0 1 (toString x) == "/"; + check = x: isStringLike x && builtins.substring 0 1 (toString x) == "/"; merge = mergeEqualOption; }; From 834f0d660a79e8f08c108237d76fc1b42024289b Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 31 Dec 2022 00:42:22 +0100 Subject: [PATCH 10/14] lib.strings: isMoreCoercibleString -> isConvertibleWithToString Yes, this function name is inconveniently long, but it is important for the name to explicitly reference the function and not be mistaken for the implicit string conversions, which only happen for a smaller set of values. --- lib/strings.nix | 11 ++++++----- nixos/modules/services/misc/nix-daemon.nix | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/strings.nix b/lib/strings.nix index 0130ea37fb1b..89574bc8367c 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -798,19 +798,20 @@ rec { in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}" result; - /* Soft-deprecated name for isMoreCoercibleToString */ + /* Soft-deprecated function. While the original implementation is available as + isConvertibleWithToString, consider using isStringLike instead, if suitable. */ isCoercibleToString = lib.warnIf (lib.isInOldestRelease 2305) - "lib.strings.isCoercibleToString is deprecated in favor of either isStringLike or isMoreCoercibleString. Only use the latter if it needs to return true for null, numbers, booleans and list of similarly coercibles." - isMoreCoercibleToString; + "lib.strings.isCoercibleToString is deprecated in favor of either isStringLike or isConvertibleWithToString. Only use the latter if it needs to return true for null, numbers, booleans and list of similarly coercibles." + isConvertibleWithToString; /* Check whether a list or other value can be passed to toString. Many types of value are coercible to string this way, including int, float, null, bool, list of similarly coercible values. */ - isMoreCoercibleToString = x: + isConvertibleWithToString = x: elem (typeOf x) [ "path" "string" "null" "int" "float" "bool" ] || - (isList x && lib.all isMoreCoercibleToString x) || + (isList x && lib.all isConvertibleWithToString x) || x ? outPath || x ? __toString; diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix index 98d5150f2436..9863cc8e574c 100644 --- a/nixos/modules/services/misc/nix-daemon.nix +++ b/nixos/modules/services/misc/nix-daemon.nix @@ -42,7 +42,7 @@ let else if isDerivation v then toString v else if builtins.isPath v then toString v else if isString v then v - else if strings.isMoreCoercibleToString v then toString v + else if strings.isConvertibleWithToString v then toString v else abort "The nix conf value: ${toPretty {} v} can not be encoded"; mkKeyValue = k: v: "${escape [ "=" ] k} = ${mkValueString v}"; From 23c25d5231856e8cfd60049582c865da97bc5d44 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 31 Dec 2022 00:49:04 +0100 Subject: [PATCH 11/14] lib.strings.isConvertibleWithToString: Refactor to reuse isStringLike --- lib/strings.nix | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/strings.nix b/lib/strings.nix index 89574bc8367c..db89f60c480c 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -810,10 +810,9 @@ rec { null, bool, list of similarly coercible values. */ isConvertibleWithToString = x: - elem (typeOf x) [ "path" "string" "null" "int" "float" "bool" ] || - (isList x && lib.all isConvertibleWithToString x) || - x ? outPath || - x ? __toString; + isStringLike x || + elem (typeOf x) [ "null" "int" "float" "bool" ] || + (isList x && lib.all isConvertibleWithToString x); /* Check whether a value can be coerced to a string. The value must be a string, path, or attribute set. From d0d0f7d0aaf1ea29f6fe269340186de12d697dea Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 31 Dec 2022 00:53:24 +0100 Subject: [PATCH 12/14] lib: Add isPath Available since Nix 2.3, which is the Nixpkgs minimum version. Thanks zimbatm! --- lib/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/default.nix b/lib/default.nix index 68e5b8dea1eb..a6da4f4bda0f 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -63,7 +63,7 @@ let inherit (builtins) add addErrorContext attrNames concatLists deepSeq elem elemAt filter genericClosure genList getAttr - hasAttr head isAttrs isBool isInt isList isString length + hasAttr head isAttrs isBool isInt isList isPath isString length lessThan listToAttrs pathExists readFile replaceStrings seq stringLength sub substring tail trace; inherit (self.trivial) id const pipe concat or and bitAnd bitOr bitXor From d103811173b7b608b2639af61d422736bf715a8e Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 31 Dec 2022 00:58:34 +0100 Subject: [PATCH 13/14] lib.isStringLike: Remove use of list In the current implementation of Nix, this list would be allocated over and over. Iirc pennae tried to optimize static list allocation, but gained no significant performance improvement. --- lib/strings.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/strings.nix b/lib/strings.nix index db89f60c480c..2188fcb1dbfd 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -18,6 +18,7 @@ rec { isInt isList isAttrs + isPath isString match parseDrvName @@ -821,7 +822,8 @@ rec { string interpolations and in most functions that expect a string. */ isStringLike = x: - elem (typeOf x) [ "path" "string" ] || + isString x || + isPath x || x ? outPath || x ? __toString; From cb98e26aaff781ed48a1885e7a6423708fb564b8 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 31 Dec 2022 01:47:28 +0100 Subject: [PATCH 14/14] lib: Add isStringLike --- lib/default.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/default.nix b/lib/default.nix index a6da4f4bda0f..f0f136adbc41 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -96,14 +96,16 @@ let concatImapStringsSep makeSearchPath makeSearchPathOutput makeLibraryPath makeBinPath optionalString hasInfix hasPrefix hasSuffix stringToCharacters stringAsChars escape - escapeShellArg escapeShellArgs isValidPosixName toShellVar toShellVars + escapeShellArg escapeShellArgs + isStorePath isStringLike + isValidPosixName toShellVar toShellVars escapeRegex escapeXML replaceChars lowerChars upperChars toLower toUpper addContextFrom splitString removePrefix removeSuffix versionOlder versionAtLeast getName getVersion mesonOption mesonBool mesonEnable nameFromURL enableFeature enableFeatureAs withFeature - withFeatureAs fixedWidthString fixedWidthNumber isStorePath + withFeatureAs fixedWidthString fixedWidthNumber toInt toIntBase10 readPathsFromFile fileContents; inherit (self.stringsWithDeps) textClosureList textClosureMap noDepEntry fullDepEntry packEntry stringAfter;