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

assert: use type constraints (generics) #255

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

Conversation

dnephin
Copy link
Member

@dnephin dnephin commented Feb 18, 2023

Closes #249

Once merged, this change would require the use of go1.18+ to import gotest.tools/v3.

This PR adds type constraints to the functions in assert and assert/cmp. By using type constraints the compiler (and also any IDE or editor that uses gopls) can warn the user if they are using different types as the arguments to comparison functions.

Also use a type constraint for BoolOrComparison. This type was always intended to be a union type, and now it's possible to express that.

Copy link

@glenjamin glenjamin left a comment

Choose a reason for hiding this comment

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

Neat! I didn't realise they'd allowed more than just builtins into the type set

@@ -197,7 +199,7 @@ func NilError(t TestingT, err error, msgAndArgs ...interface{}) {
// called from the goroutine running the test function, not from other
// goroutines created during the test. Use Check with cmp.Equal from other
// goroutines.
func Equal(t TestingT, x, y interface{}, msgAndArgs ...interface{}) {
func Equal[ANY any](t TestingT, x, y ANY, msgAndArgs ...any) {

Choose a reason for hiding this comment

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

I wonder if this one could/should be the comparable constraint?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question! I did attempt to use comparable here while working on this. It works for some types, but a few tests failed. Currently Equal works with pointers and interfaces like error. Changing to comparable caused the build to fail because error is not comparable. So I had to use any.

@@ -100,13 +100,15 @@ import (

// BoolOrComparison can be a bool, cmp.Comparison, or error. See Assert for
// details about how this type is used.
type BoolOrComparison interface{}
type BoolOrComparison interface {
bool | func() (bool, string) | ~func() cmp.Result
Copy link
Member Author

Choose a reason for hiding this comment

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

This might need to include error. I believe it was previously possible to use assert.Assert(t, err).

@glenjamin
Copy link

I wonder if this should be a v4, or do you reckon there's no breaking changes for 1.18+?

@dnephin
Copy link
Member Author

dnephin commented Feb 22, 2023

That's the big question for sure. My hope is that it can be backwards compatible for 1.18+.

The function signatures have changed, but they aren't methods on a type. I believe the only way this could break existing code is if the functions are assigned to a variable with an explicit type of func(t assert.TestingT, x any, msg...any). That seems unlikely, but it's possible.

Are there other cases where these changes might break existing code?

@glenjamin
Copy link

I suppose if the tests are currently passing, then the types should already match.

Although - what about go-cmp transforms?

@dnephin
Copy link
Member Author

dnephin commented Feb 22, 2023

The options ...cmp.Options parameter to DeepEqual isn't changing. I think those should be ok, unless I'm missing something.

@glenjamin
Copy link

I think the transform options allow different types to compare equal

@dnephin
Copy link
Member Author

dnephin commented Feb 23, 2023

Ahh, if I understand correctly you mean when the args passed to DeepEqual are different types, but a Transformer changes the type. That is possible. It seems strange that someone would do that, since they can transform the arguments before passing them to DeepEqual. I wonder how common it is to use transformers in this way. I don't think I've ever used one myself. I only use ignore and compare.

@glenjamin
Copy link

I used that feature once to ignore differences between numeric types, but that was for numbers nestled within a polymorphic structure - but never for two args at the top level

@dnephin
Copy link
Member Author

dnephin commented Feb 25, 2023

I'm still not sure if we need a v4 for this. I wrote up #257 with some other changes we might want to make for a v4 release. I think if we decide to increment the major version we should try to include other breaking changes at the same time.

cc @vdemeester as well, to get your thoughts

This was always the intent. With generics we can express the intent in a way
the compiler understands.
@vdemeester
Copy link
Member

I'm still not sure if we need a v4 for this. I wrote up #257 with some other changes we might want to make for a v4 release. I think if we decide to increment the major version we should try to include other breaking changes at the same time.

cc @vdemeester as well, to get your thoughts

I would tend to agree that it could be a good time to do a v4 and include other breaking changes there 👼🏼. So, I would go the v4 ways at least 👼🏼

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

Successfully merging this pull request may close these issues.

Generics in comparison functions?
3 participants