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

deprecated reflect mode has been replaced with package mode #207

Merged
merged 18 commits into from
Oct 3, 2024

Conversation

tulzke
Copy link
Contributor

@tulzke tulzke commented Sep 27, 2024

re-open of: #198

resolves #175
resolves #197
resolves #128

It is impossible to create an mock for a generic interface via reflect mode, because it is impossible to compile a generic type without instantiation.
This PR replaces the reflect mod for parsing using go/types.

All exists mocks have been regenerated and the tests have been passed. But since this radically changes the behavior of reflect mode, I would be grateful if there are those who want to add additional test cases that I did not provide.

We can also come up with another name instead of import mode.

benefits:
generation mocks for generic interfaces
generation mocks for aliases to interfaces
correct names for method arguments

tulzke and others added 12 commits August 9, 2024 14:34
* generation mocks for generic interfaces
* generation mocks for aliases to interfaces
* correct names for method arguments
* added detailed parsing error
* error more succinct
* removed call of iface.Complete()
* renamed checkInterfaceIsNotConstraint -> isConstraint
* reduced nesting at parse named segment
* add_generate_directive test fixed after I accidentally changed it early
* go updated up to 1.23 for correct aliases support
* loading of package was changed from fake package to direct load by pattern
* types.Alias added to same case as types.Named
* added test case for interface containing aliases in parameters and returns
# Conflicts:
#	mockgen/reflect.go
* changed errors formatting with less details
* regenerated mocks
* fixed type conversion for support go1.22
* simplified loops by using range
* removed unused method "generateSalt"
* renamed method of test interface: GiveBirth -> AddHumans
@tulzke
Copy link
Contributor Author

tulzke commented Sep 27, 2024

@JacobOaks
What do you think about this problem?
#198 (comment)

@JacobOaks
Copy link
Contributor

@tulzke - thanks for re-opening this and apologies again for the git mistakes on the other PR.

I think for now it makes sense to just use the unaliased names until we're able to update go.mod to 1.23. From the perspective of type-checking, types and their aliases are interchangeable so there shouldn't be any missing functionality/bugs introduced by just uniformly using the unaliased names for now. This also keeps the generated code consistent between the two versions. Once 1.24 is out and we can upgrade go.mod to 1.23, we can definitely then switch over to using the alias names.

I think the changes to do this aren't too bad, we would just have a separate case in parseType like so:

case *types.Alias:
    return p.parseType(types.Unalias(t))

What are your thoughts?

@tulzke
Copy link
Contributor Author

tulzke commented Sep 27, 2024

we would just have a separate case

I think, that alias for us is the same thing as a *types.Named. We need only his name and package.

types.Unalias(t) returns original type, so in mocks we will see original types. It is not completely correct behavior.

@tulzke
Copy link
Contributor Author

tulzke commented Sep 27, 2024

So, if we have agreed on the functionality, can we think about the names? What is the name of the new mode to replace reflect?
Maybe Package mode? Source mode and Package mode. That sounds good.

@JacobOaks
Copy link
Contributor

I don't have a strong opinion on a name. "package mode" or "types mode" is fine.

* tests
* help message
* README.md
@tulzke
Copy link
Contributor Author

tulzke commented Sep 27, 2024

I don't have a strong opinion on a name. "package mode" or "types mode" is fine.

I choose package mode. Check last commit changes please: 69210e9

If everything is OK, i'm waiting for a merge 🙂

@tulzke
Copy link
Contributor Author

tulzke commented Sep 27, 2024

I realized that I had made a mistake.
I deleted 3 unused flags, and this may break the existing generation methods. Since now these flags have no effect, I think they can be removed from the readme and help, and when using write a WARNING message to the console.

@JacobOaks What do you think about this?

//UPD: flags cannot be removed from help, so i added "DEPRECATED" to them usages

@tulzke tulzke changed the title deprecated reflect mode has been replaced with import mode deprecated reflect mode has been replaced with package mode Sep 29, 2024
@JacobOaks
Copy link
Contributor

JacobOaks commented Oct 1, 2024

Hey @tulzke, for --exec_only and --prog_only, I think we can do what you're suggesting.

For build flags, what do you think about maintaining backwards compatibility and passing them to packages.Load via Config which has a BuildFlags field? My understanding is these will get passed to the go list command, which accepts normal go build flags. This will keep things like build tags specified by --tag to be respected when deciding which Go files to load, for example.

@tulzke
Copy link
Contributor Author

tulzke commented Oct 2, 2024

@JacobOaks
I returned support for build_flags and added additional test case for it

