Callees that only partially implement an interface (they are embedded in another type that completes the interface) cannot be devirtualized.
e.g., consider:
func foo(r io.ReadCloser) {
b := make([]byte, 64)
r.Read(b)
}
func main() {
b := make([]byte, 64)
r := bytes.NewReader(b)
rc := io.NopCloser(r)
foo(rc)
}
io.nopCloser embeds Reader, so the PGO profile will report a call from foo -> bytes.Reader.Read. Devirtualization will consider this candidate, determine that bytes.Reader does not implement io.ReadCloser and decide that it must not be valid, but it is valid because of the wrapper type not visible from the profile.
Devirtualization could be more lenient and only type-check the single method that is called rather than the entire interface, though this risks more false positives in ambiguous situations.
cc @cherrymui @aclements
Callees that only partially implement an interface (they are embedded in another type that completes the interface) cannot be devirtualized.
e.g., consider:
io.nopCloserembedsReader, so the PGO profile will report a call fromfoo -> bytes.Reader.Read. Devirtualization will consider this candidate, determine thatbytes.Readerdoes not implementio.ReadCloserand decide that it must not be valid, but it is valid because of the wrapper type not visible from the profile.Devirtualization could be more lenient and only type-check the single method that is called rather than the entire interface, though this risks more false positives in ambiguous situations.
cc @cherrymui @aclements