From 9391b7c2342df0efec560578a0b85307b84e7ef6 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 12 Aug 2025 01:37:32 +0300 Subject: [PATCH] vls: improve and simplify autocomplete; remove the scanner hack --- vlib/v/checker/autocomplete.v | 42 ++++++++++++++++++++++++++--------- vlib/v/checker/checker.v | 5 +++-- vlib/v/parser/parser.v | 7 ++++++ vlib/v/parser/struct.v | 10 +++++++++ vlib/v/pref/line_info.v | 22 +++++------------- vlib/v/pref/pref.v | 7 +++--- vlib/v/scanner/scanner.v | 9 +------- vlib/v/token/token.v | 4 ++++ vlib/v/token/token_test.v | 20 +++++++++++++++++ 9 files changed, 86 insertions(+), 40 deletions(-) create mode 100644 vlib/v/token/token_test.v diff --git a/vlib/v/checker/autocomplete.v b/vlib/v/checker/autocomplete.v index 0b02059af3..11a7bc9db0 100644 --- a/vlib/v/checker/autocomplete.v +++ b/vlib/v/checker/autocomplete.v @@ -11,19 +11,31 @@ struct ACFieldMethod { typ string } +fn abs(a int) int { + if a < 0 { + return -a + } + return a +} + 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}"') + if c.pref.is_verbose { + println( + 'checker.ident_autocomplete() info.line_nr=${c.pref.linfo.line_nr} node.line_nr=${node.pos.line_nr} ' + + ' node.col=${node.pos.col} pwd="${os.getwd()}" file="${c.file.path}", ' + + //' pref.linfo.path="${c.pref.linfo.path}" node.name="${node.name}" expr="${c.pref.linfo.expr}"') + ' pref.linfo.path="${c.pref.linfo.path}" node.name="${node.name}" col="${c.pref.linfo.col}"') + } // 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 { + // same_name := c.pref.linfo.expr == node.name + same_col := abs(c.pref.linfo.col - node.pos.col) < 3 + // if !same_name { + if !same_col { return } abs_path := os.join_path(os.getwd(), c.file.path) @@ -39,9 +51,14 @@ fn (mut c Checker) ident_autocomplete(node ast.Ident) { 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}' + sb.writeln('{') if !c.pref.linfo.vars_printed[nt] { // avoid dups - sb.writeln('===') - sb.writeln('VAR ${nt}') //${node.name}:${sym.name}') + // sb.writeln('===') + // sb.writeln('VAR ${nt}') //${node.name}:${sym.name}') + sb.writeln('\t"name":"${node.name}",') + sb.writeln('\t"type":"${sym.name}",') + sb.writeln('\t"fields":[') + // print_backtrace() /* if sym.kind == .alias { @@ -75,9 +92,14 @@ fn (mut c Checker) ident_autocomplete(node ast.Ident) { 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}') + for i, field in fields { + // sb.writeln('${field.name}:${field.typ}') + sb.write_string('\t\t"${field.name}:${field.typ}"') + if i < fields.len - 1 { + sb.writeln(', ') + } } + sb.writeln('\n\t]\n}') res := sb.str().trim_space() if res != '' { println(res) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 6fd133e4f0..a3a252e460 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -430,12 +430,13 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) { // After the main checker run, run the line info check, print line info, and exit (if it's present) if !c.pref.linfo.is_running && c.pref.line_info != '' { //'' && 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()) + // println('setting is_running=true, pref.path=${c.pref.linfo.path} curdir' + os.getwd()) c.pref.linfo.is_running = true + // println('linfo path=${c.pref.linfo.path}') for i, file in ast_files { // println(file.path) if file.path == c.pref.linfo.path { - println('running c.check_files') + // println('running c.check_files') c.check_files([ast_files[i]]) exit(0) } else if file.path.starts_with('./') { diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index d033884901..3902521a1c 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2635,6 +2635,13 @@ fn (mut p Parser) type_decl() ast.TypeDecl { if p.disallow_declarations_in_script_mode() { return ast.SumTypeDecl{} } + if p.is_vls && p.tok.is_key() { + // End parsing after `type ` in vls mode to avoid lots of junk errors + // If next token is a key, the type wasn't finished + p.error('expecting type name') + p.should_abort = true + return ast.AliasTypeDecl{} + } mut name := p.check_name() mut language := ast.Language.v if name.len == 1 && name[0].is_capital() { diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 553e4f2d2b..819e1e5154 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -125,6 +125,16 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { } } p.check(.lcbr) + // if p.is_vls && p.tok.kind == .key_struct { // p.tok.is_key() { + if p.is_vls && p.tok.is_key() { + // End parsing after `struct Foo {` in vls mode to avoid lots of junk errors + // If next token after { is a key, the struct wasn't finished + p.error('expected `}` to finish a struct definition') + p.should_abort = true + return ast.StructDecl{ + name: name + } + } pre_comments << p.eat_comments() mut i := 0 for p.tok.kind != .rcbr { diff --git a/vlib/v/pref/line_info.v b/vlib/v/pref/line_info.v index 8139e325d8..88d7b16b05 100644 --- a/vlib/v/pref/line_info.v +++ b/vlib/v/pref/line_info.v @@ -4,7 +4,7 @@ module pref 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"' + format_err := 'wrong format, use `-line-info "file.v:24:7"' vals := line.split(':') if vals.len != 3 { eprintln(format_err) @@ -12,7 +12,8 @@ fn (mut p Preferences) parse_line_info(line string) { } file_name := vals[0] line_nr := vals[1].int() - 1 - expr := vals[2] + col := vals[2].int() - 1 + // expr := vals[2] if !file_name.ends_with('.v') || line_nr == -1 { eprintln(format_err) return @@ -20,20 +21,7 @@ fn (mut p Preferences) parse_line_info(line string) { p.linfo = LineInfo{ line_nr: line_nr path: file_name - expr: expr + // expr: expr + col: col } } - -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 -} diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index ad0a627644..1d2dbd9385 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -264,9 +264,10 @@ pub mut: 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 + 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 + col int is_running bool // so that line info is fetched only on the second checker run vars_printed map[string]bool // to avoid dups } diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index 03a3acecb8..b4d6215259 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -111,14 +111,7 @@ 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') } - 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) - } - } + raw_text := util.read_file(file_path) or { return err } mut s := &Scanner{ pref: pref_ text: raw_text diff --git a/vlib/v/token/token.v b/vlib/v/token/token.v index 41576b1627..386d874cd6 100644 --- a/vlib/v/token/token.v +++ b/vlib/v/token/token.v @@ -390,6 +390,10 @@ pub fn (t Token) is_next_to(pre_token Token) bool { return t.pos - pre_token.pos == pre_token.len } +pub fn (t Token) is_key() bool { + return int(t.kind) > int(Kind.keyword_beg) && int(t.kind) < int(Kind.keyword_end) +} + pub fn (t Token) str() string { mut s := t.kind.str() if s.len == 0 { diff --git a/vlib/v/token/token_test.v b/vlib/v/token/token_test.v new file mode 100644 index 0000000000..e99b0e38eb --- /dev/null +++ b/vlib/v/token/token_test.v @@ -0,0 +1,20 @@ +// Copyright (c) 2019-2024 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 token + +fn test_is_key() { + // Test that tokens with keyword kinds return true. + assert Token{ + kind: .key_as + }.is_key() + assert Token{ + kind: .key_fn + }.is_key() + assert Token{ + kind: .key_mut + }.is_key() + assert !Token{ + kind: .name + }.is_key() +}