Earlier quoted context omitted.
Adding conditions to the type signature would be an ABI breaking change in C++ and have nasty interactions with templates. In general though, the compiler can't optimize across the translation unit boundary without something like LTO. The code for the callee might have already been generated by the time the caller sees that the precondition is statically satisfied.
My suggestion was for C where types are for example not encoded in the name, so I thought it only matters for type checking and optimization. > In general though, the compiler can't optimize across the translation unit boundary Which is why I would put it in the function signature, so it is available in both translation units. Making the code match the function signature is currently generally the responsibility of t…
So these declarations might coexist without issue even though they have different signatures:
extern int foo(a, b); // in include/lib.h
int foo(int a, int b); // in src/foo.h
whereas this would be incompatible const int foo(int a, int b); // "nearly" compatible
If you attach things to the prototype, then you need to sort out the compatibility rules. If contract_assume(a > 0) changes the type, the extern shouldn't be compatible. This is frequently used to allow linking against libraries compatible with older language standards while allowing newer code to benefit from newer standards like C99, C11, or C23.The C23 committee ran into this issue when introducing attributes. Their solution was just exclude attributes from the signature and say they're always compatible:
Although semantically attached to a function type, the attributes described are not part of the prototype of such a function, and redeclarations and conversions that drop such an attribute are valid and constitute compatible types.