diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 39736310fc..ba7b0a6133 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -4,6 +4,7 @@ [has_globals] module ast +import time import v.cflag import v.token import v.util @@ -36,8 +37,10 @@ pub mut: cur_fn &FnDecl = 0 // previously stored in Checker.cur_fn and Gen.cur_fn cur_concrete_types []Type // current concrete types, e.g. gostmts int // how many `go` statements there were in the parsed files. - enum_decls map[string]EnumDecl // When table.gostmts > 0, __VTHREADS__ is defined, which can be checked with `$if threads {` + enum_decls map[string]EnumDecl + mdeprecated_msg map[string]string // module deprecation message + mdeprecated_after map[string]time.Time // module deprecation date } // used by vls to avoid leaks @@ -301,6 +304,15 @@ pub fn (t &Table) known_fn(name string) bool { return true } +pub fn (mut t Table) mark_module_as_deprecated(mname string, message string) { + t.mdeprecated_msg[mname] = message + t.mdeprecated_after[mname] = time.now() +} + +pub fn (mut t Table) mark_module_as_deprecated_after(mname string, after_date string) { + t.mdeprecated_after[mname] = time.parse_iso8601(after_date) or { time.now() } +} + pub fn (mut t Table) register_fn(new_fn Fn) { t.fns[new_fn.name] = new_fn } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index af2c4d759c..b024c72eaa 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3,6 +3,7 @@ module checker import os +import time import v.ast import v.vmod import v.token @@ -2240,6 +2241,11 @@ fn (mut c Checker) import_stmt(node ast.Import) { } c.error('module `$node.mod` has no constant or function `$sym.name`', sym.pos) } + if after_time := c.table.mdeprecated_after[node.mod] { + now := time.now() + deprecation_message := c.table.mdeprecated_msg[node.mod] + c.deprecate('module', node.mod, deprecation_message, now, after_time, node.pos) + } } // stmts should be used for processing normal statement lists (fn bodies, for loop bodies etc). diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 2be4130329..2485c8101b 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -1439,7 +1439,6 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { } fn (mut c Checker) deprecate_fnmethod(kind string, name string, the_fn ast.Fn, node ast.CallExpr) { - start_message := '$kind `$name`' mut deprecation_message := '' now := time.now() mut after_time := now @@ -1454,19 +1453,24 @@ fn (mut c Checker) deprecate_fnmethod(kind string, name string, the_fn ast.Fn, n } } } + c.deprecate(kind, name, deprecation_message, now, after_time, node.pos) +} + +fn (mut c Checker) deprecate(kind string, name string, deprecation_message string, now time.Time, after_time time.Time, pos token.Pos) { + start_message := '$kind `$name`' error_time := after_time.add_days(180) if error_time < now { c.error(semicolonize('$start_message has been deprecated since $after_time.ymmdd()', - deprecation_message), node.pos) + deprecation_message), pos) } else if after_time < now { c.warn(semicolonize('$start_message has been deprecated since $after_time.ymmdd(), it will be an error after $error_time.ymmdd()', - deprecation_message), node.pos) + deprecation_message), pos) } else if after_time == now { c.warn(semicolonize('$start_message has been deprecated', deprecation_message), - node.pos) + pos) } else { c.note(semicolonize('$start_message will be deprecated after $after_time.ymmdd(), and will become an error after $error_time.ymmdd()', - deprecation_message), node.pos) + deprecation_message), pos) } } diff --git a/vlib/v/checker/tests/modules/deprecated_module.out b/vlib/v/checker/tests/modules/deprecated_module.out new file mode 100644 index 0000000000..d53d16ce25 --- /dev/null +++ b/vlib/v/checker/tests/modules/deprecated_module.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/modules/deprecated_module/main.v:2:1: notice: module `deprecated_module.www.ttt` will be deprecated after 2999-01-01, and will become an error after 2999-06-30; use xxx.yyy + 1 | import deprecated_module.bbb.ccc + 2 | import deprecated_module.www.ttt + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 3 | import deprecated_module.xxx.yyy + 4 | +vlib/v/checker/tests/modules/deprecated_module/main.v:12:11: error: undefined ident: `deprecated_module.www.ttt.non_existing` + 10 | dump(ttt.f()) + 11 | dump(yyy.f()) + 12 | dump(ttt.non_existing) + | ~~~~~~~~~~~~ + 13 | } diff --git a/vlib/v/checker/tests/modules/deprecated_module/bbb/ccc/ccc.v b/vlib/v/checker/tests/modules/deprecated_module/bbb/ccc/ccc.v new file mode 100644 index 0000000000..351f12b15e --- /dev/null +++ b/vlib/v/checker/tests/modules/deprecated_module/bbb/ccc/ccc.v @@ -0,0 +1,5 @@ +module ccc + +pub fn f() int { + return 142 +} diff --git a/vlib/v/checker/tests/modules/deprecated_module/main.v b/vlib/v/checker/tests/modules/deprecated_module/main.v new file mode 100644 index 0000000000..d90a2b87f2 --- /dev/null +++ b/vlib/v/checker/tests/modules/deprecated_module/main.v @@ -0,0 +1,13 @@ +import deprecated_module.bbb.ccc +import deprecated_module.www.ttt +import deprecated_module.xxx.yyy + +// NB: www.ttt has been deprecated. +// => compiling this should produce an error, +// showing the deprecation message +fn main() { + dump(ccc.f()) + dump(ttt.f()) + dump(yyy.f()) + dump(ttt.non_existing) +} diff --git a/vlib/v/checker/tests/modules/deprecated_module/v.mod b/vlib/v/checker/tests/modules/deprecated_module/v.mod new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vlib/v/checker/tests/modules/deprecated_module/www/ttt/ttt.v b/vlib/v/checker/tests/modules/deprecated_module/www/ttt/ttt.v new file mode 100644 index 0000000000..3e26431881 --- /dev/null +++ b/vlib/v/checker/tests/modules/deprecated_module/www/ttt/ttt.v @@ -0,0 +1,7 @@ +[deprecated: 'use xxx.yyy'] +[deprecated_after: '2999-01-01'] +module ttt + +pub fn f() int { + return 1142 +} diff --git a/vlib/v/checker/tests/modules/deprecated_module/xxx/yyy/yyy.v b/vlib/v/checker/tests/modules/deprecated_module/xxx/yyy/yyy.v new file mode 100644 index 0000000000..74d6a1f6ca --- /dev/null +++ b/vlib/v/checker/tests/modules/deprecated_module/xxx/yyy/yyy.v @@ -0,0 +1,5 @@ +module yyy + +pub fn f() int { + return 42 +} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 5e7c46d2c9..003a69195a 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -478,7 +478,7 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) { f.interface_decl(node) } ast.Module { - f.mod(node) + f.module_stmt(node) } ast.Return { f.return_stmt(node) @@ -1135,7 +1135,7 @@ pub fn (mut f Fmt) interface_method(method ast.FnDecl) { f.mark_types_import_as_used(method.return_type) } -pub fn (mut f Fmt) mod(mod ast.Module) { +pub fn (mut f Fmt) module_stmt(mod ast.Module) { f.set_current_module_name(mod.name) if mod.is_skipped { return diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 958b22e6ef..429fb51567 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2967,16 +2967,19 @@ fn (mut p Parser) parse_number_literal() ast.Expr { fn (mut p Parser) module_decl() ast.Module { mut module_attrs := []ast.Attr{} mut attrs_pos := p.tok.pos() - if p.tok.kind == .lsbr { + for p.tok.kind == .lsbr { p.attributes() - module_attrs = p.attrs } + module_attrs << p.attrs mut name := 'main' - is_skipped := p.tok.kind != .key_module mut module_pos := token.Pos{} mut name_pos := token.Pos{} mut mod_node := ast.Module{} - if !is_skipped { + is_skipped := p.tok.kind != .key_module + if is_skipped { + // the attributes were for something else != module, like a struct/fn/type etc. + module_attrs = [] + } else { p.attrs = [] module_pos = p.tok.pos() p.next() @@ -3020,6 +3023,14 @@ fn (mut p Parser) module_decl() ast.Module { if !is_skipped { for ma in module_attrs { match ma.name { + 'deprecated' { + // [deprecated: 'use a replacement'] + p.table.mark_module_as_deprecated(p.mod, ma.arg) + } + 'deprecated_after' { + // [deprecated_after: '2027-12-30'] + p.table.mark_module_as_deprecated_after(p.mod, ma.arg) + } 'manualfree' { p.is_manualfree = true }