README.md Outdated
Comment on lines 101 to 102
- `-build_flags`: (reflect mode only) Flags passed verbatim to `go build`.

Copy link
Contributor

Choose a reason for hiding this comment

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

Should we keep these lines now since we are keeping the flag?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, i forgot to do this

Copy link
Contributor

@JacobOaks JacobOaks left a comment

Choose a reason for hiding this comment

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

Thanks @tulzke for all your hard work on this PR! LGTM.

@JacobOaks JacobOaks merged commit d01ed30 into uber-go:main Oct 3, 2024
3 checks passed
@tulzke
Copy link
Contributor Author

tulzke commented Oct 4, 2024

Hi @JacobOaks
I'm glad that PR was merged. So, when new release?

@ebilling ebilling mentioned this pull request Oct 9, 2024
JacobOaks added a commit to JacobOaks/mock that referenced this pull request Oct 17, 2024
… `--exec_only`

Change uber-go#207 replaced reflect mode with package mode.
In doing so, we no longer had to create separate reflect programs
that would encode the resulting package into a gob
for the rest of mockgen to read from.
Because of that, we removed the `--exec_only` flag.

However, this flag was actually used to pass in programs that created gob encodings
in other ways, allowing customization of the package loading and encoding portion of mockgen.
As an alternative, this adds a new flag to mockgen `--model_gob`,
which allows mockgen users to pass a path to a gob encoding of a `model.Package`,
so that custom ways of loading package information can still be supported.
JacobOaks added a commit that referenced this pull request Oct 17, 2024
… `--exec_only` (#214)

Change #207 replaced reflect mode with package mode. In doing so, we no
longer had to create separate reflect programs that would encode the
resulting package into a gob for the rest of mockgen to read from.
Because of that, we removed the `--exec_only` flag.

However, this flag was actually used to pass in programs that created
gob encodings in other ways, allowing customization of the package
loading and encoding portion of mockgen. As an alternative, this adds a
new flag to mockgen `--model_gob`, which allows mockgen users to pass a
path to a gob encoding of a `model.Package`, so that custom programs for
encoding package information can still be used in conjunction with
mockgen.
apricote pushed a commit to hetznercloud/fleeting-plugin-hetzner that referenced this pull request Oct 18, 2024
…eting-plugin-hetzner!135)

