From eb7ebc6fddcb254eddbe1284e2e7140c05b46b8f Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:22:29 +0530 Subject: [PATCH] interpreter: compare an uninitialized value as 0 against a number An uninitialized variable or array element is both "" and 0, so a comparison with a number is numeric: x == 0 and x > -1 are true for an unset x, as in gawk, mawk and nawk. --- interpreter/src/vm/types.rs | 11 ++++++++++- tests/by-util/test_awk.rs | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/interpreter/src/vm/types.rs b/interpreter/src/vm/types.rs index df210b4..5467ee0 100644 --- a/interpreter/src/vm/types.rs +++ b/interpreter/src/vm/types.rs @@ -497,6 +497,11 @@ impl PartialEq for AwkValue<'_> { | (&Self::Bool(AwkBool(b)), &Self::Float(AwkNum(f))) => b && f == 1., (&Self::Int(AwkInt(f)), &Self::Bool(AwkBool(b))) | (&Self::Bool(AwkBool(b)), &Self::Int(AwkInt(f))) => b && f == 1, + // An uninitialized value compares as 0 against a number + (Self::Untyped | Self::Unassigned, Self::Float(_) | Self::Int(_) | Self::Bool(_)) + | (Self::Float(_) | Self::Int(_) | Self::Bool(_), Self::Untyped | Self::Unassigned) => { + self.to_num() == other.to_num() + } // String-based comparisons _ if let (Some(lhs), Some(rhs)) = (self.as_str(), other.as_str()) => lhs == rhs, (&Self::Float(AwkNum(f)), _) if let Some(s) = other.as_str() => { @@ -519,7 +524,7 @@ impl PartialEq for AwkValue<'_> { } // True on empty string value. (Self::Untyped | Self::Unassigned, _) if let Some(s) = other.as_str() => s.is_empty(), - (_, Self::Untyped | Self::Unassigned) if let Some(s) = other.as_str() => s.is_empty(), + (_, Self::Untyped | Self::Unassigned) if let Some(s) = self.as_str() => s.is_empty(), (Self::Untyped | Self::Unassigned, Self::Untyped | Self::Unassigned) => true, (Self::Untyped | Self::Unassigned, _) | (_, Self::Untyped | Self::Unassigned) => false, (Self::Array(_), _) | (_, Self::Array(_)) => { @@ -545,6 +550,10 @@ impl PartialOrd for AwkValue<'_> { (&Self::Bool(AwkBool(b)), Self::Float(AwkNum(f))) => (b as usize as f64).partial_cmp(f), (&Self::Int(AwkInt(n)), &Self::Bool(AwkBool(b))) => n.partial_cmp(&(b as i32)), (&Self::Bool(AwkBool(b)), Self::Int(AwkInt(f))) => (b as i32).partial_cmp(f), + (Self::Untyped | Self::Unassigned, Self::Float(_) | Self::Int(_) | Self::Bool(_)) + | (Self::Float(_) | Self::Int(_) | Self::Bool(_), Self::Untyped | Self::Unassigned) => { + self.to_num().partial_cmp(&other.to_num()) + } // String-based comparisons (_, _) if let (Some(lhs), Some(rhs)) = (self.as_str(), other.as_str()) => { lhs.as_ref().partial_cmp(rhs) diff --git a/tests/by-util/test_awk.rs b/tests/by-util/test_awk.rs index 10a115c..5a83dcd 100644 --- a/tests/by-util/test_awk.rs +++ b/tests/by-util/test_awk.rs @@ -1020,3 +1020,12 @@ fn integral_numbers_print_as_integers() { .succeeds() .stdout_only("500000000000000000000\n"); } + +#[test] +fn uninitialized_value_compares_as_zero_with_a_number() { + // An uninitialized value is both "" and 0, so it compares numerically with a number. + ucmd() + .arg(r#"BEGIN { print (x == 0), (0 == x), (x < 1), (x > -1), (x >= 0), (x != 0), ("" == x), (a["k"] == 0) }"#) + .succeeds() + .stdout_only("1 1 1 1 1 0 1 1\n"); +}