Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

checker: disallow structs with @[params] attribute as mutable function parameters #21206

Merged
merged 9 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 9 additions & 2 deletions vlib/v/checker/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
c.error('generic struct `${pure_sym_name}` in fn declaration must specify the generic type names, e.g. ${pure_sym_name}[T]',
param.type_pos)
}
if param.is_mut && arg_typ_sym.info.attrs.any(it.name == 'params') {
c.error('declaring a mutable parameter that accepts a struct with the `@[params]` attribute is not allowed',
param.type_pos)
}
} else if arg_typ_sym.info is ast.Interface {
if arg_typ_sym.info.generic_types.len > 0 && !param.typ.has_flag(.generic)
&& arg_typ_sym.info.concrete_types.len == 0 {
Expand Down Expand Up @@ -1278,8 +1282,11 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast.
} else {
if param.is_mut {
tok := param.specifier()
c.error('function `${node.name}` parameter `${param.name}` is `${tok}`, so use `${tok} ${call_arg.expr}` instead',
call_arg.expr.pos())
param_sym := c.table.sym(param.typ)
if !(param_sym.info is ast.Struct && param_sym.info.attrs.any(it.name == 'params')) {
c.error('function `${node.name}` parameter `${param.name}` is `${tok}`, so use `${tok} ${call_arg.expr}` instead',
call_arg.expr.pos())
}
} else {
c.fail_if_unreadable(call_arg.expr, arg_typ, 'argument')
}
Expand Down
7 changes: 7 additions & 0 deletions vlib/v/checker/tests/mut_parms_struct_param_err.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
vlib/v/checker/tests/mut_parms_struct_param_err.vv:8:17: error: declaring a mutable parameter that accepts a struct with the `@[params]` attribute is not allowed
6 | }
7 |
8 | fn foo(mut opts Params) bool {
| ~~~~~~
9 | return opts.a
10 | }
12 changes: 12 additions & 0 deletions vlib/v/checker/tests/mut_parms_struct_param_err.vv
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@[params]
struct Params {
mut:
a bool
x int
}

fn foo(mut opts Params) bool {
return opts.a
}

foo(a: true)
24 changes: 10 additions & 14 deletions vlib/v/tests/struct_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ struct Lol {

struct User {
name string
age int
mut:
age int
}

struct Foo {
Expand Down Expand Up @@ -257,14 +258,12 @@ fn bar_config(c Config, def int) {
assert c.def == def
}

fn mut_bar_config(mut c Config, def int) &Config {
c.n = c.def
assert c.n == def
return unsafe { c }
}

fn foo_user(u User) {}

fn foo_mut_user(mut u User) {
u.age++
}

fn test_struct_literal_args() {
foo_config(20,
n: 10
Expand All @@ -277,17 +276,14 @@ fn test_struct_literal_args() {
bar_config(Config{}, 10)
bar_config(Config{ def: 4 }, 4)

mut c_ := Config{
def: 10
}
c := mut_bar_config(mut c_, 10)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should remain to be allowed, the call has mut c_.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inconsistency, is only when you use the default f(), and f gets a mutable parameter, that was created implicitly. If you pass it explicitly, you and the reader will know where it originated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inconsistency, is only when you use the default f(), and f gets a mutable parameter, that was created implicitly. If you pass it explicitly, you and the reader will know where it originated.

The test used Config (a struct with a @[params] attribute) to test passing mutable structs. That's why I added another test passing a mut struct that isn't a params struct.

Else wouldn't this disagree with alex, disallowing mut params, and lead us where I wanted to go in the second comment? Allowing a params struct like this:

mut p := Params{a: true}
foo(mut p)

Or is what is meant to be done here to only disallow trailing mut params structs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know. @medvednikov what do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@[params]
struct Config {}

It's a params struct, so it makes sense that the new mut rule is applied to it as well.

It's fine.

Thanks, great job.

assert c.n == 10
assert c.def == 10

foo_user(name: 'Peter')
foo_user(name: 'Peter')
foo_user(age: 7)
foo_user(name: 'Stew', age: 50)

mut user := User{'Stew', 50}
foo_mut_user(mut user)
assert user.age == 51
}

struct City {
Expand Down