checker: more line-info logic

This commit is contained in:
Alexander Medvednikov 2023-09-11 01:38:09 +03:00
parent 12dd6e8b39
commit b9a233b1a0
10 changed files with 227 additions and 94 deletions

View file

@ -42,6 +42,7 @@
- [ ] 64/32 bit int depending on arch (will remove array.len limitation on 64 bit systems)
- [ ] `copy()` builtin function (e.g. for easier conversion from `[]Foo` to `[4]Foo`)
- [ ] Lambdas: `a.sort(|a, b| a > b)`
- [ ] Custom attributes.
## [Version 1.0]

View file

@ -880,3 +880,4 @@ pub fn (f &File) tell() !i64 {
}
return pos
}

View file

@ -552,7 +552,12 @@ pub fn join_path(base string, dirs ...string) string {
sb.write_string(path_separator)
sb.write_string(d)
}
return sb.str()
mut res := sb.str()
if res.contains('/./') {
// Fix `join_path("/foo/bar", "./file.txt")` => `/foo/bar/./file.txt`
res = res.replace('/./', '/')
}
return res
}
// join_path_single appends the `elem` after `base`, using a platform specific

View file

@ -613,6 +613,7 @@ fn test_join() {
assert os.join_path('v', 'vlib', 'os') == 'v\\vlib\\os'
} $else {
assert os.join_path('v', 'vlib', 'os') == 'v/vlib/os'
assert os.join_path('/foo/bar', './file.txt') == '/foo/bar/file.txt'
}
}

View file

@ -0,0 +1,93 @@
// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module checker
import strings
import v.ast
import os
fn (mut c Checker) ident_autocomplete(node ast.Ident) {
// Mini LS hack (v -line-info "a.v:16")
println(
'checker.ident() info.line_nr=${c.pref.linfo.line_nr} node.line_nr=${node.pos.line_nr} ' +
' pwd="${os.getwd()}" file="${c.file.path}", ' +
' pref.linfo.path="${c.pref.linfo.path}" node.name="${node.name}" expr="${c.pref.linfo.expr}"')
// Make sure this ident is on the same line as requeste, in the same file, and has the same name
same_line := node.pos.line_nr in [c.pref.linfo.line_nr - 1, c.pref.linfo.line_nr + 1, c.pref.linfo.line_nr]
if !same_line {
return
}
same_name := c.pref.linfo.expr == node.name
if !same_name {
return
}
abs_path := os.join_path(os.getwd(), c.file.path)
if c.pref.linfo.path !in [c.file.path, abs_path] {
return
}
mut sb := strings.new_builder(10)
if node.kind == .unresolved {
// println(node)
eprintln('unresolved type, maybe "${node.name}" was not defined. otherwise this is a bug, should never happen; please report')
exit(1)
}
sym := c.table.sym(c.unwrap_generic(node.obj.typ))
// sb.writeln('VAR ${node.name}:${sym.name} ${node.pos.line_nr}')
nt := '${node.name}:${sym.name}'
if !c.pref.linfo.vars_printed[nt] { // avoid dups
sb.writeln('===')
sb.writeln('VAR ${nt}') //${node.name}:${sym.name}')
/// print_backtrace()
/*
if sym.kind == .alias {
parent_sym := c.table.sym(sym.parent_type)
}
*/
mut fields := []ACFieldMethod{cap: 10}
if sym.kind == .struct_ {
// Add fields, but only if it's a struct.
struct_info := sym.info as ast.Struct
// match struct_info {
// ast.Struct
//}
for field in struct_info.fields {
field_sym := c.table.sym(field.typ)
fields << ACFieldMethod{field.name, field_sym.name}
}
} else if sym.kind == .array {
// t := typeof(sym.info).name
if sym.info is ast.Aggregate {
} else if sym.info is ast.Array {
fields << ACFieldMethod{'len', 'int'}
fields << ACFieldMethod{'cap', 'int'}
}
// array_info := sym.info as ast.Array
}
// Aliases and other types can have methods, add them
for method in sym.methods {
method_ret_type := c.table.sym(method.return_type)
fields << ACFieldMethod{build_method_summary(method), method_ret_type.name}
}
fields.sort(a.name < b.name)
for field in fields {
sb.writeln('${field.name}:${field.typ}')
}
res := sb.str().trim_space()
if res != '' {
println(res)
c.pref.linfo.vars_printed[nt] = true
}
}
}
fn build_method_summary(method ast.Fn) string {
mut s := method.name + '('
for i, param in method.params {
s += param.name
if i < method.params.len - 1 {
s += ','
}
}
return s + ')'
}

View file

