mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
builtin: implement array.sorted() and array.sorted_with_compare() methods, that do not modify their receivers (#19251)
This commit is contained in:
parent
d9a382fb32
commit
58b6ba81d1
11 changed files with 234 additions and 38 deletions
|
@ -15,6 +15,7 @@ const (
|
|||
//
|
||||
'vlib/builtin/int_test.v',
|
||||
'vlib/builtin/array_test.v',
|
||||
'vlib/builtin/array_sorted_test.v',
|
||||
'vlib/builtin/float_test.v',
|
||||
'vlib/builtin/byte_test.v',
|
||||
'vlib/builtin/rune_test.v',
|
||||
|
|
|
@ -827,6 +827,12 @@ pub fn (a array) map(callback fn (voidptr) voidptr) array
|
|||
// Example: array.sort(b.name < a.name) // will sort descending by the .name field
|
||||
pub fn (mut a array) sort(callback fn (voidptr, voidptr) int)
|
||||
|
||||
// sorted returns a sorted copy of the original array. The original array is *NOT* modified.
|
||||
// See also .sort() .
|
||||
// Example: assert [9,1,6,3,9].sorted() == [1,3,6,9,9]
|
||||
// Example: assert [9,1,6,3,9].sorted(b < a) == [9,9,6,3,1]
|
||||
pub fn (a &array) sorted(callback fn (voidptr, voidptr) int) array
|
||||
|
||||
// sort_with_compare sorts the array in-place using the results of the
|
||||
// given function to determine sort order.
|
||||
//
|
||||
|
@ -859,6 +865,20 @@ pub fn (mut a array) sort_with_compare(callback fn (voidptr, voidptr) int) {
|
|||
}
|
||||
}
|
||||
|
||||
// sorted_with_compare sorts a clone of the array, using the results of the
|
||||
// given function to determine sort order. The original array is not modified.
|
||||
// See also .sort_with_compare()
|
||||
pub fn (a &array) sorted_with_compare(callback fn (voidptr, voidptr) int) array {
|
||||
$if freestanding {
|
||||
panic('sort does not work with -freestanding')
|
||||
} $else {
|
||||
mut r := a.clone()
|
||||
unsafe { vqsort(r.data, usize(r.len), usize(r.element_size), callback) }
|
||||
return r
|
||||
}
|
||||
return array{}
|
||||
}
|
||||
|
||||
// contains determines whether an array includes a certain value among its elements
|
||||
// It will return `true` if the array contains an element with this value.
|
||||
// It is similar to `.any` but does not take an `it` expression.
|
||||
|
|
90
vlib/builtin/array_sorted_test.v
Normal file
90
vlib/builtin/array_sorted_test.v
Normal file
|
@ -0,0 +1,90 @@
|
|||
fn test_sorted_immutable_original_should_not_change() {
|
||||
a := ['hi', '1', '5', '3']
|
||||
b := a.sorted()
|
||||
assert a == ['hi', '1', '5', '3']
|
||||
assert b == ['1', '3', '5', 'hi']
|
||||
}
|
||||
|
||||
fn test_sorted_mutable_original_should_not_change() {
|
||||
mut a := ['hi', '1', '5', '3']
|
||||
b := a.sorted()
|
||||
assert a == ['hi', '1', '5', '3']
|
||||
assert b == ['1', '3', '5', 'hi']
|
||||
}
|
||||
|
||||
fn test_sorted_reversed() {
|
||||
aa := ['hi', '1', '5', '3']
|
||||
bb := aa.sorted(a > b)
|
||||
assert aa == ['hi', '1', '5', '3']
|
||||
assert bb == ['hi', '5', '3', '1']
|
||||
}
|
||||
|
||||
fn test_sorted_by_len() {
|
||||
a := ['hi', 'abc', 'a', 'zzzzz']
|
||||
c := a.sorted(a.len < b.len)
|
||||
assert c == ['a', 'hi', 'abc', 'zzzzz']
|
||||
}
|
||||
|
||||
fn test_sorted_can_be_called_on_an_array_literals() {
|
||||
b := [5, 1, 9].sorted()
|
||||
assert b == [1, 5, 9]
|
||||
assert [5.0, 1.2, 9.4].sorted() == [1.2, 5.0, 9.4]
|
||||
assert ['a', '00', 'z', 'dd', 'xyz'].sorted(a > b) == ['z', 'xyz', 'dd', 'a', '00']
|
||||
assert ['a', '00', 'zzzzz', 'dddd', 'xyz'].sorted(a.len > b.len) == ['zzzzz', 'dddd', 'xyz',
|
||||
'00', 'a']
|
||||
assert ['a', '00', 'zzzzz', 'dddd', 'xyz'].sorted(a.len < b.len) == ['a', '00', 'xyz', 'dddd',
|
||||
'zzzzz']
|
||||
}
|
||||
|
||||
fn iarr() []int {
|
||||
return [5, 1, 9, 1, 2]
|
||||
}
|
||||
|
||||
fn test_sorted_can_be_called_on_the_result_of_a_fn() {
|
||||
assert iarr().sorted() == [1, 1, 2, 5, 9]
|
||||
assert iarr().sorted(a > b) == [9, 5, 2, 1, 1]
|
||||
}
|
||||
|
||||
fn sum(prev int, curr int) int {
|
||||
return prev + curr
|
||||
}
|
||||
|
||||
fn test_sorting_2d_arrays() {
|
||||
assert [[1, 2], [3, 4, 5], [2]].sorted(a.len > b.len) == [
|
||||
[3, 4, 5],
|
||||
[1, 2],
|
||||
[2],
|
||||
]
|
||||
assert [[1, 2], [3, 4, 5], [2]].sorted(a.len < b.len) == [
|
||||
[2],
|
||||
[1, 2],
|
||||
[3, 4, 5],
|
||||
]
|
||||
assert unsafe { [[1, 2], [3, 4, 5], [2]].sorted(a[0] > b[0]) } == [
|
||||
[3, 4, 5],
|
||||
[2],
|
||||
[1, 2],
|
||||
]
|
||||
// assert [[1, 2], [3, 4, 5], [2]].sorted( a.reduce(sum) > b.reduce(sum) ) == ... // TODO
|
||||
}
|
||||
|
||||
fn test_sorting_3d_arrays() {
|
||||
assert [[][]int{}, [[2, 22], [2]], [[3, 33], [3333], [33, 34, 35]],
|
||||
[[444]]].sorted(a.len > b.len) == [[[3, 33], [3333], [33, 34, 35]],
|
||||
[[2, 22], [2]], [[444]], [][]int{}]
|
||||
}
|
||||
|
||||
fn test_sorted_with_compare() {
|
||||
aa := ['hi', '1', '5', '3']
|
||||
bb := aa.sorted_with_compare(fn (a &string, b &string) int {
|
||||
if a < b {
|
||||
return -1
|
||||
}
|
||||
if a > b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
assert aa == ['hi', '1', '5', '3'], 'aa should stay unmodified'
|
||||
assert bb == ['1', '3', '5', 'hi'], 'bb should be sorted, according to the custom comparison callback fn'
|
||||
}
|
|
@ -67,12 +67,12 @@ fn test_deleting() {
|
|||
|
||||
fn test_slice_delete() {
|
||||
mut a := [1.5, 2.5, 3.25, 4.5, 5.75]
|
||||
b := a[2..4]
|
||||
b := unsafe { a[2..4] }
|
||||
a.delete(0)
|
||||
assert a == [2.5, 3.25, 4.5, 5.75]
|
||||
assert b == [3.25, 4.5]
|
||||
a = [3.75, 4.25, -1.5, 2.25, 6.0]
|
||||
c := a[..3]
|
||||
c := unsafe { a[..3] }
|
||||
a.delete(2)
|
||||
assert a == [3.75, 4.25, 2.25, 6.0]
|
||||
assert c == [3.75, 4.25, -1.5]
|
||||
|
@ -80,11 +80,11 @@ fn test_slice_delete() {
|
|||
|
||||
fn test_delete_many() {
|
||||
mut a := [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
b := a[2..6]
|
||||
b := unsafe { a[2..6] }
|
||||
a.delete_many(4, 3)
|
||||
assert a == [1, 2, 3, 4, 8, 9]
|
||||
assert b == [3, 4, 5, 6]
|
||||
c := a[..a.len]
|
||||
c := unsafe { a[..a.len] }
|
||||
a.delete_many(2, 0) // this should just clone
|
||||
a[1] = 17
|
||||
assert a == [1, 17, 3, 4, 8, 9]
|
||||
|
|
|
@ -26,8 +26,13 @@ const (
|
|||
)
|
||||
|
||||
pub const (
|
||||
// array_builtin_methods contains a list of all methods on array, that return other typed arrays,
|
||||
// i.e. that act as *pseudogeneric* methods, that need compiler support, so that the types of the results
|
||||
// are properly checked.
|
||||
// Note that methods that do not return anything, or that return known types, are not listed here, since they are just ordinary non generic methods.
|
||||
array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort',
|
||||
'contains', 'index', 'wait', 'any', 'all', 'first', 'last', 'pop', 'delete']
|
||||
'sorted', 'sorted_with_compare', 'contains', 'index', 'wait', 'any', 'all', 'first', 'last',
|
||||
'pop', 'delete']
|
||||
array_builtin_methods_chk = token.new_keywords_matcher_from_array_trie(array_builtin_methods)
|
||||
// TODO: remove `byte` from this list when it is no longer supported
|
||||
reserved_type_names = ['byte', 'bool', 'char', 'i8', 'i16', 'int', 'i64', 'u8', 'u16',
|
||||
|
|
|
@ -2117,7 +2117,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
|
|||
}
|
||||
}
|
||||
}
|
||||
if left_sym.info is ast.Array && method_name == 'sort_with_compare' {
|
||||
if left_sym.info is ast.Array && method_name in ['sort_with_compare', 'sorted_with_compare'] {
|
||||
elem_typ := left_sym.info.elem_type
|
||||
arg_sym := c.table.sym(arg.typ)
|
||||
if arg_sym.kind == .function {
|
||||
|
@ -2126,13 +2126,13 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
|
|||
if func_info.func.params[0].typ.nr_muls() != elem_typ.nr_muls() + 1 {
|
||||
arg_typ_str := c.table.type_to_str(func_info.func.params[0].typ)
|
||||
expected_typ_str := c.table.type_to_str(elem_typ.ref())
|
||||
c.error('sort_with_compare callback function parameter `${func_info.func.params[0].name}` with type `${arg_typ_str}` should be `${expected_typ_str}`',
|
||||
c.error('${method_name} callback function parameter `${func_info.func.params[0].name}` with type `${arg_typ_str}` should be `${expected_typ_str}`',
|
||||
func_info.func.params[0].type_pos)
|
||||
}
|
||||
if func_info.func.params[1].typ.nr_muls() != elem_typ.nr_muls() + 1 {
|
||||
arg_typ_str := c.table.type_to_str(func_info.func.params[1].typ)
|
||||
expected_typ_str := c.table.type_to_str(elem_typ.ref())
|
||||
c.error('sort_with_compare callback function parameter `${func_info.func.params[1].name}` with type `${arg_typ_str}` should be `${expected_typ_str}`',
|
||||
c.error('${method_name} callback function parameter `${func_info.func.params[1].name}` with type `${arg_typ_str}` should be `${expected_typ_str}`',
|
||||
func_info.func.params[1].type_pos)
|
||||
}
|
||||
}
|
||||
|
@ -2564,6 +2564,20 @@ fn (mut c Checker) map_builtin_method_call(mut node ast.CallExpr, left_type ast.
|
|||
return node.return_type
|
||||
}
|
||||
|
||||
// ensure_same_array_return_type makes sure, that the return type of .clone(), .sorted(), .sorted_with_compare() etc,
|
||||
// is `array_xxx`, instead of the plain `array` .
|
||||
fn (mut c Checker) ensure_same_array_return_type(mut node ast.CallExpr, left_type ast.Type) {
|
||||
node.receiver_type = left_type.ref()
|
||||
if node.left.is_auto_deref_var() {
|
||||
node.return_type = left_type.deref()
|
||||
} else {
|
||||
node.return_type = node.receiver_type.set_nr_muls(0)
|
||||
}
|
||||
if node.return_type.has_flag(.shared_f) {
|
||||
node.return_type = node.return_type.clear_flag(.shared_f)
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type ast.Type, left_sym ast.TypeSymbol) ast.Type {
|
||||
method_name := node.name
|
||||
mut elem_typ := ast.void_type
|
||||
|
@ -2579,12 +2593,14 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
|
|||
if method_name in ['filter', 'map', 'any', 'all'] {
|
||||
// position of `it` doesn't matter
|
||||
scope_register_it(mut node.scope, node.pos, elem_typ)
|
||||
} else if method_name == 'sort' {
|
||||
if node.left is ast.CallExpr {
|
||||
c.error('the `sort()` method can be called only on mutable receivers, but `${node.left}` is a call expression',
|
||||
node.pos)
|
||||
} else if method_name == 'sort' || method_name == 'sorted' {
|
||||
if method_name == 'sort' {
|
||||
if node.left is ast.CallExpr {
|
||||
c.error('the `sort()` method can be called only on mutable receivers, but `${node.left}` is a call expression',
|
||||
node.pos)
|
||||
}
|
||||
c.fail_if_immutable(mut node.left)
|
||||
}
|
||||
c.fail_if_immutable(mut node.left)
|
||||
// position of `a` and `b` doesn't matter, they're the same
|
||||
scope_register_a_b(mut node.scope, node.pos, elem_typ)
|
||||
|
||||
|
@ -2593,25 +2609,26 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
|
|||
} else if node.args.len == 1 {
|
||||
if node.args[0].expr is ast.InfixExpr {
|
||||
if node.args[0].expr.op !in [.gt, .lt] {
|
||||
c.error('`.sort()` can only use `<` or `>` comparison', node.pos)
|
||||
c.error('`.${method_name}()` can only use `<` or `>` comparison',
|
||||
node.pos)
|
||||
}
|
||||
left_name := '${node.args[0].expr.left}'[0]
|
||||
right_name := '${node.args[0].expr.right}'[0]
|
||||
if left_name !in [`a`, `b`] || right_name !in [`a`, `b`] {
|
||||
c.error('`.sort()` can only use `a` or `b` as argument, e.g. `arr.sort(a < b)`',
|
||||
c.error('`.${method_name}()` can only use `a` or `b` as argument, e.g. `arr.${method_name}(a < b)`',
|
||||
node.pos)
|
||||
} else if left_name == right_name {
|
||||
c.error('`.sort()` cannot use same argument', node.pos)
|
||||
c.error('`.${method_name}()` cannot use same argument', node.pos)
|
||||
}
|
||||
if node.args[0].expr.left !in [ast.Ident, ast.SelectorExpr, ast.IndexExpr]
|
||||
|| node.args[0].expr.right !in [ast.Ident, ast.SelectorExpr, ast.IndexExpr] {
|
||||
c.error('`.sort()` can only use ident, index or selector as argument, \ne.g. `arr.sort(a < b)`, `arr.sort(a.id < b.id)`, `arr.sort(a[0] < b[0])`',
|
||||
c.error('`.${method_name}()` can only use ident, index or selector as argument, \ne.g. `arr.${method_name}(a < b)`, `arr.${method_name}(a.id < b.id)`, `arr.${method_name}(a[0] < b[0])`',
|
||||
node.pos)
|
||||
}
|
||||
} else {
|
||||
c.error(
|
||||
'`.sort()` requires a `<` or `>` comparison as the first and only argument' +
|
||||
'\ne.g. `users.sort(a.id < b.id)`', node.pos)
|
||||
'`.${method_name}()` requires a `<` or `>` comparison as the first and only argument' +
|
||||
'\ne.g. `users.${method_name}(a.id < b.id)`', node.pos)
|
||||
}
|
||||
} else if !(c.table.sym(elem_typ).has_method('<')
|
||||
|| c.table.unalias_num_type(elem_typ) in [ast.int_type, ast.int_type.ref(), ast.string_type, ast.string_type.ref(), ast.i8_type, ast.i16_type, ast.i64_type, ast.u8_type, ast.rune_type, ast.u16_type, ast.u32_type, ast.u64_type, ast.f32_type, ast.f64_type, ast.char_type, ast.bool_type, ast.float_literal_type, ast.int_literal_type]) {
|
||||
|
@ -2682,16 +2699,21 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
|
|||
if node.args.len != 0 {
|
||||
c.error('`.clone()` does not have any arguments', node.args[0].pos)
|
||||
}
|
||||
// need to return `array_xxx` instead of `array`
|
||||
// in ['clone', 'str'] {
|
||||
node.receiver_type = left_type.ref()
|
||||
if node.left.is_auto_deref_var() {
|
||||
node.return_type = left_type.deref()
|
||||
} else {
|
||||
node.return_type = node.receiver_type.set_nr_muls(0)
|
||||
}
|
||||
if node.return_type.has_flag(.shared_f) {
|
||||
node.return_type = node.return_type.clear_flag(.shared_f)
|
||||
c.ensure_same_array_return_type(mut node, left_type)
|
||||
} else if method_name == 'sorted' {
|
||||
c.ensure_same_array_return_type(mut node, left_type)
|
||||
} else if method_name == 'sorted_with_compare' {
|
||||
c.ensure_same_array_return_type(mut node, left_type)
|
||||
// Inject a (voidptr) cast for the callback argument, to pass -cstrict, otherwise:
|
||||
// error: incompatible function pointer types passing
|
||||
// 'int (string *, string *)' (aka 'int (struct string *, struct string *)')
|
||||
// to parameter of type 'int (*)(voidptr, voidptr)' (aka 'int (*)(void *, void *)')
|
||||
node.args[0].expr = ast.CastExpr{
|
||||
expr: node.args[0].expr
|
||||
typ: ast.voidptr_type
|
||||
typname: 'voidptr'
|
||||
expr_type: c.expr(mut node.args[0].expr)
|
||||
pos: node.pos
|
||||
}
|
||||
} else if method_name == 'sort' {
|
||||
node.return_type = ast.void_type
|
||||
|
|
|
@ -585,6 +585,30 @@ fn (mut g Gen) gen_array_map(node ast.CallExpr) {
|
|||
g.inside_lambda = false
|
||||
}
|
||||
|
||||
// `susers := users.sorted(a.age < b.age)`
|
||||
fn (mut g Gen) gen_array_sorted(node ast.CallExpr) {
|
||||
past := g.past_tmp_var_new()
|
||||
defer {
|
||||
g.past_tmp_var_done(past)
|
||||
}
|
||||
atype := g.typ(node.return_type)
|
||||
sym := g.table.sym(node.return_type)
|
||||
info := sym.info as ast.Array
|
||||
depth := g.get_array_depth(info.elem_type)
|
||||
|
||||
g.write('${atype} ${past.tmp_var} = array_clone_to_depth(ADDR(${atype},')
|
||||
g.expr(node.left)
|
||||
g.writeln('), ${depth});')
|
||||
|
||||
unsafe {
|
||||
node.left = ast.Expr(ast.Ident{
|
||||
name: past.tmp_var
|
||||
})
|
||||
}
|
||||
g.gen_array_sort(node)
|
||||
g.writeln(';')
|
||||
}
|
||||
|
||||
// `users.sort(a.age < b.age)`
|
||||
fn (mut g Gen) gen_array_sort(node ast.CallExpr) {
|
||||
// println('filter s="$s"')
|
||||
|
|
|
@ -897,6 +897,10 @@ fn (mut g Gen) gen_array_method_call(node ast.CallExpr, left_type ast.Type) bool
|
|||
g.gen_array_sort(node)
|
||||
return true
|
||||
}
|
||||
'sorted' {
|
||||
g.gen_array_sorted(node)
|
||||
return true
|
||||
}
|
||||
'insert' {
|
||||
g.gen_array_insert(node)
|
||||
return true
|
||||
|
@ -1303,7 +1307,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
|
|||
receiver_type_name = 'map'
|
||||
}
|
||||
if final_left_sym.kind == .array && !(left_sym.kind == .alias && left_sym.has_method(node.name))
|
||||
&& node.name in ['clear', 'repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] {
|
||||
&& node.name in ['clear', 'repeat', 'sort_with_compare', 'sorted_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] {
|
||||
if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) {
|
||||
// `array_Xyz_clone` => `array_clone`
|
||||
receiver_type_name = 'array'
|
||||
|
|
30
vlib/v/gen/c/past_tmp_var.v
Normal file
30
vlib/v/gen/c/past_tmp_var.v
Normal file
|
@ -0,0 +1,30 @@
|
|||
module c
|
||||
|
||||
struct PastTmpVar {
|
||||
mut:
|
||||
tmp_var string
|
||||
s string
|
||||
s_ends_with_ln bool
|
||||
}
|
||||
|
||||
fn (mut g Gen) past_tmp_var_new() PastTmpVar {
|
||||
tmp_var := g.new_tmp_var()
|
||||
mut s := g.go_before_stmt(0)
|
||||
s_ends_with_ln := s.ends_with('\n')
|
||||
s = s.trim_space()
|
||||
g.empty_line = true
|
||||
return PastTmpVar{
|
||||
tmp_var: tmp_var
|
||||
s: s
|
||||
s_ends_with_ln: s_ends_with_ln
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) past_tmp_var_done(p &PastTmpVar) {
|
||||
if p.s_ends_with_ln {
|
||||
g.writeln(p.s)
|
||||
} else {
|
||||
g.write(p.s)
|
||||
}
|
||||
g.write(p.tmp_var)
|
||||
}
|
|
@ -34,9 +34,9 @@ struct Mapping {
|
|||
|
||||
struct Mappings {
|
||||
mut:
|
||||
sorted bool
|
||||
last Mapping
|
||||
values []Mapping
|
||||
is_sorted bool
|
||||
last Mapping
|
||||
values []Mapping
|
||||
}
|
||||
|
||||
fn new_mappings() Mappings {
|
||||
|
@ -47,7 +47,7 @@ fn new_mappings() Mappings {
|
|||
gen_line: 0
|
||||
}
|
||||
}
|
||||
sorted: true
|
||||
is_sorted: true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ fn new_mappings() Mappings {
|
|||
fn (mut m Mappings) add_mapping(gen_line u32, gen_column u32, sources_ind u32, source_position SourcePositionType, names_ind NameIndexType) {
|
||||
if !(gen_line > m.last.gen_line
|
||||
|| (gen_line == m.last.gen_line && gen_column >= m.last.gen_column)) {
|
||||
m.sorted = false
|
||||
m.is_sorted = false
|
||||
}
|
||||
m.values << Mapping{
|
||||
GenPosition: GenPosition{
|
||||
|
@ -71,7 +71,7 @@ fn (mut m Mappings) add_mapping(gen_line u32, gen_column u32, sources_ind u32, s
|
|||
// Returns the flat, sorted array of mappings. The mappings are sorted by generated position.
|
||||
|
||||
fn (mut m Mappings) get_sorted_array() []Mapping {
|
||||
if !m.sorted {
|
||||
if !m.is_sorted {
|
||||
panic('not implemented')
|
||||
}
|
||||
return m.values
|
||||
|
|
|
@ -3099,7 +3099,7 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
|
|||
p.name_error = true
|
||||
}
|
||||
is_filter := field_name in ['filter', 'map', 'any', 'all']
|
||||
if is_filter || field_name == 'sort' {
|
||||
if is_filter || field_name == 'sort' || field_name == 'sorted' {
|
||||
p.open_scope()
|
||||
}
|
||||
// ! in mutable methods
|
||||
|
@ -3163,7 +3163,7 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
|
|||
scope: p.scope
|
||||
comments: comments
|
||||
}
|
||||
if is_filter || field_name == 'sort' {
|
||||
if is_filter || field_name == 'sort' || field_name == 'sorted' {
|
||||
p.close_scope()
|
||||
}
|
||||
return mcall_expr
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue