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

cmp: top-level interfaces are not matched #88

Closed
dsnet opened this issue Apr 18, 2018 · 2 comments
Closed

cmp: top-level interfaces are not matched #88

dsnet opened this issue Apr 18, 2018 · 2 comments
Assignees

Comments

@dsnet
Copy link
Collaborator

dsnet commented Apr 18, 2018

Consider the following:

equalErrors := cmp.Comparer(func(_, _ error) bool { return true })

cmp.Equal(io.EOF, &os.PathError{}, equalErrors) // false

type E struct{ E error }
cmp.Equal(E{io.EOF}, E{&os.PathError{}}, equalErrors) // true

Note that the first comparison always reports false, while the later reports true. The reason is because reflect.ValueOf on an interface loses information about the fact that the value passed in is an interface. Thus, the first call to cmp.Equal reports false because the underlying types are not equal, before even giving the custom Comparer passed in a chance to apply.

We should probably box the top-level values into an empty interface to fix this.

\cc @neild

@dsnet
Copy link
Collaborator Author

dsnet commented Apr 18, 2018

In working on this, I attempted to always promote to an interface{} when retrieving value. Thus,

s.compareAny(reflect.ValueOf(x), reflect.ValueOf(y))

would become something like:

iface := reflect.TypeOf((*interface{})(nil)).Elem()
s.compareAny(reflect.ValueOf(x).Convert(iface), reflect.ValueOf(y).Convert(iface))

However, this does not work because the cmp package uses assignability to determine applicability of filters and options and interface{} is not assignable to error even if the underlying concrete type may be assignable to error.

My next thought was to dynamically create an interface type T that had the common set of methods for both the x and y values. If the underlying types of x and y are each individually assignable to some interface R, then it is guaranteed that T is also assignable to R. However, this approach requires dynamically creating an interface, which is functionality that reflect does not currently have. I commented on golang/go#4146 (comment) with this use-case.

On that thread, @Merovius commented:

I might misread this, but the documentation says that

The comparer f must be a function "func(T, T) bool" and is implicitly filtered to input values assignable to T. If T is an interface, it is possible that f is called with two values of different concrete types that both implement T.

That would seem to cover the case you mention? In particular this works.

You are correct that the Comparer itself allows for "two values of different concrete types that both implement T", but this is not property assumed by the rest of the cmp. For example, the first rule on cmp.Equal is:

If two values are not of the same type, then they are never equal and the overall result is false.

This rule prevents Equal from even attempting to use filters or comparers. Note that we cannot relax this restriction as this assumption is heavily depended upon.

@dsnet
Copy link
Collaborator Author

dsnet commented Feb 27, 2019

Fixed by #112.

@dsnet dsnet closed this as completed Feb 27, 2019
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

1 participant