Make declarations in signatures "weakly bound" until they are used
Suppose you are the author of a library in a Backpack world, and you publish a signature package which defines the entire public facing interface of your library. The library foo
which uses of your library decides to include
the signature package for convenience, but actually only uses a small portion of the API.
Later, you make a BC-breaking change in one part of the library and release a new signature package. The library bar
which uses your library includes this NEW signature package, using a different portion of the API which was unaffected by the by the BC change.
Now, a hapless user tries to use foo
and bar
, but Backpack complains that the requirements are not compatible.
What's the problem here? The practice of writing reusable signature packages for people to use caused the requirements of foo
and bar
to become too large, since they included a lot of junk that these libraries didn't actually use. It would be far better if you could include
a signature package, but only "require" the bits of it that you actually used!
How can we achieve this?
- We augment the
ModIface
of signature merges (#10690 (closed)) to record whether or not a declaration was (transitively) used or not by some module. Used declarations must be filled, but unused ones are treated more flexibly: if they are merged with a different, incompatible but used requirement, they disappear, and we don't check if an implementing module actually implemented the declaration. (If two unused incompatible requirements are merged, we just erase the name.) - How do we compute the usage info? I think it will have to be done during shaping (which runs the renamer). We only need to annotate each declaration a signature with the transitive set of names from other signatures that it has used--this can be incrementally computed. (It's not necessary to annotate declarations in modules, since they are always assumed to use holes). Then whenever a declaration from a signature is used in a module, we mark its transitive set as used. This information can then be used later when constructing the merged
ModIface
which represents the "public requirement" of the package.
So, for example, a package containing only signatures would contain all unused declarations (however, they may start being used by a package which includes them). Any unused declaration which isn't mixed with another incompatible declaration can be imported (causing it to be used), but we will complain if you try to use a name and we can't tell which declaration to use.
(PS: another moral here, is that include
s are bad UNLESS you are including a signature package! Because an include for a concrete module is a dependency you can't override...)