v: support compiler notices. Use them for `[deprecated_after: '2021-05-01']` tags

Compiler notices are like warnings, with these differences:
   a) notices use a different color.
   b) notices use a different label.
   c) notices do not prevent compilation with -prod.
   (warnings are converted to errors with -prod)
pull/9425/head
Delyan Angelov 2021-03-22 19:43:06 +02:00
parent c76c69ec35
commit a00c80b98f
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
10 changed files with 250 additions and 20 deletions

View File

@ -516,7 +516,8 @@ pub mut:
embedded_files []EmbeddedFile // list of files to embed in the binary
imported_symbols map[string]string // used for `import {symbol}`, it maps symbol => module.symbol
errors []errors.Error // all the checker errors in the file
warnings []errors.Warning // all the checker warings in the file
warnings []errors.Warning // all the checker warnings in the file
notices []errors.Notice // all the checker notices in the file
generic_fns []&FnDecl
}

View File

@ -287,7 +287,8 @@ fn (b &Builder) show_total_warns_and_errors_stats() {
if b.pref.is_stats {
estring := util.bold(b.checker.nr_errors.str())
wstring := util.bold(b.checker.nr_warnings.str())
println('checker summary: $estring V errors, $wstring V warnings')
nstring := util.bold(b.checker.nr_notices.str())
println('checker summary: $estring V errors, $wstring V warnings, $nstring V notices')
}
}
@ -304,6 +305,26 @@ fn (b &Builder) print_warnings_and_errors() {
if b.pref.is_verbose && b.checker.nr_warnings > 1 {
println('$b.checker.nr_warnings warnings')
}
if b.pref.is_verbose && b.checker.nr_notices > 1 {
println('$b.checker.nr_notices notices')
}
if b.checker.nr_notices > 0 && !b.pref.skip_warnings {
for i, err in b.checker.notices {
kind := if b.pref.is_verbose {
'$err.reporter notice #$b.checker.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')
}
if i > b.max_nr_errors {
return
}
}
}
if b.checker.nr_warnings > 0 && !b.pref.skip_warnings {
for i, err in b.checker.warnings {
kind := if b.pref.is_verbose {

View File

@ -4,6 +4,7 @@ module checker
import os
import strings
import time
import v.ast
import v.vmod
import v.table
@ -36,8 +37,10 @@ pub mut:
file &ast.File = 0
nr_errors int
nr_warnings int
nr_notices int
errors []errors.Error
warnings []errors.Warning
notices []errors.Notice
error_lines []int // to avoid printing multiple errors for the same line
expected_type table.Type
expected_or_type table.Type // fn() or { 'this type' } eg. string. expected or block type
@ -1566,13 +1569,8 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
call_expr.pos)
}
if !c.cur_fn.is_deprecated && method.is_deprecated {
mut deprecation_message := 'method `${left_type_sym.name}.$method.name` has been deprecated'
for attr in method.attrs {
if attr.name == 'deprecated' && attr.arg != '' {
deprecation_message += '; $attr.arg'
}
}
c.warn(deprecation_message, call_expr.pos)
c.deprecate_fnmethod('method', '${left_type_sym.name}.$method.name', method,
call_expr)
}
// TODO: typ optimize.. this node can get processed more than once
if call_expr.expected_arg_types.len == 0 {
@ -1924,13 +1922,7 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
c.error('function `$f.name` is private', call_expr.pos)
}
if !c.cur_fn.is_deprecated && f.is_deprecated {
mut deprecation_message := 'function `$f.name` has been deprecated'
for d in f.attrs {
if d.name == 'deprecated' && d.arg != '' {
deprecation_message += '; $d.arg'
}
}
c.warn(deprecation_message, call_expr.pos)
c.deprecate_fnmethod('function', f.name, f, call_expr)
}
if f.is_unsafe && !c.inside_unsafe
&& (f.language != .c || (f.name[2] in [`m`, `s`] && f.mod == 'builtin')) {
@ -2103,6 +2095,41 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
return f.return_type
}
fn (mut c Checker) deprecate_fnmethod(kind string, name string, the_fn table.Fn, call_expr ast.CallExpr) {
start_message := '$kind `$name`'
mut deprecation_message := ''
now := time.now()
mut after_time := now
for attr in the_fn.attrs {
if attr.name == 'deprecated' && attr.arg != '' {
deprecation_message = attr.arg
}
if attr.name == 'deprecated_after' && attr.arg != '' {
after_time = time.parse_iso8601(attr.arg) or {
c.error('invalid time format', attr.pos)
time.now()
}
}
}
if after_time < now {
c.warn(semicolonize('$start_message has been deprecated since $after_time.ymmdd()',
deprecation_message), call_expr.pos)
} else if after_time == now {
c.warn(semicolonize('$start_message has been deprecated', deprecation_message),
call_expr.pos)
} else {
c.note(semicolonize('$start_message will be deprecated after $after_time.ymmdd()',
deprecation_message), call_expr.pos)
}
}
fn semicolonize(main string, details string) string {
if details == '' {
return main
}
return '$main; $details'
}
fn (mut c Checker) type_implements(typ table.Type, inter_typ table.Type, pos token.Position) bool {
$if debug_interface_type_implements ? {
eprintln('> type_implements typ: $typ.debug() | inter_typ: $inter_typ.debug()')
@ -4291,8 +4318,10 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) table.Type {
}
c.warnings << c2.warnings
c.errors << c2.errors
c.notices << c2.notices
c.nr_warnings += c2.nr_warnings
c.nr_errors += c2.nr_errors
c.nr_notices += c2.nr_notices
}
if node.method_name == 'html' {
rtyp := c.table.find_type_idx('vweb.Result')
@ -5853,7 +5882,7 @@ pub fn (mut c Checker) add_error_detail(s string) {
pub fn (mut c Checker) warn(s string, pos token.Position) {
allow_warnings := !(c.pref.is_prod || c.pref.warns_are_errors) // allow warnings only in dev builds
c.warn_or_error(s, pos, allow_warnings) // allow warnings only in dev builds
c.warn_or_error(s, pos, allow_warnings)
}
pub fn (mut c Checker) error(message string, pos token.Position) {
@ -5897,6 +5926,24 @@ fn (c &Checker) check_struct_signature(from table.Struct, to table.Struct) bool
return true
}
pub fn (mut c Checker) note(message string, pos token.Position) {
mut details := ''
if c.error_details.len > 0 {
details = c.error_details.join('\n')
c.error_details = []
}
wrn := errors.Notice{
reporter: errors.Reporter.checker
pos: pos
file_path: c.file.path
message: message
details: details
}
c.file.notices << wrn
c.notices << wrn
c.nr_notices++
}
fn (mut c Checker) warn_or_error(message string, pos token.Position, warn bool) {
// add backtrace to issue struct, how?
// if c.pref.is_verbose {

View File

@ -0,0 +1,48 @@
vlib/v/checker/tests/deprecations.vv:54:2: notice: function `future` will be deprecated after 9999-12-30; custom message 4
52 |
53 | fn main() {
54 | future()
| ~~~~~~~~
55 | past()
56 | simply_deprecated()
vlib/v/checker/tests/deprecations.vv:60:4: notice: method `Abc.future` will be deprecated after 9999-11-01; custom message 1
58 | //
59 | a := Abc{}
60 | a.future()
| ~~~~~~~~
61 | a.past()
62 | a.simply_deprecated()
vlib/v/checker/tests/deprecations.vv:55:2: error: function `past` has been deprecated since 2021-03-01; custom message 5
53 | fn main() {
54 | future()
55 | past()
| ~~~~~~
56 | simply_deprecated()
57 | just_deprecated()
vlib/v/checker/tests/deprecations.vv:56:2: error: function `simply_deprecated` has been deprecated; custom message 6
54 | future()
55 | past()
56 | simply_deprecated()
| ~~~~~~~~~~~~~~~~~~~
57 | just_deprecated()
58 | //
vlib/v/checker/tests/deprecations.vv:57:2: error: function `just_deprecated` has been deprecated
55 | past()
56 | simply_deprecated()
57 | just_deprecated()
| ~~~~~~~~~~~~~~~~~
58 | //
59 | a := Abc{}
vlib/v/checker/tests/deprecations.vv:61:4: error: method `Abc.past` has been deprecated since 2021-03-01; custom message 2
59 | a := Abc{}
60 | a.future()
61 | a.past()
| ~~~~~~
62 | a.simply_deprecated()
63 | }
vlib/v/checker/tests/deprecations.vv:62:4: error: method `Abc.simply_deprecated` has been deprecated; custom message 3
60 | a.future()
61 | a.past()
62 | a.simply_deprecated()
| ~~~~~~~~~~~~~~~~~~~
63 | }

View File

@ -0,0 +1,63 @@
// methods using [deprecated_after]:
struct Abc {
x int
}
fn (a Abc) str() string {
return 'Abc { x: $a.x }'
}
[deprecated: 'custom message 1']
[deprecated_after: '9999-11-01']
fn (a Abc) future() {
dump(@METHOD)
dump(a)
}
[deprecated: 'custom message 2']
[deprecated_after: '2021-03-01']
fn (a Abc) past() {
dump(@METHOD)
dump(a)
}
[deprecated: 'custom message 3']
fn (a Abc) simply_deprecated() {
dump(@METHOD)
dump(a)
}
// functions using [deprecated_after]:
[deprecated: 'custom message 4']
[deprecated_after: '9999-12-30']
fn future() {
dump(@FN)
}
[deprecated: 'custom message 5']
[deprecated_after: '2021-03-01']
fn past() {
dump(@FN)
}
[deprecated: 'custom message 6']
fn simply_deprecated() {
dump(@FN)
}
[deprecated]
fn just_deprecated() {
dump(@FN)
}
fn main() {
future()
past()
simply_deprecated()
just_deprecated()
//
a := Abc{}
a.future()
a.past()
a.simply_deprecated()
}

View File

@ -27,3 +27,12 @@ pub:
pos token.Position
reporter Reporter
}
pub struct Notice {
pub:
message string
details string
file_path string
pos token.Position
reporter Reporter
}

View File

@ -29,7 +29,6 @@ fn (mut p Parser) hash() ast.HashStmt {
main_str = content.trim_space()
msg = ''
}
// p.trace('a.v', 'kind: ${kind:-10s} | pos: ${pos:-45s} | hash: $val')
return ast.HashStmt{
mod: p.mod
source_file: p.file_name

View File

@ -64,6 +64,7 @@ mut:
expecting_type bool // `is Type`, expecting type
errors []errors.Error
warnings []errors.Warning
notices []errors.Notice
vet_errors []vet.Error
cur_fn_name string
label_names []string
@ -1490,6 +1491,10 @@ pub fn (mut p Parser) warn(s string) {
p.warn_with_pos(s, p.tok.position())
}
pub fn (mut p Parser) note(s string) {
p.note_with_pos(s, p.tok.position())
}
pub fn (mut p Parser) error_with_pos(s string, pos token.Position) {
if p.pref.fatal_errors {
exit(1)
@ -1566,6 +1571,23 @@ pub fn (mut p Parser) warn_with_pos(s string, pos token.Position) {
}
}
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 {
ferror := util.formatted_error('notice:', s, p.file_name, pos)
eprintln(ferror)
} else {
p.notices << errors.Notice{
file_path: p.file_name
pos: pos
reporter: .parser
message: s
}
}
}
pub fn (mut p Parser) vet_error(msg string, line int, fix vet.FixKind) {
pos := token.Position{
line_nr: line + 1

View File

@ -51,6 +51,7 @@ pub mut:
pref &pref.Preferences
errors []errors.Error
warnings []errors.Warning
notices []errors.Notice
vet_errors []vet.Error
}
@ -1305,6 +1306,23 @@ fn (mut s Scanner) inc_line_number() {
}
}
pub fn (mut s Scanner) note(msg string) {
pos := token.Position{
line_nr: s.line_nr
pos: s.pos
}
if s.pref.output_mode == .stdout {
eprintln(util.formatted_error('notice:', msg, s.file_path, pos))
} else {
s.notices << errors.Notice{
file_path: s.file_path
pos: pos
reporter: .scanner
message: msg
}
}
}
pub fn (mut s Scanner) warn(msg string) {
if s.pref.warns_are_errors {
s.error(msg)

View File

@ -61,9 +61,11 @@ fn color(kind string, msg string) string {
}
if kind.contains('error') {
return term.red(msg)
} else {
return term.magenta(msg)
}
if kind.contains('notice') {
return term.yellow(msg)
}
return term.magenta(msg)
}
// formatted_error - `kind` may be 'error' or 'warn'