diff --git a/doc/manual/redirects.js b/doc/manual/redirects.js index f7b479106a2..0f9f91b037f 100644 --- a/doc/manual/redirects.js +++ b/doc/manual/redirects.js @@ -238,12 +238,12 @@ const redirects = { "attr-system": "language/derivations.html#attr-system", "ssec-derivation": "language/derivations.html", "ch-expression-language": "language/index.html", - "sec-constructs": "language/constructs.html", - "sect-let-language": "language/constructs.html#let-language", - "ss-functions": "language/constructs.html#functions", + "sec-constructs": "language/syntax.html", + "sect-let-language": "language/syntax.html#let-expressions", + "ss-functions": "language/syntax.html#functions", "sec-language-operators": "language/operators.html", "table-operators": "language/operators.html", - "ssec-values": "language/values.html", + "ssec-values": "language/types.html", "gloss-closure": "glossary.html#gloss-closure", "gloss-derivation": "glossary.html#gloss-derivation", "gloss-deriver": "glossary.html#gloss-deriver", @@ -335,11 +335,15 @@ const redirects = { "ssec-relnotes-2.2": "release-notes/rl-2.2.html", "ssec-relnotes-2.3": "release-notes/rl-2.3.html", }, - "language/values.html": { + "language/types.html": { "simple-values": "#primitives", "lists": "#list", "strings": "#string", "attribute-sets": "#attribute-set", + "type-number": "#type-int", + }, + "language/syntax.html": { + "scoping-rules": "scoping.html", }, "installation/installing-binary.html": { "linux": "uninstall.html#linux", diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in index cb54a3822d1..6e5c1aee17a 100644 --- a/doc/manual/src/SUMMARY.md.in +++ b/doc/manual/src/SUMMARY.md.in @@ -25,11 +25,12 @@ - [Store Types](store/types/index.md) {{#include ./store/types/SUMMARY.md}} - [Nix Language](language/index.md) - - [Data Types](language/values.md) - - [Language Constructs](language/constructs.md) + - [Data Types](language/types.md) + - [String context](language/string-context.md) + - [Syntax and semantics](language/syntax.md) + - [Scoping rules](language/scope.md) - [String interpolation](language/string-interpolation.md) - [Lookup path](language/constructs/lookup-path.md) - - [String context](language/string-context.md) - [Operators](language/operators.md) - [Derivations](language/derivations.md) - [Advanced Attributes](language/advanced-attributes.md) diff --git a/doc/manual/src/_redirects b/doc/manual/src/_redirects index 7f8edca8e64..c52ca0dddfa 100644 --- a/doc/manual/src/_redirects +++ b/doc/manual/src/_redirects @@ -27,6 +27,8 @@ /expressions/language-operators /language/operators 301! /expressions/language-values /language/values 301! /expressions/* /language/:splat 301! +/language/values /language/types 301! +/language/constructs /language/syntax 301! /installation/installation /installation 301! diff --git a/doc/manual/src/command-ref/env-common.md b/doc/manual/src/command-ref/env-common.md index 34e0dbfbd91..d3f5f9c1443 100644 --- a/doc/manual/src/command-ref/env-common.md +++ b/doc/manual/src/command-ref/env-common.md @@ -10,7 +10,7 @@ Most Nix commands interpret the following environment variables: - [`NIX_PATH`](#env-NIX_PATH) A colon-separated list of directories used to look up the location of Nix - expressions using [paths](@docroot@/language/values.md#type-path) + expressions using [paths](@docroot@/language/types.md#type-path) enclosed in angle brackets (i.e., ``), e.g. `/home/eelco/Dev:/etc/nixos`. It can be extended using the [`-I` option](@docroot@/command-ref/opt-common.md#opt-I). diff --git a/doc/manual/src/command-ref/nix-env/install.md b/doc/manual/src/command-ref/nix-env/install.md index 76aa70f71b9..748dd1e7a39 100644 --- a/doc/manual/src/command-ref/nix-env/install.md +++ b/doc/manual/src/command-ref/nix-env/install.md @@ -57,7 +57,7 @@ The arguments *args* map to store paths in a number of possible ways: easy way to copy user environment elements from one profile to another. -- If `--from-expression` is given, *args* are [Nix language functions](@docroot@/language/constructs.md#functions) that are called with the [default Nix expression] as their single argument. +- If `--from-expression` is given, *args* are [Nix language functions](@docroot@/language/syntax.md#functions) that are called with the [default Nix expression] as their single argument. The derivations returned by those function calls are installed. This allows derivations to be specified in an unambiguous way, which is necessary if there are multiple derivations with the same name. diff --git a/doc/manual/src/command-ref/opt-common.md b/doc/manual/src/command-ref/opt-common.md index 114b292f930..a42909e2d13 100644 --- a/doc/manual/src/command-ref/opt-common.md +++ b/doc/manual/src/command-ref/opt-common.md @@ -143,7 +143,7 @@ Most Nix commands accept the following command-line options: This option is accepted by `nix-env`, `nix-instantiate`, `nix-shell` and `nix-build`. When evaluating Nix expressions, the expression evaluator will automatically try to call functions that it encounters. - It can automatically call functions for which every argument has a [default value](@docroot@/language/constructs.md#functions) (e.g., `{ argName ? defaultValue }: ...`). + It can automatically call functions for which every argument has a [default value](@docroot@/language/syntax.md#functions) (e.g., `{ argName ? defaultValue }: ...`). With `--arg`, you can also call functions that have arguments without a default value (or override a default value). That is, if the evaluator encounters a function with an argument named *name*, it will call it with value *value*. diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md index 4c9b2c52e51..f65ada63a5c 100644 --- a/doc/manual/src/glossary.md +++ b/doc/manual/src/glossary.md @@ -312,7 +312,7 @@ - [package attribute set]{#package-attribute-set} - An [attribute set](@docroot@/language/values.md#attribute-set) containing the attribute `type = "derivation";` (derivation for historical reasons), as well as other attributes, such as + An [attribute set](@docroot@/language/types.md#attribute-set) containing the attribute `type = "derivation";` (derivation for historical reasons), as well as other attributes, such as - attributes that refer to the files of a [package], typically in the form of [derivation outputs](#output), - attributes that declare something about how the package is supposed to be installed or used, - other metadata or arbitrary attributes. @@ -325,9 +325,9 @@ See [String interpolation](./language/string-interpolation.md) for details. - [string]: ./language/values.md#type-string - [path]: ./language/values.md#type-path - [attribute name]: ./language/values.md#attribute-set + [string]: ./language/types.md#type-string + [path]: ./language/types.md#type-path + [attribute name]: ./language/types.md#attribute-set - [base directory]{#gloss-base-directory} diff --git a/doc/manual/src/language/constructs.md b/doc/manual/src/language/constructs.md deleted file mode 100644 index 491d221b388..00000000000 --- a/doc/manual/src/language/constructs.md +++ /dev/null @@ -1,486 +0,0 @@ -# Language Constructs - -## Recursive sets - -Recursive sets are like normal [attribute sets](./values.md#attribute-set), but the attributes can refer to each other. - -> *rec-attrset* = `rec {` [ *name* `=` *expr* `;` `]`... `}` - -Example: - -```nix -rec { - x = y; - y = 123; -}.x -``` - -This evaluates to `123`. - -Note that without `rec` the binding `x = y;` would -refer to the variable `y` in the surrounding scope, if one exists, and -would be invalid if no such variable exists. That is, in a normal -(non-recursive) set, attributes are not added to the lexical scope; in a -recursive set, they are. - -Recursive sets of course introduce the danger of infinite recursion. For -example, the expression - -```nix -rec { - x = y; - y = x; -}.x -``` - -will crash with an `infinite recursion encountered` error message. - -## Let-expressions - -A let-expression allows you to define local variables for an expression. - -> *let-in* = `let` [ *identifier* = *expr* ]... `in` *expr* - -Example: - -```nix -let - x = "foo"; - y = "bar"; -in x + y -``` - -This evaluates to `"foobar"`. - -## Inheriting attributes - -When defining an [attribute set](./values.md#attribute-set) or in a [let-expression](#let-expressions) it is often convenient to copy variables from the surrounding lexical scope (e.g., when you want to propagate attributes). -This can be shortened using the `inherit` keyword. - -Example: - -```nix -let x = 123; in -{ - inherit x; - y = 456; -} -``` - -is equivalent to - -```nix -let x = 123; in -{ - x = x; - y = 456; -} -``` - -and both evaluate to `{ x = 123; y = 456; }`. - -> **Note** -> -> This works because `x` is added to the lexical scope by the `let` construct. - -It is also possible to inherit attributes from another attribute set. - -Example: - -In this fragment from `all-packages.nix`, - -```nix -graphviz = (import ../tools/graphics/graphviz) { - inherit fetchurl stdenv libpng libjpeg expat x11 yacc; - inherit (xorg) libXaw; -}; - -xorg = { - libX11 = ...; - libXaw = ...; - ... -} - -libpng = ...; -libjpg = ...; -... -``` - -the set used in the function call to the function defined in -`../tools/graphics/graphviz` inherits a number of variables from the -surrounding scope (`fetchurl` ... `yacc`), but also inherits `libXaw` -(the X Athena Widgets) from the `xorg` set. - -Summarizing the fragment - -```nix -... -inherit x y z; -inherit (src-set) a b c; -... -``` - -is equivalent to - -```nix -... -x = x; y = y; z = z; -a = src-set.a; b = src-set.b; c = src-set.c; -... -``` - -when used while defining local variables in a let-expression or while -defining a set. - -In a `let` expression, `inherit` can be used to selectively bring specific attributes of a set into scope. For example - - -```nix -let - x = { a = 1; b = 2; }; - inherit (builtins) attrNames; -in -{ - names = attrNames x; -} -``` - -is equivalent to - -```nix -let - x = { a = 1; b = 2; }; -in -{ - names = builtins.attrNames x; -} -``` - -both evaluate to `{ names = [ "a" "b" ]; }`. - -## Functions - -Functions have the following form: - -```nix -pattern: body -``` - -The pattern specifies what the argument of the function must look like, -and binds variables in the body to (parts of) the argument. There are -three kinds of patterns: - - - If a pattern is a single identifier, then the function matches any - argument. Example: - - ```nix - let negate = x: !x; - concat = x: y: x + y; - in if negate true then concat "foo" "bar" else "" - ``` - - Note that `concat` is a function that takes one argument and returns - a function that takes another argument. This allows partial - parameterisation (i.e., only filling some of the arguments of a - function); e.g., - - ```nix - map (concat "foo") [ "bar" "bla" "abc" ] - ``` - - evaluates to `[ "foobar" "foobla" "fooabc" ]`. - - - A *set pattern* of the form `{ name1, name2, …, nameN }` matches a - set containing the listed attributes, and binds the values of those - attributes to variables in the function body. For example, the - function - - ```nix - { x, y, z }: z + y + x - ``` - - can only be called with a set containing exactly the attributes `x`, - `y` and `z`. No other attributes are allowed. If you want to allow - additional arguments, you can use an ellipsis (`...`): - - ```nix - { x, y, z, ... }: z + y + x - ``` - - This works on any set that contains at least the three named - attributes. - - It is possible to provide *default values* for attributes, in - which case they are allowed to be missing. A default value is - specified by writing `name ? e`, where *e* is an arbitrary - expression. For example, - - ```nix - { x, y ? "foo", z ? "bar" }: z + y + x - ``` - - specifies a function that only requires an attribute named `x`, but - optionally accepts `y` and `z`. - - - An `@`-pattern provides a means of referring to the whole value - being matched: - - ```nix - args@{ x, y, z, ... }: z + y + x + args.a - ``` - - but can also be written as: - - ```nix - { x, y, z, ... } @ args: z + y + x + args.a - ``` - - Here `args` is bound to the argument *as passed*, which is further - matched against the pattern `{ x, y, z, ... }`. - The `@`-pattern makes mainly sense with an ellipsis(`...`) as - you can access attribute names as `a`, using `args.a`, which was - given as an additional attribute to the function. - - > **Warning** - > - > `args@` binds the name `args` to the attribute set that is passed to the function. - > In particular, `args` does *not* include any default values specified with `?` in the function's set pattern. - > - > For instance - > - > ```nix - > let - > f = args@{ a ? 23, ... }: [ a args ]; - > in - > f {} - > ``` - > - > is equivalent to - > - > ```nix - > let - > f = args @ { ... }: [ (args.a or 23) args ]; - > in - > f {} - > ``` - > - > and both expressions will evaluate to: - > - > ```nix - > [ 23 {} ] - > ``` - -Note that functions do not have names. If you want to give them a name, -you can bind them to an attribute, e.g., - -```nix -let concat = { x, y }: x + y; -in concat { x = "foo"; y = "bar"; } -``` - -## Conditionals - -Conditionals look like this: - -```nix -if e1 then e2 else e3 -``` - -where *e1* is an expression that should evaluate to a Boolean value -(`true` or `false`). - -## Assertions - -Assertions are generally used to check that certain requirements on or -between features and dependencies hold. They look like this: - -```nix -assert e1; e2 -``` - -where *e1* is an expression that should evaluate to a Boolean value. If -it evaluates to `true`, *e2* is returned; otherwise expression -evaluation is aborted and a backtrace is printed. - -Here is a Nix expression for the Subversion package that shows how -assertions can be used:. - -```nix -{ localServer ? false -, httpServer ? false -, sslSupport ? false -, pythonBindings ? false -, javaSwigBindings ? false -, javahlBindings ? false -, stdenv, fetchurl -, openssl ? null, httpd ? null, db4 ? null, expat, swig ? null, j2sdk ? null -}: - -assert localServer -> db4 != null; ① -assert httpServer -> httpd != null && httpd.expat == expat; ② -assert sslSupport -> openssl != null && (httpServer -> httpd.openssl == openssl); ③ -assert pythonBindings -> swig != null && swig.pythonSupport; -assert javaSwigBindings -> swig != null && swig.javaSupport; -assert javahlBindings -> j2sdk != null; - -stdenv.mkDerivation { - name = "subversion-1.1.1"; - ... - openssl = if sslSupport then openssl else null; ④ - ... -} -``` - -The points of interest are: - -1. This assertion states that if Subversion is to have support for - local repositories, then Berkeley DB is needed. So if the Subversion - function is called with the `localServer` argument set to `true` but - the `db4` argument set to `null`, then the evaluation fails. - - Note that `->` is the [logical - implication](https://en.wikipedia.org/wiki/Truth_table#Logical_implication) - Boolean operation. - -2. This is a more subtle condition: if Subversion is built with Apache - (`httpServer`) support, then the Expat library (an XML library) used - by Subversion should be same as the one used by Apache. This is - because in this configuration Subversion code ends up being linked - with Apache code, and if the Expat libraries do not match, a build- - or runtime link error or incompatibility might occur. - -3. This assertion says that in order for Subversion to have SSL support - (so that it can access `https` URLs), an OpenSSL library must be - passed. Additionally, it says that *if* Apache support is enabled, - then Apache's OpenSSL should match Subversion's. (Note that if - Apache support is not enabled, we don't care about Apache's - OpenSSL.) - -4. The conditional here is not really related to assertions, but is - worth pointing out: it ensures that if SSL support is disabled, then - the Subversion derivation is not dependent on OpenSSL, even if a - non-`null` value was passed. This prevents an unnecessary rebuild of - Subversion if OpenSSL changes. - -## With-expressions - -A *with-expression*, - -```nix -with e1; e2 -``` - -introduces the set *e1* into the lexical scope of the expression *e2*. -For instance, - -```nix -let as = { x = "foo"; y = "bar"; }; -in with as; x + y -``` - -evaluates to `"foobar"` since the `with` adds the `x` and `y` attributes -of `as` to the lexical scope in the expression `x + y`. The most common -use of `with` is in conjunction with the `import` function. E.g., - -```nix -with (import ./definitions.nix); ... -``` - -makes all attributes defined in the file `definitions.nix` available as -if they were defined locally in a `let`-expression. - -The bindings introduced by `with` do not shadow bindings introduced by -other means, e.g. - -```nix -let a = 3; in with { a = 1; }; let a = 4; in with { a = 2; }; ... -``` - -establishes the same scope as - -```nix -let a = 1; in let a = 2; in let a = 3; in let a = 4; in ... -``` - -Variables coming from outer `with` expressions *are* shadowed: - -```nix -with { a = "outer"; }; -with { a = "inner"; }; -a -``` - -Does evaluate to `"inner"`. - -## Comments - -- Inline comments start with `#` and run until the end of the line. - - > **Example** - > - > ```nix - > # A number - > 2 # Equals 1 + 1 - > ``` - > - > ```console - > 2 - > ``` - -- Block comments start with `/*` and run until the next occurrence of `*/`. - - > **Example** - > - > ```nix - > /* - > Block comments - > can span multiple lines. - > */ "hello" - > ``` - > - > ```console - > "hello" - > ``` - - This means that block comments cannot be nested. - - > **Example** - > - > ```nix - > /* /* nope */ */ 1 - > ``` - > - > ```console - > error: syntax error, unexpected '*' - > - > at «string»:1:15: - > - > 1| /* /* nope */ * - > | ^ - > ``` - - Consider escaping nested comments and unescaping them in post-processing. - - > **Example** - > - > ```nix - > /* /* nested *\/ */ 1 - > ``` - > - > ```console - > 1 - > ``` - -## Scoping rules - -Nix is [statically scoped](https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scope), but with multiple scopes and shadowing rules. - -* primary scope --- explicitly-bound variables - * [`let`](#let-expressions) - * [`inherit`](#inheriting-attributes) - * function arguments - -* secondary scope --- implicitly-bound variables - * [`with`](#with-expressions) -Primary scope takes precedence over secondary scope. -See [`with`](#with-expressions) for a detailed example. diff --git a/doc/manual/src/language/constructs/lookup-path.md b/doc/manual/src/language/constructs/lookup-path.md index e87d2922bd3..11278f3a81c 100644 --- a/doc/manual/src/language/constructs/lookup-path.md +++ b/doc/manual/src/language/constructs/lookup-path.md @@ -4,7 +4,7 @@ > > *lookup-path* = `<` *identifier* [ `/` *identifier* ]... `>` -A lookup path is an identifier with an optional path suffix that resolves to a [path value](@docroot@/language/values.md#type-path) if the identifier matches a search path entry. +A lookup path is an identifier with an optional path suffix that resolves to a [path value](@docroot@/language/types.md#type-path) if the identifier matches a search path entry. The value of a lookup path is determined by [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath). diff --git a/doc/manual/src/language/derivations.md b/doc/manual/src/language/derivations.md index b95900cdd16..8879fe7069d 100644 --- a/doc/manual/src/language/derivations.md +++ b/doc/manual/src/language/derivations.md @@ -12,7 +12,7 @@ It outputs an attribute set, and produces a [store derivation] as a side effect ### Required -- [`name`]{#attr-name} ([String](@docroot@/language/values.md#type-string)) +- [`name`]{#attr-name} ([String](@docroot@/language/types.md#type-string)) A symbolic name for the derivation. It is added to the [store path] of the corresponding [store derivation] as well as to its [output paths](@docroot@/glossary.md#gloss-output-path). @@ -31,7 +31,7 @@ It outputs an attribute set, and produces a [store derivation] as a side effect > The store derivation's path will be `/nix/store/-hello.drv`. > The [output](#attr-outputs) paths will be of the form `/nix/store/-hello[-]` -- [`system`]{#attr-system} ([String](@docroot@/language/values.md#type-string)) +- [`system`]{#attr-system} ([String](@docroot@/language/types.md#type-string)) The system type on which the [`builder`](#attr-builder) executable is meant to be run. @@ -66,7 +66,7 @@ It outputs an attribute set, and produces a [store derivation] as a side effect > > [`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem) has the value of the [`system` configuration option], and defaults to the system type of the current Nix installation. -- [`builder`]{#attr-builder} ([Path](@docroot@/language/values.md#type-path) | [String](@docroot@/language/values.md#type-string)) +- [`builder`]{#attr-builder} ([Path](@docroot@/language/types.md#type-path) | [String](@docroot@/language/types.md#type-string)) Path to an executable that will perform the build. @@ -113,7 +113,7 @@ It outputs an attribute set, and produces a [store derivation] as a side effect ### Optional -- [`args`]{#attr-args} ([List](@docroot@/language/values.md#list) of [String](@docroot@/language/values.md#type-string)) +- [`args`]{#attr-args} ([List](@docroot@/language/types.md#list) of [String](@docroot@/language/types.md#type-string)) Default: `[ ]` @@ -132,7 +132,7 @@ It outputs an attribute set, and produces a [store derivation] as a side effect > }; > ``` -- [`outputs`]{#attr-outputs} ([List](@docroot@/language/values.md#list) of [String](@docroot@/language/values.md#type-string)) +- [`outputs`]{#attr-outputs} ([List](@docroot@/language/types.md#list) of [String](@docroot@/language/types.md#type-string)) Default: `[ "out" ]` diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index 3694480d718..2bfdbb8a0c6 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -53,7 +53,7 @@ This is an incomplete overview of language features, by example. - *Basic values ([primitives](@docroot@/language/values.md#primitives))* + *Basic values ([primitives](@docroot@/language/types.md#primitives))* @@ -71,7 +71,7 @@ This is an incomplete overview of language features, by example. - A [string](@docroot@/language/values.md#type-string) + A [string](@docroot@/language/types.md#type-string) @@ -102,7 +102,7 @@ This is an incomplete overview of language features, by example. - A [comment](@docroot@/language/constructs.md#comments). + A [comment](@docroot@/language/syntax.md#comments). @@ -130,7 +130,7 @@ This is an incomplete overview of language features, by example. - [Booleans](@docroot@/language/values.md#type-boolean) + [Booleans](@docroot@/language/types.md#type-boolean) @@ -142,7 +142,7 @@ This is an incomplete overview of language features, by example. - [Null](@docroot@/language/values.md#type-null) value + [Null](@docroot@/language/types.md#type-null) value @@ -154,7 +154,7 @@ This is an incomplete overview of language features, by example. - An [integer](@docroot@/language/values.md#type-number) + An [integer](@docroot@/language/types.md#type-int) @@ -166,7 +166,7 @@ This is an incomplete overview of language features, by example. - A [floating point number](@docroot@/language/values.md#type-number) + A [floating point number](@docroot@/language/types.md#type-float) @@ -178,7 +178,7 @@ This is an incomplete overview of language features, by example. - An absolute [path](@docroot@/language/values.md#type-path) + An absolute [path](@docroot@/language/types.md#type-path) @@ -190,7 +190,7 @@ This is an incomplete overview of language features, by example. - A [path](@docroot@/language/values.md#type-path) relative to the file containing this Nix expression + A [path](@docroot@/language/types.md#type-path) relative to the file containing this Nix expression @@ -202,7 +202,7 @@ This is an incomplete overview of language features, by example. - A home [path](@docroot@/language/values.md#type-path). Evaluates to the `"/.config"`. + A home [path](@docroot@/language/types.md#type-path). Evaluates to the `"/.config"`. @@ -238,7 +238,7 @@ This is an incomplete overview of language features, by example. - An [attribute set](@docroot@/language/values.md#attribute-set) with attributes named `x` and `y` + An [attribute set](@docroot@/language/types.md#attribute-set) with attributes named `x` and `y` @@ -262,7 +262,7 @@ This is an incomplete overview of language features, by example. - A [recursive set](@docroot@/language/constructs.md#recursive-sets), equivalent to `{ x = "foo"; y = "foobar"; }`. + A [recursive set](@docroot@/language/syntax.md#recursive-sets), equivalent to `{ x = "foo"; y = "foobar"; }`. @@ -278,7 +278,7 @@ This is an incomplete overview of language features, by example. - [Lists](@docroot@/language/values.md#list) with three elements. + [Lists](@docroot@/language/types.md#list) with three elements. @@ -362,7 +362,7 @@ This is an incomplete overview of language features, by example. - [Attribute selection](@docroot@/language/values.md#attribute-set) (evaluates to `1`) + [Attribute selection](@docroot@/language/types.md#attribute-set) (evaluates to `1`) @@ -374,7 +374,7 @@ This is an incomplete overview of language features, by example. - [Attribute selection](@docroot@/language/values.md#attribute-set) with default (evaluates to `3`) + [Attribute selection](@docroot@/language/types.md#attribute-set) with default (evaluates to `3`) @@ -410,7 +410,7 @@ This is an incomplete overview of language features, by example. - [Conditional expression](@docroot@/language/constructs.md#conditionals). + [Conditional expression](@docroot@/language/syntax.md#conditionals). @@ -422,7 +422,7 @@ This is an incomplete overview of language features, by example. - [Assertion](@docroot@/language/constructs.md#assertions) check (evaluates to `"yes!"`). + [Assertion](@docroot@/language/syntax.md#assertions) check (evaluates to `"yes!"`). @@ -434,7 +434,7 @@ This is an incomplete overview of language features, by example. - Variable definition. See [`let`-expressions](@docroot@/language/constructs.md#let-expressions). + Variable definition. See [`let`-expressions](@docroot@/language/syntax.md#let-expressions). @@ -448,7 +448,7 @@ This is an incomplete overview of language features, by example. Add all attributes from the given set to the scope (evaluates to `1`). - See [`with`-expressions](@docroot@/language/constructs.md#with-expressions) for details and shadowing caveats. + See [`with`-expressions](@docroot@/language/syntax.md#with-expressions) for details and shadowing caveats. @@ -462,7 +462,7 @@ This is an incomplete overview of language features, by example. Adds the variables to the current scope (attribute set or `let` binding). Desugars to `pkgs = pkgs; src = src;`. - See [Inheriting attributes](@docroot@/language/constructs.md#inheriting-attributes). + See [Inheriting attributes](@docroot@/language/syntax.md#inheriting-attributes). @@ -476,14 +476,14 @@ This is an incomplete overview of language features, by example. Adds the attributes, from the attribute set in parentheses, to the current scope (attribute set or `let` binding). Desugars to `lib = pkgs.lib; stdenv = pkgs.stdenv;`. - See [Inheriting attributes](@docroot@/language/constructs.md#inheriting-attributes). + See [Inheriting attributes](@docroot@/language/syntax.md#inheriting-attributes). - *[Functions](@docroot@/language/constructs.md#functions) (lambdas)* + *[Functions](@docroot@/language/syntax.md#functions) (lambdas)* @@ -500,7 +500,7 @@ This is an incomplete overview of language features, by example. - A [function](@docroot@/language/constructs.md#functions) that expects an integer and returns it increased by 1. + A [function](@docroot@/language/syntax.md#functions) that expects an integer and returns it increased by 1. @@ -512,7 +512,7 @@ This is an incomplete overview of language features, by example. - Curried [function](@docroot@/language/constructs.md#functions), equivalent to `x: (y: x + y)`. Can be used like a function that takes two arguments and returns their sum. + Curried [function](@docroot@/language/syntax.md#functions), equivalent to `x: (y: x + y)`. Can be used like a function that takes two arguments and returns their sum. @@ -524,7 +524,7 @@ This is an incomplete overview of language features, by example. - A [function](@docroot@/language/constructs.md#functions) call (evaluates to 101) + A [function](@docroot@/language/syntax.md#functions) call (evaluates to 101) @@ -536,7 +536,7 @@ This is an incomplete overview of language features, by example. - A [function](@docroot@/language/constructs.md#functions) bound to a variable and subsequently called by name (evaluates to 103) + A [function](@docroot@/language/syntax.md#functions) bound to a variable and subsequently called by name (evaluates to 103) @@ -548,7 +548,7 @@ This is an incomplete overview of language features, by example. - A [function](@docroot@/language/constructs.md#functions) that expects a set with required attributes `x` and `y` and concatenates them + A [function](@docroot@/language/syntax.md#functions) that expects a set with required attributes `x` and `y` and concatenates them @@ -560,7 +560,7 @@ This is an incomplete overview of language features, by example. - A [function](@docroot@/language/constructs.md#functions) that expects a set with required attribute `x` and optional `y`, using `"bar"` as default value for `y` + A [function](@docroot@/language/syntax.md#functions) that expects a set with required attribute `x` and optional `y`, using `"bar"` as default value for `y` @@ -572,7 +572,7 @@ This is an incomplete overview of language features, by example. - A [function](@docroot@/language/constructs.md#functions) that expects a set with required attributes `x` and `y` and ignores any other attributes + A [function](@docroot@/language/syntax.md#functions) that expects a set with required attributes `x` and `y` and ignores any other attributes @@ -586,7 +586,7 @@ This is an incomplete overview of language features, by example. - A [function](@docroot@/language/constructs.md#functions) that expects a set with required attributes `x` and `y`, and binds the whole set to `args` + A [function](@docroot@/language/syntax.md#functions) that expects a set with required attributes `x` and `y`, and binds the whole set to `args` diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md index 9e5ab52a210..9660a764d92 100644 --- a/doc/manual/src/language/operators.md +++ b/doc/manual/src/language/operators.md @@ -27,11 +27,11 @@ | Logical disjunction (`OR`) | *bool* \|\| *bool* | left | 13 | | [Logical implication] | *bool* `->` *bool* | right | 14 | -[string]: ./values.md#type-string -[path]: ./values.md#type-path -[number]: ./values.md#type-number -[list]: ./values.md#list -[attribute set]: ./values.md#attribute-set +[string]: ./types.md#type-string +[path]: ./types.md#type-path +[number]: ./types.md#type-float +[list]: ./types.md#list +[attribute set]: ./types.md#attribute-set ## Attribute selection @@ -42,7 +42,7 @@ Select the attribute denoted by attribute path *attrpath* from [attribute set] *attrset*. If the attribute doesn’t exist, return the *expr* after `or` if provided, otherwise abort evaluation. -An attribute path is a dot-separated list of [attribute names](./values.md#attribute-set). +An attribute path is a dot-separated list of [attribute names](./types.md#attribute-set). > **Syntax** > @@ -61,7 +61,7 @@ The result is a [Boolean] value. See also: [`builtins.hasAttr`](@docroot@/language/builtins.md#builtins-hasAttr) -[Boolean]: ./values.md#type-boolean +[Boolean]: ./types.md#type-boolean [Has attribute]: #has-attribute @@ -172,7 +172,7 @@ All comparison operators are implemented in terms of `<`, and the following equi - Numbers are type-compatible, see [arithmetic] operators. - Floating point numbers only differ up to a limited precision. -[function]: ./constructs.md#functions +[function]: ./syntax.md#functions [Equality]: #equality diff --git a/doc/manual/src/language/scope.md b/doc/manual/src/language/scope.md new file mode 100644 index 00000000000..5c6aed38d31 --- /dev/null +++ b/doc/manual/src/language/scope.md @@ -0,0 +1,14 @@ +# Scoping rules + +Nix is [statically scoped](https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scope), but with multiple scopes and shadowing rules. + +* primary scope: explicitly-bound variables + * [`let`](./syntax.md#let-expressions) + * [`inherit`](./syntax.md#inheriting-attributes) + * [function](./syntax.md#functions) arguments + +* secondary scope: implicitly-bound variables + * [`with`](./syntax.md#with-expressions) + +Primary scope takes precedence over secondary scope. +See [`with`](./syntax.md#with-expressions) for a detailed example. diff --git a/doc/manual/src/language/string-context.md b/doc/manual/src/language/string-context.md index 88ae0d8b0a1..6a3482cfd95 100644 --- a/doc/manual/src/language/string-context.md +++ b/doc/manual/src/language/string-context.md @@ -111,7 +111,7 @@ It creates an [attribute set] representing the string context, which can be insp [`builtins.hasContext`]: ./builtins.md#builtins-hasContext [`builtins.getContext`]: ./builtins.md#builtins-getContext -[attribute set]: ./values.md#attribute-set +[attribute set]: ./types.md#attribute-set ## Clearing string contexts diff --git a/doc/manual/src/language/string-interpolation.md b/doc/manual/src/language/string-interpolation.md index 7b4a5cfef2d..1778bdfa06b 100644 --- a/doc/manual/src/language/string-interpolation.md +++ b/doc/manual/src/language/string-interpolation.md @@ -4,9 +4,9 @@ String interpolation is a language feature where a [string], [path], or [attribu Such a construct is called *interpolated string*, and the expression inside is an [interpolated expression](#interpolated-expression). -[string]: ./values.md#type-string -[path]: ./values.md#type-path -[attribute set]: ./values.md#attribute-set +[string]: ./types.md#type-string +[path]: ./types.md#type-path +[attribute set]: ./types.md#attribute-set ## Examples diff --git a/doc/manual/src/language/syntax.md b/doc/manual/src/language/syntax.md new file mode 100644 index 00000000000..238c502f979 --- /dev/null +++ b/doc/manual/src/language/syntax.md @@ -0,0 +1,841 @@ +# Language Constructs + +This section covers syntax and semantics of the Nix language. + +## Basic Literals + +### String {#string-literal} + + *Strings* can be written in three ways. + + The most common way is to enclose the string between double quotes, e.g., `"foo bar"`. + Strings can span multiple lines. + The results of other expressions can be included into a string by enclosing them in `${ }`, a feature known as [string interpolation]. + + [string interpolation]: ./string-interpolation.md + + The following must be escaped to represent them within a string, by prefixing with a backslash (`\`): + + - Double quote (`"`) + + > **Example** + > + > ```nix + > "\"" + > ``` + > + > "\"" + + - Backslash (`\`) + + > **Example** + > + > ```nix + > "\\" + > ``` + > + > "\\" + + - Dollar sign followed by an opening curly bracket (`${`) – "dollar-curly" + + > **Example** + > + > ```nix + > "\${" + > ``` + > + > "\${" + + The newline, carriage return, and tab characters can be written as `\n`, `\r` and `\t`, respectively. + + A "double-dollar-curly" (`$${`) can be written literally. + + > **Example** + > + > ```nix + > "$${" + > ``` + > + > "$\${" + + String values are output on the terminal with Nix-specific escaping. + Strings written to files will contain the characters encoded by the escaping. + + The second way to write string literals is as an *indented string*, which is enclosed between pairs of *double single-quotes* (`''`), like so: + + ```nix + '' + This is the first line. + This is the second line. + This is the third line. + '' + ``` + + This kind of string literal intelligently strips indentation from + the start of each line. To be precise, it strips from each line a + number of spaces equal to the minimal indentation of the string as a + whole (disregarding the indentation of empty lines). For instance, + the first and second line are indented two spaces, while the third + line is indented four spaces. Thus, two spaces are stripped from + each line, so the resulting string is + + ```nix + "This is the first line.\nThis is the second line.\n This is the third line.\n" + ``` + + > **Note** + > + > Whitespace and newline following the opening `''` is ignored if there is no non-whitespace text on the initial line. + + > **Warning** + > + > Prefixed tab characters are not stripped. + > + > > **Example** + > > + > > The following indented string is prefixed with tabs: + > > + > > '' + > > all: + > > @echo hello + > > '' + > > + > > "\tall:\n\t\t@echo hello\n" + + Indented strings support [string interpolation]. + + The following must be escaped to represent them in an indented string: + + - `$` is escaped by prefixing it with two single quotes (`''`) + + > **Example** + > + > ```nix + > '' + > ''$ + > '' + > ``` + > + > "$\n" + + - `''` is escaped by prefixing it with one single quote (`'`) + + > **Example** + > + > ```nix + > '' + > ''' + > '' + > ``` + > + > "''\n" + + These special characters are escaped as follows: + - Linefeed (`\n`): `''\n` + - Carriage return (`\r`): `''\r` + - Tab (`\t`): `''\t` + + `''\` escapes any other character. + + A "double-dollar-curly" (`$${`) can be written literally. + + > **Example** + > + > ```nix + > '' + > $${ + > '' + > ``` + > + > "$\${\n" + + Indented strings are primarily useful in that they allow multi-line + string literals to follow the indentation of the enclosing Nix + expression, and that less escaping is typically necessary for + strings representing languages such as shell scripts and + configuration files because `''` is much less common than `"`. + Example: + + ```nix + stdenv.mkDerivation { + ... + postInstall = + '' + mkdir $out/bin $out/etc + cp foo $out/bin + echo "Hello World" > $out/etc/foo.conf + ${if enableBar then "cp bar $out/bin" else ""} + ''; + ... + } + ``` + + Finally, as a convenience, *URIs* as defined in appendix B of + [RFC 2396](http://www.ietf.org/rfc/rfc2396.txt) can be written *as + is*, without quotes. For instance, the string + `"http://example.org/foo.tar.bz2"` can also be written as + `http://example.org/foo.tar.bz2`. + +### Number {#number-literal} + + + + Numbers, which can be *integers* (like `123`) or *floating point* + (like `123.43` or `.27e13`). + + See [arithmetic] and [comparison] operators for semantics. + + [arithmetic]: ./operators.md#arithmetic + [comparison]: ./operators.md#comparison + +### Path {#path-literal} + + *Paths* are distinct from strings and can be expressed by path literals such as `./builder.sh`. + + Paths are suitable for referring to local files, and are often preferable over strings. + - Path values do not contain trailing slashes, `.` and `..`, as they are resolved when evaluating a path literal. + - Path literals are automatically resolved relative to their [base directory](@docroot@/glossary.md#gloss-base-directory). + - The files referred to by path values are automatically copied into the Nix store when used in a string interpolation or concatenation. + - Tooling can recognize path literals and provide additional features, such as autocompletion, refactoring automation and jump-to-file. + + A path literal must contain at least one slash to be recognised as such. + For instance, `builder.sh` is not a path: + it's parsed as an expression that selects the attribute `sh` from the variable `builder`. + + Path literals may also refer to absolute paths by starting with a slash. + + > **Note** + > + > Absolute paths make expressions less portable. + > In the case where a function translates a path literal into an absolute path string for a configuration file, it is recommended to write a string literal instead. + > This avoids some confusion about whether files at that location will be used during evaluation. + > It also avoids unintentional situations where some function might try to copy everything at the location into the store. + + If the first component of a path is a `~`, it is interpreted such that the rest of the path were relative to the user's home directory. + For example, `~/foo` would be equivalent to `/home/edolstra/foo` for a user whose home directory is `/home/edolstra`. + Path literals that start with `~` are not allowed in [pure](@docroot@/command-ref/conf-file.md#conf-pure-eval) evaluation. + + Paths can be used in [string interpolation] and string concatenation. + For instance, evaluating `"${./foo.txt}"` will cause `foo.txt` from the same directory to be copied into the Nix store and result in the string `"/nix/store/-foo.txt"`. + + Note that the Nix language assumes that all input files will remain _unchanged_ while evaluating a Nix expression. + For example, assume you used a file path in an interpolated string during a `nix repl` session. + Later in the same session, after having changed the file contents, evaluating the interpolated string with the file path again might not return a new [store path], since Nix might not re-read the file contents. Use `:r` to reset the repl as needed. + + [store path]: @docroot@/store/store-path.md + + Path literals can also include [string interpolation], besides being [interpolated into other expressions]. + + [interpolated into other expressions]: ./string-interpolation.md#interpolated-expressions + + At least one slash (`/`) must appear *before* any interpolated expression for the result to be recognized as a path. + + `a.${foo}/b.${bar}` is a syntactically valid number division operation. + `./a.${foo}/b.${bar}` is a path. + + [Lookup path](./constructs/lookup-path.md) literals such as `` also resolve to path values. + +## List {#list-literal} + +Lists are formed by enclosing a whitespace-separated list of values +between square brackets. For example, + +```nix +[ 123 ./foo.nix "abc" (f { x = y; }) ] +``` + +defines a list of four elements, the last being the result of a call to +the function `f`. Note that function calls have to be enclosed in +parentheses. If they had been omitted, e.g., + +```nix +[ 123 ./foo.nix "abc" f { x = y; } ] +``` + +the result would be a list of five elements, the fourth one being a +function and the fifth being a set. + +Note that lists are only lazy in values, and they are strict in length. + +Elements in a list can be accessed using [`builtins.elemAt`](./builtins.md#builtins-elemAt). + +## Attribute Set {#attrs-literal} + +An attribute set is a collection of name-value-pairs (called *attributes*) enclosed in curly brackets (`{ }`). + +An attribute name can be an identifier or a [string](#string). +An identifier must start with a letter (`a-z`, `A-Z`) or underscore (`_`), and can otherwise contain letters (`a-z`, `A-Z`), numbers (`0-9`), underscores (`_`), apostrophes (`'`), or dashes (`-`). + +> **Syntax** +> +> *name* = *identifier* | *string* \ +> *identifier* ~ `[a-zA-Z_][a-zA-Z0-9_'-]*` + +Names and values are separated by an equal sign (`=`). +Each value is an arbitrary expression terminated by a semicolon (`;`). + +> **Syntax** +> +> *attrset* = `{` [ *name* `=` *expr* `;` ]... `}` + +Attributes can appear in any order. +An attribute name may only occur once. + +Example: + +```nix +{ + x = 123; + text = "Hello"; + y = f { bla = 456; }; +} +``` + +This defines a set with attributes named `x`, `text`, `y`. + +Attributes can be accessed with the [`.` operator](./operators.md#attribute-selection). + +Example: + +```nix +{ a = "Foo"; b = "Bar"; }.a +``` + +This evaluates to `"Foo"`. + +It is possible to provide a default value in an attribute selection using the `or` keyword. + +Example: + +```nix +{ a = "Foo"; b = "Bar"; }.c or "Xyzzy" +``` + +```nix +{ a = "Foo"; b = "Bar"; }.c.d.e.f.g or "Xyzzy" +``` + +will both evaluate to `"Xyzzy"` because there is no `c` attribute in the set. + +You can use arbitrary double-quoted strings as attribute names: + +```nix +{ "$!@#?" = 123; }."$!@#?" +``` + +```nix +let bar = "bar"; in +{ "foo ${bar}" = 123; }."foo ${bar}" +``` + +Both will evaluate to `123`. + +Attribute names support [string interpolation]: + +```nix +let bar = "foo"; in +{ foo = 123; }.${bar} +``` + +```nix +let bar = "foo"; in +{ ${bar} = 123; }.foo +``` + +Both will evaluate to `123`. + +In the special case where an attribute name inside of a set declaration +evaluates to `null` (which is normally an error, as `null` cannot be coerced to +a string), that attribute is simply not added to the set: + +```nix +{ ${if foo then "bar" else null} = true; } +``` + +This will evaluate to `{}` if `foo` evaluates to `false`. + +A set that has a `__functor` attribute whose value is callable (i.e. is +itself a function or a set with a `__functor` attribute whose value is +callable) can be applied as if it were a function, with the set itself +passed in first , e.g., + +```nix +let add = { __functor = self: x: x + self.x; }; + inc = add // { x = 1; }; +in inc 1 +``` + +evaluates to `2`. This can be used to attach metadata to a function +without the caller needing to treat it specially, or to implement a form +of object-oriented programming, for example. + +## Recursive sets + +Recursive sets are like normal [attribute sets](./types.md#attribute-set), but the attributes can refer to each other. + +> *rec-attrset* = `rec {` [ *name* `=` *expr* `;` `]`... `}` + +Example: + +```nix +rec { + x = y; + y = 123; +}.x +``` + +This evaluates to `123`. + +Note that without `rec` the binding `x = y;` would +refer to the variable `y` in the surrounding scope, if one exists, and +would be invalid if no such variable exists. That is, in a normal +(non-recursive) set, attributes are not added to the lexical scope; in a +recursive set, they are. + +Recursive sets of course introduce the danger of infinite recursion. For +example, the expression + +```nix +rec { + x = y; + y = x; +}.x +``` + +will crash with an `infinite recursion encountered` error message. + +## Let-expressions + +A let-expression allows you to define local variables for an expression. + +> *let-in* = `let` [ *identifier* = *expr* ]... `in` *expr* + +Example: + +```nix +let + x = "foo"; + y = "bar"; +in x + y +``` + +This evaluates to `"foobar"`. + +## Inheriting attributes + +When defining an [attribute set](./types.md#attribute-set) or in a [let-expression](#let-expressions) it is often convenient to copy variables from the surrounding lexical scope (e.g., when you want to propagate attributes). +This can be shortened using the `inherit` keyword. + +Example: + +```nix +let x = 123; in +{ + inherit x; + y = 456; +} +``` + +is equivalent to + +```nix +let x = 123; in +{ + x = x; + y = 456; +} +``` + +and both evaluate to `{ x = 123; y = 456; }`. + +> **Note** +> +> This works because `x` is added to the lexical scope by the `let` construct. + +It is also possible to inherit attributes from another attribute set. + +Example: + +In this fragment from `all-packages.nix`, + +```nix +graphviz = (import ../tools/graphics/graphviz) { + inherit fetchurl stdenv libpng libjpeg expat x11 yacc; + inherit (xorg) libXaw; +}; + +xorg = { + libX11 = ...; + libXaw = ...; + ... +} + +libpng = ...; +libjpg = ...; +... +``` + +the set used in the function call to the function defined in +`../tools/graphics/graphviz` inherits a number of variables from the +surrounding scope (`fetchurl` ... `yacc`), but also inherits `libXaw` +(the X Athena Widgets) from the `xorg` set. + +Summarizing the fragment + +```nix +... +inherit x y z; +inherit (src-set) a b c; +... +``` + +is equivalent to + +```nix +... +x = x; y = y; z = z; +a = src-set.a; b = src-set.b; c = src-set.c; +... +``` + +when used while defining local variables in a let-expression or while +defining a set. + +In a `let` expression, `inherit` can be used to selectively bring specific attributes of a set into scope. For example + + +```nix +let + x = { a = 1; b = 2; }; + inherit (builtins) attrNames; +in +{ + names = attrNames x; +} +``` + +is equivalent to + +```nix +let + x = { a = 1; b = 2; }; +in +{ + names = builtins.attrNames x; +} +``` + +both evaluate to `{ names = [ "a" "b" ]; }`. + +## Functions + +Functions have the following form: + +```nix +pattern: body +``` + +The pattern specifies what the argument of the function must look like, +and binds variables in the body to (parts of) the argument. There are +three kinds of patterns: + + - If a pattern is a single identifier, then the function matches any + argument. Example: + + ```nix + let negate = x: !x; + concat = x: y: x + y; + in if negate true then concat "foo" "bar" else "" + ``` + + Note that `concat` is a function that takes one argument and returns + a function that takes another argument. This allows partial + parameterisation (i.e., only filling some of the arguments of a + function); e.g., + + ```nix + map (concat "foo") [ "bar" "bla" "abc" ] + ``` + + evaluates to `[ "foobar" "foobla" "fooabc" ]`. + + - A *set pattern* of the form `{ name1, name2, …, nameN }` matches a + set containing the listed attributes, and binds the values of those + attributes to variables in the function body. For example, the + function + + ```nix + { x, y, z }: z + y + x + ``` + + can only be called with a set containing exactly the attributes `x`, + `y` and `z`. No other attributes are allowed. If you want to allow + additional arguments, you can use an ellipsis (`...`): + + ```nix + { x, y, z, ... }: z + y + x + ``` + + This works on any set that contains at least the three named + attributes. + + It is possible to provide *default values* for attributes, in + which case they are allowed to be missing. A default value is + specified by writing `name ? e`, where *e* is an arbitrary + expression. For example, + + ```nix + { x, y ? "foo", z ? "bar" }: z + y + x + ``` + + specifies a function that only requires an attribute named `x`, but + optionally accepts `y` and `z`. + + - An `@`-pattern provides a means of referring to the whole value + being matched: + + ```nix + args@{ x, y, z, ... }: z + y + x + args.a + ``` + + but can also be written as: + + ```nix + { x, y, z, ... } @ args: z + y + x + args.a + ``` + + Here `args` is bound to the argument *as passed*, which is further + matched against the pattern `{ x, y, z, ... }`. + The `@`-pattern makes mainly sense with an ellipsis(`...`) as + you can access attribute names as `a`, using `args.a`, which was + given as an additional attribute to the function. + + > **Warning** + > + > `args@` binds the name `args` to the attribute set that is passed to the function. + > In particular, `args` does *not* include any default values specified with `?` in the function's set pattern. + > + > For instance + > + > ```nix + > let + > f = args@{ a ? 23, ... }: [ a args ]; + > in + > f {} + > ``` + > + > is equivalent to + > + > ```nix + > let + > f = args @ { ... }: [ (args.a or 23) args ]; + > in + > f {} + > ``` + > + > and both expressions will evaluate to: + > + > ```nix + > [ 23 {} ] + > ``` + +Note that functions do not have names. If you want to give them a name, +you can bind them to an attribute, e.g., + +```nix +let concat = { x, y }: x + y; +in concat { x = "foo"; y = "bar"; } +``` + +## Conditionals + +Conditionals look like this: + +```nix +if e1 then e2 else e3 +``` + +where *e1* is an expression that should evaluate to a Boolean value +(`true` or `false`). + +## Assertions + +Assertions are generally used to check that certain requirements on or +between features and dependencies hold. They look like this: + +```nix +assert e1; e2 +``` + +where *e1* is an expression that should evaluate to a Boolean value. If +it evaluates to `true`, *e2* is returned; otherwise expression +evaluation is aborted and a backtrace is printed. + +Here is a Nix expression for the Subversion package that shows how +assertions can be used:. + +```nix +{ localServer ? false +, httpServer ? false +, sslSupport ? false +, pythonBindings ? false +, javaSwigBindings ? false +, javahlBindings ? false +, stdenv, fetchurl +, openssl ? null, httpd ? null, db4 ? null, expat, swig ? null, j2sdk ? null +}: + +assert localServer -> db4 != null; ① +assert httpServer -> httpd != null && httpd.expat == expat; ② +assert sslSupport -> openssl != null && (httpServer -> httpd.openssl == openssl); ③ +assert pythonBindings -> swig != null && swig.pythonSupport; +assert javaSwigBindings -> swig != null && swig.javaSupport; +assert javahlBindings -> j2sdk != null; + +stdenv.mkDerivation { + name = "subversion-1.1.1"; + ... + openssl = if sslSupport then openssl else null; ④ + ... +} +``` + +The points of interest are: + +1. This assertion states that if Subversion is to have support for + local repositories, then Berkeley DB is needed. So if the Subversion + function is called with the `localServer` argument set to `true` but + the `db4` argument set to `null`, then the evaluation fails. + + Note that `->` is the [logical + implication](https://en.wikipedia.org/wiki/Truth_table#Logical_implication) + Boolean operation. + +2. This is a more subtle condition: if Subversion is built with Apache + (`httpServer`) support, then the Expat library (an XML library) used + by Subversion should be same as the one used by Apache. This is + because in this configuration Subversion code ends up being linked + with Apache code, and if the Expat libraries do not match, a build- + or runtime link error or incompatibility might occur. + +3. This assertion says that in order for Subversion to have SSL support + (so that it can access `https` URLs), an OpenSSL library must be + passed. Additionally, it says that *if* Apache support is enabled, + then Apache's OpenSSL should match Subversion's. (Note that if + Apache support is not enabled, we don't care about Apache's + OpenSSL.) + +4. The conditional here is not really related to assertions, but is + worth pointing out: it ensures that if SSL support is disabled, then + the Subversion derivation is not dependent on OpenSSL, even if a + non-`null` value was passed. This prevents an unnecessary rebuild of + Subversion if OpenSSL changes. + +## With-expressions + +A *with-expression*, + +```nix +with e1; e2 +``` + +introduces the set *e1* into the lexical scope of the expression *e2*. +For instance, + +```nix +let as = { x = "foo"; y = "bar"; }; +in with as; x + y +``` + +evaluates to `"foobar"` since the `with` adds the `x` and `y` attributes +of `as` to the lexical scope in the expression `x + y`. The most common +use of `with` is in conjunction with the `import` function. E.g., + +```nix +with (import ./definitions.nix); ... +``` + +makes all attributes defined in the file `definitions.nix` available as +if they were defined locally in a `let`-expression. + +The bindings introduced by `with` do not shadow bindings introduced by +other means, e.g. + +```nix +let a = 3; in with { a = 1; }; let a = 4; in with { a = 2; }; ... +``` + +establishes the same scope as + +```nix +let a = 1; in let a = 2; in let a = 3; in let a = 4; in ... +``` + +Variables coming from outer `with` expressions *are* shadowed: + +```nix +with { a = "outer"; }; +with { a = "inner"; }; +a +``` + +Does evaluate to `"inner"`. + +## Comments + +- Inline comments start with `#` and run until the end of the line. + + > **Example** + > + > ```nix + > # A number + > 2 # Equals 1 + 1 + > ``` + > + > ```console + > 2 + > ``` + +- Block comments start with `/*` and run until the next occurrence of `*/`. + + > **Example** + > + > ```nix + > /* + > Block comments + > can span multiple lines. + > */ "hello" + > ``` + > + > ```console + > "hello" + > ``` + + This means that block comments cannot be nested. + + > **Example** + > + > ```nix + > /* /* nope */ */ 1 + > ``` + > + > ```console + > error: syntax error, unexpected '*' + > + > at «string»:1:15: + > + > 1| /* /* nope */ * + > | ^ + > ``` + + Consider escaping nested comments and unescaping them in post-processing. + + > **Example** + > + > ```nix + > /* /* nested *\/ */ 1 + > ``` + > + > ```console + > 1 + > ``` diff --git a/doc/manual/src/language/types.md b/doc/manual/src/language/types.md new file mode 100644 index 00000000000..1b3e6b24793 --- /dev/null +++ b/doc/manual/src/language/types.md @@ -0,0 +1,91 @@ +# Data Types + +Every value in the Nix language has one of the following types: + +* [Integer](#type-int) +* [Float](#type-float) +* [Boolean](#type-bool) +* [String](#type-string) +* [Path](#type-path) +* [Null](#type-null) +* [Attribute set](#type-attrs) +* [List](#type-list) +* [Function](#type-function) +* [External](#type-external) + +## Primitives + +### Integer {#type-int} + +An _integer_ in the Nix language is a signed 64-bit integer. + +Non-negative integers can be expressed as [integer literals](syntax.md#number-literal). +Negative integers are created with the [arithmetic negation operator](./operators.md#arithmetic). +The function [`builtins.isInt`](builtins.md#builtins-isInt) can be used to determine if a value is an integer. + +### Float {#type-float} + +A _float_ in the Nix language is a 64-bit [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) floating-point number. + +Most non-negative floats can be expressed as [float literals](syntax.md#number-literal). +Negative floats are created with the [arithmetic negation operator](./operators.md#arithmetic). +The function [`builtins.isFloat`](builtins.md#builtins-isFloat) can be used to determine if a value is a float. + +### Boolean {#type-bool} + +A _boolean_ in the Nix language is one of _true_ or _false_. + + + +These values are available as attributes of [`builtins`](builtin-constants.md#builtins-builtins) as [`builtins.true`](builtin-constants.md#builtins-true) and [`builtins.false`](builtin-constants.md#builtins-false). +The function [`builtins.isBool`](builtins.md#builtins-isBool) can be used to determine if a value is a boolean. + +### String {#type-string} + +A _string_ in the Nix language is an immutable, finite-length sequence of bytes, along with a [string context](string-context.md). +Nix does not assume or support working natively with character encodings. + +String values without string context can be expressed as [string literals](syntax.md#string-literal). +The function [`builtins.isString`](builtins.md#builtins-isString) can be used to determine if a value is a string. + +### Path {#type-path} + + + +The function [`builtins.isPath`](builtins.md#builtins-isPath) can be used to determine if a value is a path. + +### Null {#type-null} + +There is a single value of type _null_ in the Nix language. + + + +This value is available as an attribute on the [`builtins`](builtin-constants.md#builtins-builtins) attribute set as [`builtins.null`](builtin-constants.md#builtins-null). + +## Compound values + +### Attribute set {#type-attrs} + + + +An attribute set can be constructed with an [attribute set literal](syntax.md#attrs-literal). +The function [`builtins.isAttrs`](builtins.md#builtins-isAttrs) can be used to determine if a value is an attribute set. + +### List {#type-list} + + + +A list can be constructed with a [list literal](syntax.md#list-literal). +The function [`builtins.isList`](builtins.md#builtins-isList) can be used to determine if a value is a list. + +## Function {#type-function} + + + +A function can be constructed with a [function expression](syntax.md#functions). +The function [`builtins.isFunction`](builtins.md#builtins-isFunction) can be used to determine if a value is a function. + +## External {#type-external} + +An _external_ value is an opaque value created by a Nix [plugin](../command-ref/conf-file.md#conf-plugin-files). +Such a value can be substituted in Nix expressions but only created and used by plugin code. diff --git a/doc/manual/src/language/values.md b/doc/manual/src/language/values.md deleted file mode 100644 index ddd55a47e18..00000000000 --- a/doc/manual/src/language/values.md +++ /dev/null @@ -1,374 +0,0 @@ -# Data Types - -## Primitives - -- String - - *Strings* can be written in three ways. - - The most common way is to enclose the string between double quotes, e.g., `"foo bar"`. - Strings can span multiple lines. - The results of other expressions can be included into a string by enclosing them in `${ }`, a feature known as [string interpolation]. - - [string interpolation]: ./string-interpolation.md - - The following must be escaped to represent them within a string, by prefixing with a backslash (`\`): - - - Double quote (`"`) - - > **Example** - > - > ```nix - > "\"" - > ``` - > - > "\"" - - - Backslash (`\`) - - > **Example** - > - > ```nix - > "\\" - > ``` - > - > "\\" - - - Dollar sign followed by an opening curly bracket (`${`) – "dollar-curly" - - > **Example** - > - > ```nix - > "\${" - > ``` - > - > "\${" - - The newline, carriage return, and tab characters can be written as `\n`, `\r` and `\t`, respectively. - - A "double-dollar-curly" (`$${`) can be written literally. - - > **Example** - > - > ```nix - > "$${" - > ``` - > - > "$\${" - - String values are output on the terminal with Nix-specific escaping. - Strings written to files will contain the characters encoded by the escaping. - - The second way to write string literals is as an *indented string*, which is enclosed between pairs of *double single-quotes* (`''`), like so: - - ```nix - '' - This is the first line. - This is the second line. - This is the third line. - '' - ``` - - This kind of string literal intelligently strips indentation from - the start of each line. To be precise, it strips from each line a - number of spaces equal to the minimal indentation of the string as a - whole (disregarding the indentation of empty lines). For instance, - the first and second line are indented two spaces, while the third - line is indented four spaces. Thus, two spaces are stripped from - each line, so the resulting string is - - ```nix - "This is the first line.\nThis is the second line.\n This is the third line.\n" - ``` - - > **Note** - > - > Whitespace and newline following the opening `''` is ignored if there is no non-whitespace text on the initial line. - - > **Warning** - > - > Prefixed tab characters are not stripped. - > - > > **Example** - > > - > > The following indented string is prefixed with tabs: - > > - > > '' - > > all: - > > @echo hello - > > '' - > > - > > "\tall:\n\t\t@echo hello\n" - - Indented strings support [string interpolation]. - - The following must be escaped to represent them in an indented string: - - - `$` is escaped by prefixing it with two single quotes (`''`) - - > **Example** - > - > ```nix - > '' - > ''$ - > '' - > ``` - > - > "$\n" - - - `''` is escaped by prefixing it with one single quote (`'`) - - > **Example** - > - > ```nix - > '' - > ''' - > '' - > ``` - > - > "''\n" - - These special characters are escaped as follows: - - Linefeed (`\n`): `''\n` - - Carriage return (`\r`): `''\r` - - Tab (`\t`): `''\t` - - `''\` escapes any other character. - - A "double-dollar-curly" (`$${`) can be written literally. - - > **Example** - > - > ```nix - > '' - > $${ - > '' - > ``` - > - > "$\${\n" - - Indented strings are primarily useful in that they allow multi-line - string literals to follow the indentation of the enclosing Nix - expression, and that less escaping is typically necessary for - strings representing languages such as shell scripts and - configuration files because `''` is much less common than `"`. - Example: - - ```nix - stdenv.mkDerivation { - ... - postInstall = - '' - mkdir $out/bin $out/etc - cp foo $out/bin - echo "Hello World" > $out/etc/foo.conf - ${if enableBar then "cp bar $out/bin" else ""} - ''; - ... - } - ``` - - Finally, as a convenience, *URIs* as defined in appendix B of - [RFC 2396](http://www.ietf.org/rfc/rfc2396.txt) can be written *as - is*, without quotes. For instance, the string - `"http://example.org/foo.tar.bz2"` can also be written as - `http://example.org/foo.tar.bz2`. - -- Number - - Numbers, which can be *integers* (like `123`) or *floating point* - (like `123.43` or `.27e13`). - - See [arithmetic] and [comparison] operators for semantics. - - [arithmetic]: ./operators.md#arithmetic - [comparison]: ./operators.md#comparison - -- Path - - *Paths* are distinct from strings and can be expressed by path literals such as `./builder.sh`. - - Paths are suitable for referring to local files, and are often preferable over strings. - - Path values do not contain trailing slashes, `.` and `..`, as they are resolved when evaluating a path literal. - - Path literals are automatically resolved relative to their [base directory](@docroot@/glossary.md#gloss-base-directory). - - The files referred to by path values are automatically copied into the Nix store when used in a string interpolation or concatenation. - - Tooling can recognize path literals and provide additional features, such as autocompletion, refactoring automation and jump-to-file. - - A path literal must contain at least one slash to be recognised as such. - For instance, `builder.sh` is not a path: - it's parsed as an expression that selects the attribute `sh` from the variable `builder`. - - Path literals may also refer to absolute paths by starting with a slash. - - > **Note** - > - > Absolute paths make expressions less portable. - > In the case where a function translates a path literal into an absolute path string for a configuration file, it is recommended to write a string literal instead. - > This avoids some confusion about whether files at that location will be used during evaluation. - > It also avoids unintentional situations where some function might try to copy everything at the location into the store. - - If the first component of a path is a `~`, it is interpreted such that the rest of the path were relative to the user's home directory. - For example, `~/foo` would be equivalent to `/home/edolstra/foo` for a user whose home directory is `/home/edolstra`. - Path literals that start with `~` are not allowed in [pure](@docroot@/command-ref/conf-file.md#conf-pure-eval) evaluation. - - Paths can be used in [string interpolation] and string concatenation. - For instance, evaluating `"${./foo.txt}"` will cause `foo.txt` from the same directory to be copied into the Nix store and result in the string `"/nix/store/-foo.txt"`. - - Note that the Nix language assumes that all input files will remain _unchanged_ while evaluating a Nix expression. - For example, assume you used a file path in an interpolated string during a `nix repl` session. - Later in the same session, after having changed the file contents, evaluating the interpolated string with the file path again might not return a new [store path], since Nix might not re-read the file contents. Use `:r` to reset the repl as needed. - - [store path]: @docroot@/store/store-path.md - - Path literals can also include [string interpolation], besides being [interpolated into other expressions]. - - [interpolated into other expressions]: ./string-interpolation.md#interpolated-expressions - - At least one slash (`/`) must appear *before* any interpolated expression for the result to be recognized as a path. - - `a.${foo}/b.${bar}` is a syntactically valid number division operation. - `./a.${foo}/b.${bar}` is a path. - - [Lookup path](./constructs/lookup-path.md) literals such as `` also resolve to path values. - -- Boolean - - *Booleans* with values `true` and `false`. - -- Null - - The null value, denoted as `null`. - -## List - -Lists are formed by enclosing a whitespace-separated list of values -between square brackets. For example, - -```nix -[ 123 ./foo.nix "abc" (f { x = y; }) ] -``` - -defines a list of four elements, the last being the result of a call to -the function `f`. Note that function calls have to be enclosed in -parentheses. If they had been omitted, e.g., - -```nix -[ 123 ./foo.nix "abc" f { x = y; } ] -``` - -the result would be a list of five elements, the fourth one being a -function and the fifth being a set. - -Note that lists are only lazy in values, and they are strict in length. - -Elements in a list can be accessed using [`builtins.elemAt`](./builtins.md#builtins-elemAt). - -## Attribute Set - -An attribute set is a collection of name-value-pairs (called *attributes*) enclosed in curly brackets (`{ }`). - -An attribute name can be an identifier or a [string](#string). -An identifier must start with a letter (`a-z`, `A-Z`) or underscore (`_`), and can otherwise contain letters (`a-z`, `A-Z`), numbers (`0-9`), underscores (`_`), apostrophes (`'`), or dashes (`-`). - -> **Syntax** -> -> *name* = *identifier* | *string* \ -> *identifier* ~ `[a-zA-Z_][a-zA-Z0-9_'-]*` - -Names and values are separated by an equal sign (`=`). -Each value is an arbitrary expression terminated by a semicolon (`;`). - -> **Syntax** -> -> *attrset* = `{` [ *name* `=` *expr* `;` ]... `}` - -Attributes can appear in any order. -An attribute name may only occur once. - -Example: - -```nix -{ - x = 123; - text = "Hello"; - y = f { bla = 456; }; -} -``` - -This defines a set with attributes named `x`, `text`, `y`. - -Attributes can be accessed with the [`.` operator](./operators.md#attribute-selection). - -Example: - -```nix -{ a = "Foo"; b = "Bar"; }.a -``` - -This evaluates to `"Foo"`. - -It is possible to provide a default value in an attribute selection using the `or` keyword. - -Example: - -```nix -{ a = "Foo"; b = "Bar"; }.c or "Xyzzy" -``` - -```nix -{ a = "Foo"; b = "Bar"; }.c.d.e.f.g or "Xyzzy" -``` - -will both evaluate to `"Xyzzy"` because there is no `c` attribute in the set. - -You can use arbitrary double-quoted strings as attribute names: - -```nix -{ "$!@#?" = 123; }."$!@#?" -``` - -```nix -let bar = "bar"; in -{ "foo ${bar}" = 123; }."foo ${bar}" -``` - -Both will evaluate to `123`. - -Attribute names support [string interpolation]: - -```nix -let bar = "foo"; in -{ foo = 123; }.${bar} -``` - -```nix -let bar = "foo"; in -{ ${bar} = 123; }.foo -``` - -Both will evaluate to `123`. - -In the special case where an attribute name inside of a set declaration -evaluates to `null` (which is normally an error, as `null` cannot be coerced to -a string), that attribute is simply not added to the set: - -```nix -{ ${if foo then "bar" else null} = true; } -``` - -This will evaluate to `{}` if `foo` evaluates to `false`. - -A set that has a `__functor` attribute whose value is callable (i.e. is -itself a function or a set with a `__functor` attribute whose value is -callable) can be applied as if it were a function, with the set itself -passed in first , e.g., - -```nix -let add = { __functor = self: x: x + self.x; }; - inc = add // { x = 1; }; -in inc 1 -``` - -evaluates to `2`. This can be used to attach metadata to a function -without the caller needing to treat it specially, or to implement a form -of object-oriented programming, for example. diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index e740d62b293..5dced249ad3 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -74,7 +74,7 @@ MixEvalArgs::MixEvalArgs() .description = R"( Add *path* to the Nix search path. The Nix search path is initialized from the colon-separated [`NIX_PATH`](@docroot@/command-ref/env-common.md#env-NIX_PATH) environment - variable, and is used to look up the location of Nix expressions using [paths](@docroot@/language/values.md#type-path) enclosed in angle + variable, and is used to look up the location of Nix expressions using [paths](@docroot@/language/types.md#type-path) enclosed in angle brackets (i.e., ``). For instance, passing diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 8ce7fb6f394..8231d8942be 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -742,11 +742,12 @@ static RegisterPrimOp primop_genericClosure(PrimOp { Each attribute set in the list `startSet` and the list returned by `operator` must have an attribute `key`, which must support equality comparison. The value of `key` can be one of the following types: - - [Number](@docroot@/language/values.md#type-number) - - [Boolean](@docroot@/language/values.md#type-boolean) - - [String](@docroot@/language/values.md#type-string) - - [Path](@docroot@/language/values.md#type-path) - - [List](@docroot@/language/values.md#list) + - [Int](@docroot@/language/types.md#type-int) + - [Float](@docroot@/language/types.md#type-float) + - [Boolean](@docroot@/language/types.md#type-boolean) + - [String](@docroot@/language/types.md#type-string) + - [Path](@docroot@/language/types.md#type-path) + - [List](@docroot@/language/types.md#list) The result is produced by calling the `operator` on each `item` that has not been called yet, including newly added items, until no new items are added. Items are compared by their `key` attribute. @@ -1751,7 +1752,7 @@ static RegisterPrimOp primop_baseNameOf({ .name = "baseNameOf", .args = {"x"}, .doc = R"( - Return the *base name* of either a [path value](@docroot@/language/values.md#type-path) *x* or a string *x*, depending on which type is passed, and according to the following rules. + Return the *base name* of either a [path value](@docroot@/language/types.md#type-path) *x* or a string *x*, depending on which type is passed, and according to the following rules. For a path value, the *base name* is considered to be the part of the path after the last directory separator, including any file extensions. This is the simple case, as path values don't have trailing slashes. @@ -1891,7 +1892,7 @@ static RegisterPrimOp primop_findFile(PrimOp { .doc = R"( Find *lookup-path* in *search-path*. - A search path is represented list of [attribute sets](./values.md#attribute-set) with two attributes: + A search path is represented list of [attribute sets](./types.md#attribute-set) with two attributes: - `prefix` is a relative path. - `path` denotes a file system location The exact syntax depends on the command line interface. @@ -1912,7 +1913,7 @@ static RegisterPrimOp primop_findFile(PrimOp { } ``` - The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/values.html#type-path) of the match: + The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/types.md#type-path) of the match: - If *lookup-path* matches `prefix`, then the remainder of *lookup-path* (the "suffix") is searched for within the directory denoted by `path`. Note that the `path` may need to be downloaded at this point to look inside. @@ -2345,7 +2346,7 @@ static RegisterPrimOp primop_toFile({ ``` Note that `${configFile}` is a - [string interpolation](@docroot@/language/values.md#type-string), so the result of the + [string interpolation](@docroot@/language/types.md#type-string), so the result of the expression `configFile` (i.e., a path like `/nix/store/m7p7jfny445k...-foo.conf`) will be spliced into the resulting string. @@ -4609,7 +4610,7 @@ void EvalState::createBaseEnv() It can be returned by [comparison operators](@docroot@/language/operators.md#Comparison) and used in - [conditional expressions](@docroot@/language/constructs.md#Conditionals). + [conditional expressions](@docroot@/language/syntax.md#Conditionals). The name `true` is not special, and can be shadowed: @@ -4629,7 +4630,7 @@ void EvalState::createBaseEnv() It can be returned by [comparison operators](@docroot@/language/operators.md#Comparison) and used in - [conditional expressions](@docroot@/language/constructs.md#Conditionals). + [conditional expressions](@docroot@/language/syntax.md#Conditionals). The name `false` is not special, and can be shadowed: diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 2ea1e15ed8b..500064ceedc 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -851,10 +851,10 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink } void createRegularFile( - const Path & path, + const CanonPath & path, std::function func) override { - auto pathComponents = tokenizeString>(path, "/"); + auto pathComponents = tokenizeString>(path.rel(), "/"); if (!prepareDirs(pathComponents, false)) return; git_writestream * stream = nullptr; @@ -862,11 +862,11 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink throw Error("creating a blob stream object: %s", git_error_last()->message); struct CRF : CreateRegularFileSink { - const Path & path; + const CanonPath & path; GitFileSystemObjectSinkImpl & back; git_writestream * stream; bool executable = false; - CRF(const Path & path, GitFileSystemObjectSinkImpl & back, git_writestream * stream) + CRF(const CanonPath & path, GitFileSystemObjectSinkImpl & back, git_writestream * stream) : path(path), back(back), stream(stream) {} void operator () (std::string_view data) override @@ -891,15 +891,15 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink : GIT_FILEMODE_BLOB); } - void createDirectory(const Path & path) override + void createDirectory(const CanonPath & path) override { - auto pathComponents = tokenizeString>(path, "/"); + auto pathComponents = tokenizeString>(path.rel(), "/"); (void) prepareDirs(pathComponents, true); } - void createSymlink(const Path & path, const std::string & target) override + void createSymlink(const CanonPath & path, const std::string & target) override { - auto pathComponents = tokenizeString>(path, "/"); + auto pathComponents = tokenizeString>(path.rel(), "/"); if (!prepareDirs(pathComponents, false)) return; git_oid oid; diff --git a/src/libstore/nar-accessor.cc b/src/libstore/nar-accessor.cc index cecf8148f45..b1079b027d6 100644 --- a/src/libstore/nar-accessor.cc +++ b/src/libstore/nar-accessor.cc @@ -71,9 +71,11 @@ struct NarAccessor : public SourceAccessor : acc(acc), source(source) { } - NarMember & createMember(const Path & path, NarMember member) + NarMember & createMember(const CanonPath & path, NarMember member) { - size_t level = std::count(path.begin(), path.end(), '/'); + size_t level = 0; + for (auto _ : path) ++level; + while (parents.size() > level) parents.pop(); if (parents.empty()) { @@ -83,14 +85,14 @@ struct NarAccessor : public SourceAccessor } else { if (parents.top()->stat.type != Type::tDirectory) throw Error("NAR file missing parent directory of path '%s'", path); - auto result = parents.top()->children.emplace(baseNameOf(path), std::move(member)); + auto result = parents.top()->children.emplace(*path.baseName(), std::move(member)); auto & ref = result.first->second; parents.push(&ref); return ref; } } - void createDirectory(const Path & path) override + void createDirectory(const CanonPath & path) override { createMember(path, NarMember{ .stat = { .type = Type::tDirectory, @@ -100,7 +102,7 @@ struct NarAccessor : public SourceAccessor } }); } - void createRegularFile(const Path & path, std::function func) override + void createRegularFile(const CanonPath & path, std::function func) override { auto & nm = createMember(path, NarMember{ .stat = { .type = Type::tRegular, @@ -112,7 +114,7 @@ struct NarAccessor : public SourceAccessor func(nmc); } - void createSymlink(const Path & path, const std::string & target) override + void createSymlink(const CanonPath & path, const std::string & target) override { createMember(path, NarMember{ diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc index 22be392d465..e2ebcda0c57 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -165,7 +165,7 @@ struct CaseInsensitiveCompare }; -static void parse(FileSystemObjectSink & sink, Source & source, const Path & path) +static void parse(FileSystemObjectSink & sink, Source & source, const CanonPath & path) { std::string s; @@ -246,7 +246,7 @@ static void parse(FileSystemObjectSink & sink, Source & source, const Path & pat } } else if (s == "node") { if (name.empty()) throw badArchive("entry name missing"); - parse(sink, source, path + "/" + name); + parse(sink, source, path / name); } else throw badArchive("unknown field " + s); } @@ -290,11 +290,11 @@ void parseDump(FileSystemObjectSink & sink, Source & source) } if (version != narVersionMagic1) throw badArchive("input doesn't look like a Nix archive"); - parse(sink, source, ""); + parse(sink, source, CanonPath::root); } -void restorePath(const Path & path, Source & source) +void restorePath(const std::filesystem::path & path, Source & source) { RestoreSink sink; sink.dstPath = path; diff --git a/src/libutil/archive.hh b/src/libutil/archive.hh index bd70072cec3..2da8f0cb185 100644 --- a/src/libutil/archive.hh +++ b/src/libutil/archive.hh @@ -75,7 +75,7 @@ void dumpString(std::string_view s, Sink & sink); void parseDump(FileSystemObjectSink & sink, Source & source); -void restorePath(const Path & path, Source & source); +void restorePath(const std::filesystem::path & path, Source & source); /** * Read a NAR from 'source' and write it to 'sink'. diff --git a/src/libutil/fs-sink.cc b/src/libutil/fs-sink.cc index a6a74373767..597272ec98c 100644 --- a/src/libutil/fs-sink.cc +++ b/src/libutil/fs-sink.cc @@ -14,7 +14,7 @@ namespace nix { void copyRecursive( SourceAccessor & accessor, const CanonPath & from, - FileSystemObjectSink & sink, const Path & to) + FileSystemObjectSink & sink, const CanonPath & to) { auto stat = accessor.lstat(from); @@ -43,7 +43,7 @@ void copyRecursive( for (auto & [name, _] : accessor.readDirectory(from)) { copyRecursive( accessor, from / name, - sink, to + "/" + name); + sink, to / name); break; } break; @@ -69,17 +69,9 @@ static RestoreSinkSettings restoreSinkSettings; static GlobalConfig::Register r1(&restoreSinkSettings); -void RestoreSink::createDirectory(const Path & path) +void RestoreSink::createDirectory(const CanonPath & path) { - Path p = dstPath + path; - if ( -#ifndef _WIN32 // TODO abstract mkdir perms for Windows - mkdir(p.c_str(), 0777) == -1 -#else - !CreateDirectoryW(pathNG(p).c_str(), NULL) -#endif - ) - throw NativeSysError("creating directory '%1%'", p); + std::filesystem::create_directory(dstPath / path.rel()); }; struct RestoreRegularFile : CreateRegularFileSink { @@ -90,13 +82,18 @@ struct RestoreRegularFile : CreateRegularFileSink { void preallocateContents(uint64_t size) override; }; -void RestoreSink::createRegularFile(const Path & path, std::function func) +void RestoreSink::createRegularFile(const CanonPath & path, std::function func) { - Path p = dstPath + path; + auto p = dstPath; + + if (!path.rel().empty()) { + p = p / path.rel(); + } + RestoreRegularFile crf; crf.fd = #ifdef _WIN32 - CreateFileW(pathNG(path).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) + CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) #else open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666) #endif @@ -141,14 +138,16 @@ void RestoreRegularFile::operator () (std::string_view data) writeFull(fd.get(), data); } -void RestoreSink::createSymlink(const Path & path, const std::string & target) +void RestoreSink::createSymlink(const CanonPath & path, const std::string & target) { - Path p = dstPath + path; + auto p = dstPath; + if (!path.rel().empty()) + p = dstPath / path.rel(); nix::createSymlink(target, p); } -void RegularFileSink::createRegularFile(const Path & path, std::function func) +void RegularFileSink::createRegularFile(const CanonPath & path, std::function func) { struct CRF : CreateRegularFileSink { RegularFileSink & back; @@ -163,7 +162,7 @@ void RegularFileSink::createRegularFile(const Path & path, std::function func) +void NullFileSystemObjectSink::createRegularFile(const CanonPath & path, std::function func) { struct : CreateRegularFileSink { void operator () (std::string_view data) override {} diff --git a/src/libutil/fs-sink.hh b/src/libutil/fs-sink.hh index ae577819a25..cf7d34d2273 100644 --- a/src/libutil/fs-sink.hh +++ b/src/libutil/fs-sink.hh @@ -28,17 +28,17 @@ struct FileSystemObjectSink { virtual ~FileSystemObjectSink() = default; - virtual void createDirectory(const Path & path) = 0; + virtual void createDirectory(const CanonPath & path) = 0; /** * This function in general is no re-entrant. Only one file can be * written at a time. */ virtual void createRegularFile( - const Path & path, + const CanonPath & path, std::function) = 0; - virtual void createSymlink(const Path & path, const std::string & target) = 0; + virtual void createSymlink(const CanonPath & path, const std::string & target) = 0; }; /** @@ -46,17 +46,17 @@ struct FileSystemObjectSink */ void copyRecursive( SourceAccessor & accessor, const CanonPath & sourcePath, - FileSystemObjectSink & sink, const Path & destPath); + FileSystemObjectSink & sink, const CanonPath & destPath); /** * Ignore everything and do nothing */ struct NullFileSystemObjectSink : FileSystemObjectSink { - void createDirectory(const Path & path) override { } - void createSymlink(const Path & path, const std::string & target) override { } + void createDirectory(const CanonPath & path) override { } + void createSymlink(const CanonPath & path, const std::string & target) override { } void createRegularFile( - const Path & path, + const CanonPath & path, std::function) override; }; @@ -65,15 +65,15 @@ struct NullFileSystemObjectSink : FileSystemObjectSink */ struct RestoreSink : FileSystemObjectSink { - Path dstPath; + std::filesystem::path dstPath; - void createDirectory(const Path & path) override; + void createDirectory(const CanonPath & path) override; void createRegularFile( - const Path & path, + const CanonPath & path, std::function) override; - void createSymlink(const Path & path, const std::string & target) override; + void createSymlink(const CanonPath & path, const std::string & target) override; }; /** @@ -88,18 +88,18 @@ struct RegularFileSink : FileSystemObjectSink RegularFileSink(Sink & sink) : sink(sink) { } - void createDirectory(const Path & path) override + void createDirectory(const CanonPath & path) override { regular = false; } - void createSymlink(const Path & path, const std::string & target) override + void createSymlink(const CanonPath & path, const std::string & target) override { regular = false; } void createRegularFile( - const Path & path, + const CanonPath & path, std::function) override; }; diff --git a/src/libutil/git.cc b/src/libutil/git.cc index 8c538c98820..a6968a43eed 100644 --- a/src/libutil/git.cc +++ b/src/libutil/git.cc @@ -53,7 +53,7 @@ static std::string getString(Source & source, int n) void parseBlob( FileSystemObjectSink & sink, - const Path & sinkPath, + const CanonPath & sinkPath, Source & source, BlobMode blobMode, const ExperimentalFeatureSettings & xpSettings) @@ -116,7 +116,7 @@ void parseBlob( void parseTree( FileSystemObjectSink & sink, - const Path & sinkPath, + const CanonPath & sinkPath, Source & source, std::function hook, const ExperimentalFeatureSettings & xpSettings) @@ -147,7 +147,7 @@ void parseTree( Hash hash(HashAlgorithm::SHA1); std::copy(hashs.begin(), hashs.end(), hash.hash); - hook(name, TreeEntry { + hook(CanonPath{name}, TreeEntry { .mode = mode, .hash = hash, }); @@ -171,7 +171,7 @@ ObjectType parseObjectType( void parse( FileSystemObjectSink & sink, - const Path & sinkPath, + const CanonPath & sinkPath, Source & source, BlobMode rootModeIfBlob, std::function hook, @@ -208,7 +208,7 @@ std::optional convertMode(SourceAccessor::Type type) void restore(FileSystemObjectSink & sink, Source & source, std::function hook) { - parse(sink, "", source, BlobMode::Regular, [&](Path name, TreeEntry entry) { + parse(sink, CanonPath::root, source, BlobMode::Regular, [&](CanonPath name, TreeEntry entry) { auto [accessor, from] = hook(entry.hash); auto stat = accessor->lstat(from); auto gotOpt = convertMode(stat.type); diff --git a/src/libutil/git.hh b/src/libutil/git.hh index a65edb964fd..2190cc5509c 100644 --- a/src/libutil/git.hh +++ b/src/libutil/git.hh @@ -64,7 +64,7 @@ using Tree = std::map; * Implementations may seek to memoize resources (bandwidth, storage, * etc.) for the same Git hash. */ -using SinkHook = void(const Path & name, TreeEntry entry); +using SinkHook = void(const CanonPath & name, TreeEntry entry); /** * Parse the "blob " or "tree " prefix. @@ -89,13 +89,13 @@ enum struct BlobMode : RawMode }; void parseBlob( - FileSystemObjectSink & sink, const Path & sinkPath, + FileSystemObjectSink & sink, const CanonPath & sinkPath, Source & source, BlobMode blobMode, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); void parseTree( - FileSystemObjectSink & sink, const Path & sinkPath, + FileSystemObjectSink & sink, const CanonPath & sinkPath, Source & source, std::function hook, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); @@ -108,7 +108,7 @@ void parseTree( * a blob, this is ignored. */ void parse( - FileSystemObjectSink & sink, const Path & sinkPath, + FileSystemObjectSink & sink, const CanonPath & sinkPath, Source & source, BlobMode rootModeIfBlob, std::function hook, diff --git a/src/libutil/memory-source-accessor.cc b/src/libutil/memory-source-accessor.cc index b7207cffb9b..c4eee1031cf 100644 --- a/src/libutil/memory-source-accessor.cc +++ b/src/libutil/memory-source-accessor.cc @@ -124,9 +124,9 @@ SourcePath MemorySourceAccessor::addFile(CanonPath path, std::string && contents using File = MemorySourceAccessor::File; -void MemorySink::createDirectory(const Path & path) +void MemorySink::createDirectory(const CanonPath & path) { - auto * f = dst.open(CanonPath{path}, File { File::Directory { } }); + auto * f = dst.open(path, File { File::Directory { } }); if (!f) throw Error("file '%s' cannot be made because some parent file is not a directory", path); @@ -146,9 +146,9 @@ struct CreateMemoryRegularFile : CreateRegularFileSink { void preallocateContents(uint64_t size) override; }; -void MemorySink::createRegularFile(const Path & path, std::function func) +void MemorySink::createRegularFile(const CanonPath & path, std::function func) { - auto * f = dst.open(CanonPath{path}, File { File::Regular {} }); + auto * f = dst.open(path, File { File::Regular {} }); if (!f) throw Error("file '%s' cannot be made because some parent file is not a directory", path); if (auto * rp = std::get_if(&f->raw)) { @@ -173,9 +173,9 @@ void CreateMemoryRegularFile::operator () (std::string_view data) regularFile.contents += data; } -void MemorySink::createSymlink(const Path & path, const std::string & target) +void MemorySink::createSymlink(const CanonPath & path, const std::string & target) { - auto * f = dst.open(CanonPath{path}, File { File::Symlink { } }); + auto * f = dst.open(path, File { File::Symlink { } }); if (!f) throw Error("file '%s' cannot be made because some parent file is not a directory", path); if (auto * s = std::get_if(&f->raw)) diff --git a/src/libutil/memory-source-accessor.hh b/src/libutil/memory-source-accessor.hh index c8f793922d6..cd5146c8913 100644 --- a/src/libutil/memory-source-accessor.hh +++ b/src/libutil/memory-source-accessor.hh @@ -81,13 +81,13 @@ struct MemorySink : FileSystemObjectSink MemorySink(MemorySourceAccessor & dst) : dst(dst) { } - void createDirectory(const Path & path) override; + void createDirectory(const CanonPath & path) override; void createRegularFile( - const Path & path, + const CanonPath & path, std::function) override; - void createSymlink(const Path & path, const std::string & target) override; + void createSymlink(const CanonPath & path, const std::string & target) override; }; } diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc index f0e24e937cd..c7faedd1ec0 100644 --- a/src/libutil/tarfile.cc +++ b/src/libutil/tarfile.cc @@ -178,6 +178,7 @@ time_t unpackTarfileToSink(TarArchive & archive, FileSystemObjectSink & parseSin auto path = archive_entry_pathname(entry); if (!path) throw Error("cannot get archive member name: %s", archive_error_string(archive.archive)); + auto cpath = CanonPath{path}; if (r == ARCHIVE_WARN) warn(archive_error_string(archive.archive)); else @@ -188,11 +189,11 @@ time_t unpackTarfileToSink(TarArchive & archive, FileSystemObjectSink & parseSin switch (archive_entry_filetype(entry)) { case AE_IFDIR: - parseSink.createDirectory(path); + parseSink.createDirectory(cpath); break; case AE_IFREG: { - parseSink.createRegularFile(path, [&](auto & crf) { + parseSink.createRegularFile(cpath, [&](auto & crf) { if (archive_entry_mode(entry) & S_IXUSR) crf.isExecutable(); @@ -216,7 +217,7 @@ time_t unpackTarfileToSink(TarArchive & archive, FileSystemObjectSink & parseSin case AE_IFLNK: { auto target = archive_entry_symlink(entry); - parseSink.createSymlink(path, target); + parseSink.createSymlink(cpath, target); break; } diff --git a/src/nix/unix/daemon.cc b/src/nix/unix/daemon.cc index 41ea1f5a467..4a7997b1fb0 100644 --- a/src/nix/unix/daemon.cc +++ b/src/nix/unix/daemon.cc @@ -203,7 +203,11 @@ static PeerInfo getPeerInfo(int remote) #if defined(SO_PEERCRED) - ucred cred; +# if defined(__OpenBSD__) + struct sockpeercred cred; +# else + ucred cred; +# endif socklen_t credLen = sizeof(cred); if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1) throw SysError("getting peer credentials"); @@ -211,9 +215,9 @@ static PeerInfo getPeerInfo(int remote) #elif defined(LOCAL_PEERCRED) -#if !defined(SOL_LOCAL) -#define SOL_LOCAL 0 -#endif +# if !defined(SOL_LOCAL) +# define SOL_LOCAL 0 +# endif xucred cred; socklen_t credLen = sizeof(cred); diff --git a/tests/unit/libutil/git.cc b/tests/unit/libutil/git.cc index ff934c117b8..a0125d023ba 100644 --- a/tests/unit/libutil/git.cc +++ b/tests/unit/libutil/git.cc @@ -67,7 +67,7 @@ TEST_F(GitTest, blob_read) { StringSink out; RegularFileSink out2 { out }; ASSERT_EQ(parseObjectType(in, mockXpSettings), ObjectType::Blob); - parseBlob(out2, "", in, BlobMode::Regular, mockXpSettings); + parseBlob(out2, CanonPath::root, in, BlobMode::Regular, mockXpSettings); auto expected = readFile(goldenMaster("hello-world.bin")); @@ -132,8 +132,8 @@ TEST_F(GitTest, tree_read) { NullFileSystemObjectSink out; Tree got; ASSERT_EQ(parseObjectType(in, mockXpSettings), ObjectType::Tree); - parseTree(out, "", in, [&](auto & name, auto entry) { - auto name2 = name; + parseTree(out, CanonPath::root, in, [&](auto & name, auto entry) { + auto name2 = std::string{name.rel()}; if (entry.mode == Mode::Directory) name2 += '/'; got.insert_or_assign(name2, std::move(entry)); @@ -210,14 +210,14 @@ TEST_F(GitTest, both_roundrip) { MemorySink sinkFiles2 { *files2 }; - std::function mkSinkHook; + std::function mkSinkHook; mkSinkHook = [&](auto prefix, auto & hash, auto blobMode) { StringSource in { cas[hash] }; parse( sinkFiles2, prefix, in, blobMode, - [&](const Path & name, const auto & entry) { + [&](const CanonPath & name, const auto & entry) { mkSinkHook( - prefix + "/" + name, + prefix / name, entry.hash, // N.B. this cast would not be acceptable in real // code, because it would make an assert reachable, @@ -227,7 +227,7 @@ TEST_F(GitTest, both_roundrip) { mockXpSettings); }; - mkSinkHook("", root.hash, BlobMode::Regular); + mkSinkHook(CanonPath::root, root.hash, BlobMode::Regular); ASSERT_EQ(*files, *files2); }