This MR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [go.uber.org/mock](https://github.com/uber/mock) | require | minor | `v0.4.0` -> `v0.5.0` |

---

### Release Notes

<details>
<summary>uber/mock (go.uber.org/mock)</summary>

### [`v0.5.0`](https://github.com/uber-go/mock/releases/tag/v0.5.0)

[Compare Source](uber-go/mock@v0.4.0...v0.5.0)

#### 0.5.0 (15 Oct 2024)

##### Added

-   [#&#8203;153][]: Add `--write_command_comment` flag to specify whether to include
    `Generated by this command` comment.
-   [#&#8203;191][]: Add `--build_constraint` flag to add `//go:build` directives
    to generated mocks
-   [#&#8203;214][]: Add gob mode to support custom package loading techniques in place
    of --exec_only

##### Changed

-   [#&#8203;181][]: Made mockgen faster by changing flags passed to `go list`.
-   [#&#8203;183][]: Made `Cond` matcher generic.
-   [#&#8203;204][]: Removed `ISGOMOCK()` from generated mocks.
-   [#&#8203;207][]: Deprecated reflect mode and replaced it with the new package mode.

##### Fixed

-   [#&#8203;144][]: Fix a deadlock that can happen when mocking an interface that
    matches `fmt.Stringer`.
-   [#&#8203;168][]: Fix an issue where the "generated by" comment was being included
    in the package comment of generated mocks.

[#&#8203;144]: uber-go/mock#144

[#&#8203;153]: uber-go/mock#153

[#&#8203;168]: uber-go/mock#168

[#&#8203;181]: uber-go/mock#181

[#&#8203;183]: uber-go/mock#183

[#&#8203;191]: uber-go/mock#191

[#&#8203;204]: uber-go/mock#204

[#&#8203;207]: uber-go/mock#207

[#&#8203;214]: uber-go/mock#214

Thanks to [@&#8203;tulzke](https://github.com/tulzke) [@&#8203;JacobOaks](https://github.com/JacobOaks) [@&#8203;ARR4N](https://github.com/ARR4N) [@&#8203;sashamelentyev](https://github.com/sashamelentyev) [@&#8203;sywhang](https://github.com/sywhang) [@&#8203;fasmat](https://github.com/fasmat)
[@&#8203;eyasy1217](https://github.com/eyasy1217) [@&#8203;ghouscht](https://github.com/ghouscht) [@&#8203;tie](https://github.com/tie) [@&#8203;Neo2308](https://github.com/Neo2308) [@&#8203;carson-brill](https://github.com/carson-brill) [@&#8203;alexandear](https://github.com/alexandear) [@&#8203;sodul](https://github.com/sodul)
[@&#8203;nbgraham](https://github.com/nbgraham) for their contributions this release.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjcuMCIsInVwZGF0ZWRJblZlciI6IjM4LjEyNy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
JacobOaks added a commit to JacobOaks/mock that referenced this pull request Oct 24, 2024
v0.5.0 included uber-go#207, which replaced reflect mode with package mode.
One issue with package mode that came up (ref: uber-go#216) was that
generated mocks for interfaces that referred to alias types
were referring to the aliases' underlying names instead.

e.g.,
source:
```go
import "github.com/tikv/client-go/v2/internal/apicodec"
...
type Codec = apicodec.Codec

type Foo interface{
	Bar() Codec
}
```
mock:
```go
func (m *MockFoo) Bar() apicodec.Codec { // This is a problem, since apicodec is an internal package.
    // ...
}
```

While technically this problem is solved in Go 1.23 with explicit alias types representation,
(indeed, if you run mockgen on the example in the linked issue with
`GODEBUG=gotypesalias=1`, you get the expected behavior)
since we support the last two versions, we can't bump `go.mod` to 1.23 yet.
This leaves us with the old behavior, where `go/types` does not track alias types.
You can tell if an object is an alias, but not a type itself,
and there is no way to retrieve the object of interest
at the point where we are recursively parsing method types.

This PR works around this issue (temporarily) by using syntax information
to find all references to aliases in the source package.
When we find one, we record it in a mapping of underlying type -> alias name.
Later, while we parse the type tree, we replace any underlying types
in the mapping with their alias names.

The unexpected side effect of this is that _all_ references to the underlying type
in the generated mocks will be replaced with the alias, even if
the source used the underlying name. This is fine because:
* If the alias is in the mapping, it was used at least once, which means its accessible.
* From a type-checking perspective, aliases and their underlying types are equivalent.

With this PR, the mocks get generated correctly now:
```go
func (m *MockFoo) Bar() Codec {
    // ...
}
```

Once we can bump `go.mod` to 1.23, we should definitely remove this,
since the new type alias type nodes solve this problem automatically.
JacobOaks added a commit that referenced this pull request Oct 28, 2024
v0.5.0 included #207, which replaced reflect mode with package mode. One
issue with package mode that came up (ref: #216) was that generated
mocks for interfaces that referred to alias types were referring to the
aliases' underlying names instead.

e.g.,
some package:
```go
package somgpkg

import "somepkg/internal/apicodec"
...
type Codec = apicodec.Codec
```

mockgen input:
```go
type Foo interface{
	Bar() somepkg.Codec
}
```
mock:
```go
func (m *MockFoo) Bar() apicodec.Codec { // This is a problem, since apicodec is an internal package.
    // ...
}
```

While technically this problem is solved in Go 1.23 with explicit alias
types representation, (indeed, if you run mockgen on the example in the
linked issue with `GODEBUG=gotypesalias=1`, you get the expected
behavior) since we support the last two versions, we can't bump `go.mod`
to 1.23 yet. This leaves us with the old behavior, where `go/types` does
not track alias types. You can tell if an object is an alias, but not a
type itself, and there is no way to retrieve the object of interest at
the point where we are recursively parsing method types.

This PR works around this issue (temporarily) by using syntax
information to find all references to aliases in the source package.
When we find one, we record it in a mapping of underlying type -> alias
name. Later, while we parse the type tree, we replace any underlying
types in the mapping with their alias names.

The unexpected side effect of this is that _all_ references to the
underlying type in the generated mocks will be replaced with the alias,
even if the source used the underlying name. This is fine because:
* If the alias is in the mapping, it was used at least once, which means
its accessible.
* From a type-checking perspective, aliases and their underlying types
are equivalent.

The nice exception to the side effect is when we explicitly request mock
generation for an alias type, since at that point we are dealing with
the object, not the type.

With this PR, the mocks get generated correctly now:
```go
func (m *MockFoo) Bar() Codec {
    // ...
}
```

Once we can bump `go.mod` to 1.23, we should definitely remove this,
since the new type alias type nodes solve this problem automatically.
utgwkk added a commit to utgwkk/bulkmockgen that referenced this pull request Oct 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants