Skip to content

Commit

Permalink
checker: fix comptime evaluation on infix expr (fix #23341) (#23344)
Browse files Browse the repository at this point in the history
  • Loading branch information
felipensp authored Jan 3, 2025
1 parent 5eecd04 commit 3ed799e
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 34 deletions.
16 changes: 11 additions & 5 deletions vlib/math/stats/stats.v
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ pub fn covariance_mean[T](data1 []T, data2 []T, mean1 T, mean2 T) T {
for i in 0 .. n {
delta1 := data1[i] - mean1
delta2 := data2[i] - mean2
covariance += T((delta1 * delta2 - covariance) / (i + T(1)))
covariance += T((delta1 * delta2 - covariance) / (T(i) + T(1)))
}
return covariance
}
Expand All @@ -459,8 +459,11 @@ pub fn lag1_autocorrelation_mean[T](data []T, mean T) T {
for i := 1; i < data.len; i++ {
delta0 := data[i - 1] - mean
delta1 := data[i] - mean
q += T((delta0 * delta1 - q) / (i + T(1)))
v += T((delta1 * delta1 - v) / (T(i) + T(1)))
d01 := delta0 * delta1
d11 := delta1 * delta1
ti1 := T(i) + T(1)
q += T((d01 - q) / ti1)
v += T((d11 - v) / ti1)
}
return T(q / v)
}
Expand All @@ -486,7 +489,9 @@ pub fn kurtosis_mean_stddev[T](data []T, mean T, sd T) T {
*/
for i, v in data {
x := (v - mean) / sd
avg += T((x * x * x * x - avg) / (i + T(1)))
x4 := x * x * x * x
ti1 := (T(i) + T(1))
avg += T((x4 - avg) / ti1)
}
return avg - T(3)
}
Expand All @@ -511,7 +516,8 @@ pub fn skew_mean_stddev[T](data []T, mean T, sd T) T {
*/
for i, v in data {
x := (v - mean) / sd
skew += T((x * x * x - skew) / (i + T(1)))
x3 := x * x * x
skew += T((x3 - skew) / (T(i) + T(1)))
}
return skew
}
Expand Down
47 changes: 30 additions & 17 deletions vlib/math/stats/stats_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -400,15 +400,15 @@ fn test_covariance() {
mut data0 := [10.0, 4.45, 5.9, 2.7]
mut data1 := [5.0, 14.45, -15.9, 22.7]
mut o := stats.covariance(data0, data1)
assert math.alike(o, -17.37078207731247)
assert math.alike(o, -17.37078125)
data0 = [-3.0, 67.31, 4.4, 1.89]
data1 = [5.0, 77.31, 44.4, 11.89]
o = stats.covariance(data0, data1)
assert math.alike(o, 740.0695419311523)
assert math.alike(o, 740.06955)
data0 = [12.0, 7.88, 76.122, 54.83]
data1 = [2.0, 5.88, 7.122, 5.83]
o = stats.covariance(data0, data1)
assert math.alike(o, 36.65028190612793)
assert math.alike(o, 36.650282000000004)

// test for int, i64, f32 array
data0_int := [1, 2, 3, 1]
Expand All @@ -428,13 +428,15 @@ fn test_covariance() {
fn test_lag1_autocorrelation() {
mut data := [10.0, 4.45, 5.9, 2.7]
mut o := stats.lag1_autocorrelation(data)
assert math.alike(o, -0.554228566606572)
mut e := 0.0
assert math.alike(o, -0.5542285481446095)
data = [-3.0, 67.31, 4.4, 1.89]
o = stats.lag1_autocorrelation(data)
assert math.alike(o, -0.5102510823460722)
assert math.alike(o, -0.5102510654033415)
data = [12.0, 7.88, 76.122, 54.83]
o = stats.lag1_autocorrelation(data)
assert math.alike(o, 0.10484451825170164)
e = 0.10484450460892072
assert math.alike(o, e), diff(o, e)

// test for int, i64, f32 array
assert stats.lag1_autocorrelation[int]([1, 2, 3, 1]) == 0
Expand All @@ -443,54 +445,65 @@ fn test_lag1_autocorrelation() {
assert math.alike(o, 0.1975308507680893)
}

fn diff(actual f64, expected f64) string {
return '\n actual:${actual:40.35f}\nexpected:${expected:40.35f}\n diff:${actual - expected:40.35f}'
}

fn test_kurtosis() {
mut data := [10.0, 4.45, 5.9, 2.7]
mut o := stats.kurtosis(data)
assert math.alike(o, -1.0443214689384779)
mut e := -1.0443212849233845
assert math.close(o, e), diff(o, e)
data = [-3.0, 67.31, 4.4, 1.89]
o = stats.kurtosis(data)
assert math.alike(o, -0.688495594786176)
e = -0.6884953374814851
assert math.close(o, e), diff(o, e)
data = [12.0, 7.88, 76.122, 54.83]
o = stats.kurtosis(data)
assert math.alike(o, -1.7323772574195067)
assert math.alike(o, -1.7323772836921467)

// test for int, i64, f32 array
assert stats.kurtosis[int]([1, 2, 3, 1]) == 1
assert stats.kurtosis[i64]([i64(1), 2, 3, 1]) == 1
o = stats.kurtosis[f32]([f32(1.0), 3, 5, 7, 3])
assert math.alike(o, -1.0443782806396484)
e = -1.044378399848938
assert math.alike(o, e), diff(o, e)
}

fn test_skew() {
mut data := [10.0, 4.45, 5.9, 2.7]
mut o := stats.skew(data)
assert math.alike(o, 0.5754020379048158)
mut e := 0.5754021106320453
assert math.veryclose(o, e), diff(o, e)
data = [-3.0, 67.31, 4.4, 1.89]
o = stats.skew(data)
assert math.alike(o, 1.1248732608899568)
e = 1.1248733711136492
assert math.veryclose(o, e), diff(o, e)
data = [12.0, 7.88, 76.122, 54.83]
o = stats.skew(data)
assert math.alike(o, 0.19007917421924964)
e = 0.19007911706827735
assert math.alike(o, e), diff(o, e)

// test for int, i64, f32 array
assert stats.skew[int]([1, 2, 3, 1]) == 2
assert stats.skew[i64]([i64(1), 2, 3, 1]) == 2
o = stats.skew[f32]([f32(1.0), 3, 5, 7, 3])
assert math.alike(o, 0.2715454697608948)
e = 0.27154541015625
assert math.alike(o, e), diff(o, e)
}

fn test_quantile() {
// Assumes sorted array

mut data := [2.7, 4.45, 5.9, 10.0]
mut o := stats.quantile(data, 0.1)!
assert math.alike(o, 3.225000020861626)
assert math.alike(o, 3.225)
data = [-3.0, 1.89, 4.4, 67.31]
o = stats.quantile(data, 0.2)!
assert math.alike(o, -0.06599988341331486)
assert math.alike(o, -0.06599999999999961)
data = [7.88, 12.0, 54.83, 76.122]
o = stats.quantile(data, 0.3)!
assert math.alike(o, 11.587999901771546)
assert math.alike(o, 11.588)

stats.quantile(data, -0.3) or { assert err.msg() == 'index out of range' }

Expand Down
16 changes: 16 additions & 0 deletions vlib/strconv/format.v
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ const dec_round = [
0.000000000000000005,
0.0000000000000000005,
0.00000000000000000005,
0.000000000000000000005,
0.0000000000000000000005,
0.00000000000000000000005,
0.000000000000000000000005,
0.0000000000000000000000005,
0.00000000000000000000000005,
0.000000000000000000000000005,
0.0000000000000000000000000005,
0.00000000000000000000000000005,
0.000000000000000000000000000005,
0.0000000000000000000000000000005,
0.00000000000000000000000000000005,
0.000000000000000000000000000000005,
0.0000000000000000000000000000000005,
0.00000000000000000000000000000000005,
0.000000000000000000000000000000000005,
]!

// Single format functions
Expand Down
2 changes: 1 addition & 1 deletion vlib/strconv/format_mem.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ pub fn f64_to_str_lnd1(f f64, dec_digit int) string {

// allocate exp+32 chars for the return string
// mut res := []u8{len:exp+32,init:`0`}
mut res := []u8{len: exp + 32, init: 0}
mut res := []u8{len: exp + 40, init: 0}
mut r_i := 0 // result string buffer index

// println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}")
Expand Down
42 changes: 35 additions & 7 deletions vlib/v/checker/assign.v
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) {
node.left_types = []
mut right_len := node.right.len
mut right_first_type := ast.void_type
old_recheck := c.inside_recheck
// check if we are rechecking an already checked expression on generic rechecking
c.inside_recheck = old_recheck || node.right_types.len > 0
defer {
c.inside_recheck = old_recheck
}
for i, mut right in node.right {
if right in [ast.CallExpr, ast.IfExpr, ast.LockExpr, ast.MatchExpr, ast.DumpExpr,
ast.SelectorExpr, ast.ParExpr, ast.ComptimeCall] {
Expand All @@ -37,7 +43,9 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) {
}
right_type_sym := c.table.sym(right_type)
// fixed array returns an struct, but when assigning it must be the array type
right_type = c.cast_fixed_array_ret(right_type, right_type_sym)
if right_type_sym.info is ast.ArrayFixed {
right_type = c.cast_fixed_array_ret(right_type, right_type_sym)
}
if i == 0 {
right_first_type = right_type
node.right_types = [
Expand All @@ -62,11 +70,23 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) {
}
}
}
}
if mut right is ast.InfixExpr {
} else if mut right is ast.InfixExpr {
if right.op == .arrow {
c.error('cannot use `<-` on the right-hand side of an assignment, as it does not return any values',
right.pos)
} else if c.inside_recheck {
mut right_type := c.expr(mut right)
right_type_sym := c.table.sym(right_type)
// fixed array returns an struct, but when assigning it must be the array type
if right_type_sym.info is ast.ArrayFixed {
right_type = c.cast_fixed_array_ret(right_type, right_type_sym)
}
if i == 0 {
right_first_type = right_type
node.right_types = [
c.check_expr_option_or_result_call(right, right_first_type),
]
}
}
}
if mut right is ast.Ident {
Expand Down Expand Up @@ -182,16 +202,13 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) {
node.right_types << c.check_expr_option_or_result_call(node.right[i],
right_type)
}
} else {
} else if c.inside_recheck {
// on generic recheck phase it might be needed to resolve the rhs again
if i < node.right.len && c.comptime.has_comptime_expr(node.right[i]) {
mut expr := mut node.right[i]
old_inside_recheck := c.inside_recheck
c.inside_recheck = true
right_type := c.expr(mut expr)
node.right_types[i] = c.check_expr_option_or_result_call(node.right[i],
right_type)
c.inside_recheck = old_inside_recheck
}
}
mut right := if i < node.right.len { node.right[i] } else { node.right[0] }
Expand Down Expand Up @@ -393,6 +410,17 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) {
left.obj.ct_type_var = .field_var
left.obj.typ = c.comptime.comptime_for_field_type
}
} else if mut right is ast.InfixExpr {
right_ct_var := c.comptime.get_ct_type_var(right.left)
if right_ct_var != .no_comptime {
left.obj.ct_type_var = right_ct_var
}
} else if mut right is ast.IndexExpr
&& c.comptime.is_comptime(right) {
right_ct_var := c.comptime.get_ct_type_var(right.left)
if right_ct_var != .no_comptime {
left.obj.ct_type_var = right_ct_var
}
} else if mut right is ast.Ident && right.obj is ast.Var
&& right.or_expr.kind == .absent {
right_obj_var := right.obj as ast.Var
Expand Down
13 changes: 13 additions & 0 deletions vlib/v/gen/c/assign.v
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,19 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
left.obj.typ = var_type
g.assign_ct_type = var_type
}
} else if val is ast.InfixExpr && val.op in [.plus, .minus, .mul, .div, .mod]
&& g.comptime.is_comptime(val.left) {
ctyp := g.unwrap_generic(g.type_resolver.get_type(val.left))
if ctyp != ast.void_type {
ct_type_var := g.comptime.get_ct_type_var(val.left)
if ct_type_var in [.key_var, .value_var] {
g.type_resolver.update_ct_type(left.name, g.unwrap_generic(ctyp))
}
var_type = ctyp
val_type = var_type
left.obj.typ = var_type
g.assign_ct_type = var_type
}
}
is_auto_heap = left.obj.is_auto_heap
}
Expand Down
4 changes: 3 additions & 1 deletion vlib/v/gen/c/infix.v
Original file line number Diff line number Diff line change
Expand Up @@ -1188,8 +1188,10 @@ fn (mut g Gen) gen_plain_infix_expr(node ast.InfixExpr) {
&& node.op in [.plus, .minus, .mul, .div, .mod] && !(g.pref.translated
|| g.file.is_translated)
if needs_cast {
typ_str := if g.comptime.is_comptime(node.left) {
typ_str := if !node.left.is_literal() && g.comptime.is_comptime(node.left) {
g.styp(g.type_resolver.get_type_or_default(node.left, node.promoted_type))
} else if !node.right.is_literal() && g.comptime.is_comptime(node.right) {
g.styp(g.type_resolver.get_type_or_default(node.right, node.promoted_type))
} else {
g.styp(node.promoted_type)
}
Expand Down
62 changes: 62 additions & 0 deletions vlib/v/tests/comptime/comptime_infix_assign_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
enum Flag {
usa_old_glory
all_other_bad_excuses_for_a_flag
}

struct Test {
is_foo bool
name [5]u8
}

fn enc[T](item T) string {
$if T is $int {
len := match typeof(item).name {
'i8', 'u8' { u8(2) }
'i16', 'u16' { 4 }
'int', 'u32', 'i32' { 8 }
'i64', 'u64' { 16 }
else { return '' }
}
return u64_to_hex(item, len)
} $else $if T is $array {
mut hex := ''
for val in item {
hex += enc(val)
}
return hex
} $else $if T is $struct {
mut hex := ''
$for field in T.fields {
hex += enc(item.$(field.name)) + '_'
}
return hex
} $else {
if typeof(item).name == 'bool' {
return enc(int(item))
}
$if debug {
println('cannot encode ${T}(s)')
}
return ''
}
}

@[direct_array_access; inline]
fn u64_to_hex(nn u64, len u8) string {
mut n := nn
mut buf := [17]u8{}
buf[len] = 0
mut i := 0
for i = len - 1; i >= 0; i-- {
d := u8(n & 0xF)
buf[i] = if d < 10 { d + `0` } else { d + 87 }
n = n >> 4
}
return unsafe { tos(memdup(&buf[0], len + 1), len) }
}

fn test_main() {
assert enc(Test{}) == '00000000_0000000000_'
assert enc(Test{ is_foo: true }) == '00000001_0000000000_'
assert enc(Test{ name: [u8(1), 2, 3, 4, 5]! }) == '00000000_0102030405_'
}
10 changes: 9 additions & 1 deletion vlib/v/type_resolver/comptime_resolver.v
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ pub fn (t &ResolverInfo) is_comptime(node ast.Expr) bool {
return node.expr is ast.Ident && node.expr.ct_expr
}
ast.InfixExpr {
return t.is_comptime(node.left) || t.is_comptime(node.right)
if node.op in [.plus, .minus, .mul, .div, .mod] {
t.is_comptime(node.left) || t.is_comptime(node.right)
} else {
false
}
}
ast.ParExpr {
return t.is_comptime(node.expr)
Expand All @@ -76,6 +80,10 @@ pub fn (t &ResolverInfo) get_ct_type_var(node ast.Expr) ast.ComptimeVarKind {
}
} else if node is ast.IndexExpr {
return t.get_ct_type_var(node.left)
} else if node is ast.InfixExpr {
return t.get_ct_type_var(node.left)
} else if node is ast.ParExpr {
return t.get_ct_type_var(node.expr)
}
return .no_comptime
}
Expand Down
Loading

0 comments on commit 3ed799e

Please sign in to comment.