-
Notifications
You must be signed in to change notification settings - Fork 20
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
What does Breaking Changes mean? #187
Comments
Anything that's reasonably likely to cause practical problems for end users. This is a very fuzzy definition, but these notes are to help humans, so that's fine.
I don't think we should be aiming for a formal definition, with a 100% completeness: this would be less useful than what we have now. If changes like marking a function as pure are considered breaking changes, this will effectively be a copy of
No strong feelings, but given this is meant to be practical advice for humans to take in their projects, I don't really see a need.
This seems extremely unlikely, ever; bytecode will continue to change as needed for performance. |
That's exactly why binary backward compatibility (or the corresponding concept in Hack) is a thing. It is much easier to keep binary backward compatibility than source backward compatibility in other languages. |
(copy from internal post) The goal of keeping binary backward compatibility in other languages is to allow for upgrading transitive dependencies. For example, given an application A depending on library B depending on library C, if library B is updating to a binary backward compatible and source level backward incompatible version, application A must be migrated upon the new version to pass type check. However, if library C is updating to a binary backward compatible and source level backward incompatible version, application A does not have to migrate upon the new version. In Hack, the maintainer of the application A cannot simply update library C, unless the developer of library B migrate first, because there is no binary form. Something similar to binary backward compatibility in Hack is "runtime backward compatibility", if we could exclude the |
This is possible in existing codebases by generating HHIs if desireed, and using It is not enough though: changes in internals (e.g. warnings in array operations) and builtin functions also change between releases.
Narrowing of return types of functions (or in non-final classes) is not usually considered a BC break by other languages; in the case of C++, the ABI is actually weaker than the API here - a narrowed return type is not an API-breaking change, a widened is - but neither is an ABI-breaking change. The return type is not part of the mangling. Also, I am not arguing that an ABI would not be useful for Hack - just that it is impractical from an implementation side. It would require:
The last is a hard problem; the first two are extremely unlikely to ever be practical, unless Facebook stop needing to optimize HHVM for www performance and reliability. |
I think "runtime backward compatibility" is more realistic than binary, because currently all inferred type parameters do not change the runtime behavior. Reified type parameters do change the runtime behavior. Fortunately they cannot be inferred. |
I'd go with variance rules when documenting signature changes. Non-final methods are invariant on their parameters, return type, and coeffects. All final or free standing functions are contravariant on their parameters, covariant on their return type, and contravariant on their coeffects. Yes, there are indeed ways the typechecker can emit errors where it previously did not. These callsites can be restored by explicitly typing out the previously inferred types. If that type is not denotable, maybe mention it in the blog. |
It is trivial to fix errors like this in a monorepo if all the source files of all dependencies are in the same monorepo. However, it is not easy to fix an even trivial typing error in |
Would you happen to be able to give an example where project B and project C are both independently compatible with hhvm version (next). Project A depends on B, which depends on C. The error only shows up in project A, but not in a source file controlled by A? From my understanding, if C typechecks and B typechecks, wouldn't all new type errors have their primary error location in A? Edit: I misunderstood the example from the internal post. The issue is not, whose error is this to fix, but are there ways to allow incompatible changes to remain unfixed. If A doesn't know about the new signature, it can pretend to use the old one (provided they are compatible). |
Yes. The problem is not only about changes introduced by HHVM, but also changes introduced by third party libraries, for users who don't have a monorepo. |
We commonly include a Breaking Changes section in release notes. What does the terminology actually mean?
I used to think it is similar to breakages in binary backward compatibility in C++ or Java. However, we don't have a binary release form of a library.
If it means breakage at the source level backward compatibility, it could be too sensitive, and we might have missed many breaking changes.
For example, we used to assume changing a function from the
defaults
capabilities to pure capability is a backward compatible change. However the assumption is not true if we take the type checker into account:Given a library including the following function:
When changing it to
The following user code, which could type check originally, will be failed to type check any more:
Similarly, changing the return type of a function into a more specific type is also backward incompatible at source level, even though similar changes are usually considered binary backward compatible in other languages.
Key takeaways
The text was updated successfully, but these errors were encountered: