Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions vlib/v/compiler_tests/vls_generic_parameter_scope_review_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,93 @@ fn test_type_parameter_hover_and_definition_stay_in_their_declaration_scope() {
assert scope_review_query(path, '19:19', 'hv').contains('[T]')
assert scope_review_query(path, '19:19', 'gd') == '${path}:19:12'
}

// receiver_scope_layouts put right before a method of `Box[T]` each kind of
// declaration that the `T` written in its receiver was taken from: its struct,
// another method, and a generic function with and without a `T` of its own.
const receiver_scope_layouts = [
'',
'fn (b Box[T]) first() T {\n\treturn b.value\n}\n\n',
'fn identity[T](x T) T {\n\treturn x\n}\n\n',
'fn helper[U](u U) U {\n\treturn u\n}\n\n',
]

const receiver_forms_program = 'module main\n\nstruct T {\n\tvalue int\n}\n\nstruct Box[T] {\n\tvalue T\n}\n\nstruct Pair[K, V] {\n\tkey K\n\tvalue V\n}\n\nfn (p Pair[K, V]) entry() (K, V) {\n\treturn p.key, p.value\n}\n\nfn (mut b Box[T]) set(value T) {\n\tb.value = value\n}\n\nfn (b &Box[T]) peek() T {\n\treturn b.value\n}\n\nfn (b Box[T]) convert[U](f fn (T) U) Box[U] {\n\treturn Box[U]{\n\t\tvalue: f(b.value)\n\t}\n}\n\n__global after_methods T = T{}\n\nfn main() {\n\tprintln(after_methods.value)\n}\n'

// word_position is `line:column` of the `nth` type parameter `name` in the
// first line of `source` that starts with `prefix`.
fn word_position(source string, prefix string, name string, nth int) string {
mut seen := 0
for i, line in source.split_into_lines() {
if !line.starts_with(prefix) {
continue
}
for col, c in line {
if c == name[0] && (col == 0 || !line[col - 1].is_alnum())
&& (col + 1 == line.len || !line[col + 1].is_alnum()) {
if seen == nth {
return '${i + 1}:${col}'
}
seen++
}
}
break
}
panic('no `${name}` #${nth} in the line `${prefix}`')
}

fn test_a_type_parameter_in_a_method_receiver_belongs_to_the_method() {
root := os.join_path(os.vtmp_dir(), 'vls_receiver_scope_review_${os.getpid()}')
os.mkdir_all(root)!
defer { os.rmdir_all(root) or {} }
for i, layout in receiver_scope_layouts {
path := os.join_path(root, 'layout_${i}.v')
source := 'module main\n\nstruct Box[T] {\n\tvalue T\n}\n\n${layout}fn (b Box[T]) get() T {\n\t_ := []T{}\n\treturn b.value\n}\n\nfn main() {\n\tprintln(Box[int]{}.get())\n}\n'
os.write_file(path, source)!
receiver := word_position(source, 'fn (b Box[T]) get', 'T', 0)
for position in [receiver, word_position(source, 'fn (b Box[T]) get', 'T', 1),
word_position(source, '\t_ := []T{}', 'T', 0)] {
assert scope_review_query(path, position, 'hv').contains('[T]'), 'layout ${i} at ${position}'
assert scope_review_query(path, position, 'gd') == '${path}:${receiver}', 'layout ${i} at ${position}'
}
if layout.starts_with('fn (b Box[T]) first') {
first := word_position(source, 'fn (b Box[T]) first', 'T', 0)
assert scope_review_query(path, first, 'gd') == '${path}:${first}'
}
}
}

fn test_type_parameters_in_receivers_of_every_form_belong_to_their_method() {
root := os.join_path(os.vtmp_dir(), 'vls_receiver_forms_review_${os.getpid()}')
os.mkdir_all(root)!
defer { os.rmdir_all(root) or {} }
path := os.join_path(root, 'forms.v')
source := receiver_forms_program
os.write_file(path, source)!
for prefix, names in {
'fn (p Pair[K, V]) entry': ['K', 'V']
'fn (mut b Box[T]) set': ['T']
'fn (b &Box[T]) peek': ['T']
'fn (b Box[T]) convert': ['T']
} {
for name in names {
declared := word_position(source, prefix, name, 0)
hover := scope_review_query(path, declared, 'hv')
assert hover.contains('[${name}]'), '${prefix}: ${hover}'
assert !hover.contains('struct'), '${prefix}: ${hover}'
assert scope_review_query(path, declared, 'gd') == '${path}:${declared}', prefix
// Its next use in the signature leads to the receiver too.
used := word_position(source, prefix, name, 1)
assert scope_review_query(path, used, 'gd') == '${path}:${declared}', '${prefix} ${used}'
}
}
// The own type parameter of the method is declared after its name.
own := word_position(source, 'fn (b Box[T]) convert', 'U', 0)
result := word_position(source, 'fn (b Box[T]) convert', 'U', 2)
assert scope_review_query(path, result, 'gd') == '${path}:${own}'
// A declaration after the methods still finds the module type `T`.
after := word_position(source, '__global after_methods', 'T', 0)
hover := scope_review_query(path, after, 'hv')
assert hover.contains('struct T') && !hover.contains('[T]'), hover
assert scope_review_query(path, after, 'gd') == '${path}:3:7'
}
63 changes: 63 additions & 0 deletions vlib/v/types/vls_header_scope_review_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,66 @@ fn test_bodyless_generic_header_keeps_function_return_types_in_scope() {
}
}
}

