Skip to content

Commit 3b3b936

Browse files
LemonBoychrisbra
authored andcommitted
patch 9.1.1684: min()/max() does not handle float data types
Problem: min()/max() does not handle float data types (ubaldot) Solution: Extend min() and max() to every comparable type (LemonBoy) Re-use the logic used for plain old comparison operators, this way we gain support for float values and unify the logic handling the comparisons. fixes: #18052 closes: 18055 Signed-off-by: LemonBoy <thatlemon@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent b922b30 commit 3b3b936

File tree

13 files changed

+167
-145
lines changed

13 files changed

+167
-145
lines changed

runtime/doc/builtin.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*builtin.txt* For Vim version 9.1. Last change: 2025 Aug 23
1+
*builtin.txt* For Vim version 9.1. Last change: 2025 Aug 24
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -7626,7 +7626,7 @@ max({expr}) *max()*
76267626
Can also be used as a |method|: >
76277627
mylist->max()
76287628
<
7629-
Return type: |Number|
7629+
Return type: any, depending on {expr}
76307630

76317631

76327632
menu_info({name} [, {mode}]) *menu_info()*
@@ -7718,7 +7718,7 @@ min({expr}) *min()*
77187718
Can also be used as a |method|: >
77197719
mylist->min()
77207720
<
7721-
Return type: |Number|
7721+
Return type: any, depending on {expr}
77227722

77237723

77247724
mkdir({name} [, {flags} [, {prot}]]) *mkdir()* *E739*

runtime/doc/version9.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*version9.txt* For Vim version 9.1. Last change: 2025 Aug 23
1+
*version9.txt* For Vim version 9.1. Last change: 2025 Aug 24
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -41752,6 +41752,7 @@ Others: ~
4175241752
feature, see |socketserver-clientserver|.
4175341753
- |CmdlineLeave| sets |v:char| to the character that caused exiting the
4175441754
Command-line.
41755+
- |min()|/|max()| can handle all comparable data types.
4175541756

4175641757
Platform specific ~
4175741758
- MS-Winodws: Paths like "\Windows" and "/Windows" are now considered to be

src/evalfunc.c

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1799,6 +1799,30 @@ ret_remove(int argcount,
17991799
return &t_any;
18001800
}
18011801

