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 000000000..aa98a082a --- /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 000000000..aa98a082a --- /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 4caef653f..66697a7f2 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. ///