// The node of a method is at its name: the receiver before it declares type
// parameters of the method too, so it belongs to the declaration.
fn test_a_method_declaration_starts_at_its_receiver() {
for source, name in {
'fn (b Box[T]) get() T {\n\treturn b.value\n}\n': 'get'
'pub fn (mut b Box[T]) set(value T) {\n}\n': 'set'
'fn (b Box[fn (T) T]) call(x T) T {\n\treturn x\n}\n': 'call'
'fn (a Vec[T]) + (b Vec[T]) Vec[T] {\n\treturn a\n}\n': '+'
} {
mut a := flat.FlatAst.new()
mut file_set := token.FileSet.new()
a.source_files[1] = file_set.add_file('receiver.v', source.len)
name_at := (source.index(') ${name}') or { panic('missing ${name}') }) + 2
decl := a.add_node(flat.Node{
kind: .fn_decl
pos: token.new_pos(1, name_at)
})
mut tc := TypeChecker.new(&a)
tc.source_texts_by_file['receiver.v'] = source
receiver := source.index('T') or { panic('missing receiver T') }
found := tc.vls_decl_at(1, receiver) or { flat.NodeId(-1) }
assert found == decl, source
}
// A static method has no receiver: what comes before its name is not its.
source := 'fn Box.new[T](value T) Box[T] {\n\treturn Box[T]{}\n}\n'
mut a := flat.FlatAst.new()
mut file_set := token.FileSet.new()
a.source_files[1] = file_set.add_file('static.v', source.len)
a.add_node(flat.Node{
kind: .fn_decl
pos: token.new_pos(1, source.index('new') or { panic('missing new') })
})
mut tc := TypeChecker.new(&a)
tc.source_texts_by_file['static.v'] = source
found := tc.vls_decl_at(1, source.index('Box') or { panic('missing Box') }) or {
flat.NodeId(-1)
}
assert found == flat.NodeId(-1)
}

fn test_a_header_method_on_the_line_of_another_declaration_keeps_its_receiver() {
source := 'fn first[U](x U) U; fn (b Box[T]) get() T\n'
mut a := flat.FlatAst.new()
mut file_set := token.FileSet.new()
a.source_files[1] = file_set.add_file('same_line.vh', source.len)
first := a.add_node(flat.Node{
kind: .fn_decl
pos: token.new_pos(1, source.index('first') or { panic('missing first') })
is_mut: true
})
get := a.add_node(flat.Node{
kind: .fn_decl
pos: token.new_pos(1, source.index('get') or { panic('missing get') })
is_mut: true
})
mut tc := TypeChecker.new(&a)
tc.source_texts_by_file['same_line.vh'] = source
in_first := (source.index('x U') or { panic('missing x U') }) + 2
in_receiver := (source.index('[T]') or { panic('missing [T]') }) + 1
assert (tc.vls_decl_at(1, in_first) or { flat.NodeId(-1) }) == first
assert (tc.vls_decl_at(1, in_receiver) or { flat.NodeId(-1) }) == get
}
36 changes: 34 additions & 2 deletions vlib/v/types/vls_query.v
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,9 @@ fn (tc &TypeChecker) vls_type_param_definition_at(file_id int, offset int, word
}

// vls_decl_at returns the declaration of a function or a type of `file_id`
// whose signature or body contains `offset`.
// whose receiver, signature or body contains `offset`.
fn (tc &TypeChecker) vls_decl_at(file_id int, offset int) ?flat.NodeId {
source := tc.vls_source(file_id)
mut best := -1
mut best_start := -1
for idx in tc.a.user_code_start .. tc.a.nodes.len {
Expand All @@ -259,7 +260,7 @@ fn (tc &TypeChecker) vls_decl_at(file_id int, offset int) ?flat.NodeId {
|| node.kind !in [.fn_decl, .struct_decl, .interface_decl, .type_decl] {
continue
}
start := int(node.pos.offset)
start := vls_decl_start(source, node)
if start <= offset && start > best_start {
best = idx
best_start = start
Expand All @@ -271,6 +272,37 @@ fn (tc &TypeChecker) vls_decl_at(file_id int, offset int) ?flat.NodeId {
return flat.NodeId(best)
}

// vls_decl_start is where the declaration `node` of `source` starts: the
// receiver of a method, `(b Box[T])` of `fn (b Box[T]) get() T`, which declares
// type parameters of the method too, or else its node, at its name.
fn vls_decl_start(source string, node flat.Node) int {
name := int(node.pos.offset)
if node.kind != .fn_decl || name <= 0 || name > source.len {
return name
}
mut i := name - 1
for i >= 0 && source[i] in [` `, `\t`, `\r`, `\n`] {
i--
}
// Only the `)` of a receiver comes right before the name of a function.
if i < 0 || source[i] != `)` {
return name
}
mut depth := 0
for i >= 0 {
if source[i] == `)` {
depth++
} else if source[i] == `(` {
depth--
if depth == 0 {
return i
}
}
i--
}
return name
}

// vls_decl_contains_offset bounds a declaration whose node only spans its name.
// Signatures and brace-delimited bodies remain in scope; following declarations do not.
fn (tc &TypeChecker) vls_decl_contains_offset(decl flat.Node, offset int) bool {
Expand Down