Skip to content

libexpr: accurately report string interpolation in eval traces (fixes #16253) - #16522

Open
akashhg2007 wants to merge 1 commit into
NixOS:masterfrom
akashhg2007:fix/string-interpolation-trace
Open

akashhg2007 wants to merge 1 commit into
NixOS:masterfrom
akashhg2007:fix/string-interpolation-trace

Conversation

@akashhg2007

Copy link
Copy Markdown

Summary

Fixes #16253.

When evaluating Nix expressions that encounter a coercion error or evaluation error in string interpolation (e.g. "${{ __toString = self: null; }}" or "${x: x}") or binary + concatenation with strings (e.g. "foo" + 1 or builtins.mapAttrs (x: y: x + 1) ...), Nix was inaccurately reporting:

… while evaluating a path segment

in the evaluation trace.

Root Cause

In src/libexpr/eval.cc, the AST node ExprConcatStrings::eval is used for:

  1. String interpolation (forceString == true, e.g., "${...}" and ''${...}'')
  2. Path interpolation and path concatenation (firstType == nPath, e.g. ./foo/${...} or ./foo + ...)
  3. Binary + operator expressions (which can be integers, floats, paths, or strings)

In ExprConcatStrings::eval, the error context passed to state.coerceToString() was unconditionally hardcoded as "while evaluating a path segment":

// Before:
auto part = state.coerceToString(
    i_pos, vTmp, context, "while evaluating a path segment", false, firstType == nString, !first);

and i->eval() was called with "in an operand of '+'" regardless of whether the expression was a string interpolation or a path interpolation.

Because of this hardcoded trace context:

  • In string interpolation (forceString == true), any coercion failure (such as returning a non-coercible value from __toString) printed … while evaluating a path segment.
  • In binary + string concatenation (firstType == nString), any coercion failure of the right-hand operand also printed … while evaluating a path segment.

Fix

Dynamically choose the appropriate trace context depending on whether the expression is string interpolation, a path segment, or an operand of +:

std::string_view evalErrorCtx =
    forceString ? "while evaluating a string interpolation"
    : (!first && firstType == nPath) ? "while evaluating a path segment"
    : "in an operand of '+'";
i->eval(state, env, vTmp, evalErrorCtx);

and for coerceToString:

std::string_view traceContext =
    firstType == nPath ? "while evaluating a path segment"
    : forceString ? "while evaluating a string interpolation"
    : "in an operand of '+'";
auto part = state.coerceToString(
    i_pos, vTmp, context, traceContext, false, firstType == nString, !first);

Trace Comparisons

1. String Interpolation

Before:

error:
       … while using the result of the `__toString` attribute
         at ...:
            2| "${{ __toString = self: null; }}"
             |      ^

       … while evaluating a path segment
         at ...:
            2| "${{ __toString = self: null; }}"
             |  ^

       error: cannot coerce null to a string: null

After:

error:
       … while using the result of the `__toString` attribute
         at ...:
            2| "${{ __toString = self: null; }}"
             |      ^

       … while evaluating a string interpolation
         at ...:
            2| "${{ __toString = self: null; }}"
             |  ^

       error: cannot coerce null to a string: null

2. Binary + String Concatenation ("foo" + 1)

Before:

       … while evaluating a path segment
         at ...:
            1| builtins.mapAttrs (x: y: x + 1) { foo.bar = 1; }
             |                              ^

After:

       … in an operand of '+'
         at ...:
            1| builtins.mapAttrs (x: y: x + 1) { foo.bar = 1; }
             |                              ^

Test Coverage

  • Updated existing characterisation tests that were asserting the legacy inaccurate while evaluating a path segment trace for string interpolation and + concatenation:
    • eval-fail-bad-string-interpolation-1
    • eval-fail-bad-string-interpolation-3
    • eval-fail-bad-string-interpolation-4
    • eval-fail-interpolation-list
    • eval-fail-toString-returns-non-coercible
    • eval-fail-mapAttrs-4
    • eval-fail-zipAttrsWith-4
    • eval-fail-nested-list-items
  • Added new test cases ensuring path interpolation and path concatenation continue to correctly report while evaluating a path segment:
    • eval-fail-bad-path-interpolation (./foo/${x: x})
    • eval-fail-bad-path-concat (./foo + (x: x))
    • eval-fail-bad-string-concat ("foo" + (x: x))

…ixOS#16253)

Previously, evaluating an interpolated string or string concatenation
with '+' (such as 'builtins.mapAttrs (x: y: x + 1)') would report:

  … while evaluating a path segment

in evaluation error traces.

Root cause:
`ExprConcatStrings::eval` in `src/libexpr/eval.cc` is reused for:
1. String interpolation (`forceString == true`)
2. Path interpolation (`firstType == nPath` after evaluating `path_start`)
3. Binary `+` expressions (which can be integers, floats, paths, or strings)

However, when coercing components to string via `state.coerceToString()`,
the error context was unconditionally hardcoded to "while evaluating a path segment".
Furthermore, `i->eval()` was called with hardcoded "in an operand of '+'".

Fix:
- Dynamically determine the error context for both `i->eval()` and `state.coerceToString()`:
  * When `firstType == nPath`, report "while evaluating a path segment".
  * When `forceString` is true, report "while evaluating a string interpolation".
  * Otherwise (e.g. `+` concatenation), report "in an operand of '+'".
- Update existing characterisation tests that were asserting the old, incorrect
  `while evaluating a path segment` trace for string interpolation and `+` concatenation.
- Add test cases verifying that actual path interpolation and path concatenation
  continue to correctly report "while evaluating a path segment".
@github-actions github-actions Bot added the with-tests Issues related to testing. PRs with tests have some priority label Sep 26, 2026

This branch has not been deployed

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

Labels

with-tests Issues related to testing. PRs with tests have some priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

String interpolation shows up as "path segment" in trace

1 participant