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',
'-show-timings',
'-check-syntax',
'-check',
'-v',
'-progress',
'-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.
These options allow you to override the default detection.
-check
Scans, parses, and checks the files without compiling the program.
-check-syntax
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 v.token
import v.pref
import v.errors
import v.util
import v.ast
import v.vmod
@ -26,6 +27,9 @@ mut:
out_name_js string
stats_lines int // size of backend generated source code in lines
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:
module_search_paths []string
parsed_files []&ast.File
@ -86,6 +90,9 @@ pub fn (mut b Builder) middle_stages() ? {
b.checker.check_files(b.parsed_files)
util.timing_measure('CHECK')
b.print_warnings_and_errors()
if b.pref.check_only {
return error('stop_after_checker')
}
util.timing_start('TRANSFORM')
b.transformer.transform_files(b.parsed_files)
util.timing_measure('TRANSFORM')
@ -133,7 +140,8 @@ pub fn (mut b Builder) parse_imports() {
for imp in ast_file.imports {
mod := imp.mod
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
}
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 {
// v.parsers[i].error_with_token_index('cannot import module "$mod" (not found)', v.parsers[i].import_ast.get_import_tok_idx(mod))
// break
error_with_pos('cannot import module "$mod" (not found)', ast_file.path,
imp.pos)
b.parsed_files[i].errors << b.error_with_pos('cannot import module "$mod" (not found)',
ast_file.path, imp.pos)
break
}
v_files := b.v_files_from_dir(import_path)
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))
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)
continue
}
// Add all imports referenced by these libs
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 {
// 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)
}
}
@ -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() {
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
}
if b.pref.is_stats {
estring := util.bold(b.checker.errors.len.str())
wstring := util.bold(b.checker.warnings.len.str())
nstring := util.bold(b.checker.nr_notices.str())
mut nr_errors := b.checker.errors.len
mut nr_warnings := b.checker.warnings.len
mut nr_notices := b.checker.notices.len
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 {
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.checker.nr_errors > 0 {
if b.nr_errors > 0 {
exit(1)
}
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 {
println('$b.checker.nr_warnings warnings')
}
@ -455,12 +541,21 @@ struct FunctionRedefinition {
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 {
if !b.pref.check_only {
ferror := util.formatted_error('builder error:', s, fpath, pos)
eprintln(ferror)
exit(1)
}
return errors.Error{
file_path: fpath
pos: pos
reporter: .builder
message: s
}
}
[noreturn]
fn verror(s string) {
util.verror('builder error', s)

View File

@ -489,6 +489,12 @@ fn (mut v Builder) cc() {
}
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() {
// output to stdout
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 {
return
}
if b.pref.only_check_syntax {
if b.pref.only_check_syntax || b.pref.check_only {
return
}
if b.pref.should_output_to_stdout() {

View File

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

View File

@ -273,6 +273,17 @@ pub fn (mut p Parser) parse() &ast.File {
}
}
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{
path: p.file_name
path_base: p.file_base
@ -286,8 +297,9 @@ pub fn (mut p Parser) parse() &ast.File {
stmts: stmts
scope: p.scope
global_scope: p.table.global_scope
errors: p.errors
warnings: p.warnings
errors: errors
warnings: warnings
notices: notices
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)
}
mut kind := 'error:'
if p.pref.output_mode == .stdout {
if p.pref.output_mode == .stdout && !p.pref.check_only {
if p.pref.is_verbose {
print_backtrace()
kind = 'parser error:'
@ -1628,6 +1640,12 @@ pub fn (mut p Parser) error_with_pos(s string, pos token.Position) ast.NodeError
reporter: .parser
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 {
// 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)
}
mut kind := 'error:'
if p.pref.output_mode == .stdout {
if p.pref.output_mode == .stdout && !p.pref.check_only {
if p.pref.is_verbose {
print_backtrace()
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 {
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)
eprintln(ferror)
} else {
@ -1700,7 +1718,7 @@ pub fn (mut p Parser) note_with_pos(s string, pos token.Position) {
if p.pref.skip_warnings {
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)
eprintln(ferror)
} else {

View File

@ -178,6 +178,7 @@ pub mut:
is_parallel bool
is_vweb bool // skip _ var warning in templates
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
skip_unused bool // skip generating C code for functions, that are not used
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' {
res.only_check_syntax = true
}
'-check' {
res.check_only = true
}
'-h', '-help', '--help' {
// NB: help is *very important*, just respond to all variations:
res.is_help = true

View File

@ -1336,7 +1336,7 @@ pub fn (mut s Scanner) note(msg string) {
line_nr: s.line_nr
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))
} else {
s.notices << errors.Notice{
@ -1358,7 +1358,7 @@ pub fn (mut s Scanner) warn(msg string) {
pos: s.pos
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))
} else {
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
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))
exit(1)
} else {