@ -13,7 +13,6 @@ import v.util.version
import v.errors
import v.pkgconfig
import v.transformer
import strings
const (
int_min = int(0x80000000)
@ -44,8 +43,9 @@ pub const (
[heap; minify]
pub struct Checker {
pref &pref.Preferences = unsafe { nil } // Preferences shared from V struct
pub mut:
pref &pref.Preferences = unsafe { nil } // Preferences shared from V struct
//
table &ast.Table = unsafe { nil }
file &ast.File = unsafe { nil }
nr_errors int
@ -123,8 +123,8 @@ mut:
inside_decl_rhs bool
inside_if_guard bool // true inside the guard condition of `if x := opt() {}`
inside_assign bool
doing_line_info int // a quick single file run when called with v -line-info (contains line nr to inspect)
doing_line_path string // same, but stores the path being parsed
// doing_line_info int // a quick single file run when called with v -line-info (contains line nr to inspect)
// doing_line_path string // same, but stores the path being parsed
is_index_assign bool
comptime_call_pos int // needed for correctly checking use before decl for templates
goto_labels map[string]ast.GotoLabel // to check for unused goto labels
@ -293,6 +293,7 @@ pub fn (mut c Checker) change_current_file(file &ast.File) {
}
pub fn (mut c Checker) check_files(ast_files []&ast.File) {
// println('check_files')
// c.files = ast_files
mut has_main_mod_file := false
mut has_main_fn := false
@ -381,9 +382,27 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) {
c.error('a _test.v file should have *at least* one `test_` function', token.Pos{})
}
}
// Print line info and exit
if c.pref.line_info != '' && c.doing_line_info == 0 {
c.do_line_info(c.pref.line_info, ast_files)
// After the main checker run, run the line info check, print line info, and exit (if it's present)
if c.pref.line_info != '' && !c.pref.linfo.is_running { //'' && c.pref.linfo.line_nr == 0 {
// c.do_line_info(c.pref.line_info, ast_files)
println('setting is_running=true, pref.path=${c.pref.linfo.path} curdir' + os.getwd())
c.pref.linfo.is_running = true
for i, file in ast_files {
// println(file.path)
if file.path == c.pref.linfo.path {
println('running c.check_files')
c.check_files([ast_files[i]])
exit(0)
} else if file.path.starts_with('./') {
// Maybe it's a "./foo.v", linfo.path has an absolute path
abs_path := os.join_path(os.getwd(), file.path).replace('/./', '/') // TODO join_path shouldn't have /./
if abs_path == c.pref.linfo.path {
c.check_files([ast_files[i]])
exit(0)
}
}
}
println('failed to find file "${c.pref.linfo.path}"')
exit(0)
}
// Make sure fn main is defined in non lib builds
@ -1605,7 +1624,7 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type {
return ast.int_type
}
c.error('`${unwrapped_sym.name}` has no property `${node.field_name}`', node.pos)
c.error('`${unwrapped_sym.name}` 1has no property `${node.field_name}`', node.pos)
}
} else {
if sym.info is ast.Struct {
@ -3362,34 +3381,9 @@ struct ACFieldMethod {
}
fn (mut c Checker) ident(mut node ast.Ident) ast.Type {
if c.doing_line_info > 0 {
mut sb := strings.new_builder(10)
if c.pref.linfo.is_running {
// Mini LS hack (v -line-info "a.v:16")
// println('line_nr=${node.pos.line_nr} doing line nr=${c.doing_line_info}')
// println('Start line_nr=${node.pos.line_nr} line2=${c.doing_line_info} file="${c.file.path}", pppp="${c.doing_line_path}"')
if node.pos.line_nr == c.doing_line_info && c.file.path == c.doing_line_path {
sb.writeln('===')
sym := c.table.sym(node.obj.typ)
sb.writeln('VAR ${node.name}:${sym.name}')
mut struct_info := sym.info as ast.Struct
mut fields := []ACFieldMethod{cap: struct_info.fields.len}
for field in struct_info.fields {
field_sym := c.table.sym(field.typ)
fields << ACFieldMethod{field.name, field_sym.name}
}
for method in sym.methods {
method_ret_type := c.table.sym(method.return_type)
fields << ACFieldMethod{method.name + '()', method_ret_type.name}
}
fields.sort(a.name < b.name)
for field in fields {
sb.writeln('${field.name}:${field.typ}')
}
res := sb.str().trim_space()
if res != '' {
println(res)
}
}
c.ident_autocomplete(node)
}
// TODO: move this
if c.const_deps.len > 0 {

View file

@ -1,52 +0,0 @@
// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module checker
import v.ast
// import os
fn (mut c Checker) do_line_info(line string, all_ast_files []&ast.File) {
// println("do_line_info '${line}'")
format_err := 'wrong format, use `-line-info "file.v:24"'
vals := line.split(':')
if vals.len != 2 {
eprintln(format_err)
return
}
file_name := vals[0]
line_nr := vals[1].int() - 1
if !file_name.ends_with('.v') || line_nr == -1 {
eprintln(format_err)
return
}
// println('ok ${c.files.len}')
// Find which file contains the line
mut found := false
mut found_path := ''
mut found_file_idx := -1
for i, file in all_ast_files {
// base := os.base(file.path)
base := file.path // os.base(file.path)
// println(base)
if base == file_name {
if found {
eprintln('more than one "${file_name}" found: "${file.path}" and "${found_path}"')
return
}
found = true
found_path = file.path
found_file_idx = i
}
}
if !found {
eprintln('file "${file_name}" not found among those parsed')
return
}
// println('found ${found_path}')
c.doing_line_info = line_nr
c.doing_line_path = file_name
c.check_files([all_ast_files[found_file_idx]])
}

72
vlib/v/pref/line_info.v Normal file
View file

@ -0,0 +1,72 @@
// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module pref
// import v.ast
// import v.pref
// import os
// fn (mut p Pref) parse_line_info(line string, all_ast_files []&ast.File) {
fn (mut p Preferences) parse_line_info(line string) {
// println("parse_line_info '${line}'")
format_err := 'wrong format, use `-line-info "file.v:24:expr_to_look_up"'
vals := line.split(':')
if vals.len != 3 {
eprintln(format_err)
return
}
file_name := vals[0]
line_nr := vals[1].int() - 1
expr := vals[2]
if !file_name.ends_with('.v') || line_nr == -1 {
eprintln(format_err)
return
}
// println('files.len=${c.files.len}')
// Find which file contains the line
mut found := true // false
//// mut found_path := ''
// mut found_file_idx := -1
/*
for i, file in all_ast_files {
// base := os.base(file.path)
base := file.path // os.base(file.path)
// println(base)
if base == file_name {
if found {
eprintln('more than one "${file_name}" found: "${file.path}" and "${found_path}"')
return
}
found = true
found_path = file.path
found_file_idx = i
}
}
*/
if !found {
eprintln('file "${file_name}" not found among those parsed')
return
}
p.linfo = LineInfo{
line_nr: line_nr
path: file_name
expr: expr
}
}
pub fn add_line_info_expr_to_program_text(raw_text string, linfo LineInfo) string {
lines := raw_text.split('\n')
lines_before := lines[..linfo.line_nr].join('\n')
mut expr := linfo.expr
if !expr.contains('.') {
// Single variable, `foo` => `foo.xx`
// expr += '.xxx'
// expr = '_ = ' + expr
expr = 'println(' + expr + ')'
}
lines_after := lines[linfo.line_nr..].join('\n')
return lines_before + '\n' + expr + '\n' + lines_after
}

View file

@ -190,6 +190,7 @@ pub mut:
out_name string
path string // Path to file/folder to compile
line_info string // `-line-info="file.v:28"`: for "mini VLS" (shows information about objects on provided line)
linfo LineInfo
//
run_only []string // VTEST_ONLY_FN and -run-only accept comma separated glob patterns.
exclude []string // glob patterns for excluding .v files from the list of .v files that otherwise would have been used for a compilation, example: `-exclude @vlib/math/*.c.v`
@ -240,6 +241,15 @@ pub mut:
wasm_validate bool // validate webassembly code, by calling `wasm-validate`
}
pub struct LineInfo {
pub mut:
line_nr int // a quick single file run when called with v -line-info (contains line nr to inspect)
path string // same, but stores the path being parsed
expr string // "foo" or "foo.bar" V code (expression) which needs autocomplete
is_running bool // so that line info is fetched only on the second checker run
vars_printed map[string]bool // to avoid dups
}
pub fn parse_args(known_external_commands []string, args []string) (&Preferences, string) {
return parse_args_and_show_errors(known_external_commands, args, false)
}
@ -835,6 +845,7 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
}
'-line-info' {
res.line_info = cmdline.option(current_args, arg, '')
res.parse_line_info(res.line_info)
i++
}
'-use-coroutines' {

View file

@ -113,7 +113,14 @@ pub fn new_scanner_file(file_path string, comments_mode CommentsMode, pref_ &pre
if !os.is_file(file_path) {
return error('${file_path} is not a .v file')
}
raw_text := util.read_file(file_path) or { return err }
mut raw_text := util.read_file(file_path) or { return err }
if pref_.line_info != '' {
// Add line info expr to the scanner text
abs_path := os.join_path(os.getwd(), file_path)
if pref_.linfo.path in [file_path, abs_path] {
raw_text = pref.add_line_info_expr_to_program_text(raw_text, pref_.linfo)
}
}
mut s := &Scanner{
pref: pref_
text: raw_text