v.builder: implement a `-check` mode, that runs only the parser + the checker, without codegen (#11414)

pull/11426/head
Ned Palacios 2021-09-07 12:17:53 +08:00 committed by GitHub
parent 823c9a3c84
commit aedb6b8e84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 154 additions and 26 deletions

View File

@ -94,6 +94,7 @@ const (
'-apk', '-apk',
'-show-timings', '-show-timings',
'-check-syntax', '-check-syntax',
'-check',
'-v', '-v',
'-progress', '-progress',
'-silent', '-silent',

View File

@ -125,6 +125,9 @@ NB: the build flags are shared with the run command too:
that it uses to detect whether or not to use ANSI colors may not work in all cases. that it uses to detect whether or not to use ANSI colors may not work in all cases.
These options allow you to override the default detection. These options allow you to override the default detection.
-check
Scans, parses, and checks the files without compiling the program.
-check-syntax -check-syntax
Only scan and parse the files, but then stop. Useful for very quick syntax checks. Only scan and parse the files, but then stop. Useful for very quick syntax checks.

View File

@ -3,6 +3,7 @@ module builder
import os import os
import v.token import v.token
import v.pref import v.pref
import v.errors
import v.util import v.util
import v.ast import v.ast
import v.vmod import v.vmod
@ -26,6 +27,9 @@ mut:
out_name_js string out_name_js string
stats_lines int // size of backend generated source code in lines stats_lines int // size of backend generated source code in lines
stats_bytes int // size of backend generated source code in bytes stats_bytes int // size of backend generated source code in bytes
nr_errors int // accumulated error count of scanner, parser, checker, and builder
nr_warnings int // accumulated warning count of scanner, parser, checker, and builder
nr_notices int // accumulated notice count of scanner, parser, checker, and builder
pub mut: pub mut:
module_search_paths []string module_search_paths []string
parsed_files []&ast.File parsed_files []&ast.File
@ -86,6 +90,9 @@ pub fn (mut b Builder) middle_stages() ? {
b.checker.check_files(b.parsed_files) b.checker.check_files(b.parsed_files)
util.timing_measure('CHECK') util.timing_measure('CHECK')
b.print_warnings_and_errors() b.print_warnings_and_errors()
if b.pref.check_only {
return error('stop_after_checker')
}
util.timing_start('TRANSFORM') util.timing_start('TRANSFORM')
b.transformer.transform_files(b.parsed_files) b.transformer.transform_files(b.parsed_files)
util.timing_measure('TRANSFORM') util.timing_measure('TRANSFORM')
@ -133,7 +140,8 @@ pub fn (mut b Builder) parse_imports() {
for imp in ast_file.imports { for imp in ast_file.imports {
mod := imp.mod mod := imp.mod
if mod == 'builtin' { if mod == 'builtin' {
error_with_pos('cannot import module "builtin"', ast_file.path, imp.pos) b.parsed_files[i].errors << b.error_with_pos('cannot import module "builtin"',
ast_file.path, imp.pos)
break break
} }
if mod in done_imports { if mod in done_imports {
@ -142,15 +150,16 @@ pub fn (mut b Builder) parse_imports() {
import_path := b.find_module_path(mod, ast_file.path) or { import_path := b.find_module_path(mod, ast_file.path) or {
// v.parsers[i].error_with_token_index('cannot import module "$mod" (not found)', v.parsers[i].import_ast.get_import_tok_idx(mod)) // v.parsers[i].error_with_token_index('cannot import module "$mod" (not found)', v.parsers[i].import_ast.get_import_tok_idx(mod))
// break // break
error_with_pos('cannot import module "$mod" (not found)', ast_file.path, b.parsed_files[i].errors << b.error_with_pos('cannot import module "$mod" (not found)',
imp.pos) ast_file.path, imp.pos)
break break
} }
v_files := b.v_files_from_dir(import_path) v_files := b.v_files_from_dir(import_path)
if v_files.len == 0 { if v_files.len == 0 {
// v.parsers[i].error_with_token_index('cannot import module "$mod" (no .v files in "$import_path")', v.parsers[i].import_ast.get_import_tok_idx(mod)) // v.parsers[i].error_with_token_index('cannot import module "$mod" (no .v files in "$import_path")', v.parsers[i].import_ast.get_import_tok_idx(mod))
error_with_pos('cannot import module "$mod" (no .v files in "$import_path")', b.parsed_files[i].errors << b.error_with_pos('cannot import module "$mod" (no .v files in "$import_path")',
ast_file.path, imp.pos) ast_file.path, imp.pos)
continue
} }
// Add all imports referenced by these libs // Add all imports referenced by these libs
parsed_files := parser.parse_files(v_files, b.table, b.pref) parsed_files := parser.parse_files(v_files, b.table, b.pref)
@ -161,7 +170,7 @@ pub fn (mut b Builder) parse_imports() {
} }
if name != mod { if name != mod {
// v.parsers[pidx].error_with_token_index('bad module definition: ${v.parsers[pidx].file_path} imports module "$mod" but $file is defined as module `$p_mod`', 1 // v.parsers[pidx].error_with_token_index('bad module definition: ${v.parsers[pidx].file_path} imports module "$mod" but $file is defined as module `$p_mod`', 1
error_with_pos('bad module definition: $ast_file.path imports module "$mod" but $file.path is defined as module `$name`', b.parsed_files[i].errors << b.error_with_pos('bad module definition: $ast_file.path imports module "$mod" but $file.path is defined as module `$name`',
ast_file.path, imp.pos) ast_file.path, imp.pos)
} }
} }
@ -335,27 +344,104 @@ pub fn (b &Builder) find_module_path(mod string, fpath string) ?string {
} }
fn (b &Builder) show_total_warns_and_errors_stats() { fn (b &Builder) show_total_warns_and_errors_stats() {
if b.checker.nr_errors == 0 && b.checker.nr_warnings == 0 && b.checker.nr_notices == 0 { if b.nr_errors == 0 && b.nr_warnings == 0 && b.nr_notices == 0 {
return return
} }
if b.pref.is_stats { if b.pref.is_stats {
estring := util.bold(b.checker.errors.len.str()) mut nr_errors := b.checker.errors.len
wstring := util.bold(b.checker.warnings.len.str()) mut nr_warnings := b.checker.warnings.len
nstring := util.bold(b.checker.nr_notices.str()) mut nr_notices := b.checker.notices.len
println('checker summary: $estring V errors, $wstring V warnings, $nstring V notices')
if b.pref.check_only {
nr_errors = b.nr_errors
nr_warnings = b.nr_warnings
nr_notices = b.nr_notices
}
estring := util.bold(nr_errors.str())
wstring := util.bold(nr_warnings.str())
nstring := util.bold(nr_notices.str())
if b.pref.check_only {
println('summary: $estring V errors, $wstring V warnings, $nstring V notices')
} else {
println('checker summary: $estring V errors, $wstring V warnings, $nstring V notices')
}
} }
} }
fn (b &Builder) print_warnings_and_errors() { fn (mut b Builder) print_warnings_and_errors() {
defer { defer {
b.show_total_warns_and_errors_stats() b.show_total_warns_and_errors_stats()
} }
for file in b.parsed_files {
b.nr_errors += file.errors.len
b.nr_warnings += file.warnings.len
b.nr_notices += file.notices.len
}
if b.pref.output_mode == .silent { if b.pref.output_mode == .silent {
if b.checker.nr_errors > 0 { if b.nr_errors > 0 {
exit(1) exit(1)
} }
return return
} }
if b.pref.check_only {
for file in b.parsed_files {
if !b.pref.skip_warnings {
for err in file.notices {
kind := if b.pref.is_verbose {
'$err.reporter notice #$b.nr_notices:'
} else {
'notice:'
}
ferror := util.formatted_error(kind, err.message, err.file_path, err.pos)
eprintln(ferror)
if err.details.len > 0 {
eprintln('Details: $err.details')
}
}
}
}
for file in b.parsed_files {
for err in file.errors {
kind := if b.pref.is_verbose {
'$err.reporter error #$b.nr_errors:'
} else {
'error:'
}
ferror := util.formatted_error(kind, err.message, err.file_path, err.pos)
eprintln(ferror)
if err.details.len > 0 {
eprintln('Details: $err.details')
}
}
}
for file in b.parsed_files {
if !b.pref.skip_warnings {
for err in file.warnings {
kind := if b.pref.is_verbose {
'$err.reporter warning #$b.nr_warnings:'
} else {
'warning:'
}
ferror := util.formatted_error(kind, err.message, err.file_path, err.pos)
eprintln(ferror)
if err.details.len > 0 {
eprintln('Details: $err.details')
}
}
}
}
b.show_total_warns_and_errors_stats()
exit(1)
}
if b.pref.is_verbose && b.checker.nr_warnings > 1 { if b.pref.is_verbose && b.checker.nr_warnings > 1 {
println('$b.checker.nr_warnings warnings') println('$b.checker.nr_warnings warnings')
} }
@ -455,10 +541,19 @@ struct FunctionRedefinition {
f ast.FnDecl f ast.FnDecl
} }
fn error_with_pos(s string, fpath string, pos token.Position) { fn (b &Builder) error_with_pos(s string, fpath string, pos token.Position) errors.Error {
ferror := util.formatted_error('builder error:', s, fpath, pos) if !b.pref.check_only {
eprintln(ferror) ferror := util.formatted_error('builder error:', s, fpath, pos)
exit(1) eprintln(ferror)
exit(1)
}
return errors.Error{
file_path: fpath
pos: pos
reporter: .builder
message: s
}
} }
[noreturn] [noreturn]

View File

@ -489,6 +489,12 @@ fn (mut v Builder) cc() {
} }
return return
} }
if v.pref.check_only {
if v.pref.is_verbose {
println('builder.cc returning early, since pref.check_only is true')
}
return
}
if v.pref.should_output_to_stdout() { if v.pref.should_output_to_stdout() {
// output to stdout // output to stdout
content := os.read_file(v.out_name_c) or { panic(err) } content := os.read_file(v.out_name_c) or { panic(err) }

View File

@ -106,7 +106,7 @@ fn (mut b Builder) run_compiled_executable_and_exit() {
if b.pref.skip_running { if b.pref.skip_running {
return return
} }
if b.pref.only_check_syntax { if b.pref.only_check_syntax || b.pref.check_only {
return return
} }
if b.pref.should_output_to_stdout() { if b.pref.should_output_to_stdout() {

View File

@ -6,6 +6,7 @@ pub enum Reporter {
scanner scanner
parser parser
checker checker
builder
gen gen
} }

View File

@ -273,6 +273,17 @@ pub fn (mut p Parser) parse() &ast.File {
} }
} }
p.scope.end_pos = p.tok.pos p.scope.end_pos = p.tok.pos
mut errors := p.errors
mut warnings := p.warnings
mut notices := p.notices
if p.pref.check_only {
errors << p.scanner.errors
warnings << p.scanner.warnings
notices << p.scanner.notices
}
return &ast.File{ return &ast.File{
path: p.file_name path: p.file_name
path_base: p.file_base path_base: p.file_base
@ -286,8 +297,9 @@ pub fn (mut p Parser) parse() &ast.File {
stmts: stmts stmts: stmts
scope: p.scope scope: p.scope
global_scope: p.table.global_scope global_scope: p.table.global_scope
errors: p.errors errors: errors
warnings: p.warnings warnings: warnings
notices: notices
global_labels: p.global_labels global_labels: p.global_labels
} }
} }
@ -1613,7 +1625,7 @@ pub fn (mut p Parser) error_with_pos(s string, pos token.Position) ast.NodeError
exit(1) exit(1)
} }
mut kind := 'error:' mut kind := 'error:'
if p.pref.output_mode == .stdout { if p.pref.output_mode == .stdout && !p.pref.check_only {
if p.pref.is_verbose { if p.pref.is_verbose {
print_backtrace() print_backtrace()
kind = 'parser error:' kind = 'parser error:'
@ -1628,6 +1640,12 @@ pub fn (mut p Parser) error_with_pos(s string, pos token.Position) ast.NodeError
reporter: .parser reporter: .parser
message: s message: s
} }
// To avoid getting stuck after an error, the parser
// will proceed to the next token.
if p.pref.check_only {
p.next()
}
} }
if p.pref.output_mode == .silent { if p.pref.output_mode == .silent {
// Normally, parser errors mean that the parser exits immediately, so there can be only 1 parser error. // Normally, parser errors mean that the parser exits immediately, so there can be only 1 parser error.
@ -1647,7 +1665,7 @@ pub fn (mut p Parser) error_with_error(error errors.Error) {
exit(1) exit(1)
} }
mut kind := 'error:' mut kind := 'error:'
if p.pref.output_mode == .stdout { if p.pref.output_mode == .stdout && !p.pref.check_only {
if p.pref.is_verbose { if p.pref.is_verbose {
print_backtrace() print_backtrace()
kind = 'parser error:' kind = 'parser error:'
@ -1679,7 +1697,7 @@ pub fn (mut p Parser) warn_with_pos(s string, pos token.Position) {
if p.pref.skip_warnings { if p.pref.skip_warnings {
return return
} }
if p.pref.output_mode == .stdout { if p.pref.output_mode == .stdout && !p.pref.check_only {
ferror := util.formatted_error('warning:', s, p.file_name, pos) ferror := util.formatted_error('warning:', s, p.file_name, pos)
eprintln(ferror) eprintln(ferror)
} else { } else {
@ -1700,7 +1718,7 @@ pub fn (mut p Parser) note_with_pos(s string, pos token.Position) {
if p.pref.skip_warnings { if p.pref.skip_warnings {
return return
} }
if p.pref.output_mode == .stdout { if p.pref.output_mode == .stdout && !p.pref.check_only {
ferror := util.formatted_error('notice:', s, p.file_name, pos) ferror := util.formatted_error('notice:', s, p.file_name, pos)
eprintln(ferror) eprintln(ferror)
} else { } else {

View File

@ -178,6 +178,7 @@ pub mut:
is_parallel bool is_parallel bool
is_vweb bool // skip _ var warning in templates is_vweb bool // skip _ var warning in templates
only_check_syntax bool // when true, just parse the files, then stop, before running checker only_check_syntax bool // when true, just parse the files, then stop, before running checker
check_only bool // same as only_check_syntax, but also runs the checker
experimental bool // enable experimental features experimental bool // enable experimental features
skip_unused bool // skip generating C code for functions, that are not used skip_unused bool // skip generating C code for functions, that are not used
show_timings bool // show how much time each compiler stage took show_timings bool // show how much time each compiler stage took
@ -245,6 +246,9 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
'-check-syntax' { '-check-syntax' {
res.only_check_syntax = true res.only_check_syntax = true
} }
'-check' {
res.check_only = true
}
'-h', '-help', '--help' { '-h', '-help', '--help' {
// NB: help is *very important*, just respond to all variations: // NB: help is *very important*, just respond to all variations:
res.is_help = true res.is_help = true

View File

@ -1336,7 +1336,7 @@ pub fn (mut s Scanner) note(msg string) {
line_nr: s.line_nr line_nr: s.line_nr
pos: s.pos pos: s.pos
} }
if s.pref.output_mode == .stdout { if s.pref.output_mode == .stdout && !s.pref.check_only {
eprintln(util.formatted_error('notice:', msg, s.file_path, pos)) eprintln(util.formatted_error('notice:', msg, s.file_path, pos))
} else { } else {
s.notices << errors.Notice{ s.notices << errors.Notice{
@ -1358,7 +1358,7 @@ pub fn (mut s Scanner) warn(msg string) {
pos: s.pos pos: s.pos
col: s.current_column() - 1 col: s.current_column() - 1
} }
if s.pref.output_mode == .stdout { if s.pref.output_mode == .stdout && !s.pref.check_only {
eprintln(util.formatted_error('warning:', msg, s.file_path, pos)) eprintln(util.formatted_error('warning:', msg, s.file_path, pos))
} else { } else {
if s.pref.message_limit >= 0 && s.warnings.len >= s.pref.message_limit { if s.pref.message_limit >= 0 && s.warnings.len >= s.pref.message_limit {
@ -1380,7 +1380,7 @@ pub fn (mut s Scanner) error(msg string) {
pos: s.pos pos: s.pos
col: s.current_column() - 1 col: s.current_column() - 1
} }
if s.pref.output_mode == .stdout { if s.pref.output_mode == .stdout && !s.pref.check_only {
eprintln(util.formatted_error('error:', msg, s.file_path, pos)) eprintln(util.formatted_error('error:', msg, s.file_path, pos))
exit(1) exit(1)
} else { } else {