about summary refs log tree commit diff
path: root/tvix/eval/src
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/eval/src')
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.nix1
-rw-r--r--tvix/eval/src/value/string.rs25
3 files changed, 26 insertions, 1 deletions
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.exp
new file mode 100644
index 000000000000..aa98a082a8ac
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.exp
@@ -0,0 +1 @@
+{ "3" = 3; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.nix
new file mode 100644
index 000000000000..aa98a082a8ac
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.nix
@@ -0,0 +1 @@
+{ "3" = 3; }
diff --git a/tvix/eval/src/value/string.rs b/tvix/eval/src/value/string.rs
index 4caef653f2cc..66697a7f2f4f 100644
--- a/tvix/eval/src/value/string.rs
+++ b/tvix/eval/src/value/string.rs
@@ -118,7 +118,13 @@ impl NixString {
         match escaped {
             // A borrowed string is unchanged and can be returned as
             // is.
-            Cow::Borrowed(_) => escaped,
+            Cow::Borrowed(_) => {
+                if is_valid_nix_identifier(&escaped) {
+                    escaped
+                } else {
+                    Cow::Owned(format!("\"{}\"", escaped))
+                }
+            }
 
             // An owned string has escapes, and needs the outer quotes
             // for display.
@@ -152,6 +158,23 @@ fn nix_escape_char(ch: char, next: Option<&char>) -> Option<&'static str> {
     }
 }
 
+/// Return true if this string can be used as an identifier in Nix.
+fn is_valid_nix_identifier(s: &str) -> bool {
+    // adapted from rnix-parser's tokenizer.rs
+    let mut chars = s.chars();
+    match chars.next() {
+        Some('a'..='z' | 'A'..='Z' | '_') => (),
+        _ => return false,
+    }
+    for c in chars {
+        match c {
+            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' => (),
+            _ => return false,
+        }
+    }
+    return true;
+}
+
 /// Escape a Nix string for display, as most user-visible representation
 /// are escaped strings.
 ///