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

Linter to disallow struct initialization without field names #1059

Open
arxeiss opened this issue Oct 9, 2024 · 5 comments
Open

Linter to disallow struct initialization without field names #1059

arxeiss opened this issue Oct 9, 2024 · 5 comments

Comments

@arxeiss
Copy link

arxeiss commented Oct 9, 2024

Is your feature request related to a problem? Please describe.
Go allows you to initialize struct only by values, if they follow same order as in structure definition.
This can be useful if structure has 1 field, but otherwise it can lead to syntax errors if order is changed. And even worse, to hidden bugs, if there are fields with same type, and their order is changed.

type X struct {
	x string
	y string
}

func DoSomething() {
	l := X{"xval", "yval"}
	fmt.Printf("%+v\n", l)
}

Describe the solution you'd like
Do a linter, which will catch that and require to have names of fields inside struct initialization.

Describe alternatives you've considered
I have a feeling, that there was such linter. But I searched for that (not only in Revive) and I cannot find anything.

Additional context
I can try to prepare PR, but it is suggested to create issue first.

@arxeiss
Copy link
Author

arxeiss commented Oct 10, 2024

I wanted to try out to implement this rule, but I have a feeling it is not possible with Revive.
It would require reflect package, or something more advanced. Because I cannot differentiate struct vs array initialization.

type A []string

type X struct {
	x string
	y string
}

func DoSomething() {
	_ = A{"xval", "yval"}
	_ = X{"xval", "yval"}
}

@chavacava
Copy link
Collaborator

Hi @arxeiss thanks for the proposal.
Struct initialization was discussed at #682, your proposal is not exactly the same. IMO it's something that we could add.
There are some cases for which initialization without names should be allowed: empty structs and structs with only one field. This might need access to the definition of the structure, something that is not necessarily available in all contexts

@chavacava
Copy link
Collaborator

I wanted to try out to implement this rule, but I have a feeling it is not possible with Revive. It would require reflect package, or something more advanced. Because I cannot differentiate struct vs array initialization.

You can leverage type information to make the difference between arrays and structs (as in string-of-int)

That said, you could try implementing a naive version (no type info) to see if there are too much false positives when analyzing real code bases.

@chavacava
Copy link
Collaborator

BTW, a language hack to force using field names when instantiation a struct is to declare a blank identified _ field as the first field of the struct:

type position struct {
	_ struct{}  // just to force named fields in instantiation
	x int
	y int
}

(again, it's a hack)

@arxeiss
Copy link
Author

arxeiss commented Oct 10, 2024

There are some cases for which initialization without names should be allowed: empty structs and structs with only one field. This might need access to the definition of the structure, something that is not necessarily available in all contexts

This is not an issue actually.

  • If you have _ = X{} and it is empty, I don't care how many fields the struct X has.
  • If you will write _ = X{"some value"} and X has only 1 field, it is OK too.
  • If it has multiple fields, _ = X{"some value"} will raise an compile error as you have to define all or nothing. (unless you specify names of fields)

So to detect when to raise an error and when not you don't need to know how many fields struct has.

The real issue is, that on AST you don't know what type it is. In my example above it is visible. but it that struct would be defined on different package, you are lost. Even what you link (string-of-int rule) will not help in that case.

func DoSomething() {
	_ = pkg1.Values{"xval", "yval"} // is this definition of slice or struct?
	_ = pkg2.Values{"xval", "yval"}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants