all: add support for struct field deprecation (#14527)

Delyan Angelov 2022-05-26 00:44:18 +03:00 committed by Chewing_Bever
parent a61316ceea
commit 140d494d4c
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
13 changed files with 201 additions and 26 deletions

View File

@ -5905,6 +5905,19 @@ fn main() {
}
```
Struct field deprecations:
```v oksyntax
module abc
// Note that only *direct* accesses to Xyz.d in *other modules*, will produce deprecation notices/warnings:
pub struct Xyz {
pub mut:
a int
d int [deprecated: 'use Xyz.a instead'; deprecated_after: '2999-03-01'] // produce a notice, the deprecation date is in the far future
}
```
Function/method deprecations:
```v
// Calling this function will result in a deprecation warning
[deprecated]

View File

@ -304,6 +304,10 @@ pub:
is_mut bool
is_global bool
is_volatile bool
//
is_deprecated bool
deprecation_msg string
deprecated_after string
pub mut:
default_expr Expr
default_expr_typ Type

View File

@ -614,7 +614,7 @@ pub fn (mut c Checker) infer_fn_generic_types(func ast.Fn, mut node ast.CallExpr
sym := c.table.sym(node.receiver_type)
match sym.info {
ast.Struct, ast.Interface, ast.SumType {
if c.table.cur_fn.generic_names.len > 0 { // in generic fn
if !isnil(c.table.cur_fn) && c.table.cur_fn.generic_names.len > 0 { // in generic fn
if gt_name in c.table.cur_fn.generic_names
&& c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len {
idx := c.table.cur_fn.generic_names.index(gt_name)
@ -671,6 +671,7 @@ pub fn (mut c Checker) infer_fn_generic_types(func ast.Fn, mut node ast.CallExpr
mut param_elem_sym := c.table.sym(param_elem_info.elem_type)
for {
if arg_elem_sym.kind == .array && param_elem_sym.kind == .array
&& !isnil(c.table.cur_fn)
&& param_elem_sym.name !in c.table.cur_fn.generic_names {
arg_elem_info = arg_elem_sym.info as ast.Array
arg_elem_sym = c.table.sym(arg_elem_info.elem_type)
@ -690,6 +691,7 @@ pub fn (mut c Checker) infer_fn_generic_types(func ast.Fn, mut node ast.CallExpr
mut param_elem_sym := c.table.sym(param_elem_info.elem_type)
for {
if arg_elem_sym.kind == .array_fixed && param_elem_sym.kind == .array_fixed
&& !isnil(c.table.cur_fn)
&& param_elem_sym.name !in c.table.cur_fn.generic_names {
arg_elem_info = arg_elem_sym.info as ast.ArrayFixed
arg_elem_sym = c.table.sym(arg_elem_info.elem_type)

View File

@ -934,8 +934,8 @@ pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast
pub fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_return_type ast.Type) {
if node.kind == .propagate_option {
if !c.table.cur_fn.return_type.has_flag(.optional) && c.table.cur_fn.name != 'main.main'
&& !c.inside_const {
if !isnil(c.table.cur_fn) && !c.table.cur_fn.return_type.has_flag(.optional)
&& c.table.cur_fn.name != 'main.main' && !c.inside_const {
c.error('to propagate the call, `$c.table.cur_fn.name` must return an optional type',
node.pos)
}
@ -951,8 +951,8 @@ pub fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_re
return
}
if node.kind == .propagate_result {
if !c.table.cur_fn.return_type.has_flag(.result) && c.table.cur_fn.name != 'main.main'
&& !c.inside_const {
if !isnil(c.table.cur_fn) && !c.table.cur_fn.return_type.has_flag(.result)
&& c.table.cur_fn.name != 'main.main' && !c.inside_const {
c.error('to propagate the call, `$c.table.cur_fn.name` must return an result type',
node.pos)
}
@ -1071,7 +1071,8 @@ pub fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type {
match mut node.expr {
ast.Ident {
name := node.expr.name
valid_generic := util.is_generic_type_name(name) && name in c.table.cur_fn.generic_names
valid_generic := util.is_generic_type_name(name) && !isnil(c.table.cur_fn)
&& name in c.table.cur_fn.generic_names
if valid_generic {
name_type = ast.Type(c.table.find_type_idx(name)).set_flag(.generic)
}
@ -1220,11 +1221,23 @@ pub fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type {
// <<<
if has_field {
if sym.mod != c.mod && !field.is_pub && sym.language != .c {
is_used_outside := sym.mod != c.mod
if is_used_outside && !field.is_pub && sym.language != .c {
unwrapped_sym := c.table.sym(c.unwrap_generic(typ))
c.error('field `${unwrapped_sym.name}.$field_name` is not public', node.pos)
}
field_sym := c.table.sym(field.typ)
if field.is_deprecated && is_used_outside {
now := time.now()
mut after_time := now
if field.deprecated_after != '' {
after_time = time.parse_iso8601(field.deprecated_after) or {
c.error('invalid time format', field.pos)
now
}
}
c.deprecate('field', field_name, field.deprecation_msg, now, after_time, node.pos)
}
if field_sym.kind in [.sum_type, .interface_] {
if !prevent_sum_type_unwrapping_once {
if scope_field := node.scope.find_struct_field(node.expr.str(), typ, field_name) {
@ -1452,7 +1465,7 @@ fn (mut c Checker) stmt(node_ ast.Stmt) {
c.inside_const = false
}
ast.DeferStmt {
if node.idx_in_fn < 0 {
if node.idx_in_fn < 0 && !isnil(c.table.cur_fn) {
node.idx_in_fn = c.table.cur_fn.defer_stmts.len
c.table.cur_fn.defer_stmts << unsafe { &node }
}
@ -1541,7 +1554,7 @@ fn (mut c Checker) stmt(node_ ast.Stmt) {
c.warn('`goto` requires `unsafe` (consider using labelled break/continue)',
node.pos)
}
if node.name !in c.table.cur_fn.label_names {
if !isnil(c.table.cur_fn) && node.name !in c.table.cur_fn.label_names {
c.error('unknown label `$node.name`', node.pos)
}
// TODO: check label doesn't bypass variable declarations
@ -1981,7 +1994,7 @@ fn (mut c Checker) stmts_ending_with_expression(stmts []ast.Stmt) {
}
pub fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type {
if typ.has_flag(.generic) {
if typ.has_flag(.generic) && !isnil(c.table.cur_fn) {
if t_typ := c.table.resolve_generic_to_concrete(typ, c.table.cur_fn.generic_names,
c.table.cur_concrete_types)
{
@ -2531,9 +2544,15 @@ pub fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type {
fn (mut c Checker) at_expr(mut node ast.AtExpr) ast.Type {
match node.kind {
.fn_name {
if isnil(c.table.cur_fn) {
return ast.void_type
}
node.val = c.table.cur_fn.name.all_after_last('.')
}
.method_name {
if isnil(c.table.cur_fn) {
return ast.void_type
}
fname := c.table.cur_fn.name.all_after_last('.')
if c.table.cur_fn.is_method {
node.val = c.table.type_to_str(c.table.cur_fn.receiver.typ).all_after_last('.') +
@ -2543,6 +2562,9 @@ fn (mut c Checker) at_expr(mut node ast.AtExpr) ast.Type {
}
}
.mod_name {
if isnil(c.table.cur_fn) {
return ast.void_type
}
node.val = c.table.cur_fn.mod
}
.struct_name {

View File

@ -52,7 +52,8 @@ pub fn (mut c Checker) array_init(mut node ast.ArrayInit) ast.Type {
c.ensure_sumtype_array_has_default_value(node)
}
c.ensure_type_exists(node.elem_type, node.elem_type_pos) or {}
if node.typ.has_flag(.generic) && c.table.cur_fn.generic_names.len == 0 {
if node.typ.has_flag(.generic) && !isnil(c.table.cur_fn)
&& c.table.cur_fn.generic_names.len == 0 {
c.error('generic struct cannot use in non-generic function', node.pos)
}
return node.typ

View File

@ -421,8 +421,8 @@ pub fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type {
c.expected_or_type = node.return_type.clear_flag(.optional)
c.stmts_ending_with_expression(node.or_block.stmts)
c.expected_or_type = ast.void_type
if node.or_block.kind == .propagate_option && !c.table.cur_fn.return_type.has_flag(.optional)
&& !c.inside_const {
if node.or_block.kind == .propagate_option && !isnil(c.table.cur_fn)
&& !c.table.cur_fn.return_type.has_flag(.optional) && !c.inside_const {
if !c.table.cur_fn.is_main {
c.error('to propagate the optional call, `$c.table.cur_fn.name` must return an optional',
node.or_block.pos)
@ -482,7 +482,9 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool)
c.error('JS.await: first argument must be a promise, got `$tsym.name`', node.pos)
return ast.void_type
}
c.table.cur_fn.has_await = true
if !isnil(c.table.cur_fn) {
c.table.cur_fn.has_await = true
}
match tsym.info {
ast.Struct {
mut ret_type := tsym.info.concrete_types[0]
@ -1026,14 +1028,15 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool)
}
}
// resolve return generics struct to concrete type
if func.generic_names.len > 0 && func.return_type.has_flag(.generic)
if func.generic_names.len > 0 && func.return_type.has_flag(.generic) && !isnil(c.table.cur_fn)
&& c.table.cur_fn.generic_names.len == 0 {
node.return_type = c.table.unwrap_generic_type(func.return_type, func.generic_names,
concrete_types)
} else {
node.return_type = func.return_type
}
if node.concrete_types.len > 0 && func.return_type != 0 && c.table.cur_fn.generic_names.len == 0 {
if node.concrete_types.len > 0 && func.return_type != 0 && !isnil(c.table.cur_fn)
&& c.table.cur_fn.generic_names.len == 0 {
if typ := c.table.resolve_generic_to_concrete(func.return_type, func.generic_names,
concrete_types)
{
@ -1075,7 +1078,7 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
node.return_type = left_type
node.receiver_type = left_type
if c.table.cur_fn.generic_names.len > 0 {
if !isnil(c.table.cur_fn) && c.table.cur_fn.generic_names.len > 0 {
c.table.unwrap_generic_type(left_type, c.table.cur_fn.generic_names, c.table.cur_concrete_types)
}
unwrapped_left_type := c.unwrap_generic(left_type)
@ -1155,7 +1158,9 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
if node.args.len > 0 {
c.error('wait() does not have any arguments', node.args[0].pos)
}
c.table.cur_fn.has_await = true
if !isnil(c.table.cur_fn) {
c.table.cur_fn.has_await = true
}
node.return_type = info.concrete_types[0]
node.return_type.set_flag(.optional)
return node.return_type
@ -1454,7 +1459,7 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
c.warn('method `${left_sym.name}.$method_name` must be called from an `unsafe` block',
node.pos)
}
if !c.table.cur_fn.is_deprecated && method.is_deprecated {
if !isnil(c.table.cur_fn) && !c.table.cur_fn.is_deprecated && method.is_deprecated {
c.deprecate_fnmethod('method', '${left_sym.name}.$method.name', method, node)
}
c.set_node_expected_arg_types(mut node, method)
@ -1478,13 +1483,13 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
}
// resolve return generics struct to concrete type
if method.generic_names.len > 0 && method.return_type.has_flag(.generic)
&& c.table.cur_fn.generic_names.len == 0 {
&& !isnil(c.table.cur_fn) && c.table.cur_fn.generic_names.len == 0 {
node.return_type = c.table.unwrap_generic_type(method.return_type, method.generic_names,
concrete_types)
} else {
node.return_type = method.return_type
}
if node.concrete_types.len > 0 && method.return_type != 0
if node.concrete_types.len > 0 && method.return_type != 0 && !isnil(c.table.cur_fn)
&& c.table.cur_fn.generic_names.len == 0 {
if typ := c.table.resolve_generic_to_concrete(method.return_type, method.generic_names,
concrete_types)
@ -1615,7 +1620,7 @@ fn (mut c Checker) deprecate_fnmethod(kind string, name string, the_fn ast.Fn, n
if attr.name == 'deprecated_after' && attr.arg != '' {
after_time = time.parse_iso8601(attr.arg) or {
c.error('invalid time format', attr.pos)
time.now()
now
}
}
}

View File

@ -7,6 +7,9 @@ import v.pref
// TODO: non deferred
pub fn (mut c Checker) return_stmt(mut node ast.Return) {
if isnil(c.table.cur_fn) {
return
}
c.expected_type = c.table.cur_fn.return_type
mut expected_type := c.unwrap_generic(c.expected_type)
expected_type_sym := c.table.sym(expected_type)

View File

@ -97,7 +97,7 @@ pub fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) ast.Typ
node.need_fmts[i] = fmt != c.get_default_fmt(ftyp, typ)
}
// check recursive str
if c.table.cur_fn.is_method && c.table.cur_fn.name == 'str'
if !isnil(c.table.cur_fn) && c.table.cur_fn.is_method && c.table.cur_fn.name == 'str'
&& c.table.cur_fn.receiver.name == expr.str() {
c.error('cannot call `str()` method recursively', expr.pos())
}

View File

@ -219,7 +219,7 @@ pub fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type {
&& node.generic_types.len != struct_sym.info.generic_types.len {
c.error('generic struct init expects $struct_sym.info.generic_types.len generic parameter, but got $node.generic_types.len',
node.pos)
} else if node.generic_types.len > 0 {
} else if node.generic_types.len > 0 && !isnil(c.table.cur_fn) {
for gtyp in node.generic_types {
gtyp_name := c.table.sym(gtyp).name
if gtyp_name !in c.table.cur_fn.generic_names {
@ -247,7 +247,7 @@ pub fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type {
}
}
// register generic struct type when current fn is generic fn
if c.table.cur_fn.generic_names.len > 0 {
if !isnil(c.table.cur_fn) && c.table.cur_fn.generic_names.len > 0 {
c.table.unwrap_generic_type(node.typ, c.table.cur_fn.generic_names, c.table.cur_concrete_types)
}
c.ensure_type_exists(node.typ, node.pos) or {}
@ -291,7 +291,7 @@ pub fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type {
'it cannot be initialized with `$type_sym.name{}`', node.pos)
}
}
if type_sym.name.len == 1 && c.table.cur_fn.generic_names.len == 0 {
if type_sym.name.len == 1 && !isnil(c.table.cur_fn) && c.table.cur_fn.generic_names.len == 0 {
c.error('unknown struct `$type_sym.name`', node.pos)
return 0
}

View File

@ -0,0 +1,42 @@
vlib/v/checker/tests/field_deprecations.vv:23:9: notice: field `d` will be deprecated after 2999-03-01, and will become an error after 2999-08-28; d use Xyz.a instead
21 | dump(x.c)
22 | x.c = 11
23 | dump(x.d)
| ^
24 | x.d = 45
25 | }
vlib/v/checker/tests/field_deprecations.vv:24:4: notice: field `d` will be deprecated after 2999-03-01, and will become an error after 2999-08-28; d use Xyz.a instead
22 | x.c = 11
23 | dump(x.d)
24 | x.d = 45
| ^
25 | }
26 |
vlib/v/checker/tests/field_deprecations.vv:19:9: warning: field `b` has been deprecated
17 | dump(x.a)
18 | x.a = 123
19 | dump(x.b)
| ^
20 | x.b = 456
21 | dump(x.c)
vlib/v/checker/tests/field_deprecations.vv:20:4: warning: field `b` has been deprecated
18 | x.a = 123
19 | dump(x.b)
20 | x.b = 456
| ^
21 | dump(x.c)
22 | x.c = 11
vlib/v/checker/tests/field_deprecations.vv:21:9: error: field `c` has been deprecated since 2021-03-01; c use Xyz.a instead
19 | dump(x.b)
20 | x.b = 456
21 | dump(x.c)
| ^
22 | x.c = 11
23 | dump(x.d)
vlib/v/checker/tests/field_deprecations.vv:22:4: error: field `c` has been deprecated since 2021-03-01; c use Xyz.a instead
20 | x.b = 456
21 | dump(x.c)
22 | x.c = 11
| ^
23 | dump(x.d)
24 | x.d = 45

View File

@ -0,0 +1,36 @@
import v.checker.tests.module_with_structs_with_deprecated_fields as m
struct Abc {
mut:
x int
d int [deprecated]
z int
}
fn use_m_externally() {
x := m.Xyz{}
dump(x)
}
fn use_m_externally_and_use_deprecated_fields() {
mut x := m.Xyz{}
dump(x.a)
x.a = 123
dump(x.b)
x.b = 456
dump(x.c)
x.c = 11
dump(x.d)
x.d = 45
}
fn main() {
mut a := Abc{}
a.x = 1
a.d = 1
a.z = 1
dump(a)
println(a.d)
x := a.d + 1
dump(x)
}

View File

@ -0,0 +1,25 @@
module module_with_structs_with_deprecated_fields
pub struct Xyz {
pub mut:
a int
b int [deprecated]
c int [deprecated: 'c use Xyz.a instead'; deprecated_after: '2021-03-01']
d int [deprecated: 'd use Xyz.a instead'; deprecated_after: '2999-03-01']
}
fn some_internal_function() {
mut x := Xyz{} // initialisation; no error
// reads:
dump(x.a) // no error
dump(x.b) // no error internally
dump(x.c) // no error internally
dump(x.d) // no error internally
// writes:
x.a = 1 // no error
x.b = 1 // no error internally
x.c = 1 // no error internally
x.d = 1 // no error internally
}

View File

@ -179,6 +179,9 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
}
field_start_pos := p.tok.pos()
mut is_field_volatile := false
mut is_field_deprecated := false
mut field_deprecation_msg := ''
mut field_deprecated_after := ''
if p.tok.kind == .key_volatile {
p.next()
is_field_volatile = true
@ -244,6 +247,19 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
if p.tok.kind == .lsbr {
// attrs are stored in `p.attrs`
p.attributes()
for fa in p.attrs {
match fa.name {
'deprecated' {
// [deprecated: 'use a replacement']
is_field_deprecated = true
field_deprecation_msg = fa.arg
}
'deprecated_after' {
field_deprecated_after = fa.arg
}
else {}
}
}
}
mut default_expr := ast.empty_expr()
mut has_default_expr := false
@ -274,6 +290,9 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
is_mut: is_embed || is_field_mut
is_global: is_field_global
is_volatile: is_field_volatile
is_deprecated: is_field_deprecated
deprecation_msg: field_deprecation_msg
deprecated_after: field_deprecated_after
}
}
// save embeds as table fields too, it will be used in generation phase
@ -291,6 +310,9 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
is_mut: is_embed || is_field_mut
is_global: is_field_global
is_volatile: is_field_volatile
is_deprecated: is_field_deprecated
deprecation_msg: field_deprecation_msg
deprecated_after: field_deprecated_after
}
p.attrs = []
i++