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
U1000: reduce false positive that relates to type parameters #1448
base: master
Are you sure you want to change the base?
Conversation
@dominikh any chance to get reviewed? |
I'll take a look when I get the chance |
I'm wondering if instead we can use https://pkg.go.dev/golang.org/x/exp/typeparams#GenericAssignableTo — that is, replace our implementation of implements with a modified version of GenericAssignableTo. |
Thank you for your review. I'll take a look. |
|
Ah, I mistakenly tested |
@dominikh
In contrast, to resolve #1294, we want to check whether concrete type V is assignable to one of instantiation of T[A_1, ..., A_N] or not. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall the code looks fine.
I'm very torn on the overall change, however. On the one hand, this fixes some of the common false positives. On the other hand, the incomplete nature of the change means that minor changes to an interface can reintroduce a false positive.
For example, we'd be able to correctly handle type I[T any] interface { foo() T }
, but not type I[T any] interface { foo() []T }
. This might be more confusing for users, who will not understand why a minor change to the interface introduces a new false positive.
/cc @mvdan for his opinion.
Note that I don't think we can realistically solve this problem unless we reuse the whole unification step of go/types, which unfortunately isn't currently exported. I'll look into rectifying that.
unused/implements.go
Outdated
|
||
func satisfiesConstraint(t types.Type, tp *types.TypeParam) bool { | ||
bound, ok := tp.Underlying().(*types.Interface) | ||
if !ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
types.TypeParam.Underlying is guaranteed to be a *types.Interface (for valid programs, and only valid programs reach our checks), so we can drop the , ok
— I'd rather this panic if that invariant is ever violated.
unused/implements.go
Outdated
if !ok { | ||
return false | ||
} | ||
return types.Implements(t, bound) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably use types.Satisfies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, changing it into types.Satisfies()
make some tests fail and clarify that I'm misunderstanding something around here. Probably bound
should also be tp.Constraint()
or something but it sometimes returns Type
which isn't *types.Interface
so it can't obviously passed to types.Satisfies()
.
I'll investigate it and add more test cases.
unused/implements.go
Outdated
} | ||
|
||
func newMethodChecker() *methodsChecker { | ||
return &methodsChecker{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to avoid this allocation when there are no type parameters.
if types.Identical(implType, interfaceType) { | ||
return true | ||
} | ||
tp, ok := interfaceType.(*types.TypeParam) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't support methods like foo(x []T)
. We should at least have a comment here to document this limitation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still needs a comment stating that we only support trivial uses of type parameters and that this doesn't constitute full unification.
Thank you for looking into this. |
Thinking more about this, I think we should merge this PR, then improve upon it with better/full unification in a future change. |
@dominikh Please take another look. For avoiding allocation, I can't find a lightweight approach to know whether a |
@dominikh I think your analysis above is sensible. I think we should push |
FWIW, I've filed golang/go#63982 |
unused/implements.go
Outdated
typeParams map[*types.TypeParam]types.Type | ||
} | ||
|
||
func newMethodChecker() methodsChecker { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need this constructor.
unused/implements.go
Outdated
@@ -60,6 +62,7 @@ func implements(V types.Type, T *types.Interface, msV *types.MethodSet) ([]*type | |||
|
|||
// A concrete type implements T if it implements all methods of T. | |||
var sels []*types.Selection | |||
c := newMethodChecker() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can simply be var c methodsChecker
unused/implements.go
Outdated
func satisfiesConstraint(t types.Type, tp *types.TypeParam) bool { | ||
bound, ok := tp.Constraint().Underlying().(*types.Interface) | ||
if !ok { | ||
panic("unexpected failure on type assertion (types.Type to *types.Interface)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just do bound := tp.Constraint().Underlying().(*types.Interface)
— we don't need to check for ok just to manually panic — the panic we get on the failed type assertion is fine.
if types.Identical(implType, interfaceType) { | ||
return true | ||
} | ||
tp, ok := interfaceType.(*types.TypeParam) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still needs a comment stating that we only support trivial uses of type parameters and that this doesn't constitute full unification.
- remove constructor - remove manual panic - add a comment
@dominikh Thank you for your review. I fixed them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, will merge in a couple days.
Addresses #1294.
NOTE
Probably some false-positives for complex generic types still exist. And this doesn't address other similar problems such as #1440.
Though I consider this makes a improvement, feel free to reject this if you feel this incomplete fix can be harmful.