1802+
static type_T *
1803+
ret_max_min(int argcount,
1804+
type2_T *argtypes,
1805+
type_T **decl_type)
1806+
{
1807+
if (argcount > 0)
1808+
{
1809+
type_T *t = argtypes[0].type_decl;
1810+
if (t->tt_type == VAR_LIST || t->tt_type == VAR_DICT)
1811+
t = t->tt_member;
1812+
else
1813+
t = &t_any;
1814+
*decl_type = t;
1815+
1816+
t = argtypes[0].type_curr;
1817+
if (t->tt_type == VAR_LIST || t->tt_type == VAR_DICT)
1818+
t = t->tt_member;
1819+
else
1820+
t = &t_any;
1821+
return t;
1822+
}
1823+
return &t_any;
1824+
}
1825+
18021826
static type_T *
18031827
ret_getreg(int argcount,
18041828
type2_T *argtypes UNUSED,
@@ -2541,7 +2565,7 @@ static funcentry_T global_functions[] =
25412565
{"matchstrpos", 2, 4, FEARG_1, arg24_match_func,
25422566
ret_list_any, f_matchstrpos},
25432567
{"max", 1, 1, FEARG_1, arg1_list_or_tuple_or_dict,
2544-
ret_number, f_max},
2568+
ret_max_min, f_max},
25452569
{"menu_info", 1, 2, FEARG_1, arg2_string,
25462570
ret_dict_any,
25472571
#ifdef FEAT_MENU
@@ -2551,7 +2575,7 @@ static funcentry_T global_functions[] =
25512575
#endif
25522576
},
25532577
{"min", 1, 1, FEARG_1, arg1_list_or_tuple_or_dict,
2554-
ret_number, f_min},
2578+
ret_max_min, f_min},
25552579
{"mkdir", 1, 3, FEARG_1, arg3_string_string_number,
25562580
ret_number_bool, f_mkdir},
25572581
{"mode", 0, 1, FEARG_1, arg1_bool,
@@ -9535,17 +9559,18 @@ f_matchstrpos(typval_T *argvars, typval_T *rettv)
95359559
max_min(typval_T *argvars, typval_T *rettv, int domax)
95369560
{
95379561
varnumber_T n = 0;
9538-
varnumber_T i;
9539-
int error = FALSE;
95409562

95419563
if (in_vim9script() &&
95429564
check_for_list_or_tuple_or_dict_arg(argvars, 0) == FAIL)
95439565
return;
95449566

9567+
rettv->vval.v_number = 0;
9568+
95459569
if (argvars[0].v_type == VAR_LIST)
95469570
{
95479571
list_T *l;
95489572
listitem_T *li;
9573+
typval_T *tv = NULL;
95499574

95509575
l = argvars[0].vval.v_list;
95519576
if (l != NULL && l->lv_len > 0)
@@ -9557,42 +9582,44 @@ max_min(typval_T *argvars, typval_T *rettv, int domax)
95579582
else
95589583
n = l->lv_u.nonmat.lv_start + ((varnumber_T)l->lv_len - 1)
95599584
* l->lv_u.nonmat.lv_stride;
9585+
rettv->vval.v_number = n;
95609586
}
95619587
else
95629588
{
9563-
li = l->lv_first;
9564-
if (li != NULL)
9589+
FOR_ALL_LIST_ITEMS(l, li)
95659590
{
9566-
n = tv_get_number_chk(&li->li_tv, &error);
9567-
if (error)
9568-
return; // type error; errmsg already given
9569-
for (;;)
9591+
if (tv == NULL)
9592+
tv = &li->li_tv;
9593+
else
95709594
{
9571-
li = li->li_next;
9572-
if (li == NULL)
9573-
break;
9574-
i = tv_get_number_chk(&li->li_tv, &error);
9575-
if (error)
9576-
return; // type error; errmsg already given
9577-
if (domax ? i > n : i < n)
9578-
n = i;
9595+
int res;
9596+
if (typval_compare2(&li->li_tv, tv,
9597+
domax ? EXPR_GREATER : EXPR_SMALLER, FALSE, &res) == FAIL)
9598+
return;
9599+
if (res == OK)
9600+
tv = &li->li_tv;
95799601
}
95809602
}
9603+
9604+
if (tv != NULL)
9605+
copy_tv(tv, rettv);
95819606
}
95829607
}
95839608
}
95849609
else if (argvars[0].v_type == VAR_TUPLE)
95859610
{
9586-
n = tuple_max_min(argvars[0].vval.v_tuple, domax, &error);
9587-
if (error)
9588-
return;
9611+
typval_T *tv;
9612+
9613+
tv = tuple_max_min(argvars[0].vval.v_tuple, domax);
9614+
if (tv != NULL)
9615+
copy_tv(tv, rettv);
95899616
}
95909617
else if (argvars[0].v_type == VAR_DICT)
95919618
{
95929619
dict_T *d;
9593-
int first = TRUE;
95949620
hashitem_T *hi;
95959621
int todo;
9622+
typval_T *tv = NULL;
95969623

95979624
d = argvars[0].vval.v_dict;
95989625
if (d != NULL)
@@ -9603,24 +9630,26 @@ max_min(typval_T *argvars, typval_T *rettv, int domax)
96039630
if (!HASHITEM_EMPTY(hi))
96049631
{
96059632
--todo;
9606-
i = tv_get_number_chk(&HI2DI(hi)->di_tv, &error);
9607-
if (error)
9608-
return; // type error; errmsg already given
9609-
if (first)
9633+
if (tv == NULL)
9634+
tv = &HI2DI(hi)->di_tv;
9635+
else
96109636
{
9611-
n = i;
9612-
first = FALSE;
9637+
int res;
9638+
if (typval_compare2(&HI2DI(hi)->di_tv, tv,
9639+
domax ? EXPR_GREATER : EXPR_SMALLER, FALSE, &res) == FAIL)
9640+
return;
9641+
if (res == OK)
9642+
tv = &HI2DI(hi)->di_tv;
96139643
}
9614-
else if (domax ? i > n : i < n)
9615-
n = i;
96169644
}
96179645
}
96189646
}
9647+
9648+
if (tv != NULL)
9649+
copy_tv(tv, rettv);
96199650
}
96209651
else
96219652
semsg(_(e_argument_of_str_must_be_list_or_dictionary), domax ? "max()" : "min()");
9622-
9623-
rettv->vval.v_number = n;
96249653
}
96259654

