diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index bb66065e5b..bd99fd74c0 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -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 } diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index dd57b85e6e..9f61a69d99 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -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 { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 3948bef58f..56ab0c3607 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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 { diff --git a/vlib/v/checker/tests/deprecations.out b/vlib/v/checker/tests/deprecations.out new file mode 100644 index 0000000000..55b205f1c0 --- /dev/null +++ b/vlib/v/checker/tests/deprecations.out @@ -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 | } diff --git a/vlib/v/checker/tests/deprecations.vv b/vlib/v/checker/tests/deprecations.vv new file mode 100644 index 0000000000..4a731ce616 --- /dev/null +++ b/vlib/v/checker/tests/deprecations.vv @@ -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() +} diff --git a/vlib/v/errors/errors.v b/vlib/v/errors/errors.v index a1cb6bfde8..16f31ed402 100644 --- a/vlib/v/errors/errors.v +++ b/vlib/v/errors/errors.v @@ -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 +} diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index 619eb1e660..8398989bb2 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -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 diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 6c42bee9e7..00da395549 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -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 diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index 5e200bc1b5..f0e6ab5230 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -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) diff --git a/vlib/v/util/errors.v b/vlib/v/util/errors.v index 21790c0ebb..de5ec6cd05 100644 --- a/vlib/v/util/errors.v +++ b/vlib/v/util/errors.v @@ -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'