96269655
/*

src/proto/tuple.pro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ long tuple_count(tuple_T *tuple, typval_T *needle, long idx, int ic);
2626
void tuple2items(typval_T *argvars, typval_T *rettv);
2727
int index_tuple(tuple_T *tuple, typval_T *tv, int start_idx, int ic);
2828
int indexof_tuple(tuple_T *tuple, long startidx, typval_T *expr);
29-
varnumber_T tuple_max_min(tuple_T *tuple, int domax, int *error);
29+
typval_T *tuple_max_min(tuple_T *tuple, int domax);
3030
void tuple_repeat(tuple_T *tuple, int n, typval_T *rettv);
3131
void tuple_reverse(tuple_T *tuple, typval_T *rettv);
3232
void tuple_reduce(typval_T *argvars, typval_T *expr, typval_T *rettv);

src/proto/typval.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ char_u *tv_stringify(typval_T *varp, char_u *buf);
6868
int tv_check_lock(typval_T *tv, char_u *name, int use_gettext);
6969
void copy_tv(typval_T *from, typval_T *to);
7070
int typval_compare(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic);
71+
int typval_compare2(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res);
7172
int typval_compare_list(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res);
7273
int typval_compare_tuple(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res);
7374
int typval_compare_null(typval_T *tv1, typval_T *tv2);

src/testdir/test_functions.vim

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,35 +133,57 @@ func Test_max()
133133
call assert_equal(0, max([]))
134134
call assert_equal(2, max([2]))
135135
call assert_equal(2, max([1, 2]))
136+
call assert_equal(3, max([1.0, 2, 3]))
137+
call assert_equal(3.0, max([1, 2, 3.0]))
136138
call assert_equal(2, max([1, 2, v:null]))
137139

140+
call assert_equal(0, max(()))
141+
call assert_equal(2, max((2, )))
142+
call assert_equal(2, max((1, 2)))
143+
call assert_equal(3, max((1.0, 2, 3)))
144+
call assert_equal(3.0, max((1, 2, 3.0)))
145+
call assert_equal(2, max((1, 2, v:null)))
146+
138147
call assert_equal(0, max({}))
139148
call assert_equal(2, max({'a':1, 'b':2}))
140149

150+
call assert_equal('abz', max(['abc', 'aba', 'abz']))
151+
141152
call assert_fails('call max(1)', 'E712:')
142153
call assert_fails('call max(v:none)', 'E712:')
143154

144155
" check we only get one error
145-
call assert_fails('call max([#{}, [1]])', ['E728:', 'E728:'])
146-
call assert_fails('call max(#{a: {}, b: [1]})', ['E728:', 'E728:'])
156+
call assert_fails('call max([#{}, [1]])', ['E691:', 'E691:'])
157+
call assert_fails('call max(#{a: {}, b: [1]})', ['E691:', 'E691:'])
147158
endfunc
148159

149160
func Test_min()
150161
call assert_equal(0, min([]))
151162
call assert_equal(2, min([2]))
152163
call assert_equal(1, min([1, 2]))
153-
call assert_equal(0, min([1, 2, v:null]))
164+
call assert_equal(1, min([1, 2, 3.0]))
165+
call assert_equal(1.0, min([1.0, 2]))
166+
call assert_equal(v:null, min([1, 2, v:null]))
167+
168+
call assert_equal(0, min(()))
169+
call assert_equal(2, min((2, )))
170+
call assert_equal(1, min((1, 2)))
171+
call assert_equal(1, min((1, 2, 3.0)))
172+
call assert_equal(1.0, min((1.0, 2)))
173+
call assert_equal(v:null, min((1, 2, v:null)))
154174

155175
call assert_equal(0, min({}))
156176
call assert_equal(1, min({'a':1, 'b':2}))
157177

178+
call assert_equal('aba', min(['abc', 'aba', 'abz']))
179+
158180
call assert_fails('call min(1)', 'E712:')
159181
call assert_fails('call min(v:none)', 'E712:')
160-
call assert_fails('call min([1, {}])', 'E728:')
182+
call assert_fails('call min([1, {}])', 'E735:')
161183

162184
" check we only get one error
163-
call assert_fails('call min([[1], #{}])', ['E745:', 'E745:'])
164-
call assert_fails('call min(#{a: [1], b: #{}})', ['E745:', 'E745:'])
185+
call assert_fails('call min([[1], #{}])', ['E691:', 'E691:'])
186+
call assert_fails('call min(#{a: [1], b: #{}})', ['E691:', 'E691:'])
165187
endfunc
166188

167189
func Test_strwidth()

src/testdir/test_tuple.vim

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1994,22 +1994,13 @@ func Test_tuple_max()
19941994
END
19951995
call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "b"')
19961996

1997-
let lines =<< trim END
1998-
vim9script
1999-
def Fn()
2000-
var x = max(('a', 'b'))
2001-
enddef
2002-
Fn()
2003-
END
2004-
call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "a"')
2005-
20061997
let lines =<< trim END
20071998
echo max([('a', 'b'), 20])
20081999
END
20092000
call v9.CheckSourceLegacyAndVim9Failure(lines, [
2010-
\ 'E1520: Using a Tuple as a Number',
2011-
\ 'E1520: Using a Tuple as a Number',
2012-
\ 'E1520: Using a Tuple as a Number'])
2001+
\ 'E1517: Can only compare Tuple with Tuple',
2002+
\ 'E1517: Can only compare Tuple with Tuple',
2003+
\ 'E1517: Can only compare Tuple with Tuple'])
20132004
endfunc
20142005

20152006
" Test for min()
@@ -2035,16 +2026,6 @@ func Test_tuple_min()
20352026
var x = min((1, 'b'))
20362027
END
20372028
call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "b"')
2038-
2039-
2040-
let lines =<< trim END
2041-
vim9script
2042-
def Fn()
2043-
var x = min(('a', 'b'))
2044-
enddef
2045-
Fn()
2046-
END
2047-
call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "a"')
20482029
endfunc
20492030

20502031
" Test for reduce()

src/testdir/test_vim9_assign.vim

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3229,7 +3229,7 @@ def Test_type_check()
32293229
assert_fails('N = d', 'E1012: Type mismatch; expected number but got dict<number>')
32303230
assert_fails('N = l', 'E1012: Type mismatch; expected number but got list<number>')
32313231
assert_fails('N = b', 'E1012: Type mismatch; expected number but got blob')
3232-
assert_fails('N = Fn', 'E1012: Type mismatch; expected number but got func([unknown]): number')
3232+
assert_fails('N = Fn', 'E1012: Type mismatch; expected number but got func([unknown]): any')
32333233
assert_fails('N = A', 'E1405: Class "A" cannot be used as a value')
32343234
assert_fails('N = o', 'E1012: Type mismatch; expected number but got object<A>')
32353235
assert_fails('N = T', 'E1403: Type alias "T" cannot be used as a value')
@@ -3247,7 +3247,7 @@ def Test_type_check()
32473247
assert_fails('var [X1: number, Y: number] = [1, d]', 'E1012: Type mismatch; expected number but got dict<number>')
32483248
assert_fails('var [X2: number, Y: number] = [1, l]', 'E1012: Type mismatch; expected number but got list<number>')
32493249
assert_fails('var [X3: number, Y: number] = [1, b]', 'E1012: Type mismatch; expected number but got blob')
3250-
assert_fails('var [X4: number, Y: number] = [1, Fn]', 'E1012: Type mismatch; expected number but got func([unknown]): number')
3250+
assert_fails('var [X4: number, Y: number] = [1, Fn]', 'E1012: Type mismatch; expected number but got func([unknown]): any')
32513251
assert_fails('var [X7: number, Y: number] = [1, A]', 'E1405: Class "A" cannot be used as a value')
32523252
assert_fails('var [X8: number, Y: number] = [1, o]', 'E1012: Type mismatch; expected number but got object<A>')
32533253
assert_fails('var [X8: number, Y: number] = [1, T]', 'E1403: Type alias "T" cannot be used as a value')
@@ -3345,7 +3345,7 @@ def Test_func_argtype_check()
33453345
assert_fails('IntArg(d)', 'E1013: Argument 1: type mismatch, expected number but got dict<number>')
33463346
assert_fails('IntArg(l)', 'E1013: Argument 1: type mismatch, expected number but got list<number>')
33473347
assert_fails('IntArg(b)', 'E1013: Argument 1: type mismatch, expected number but got blob')
3348-
assert_fails('IntArg(Fn)', 'E1013: Argument 1: type mismatch, expected number but got func([unknown]): number')
3348+
assert_fails('IntArg(Fn)', 'E1013: Argument 1: type mismatch, expected number but got func([unknown]): any')
33493349
if has('channel')
33503350
var j: job = test_null_job()
33513351
var ch: channel = test_null_channel()

src/testdir/test_vim9_builtin.vim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4910,8 +4910,8 @@ def Test_typename()
49104910
if has('channel')
49114911
assert_equal('channel', test_null_channel()->typename())
49124912
endif
4913-
var l: list<func(list<number>): number> = [function('min')]
4914-
assert_equal('list<func(list<number>): number>', typename(l))
4913+
var l: list<func(list<number>): any> = [function('min')]
4914+
assert_equal('list<func(list<number>): any>', typename(l))
49154915
enddef
49164916

49174917
def Test_undofile()

src/testdir/test_vim9_script.vim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4111,7 +4111,7 @@ enddef
41114111
def Test_source_func_script_var()
41124112
var lines =<< trim END
41134113
vim9script noclear
4114-
var Fn: func(list<any>): number
4114+
var Fn: func(list<any>): any
41154115
Fn = function('min')
41164116
assert_equal(2, Fn([4, 2]))
41174117
END

0 commit comments

Comments
 (0)