@440bx
Drop some pseudocode of how you picture it - a modifier on the import declaration? And who annotates all the API units (Windows, etc.) with these? Plus, as @Martin_fr said, the free can be in another scope, so the in-scope check won't always hold up.
IMHO you need something like
procedure Alloc; demands Free;
procedure Alloc; demands Free 'You need to call UnAlloc after Alloc'; // optional message
"Free" is just a token, it could be "FooBar" => the fulfiller needs to give the same token => the fulfiller must be in the same unit, or the token must be fully qualified. The token is only valid within the require/provide modifiers, and does not conflict with any other identifier used in the sources (not even the name of the function on which it is used.
"provides Free" in a different unit, would be a different token, and not match the above. It would need to be "provides AboveUnit.Free"
procedure UnAlloc; provides Free;
procedure SaveUnAlloc; provides Free;
Either of the 2 can be used...
If a message is given, it will be used if the the call is made without matching "Alloc". If no message is given dangling calls will not trigger a warning at all.
procedure Setup; demands Free forward;
procedure Setup; demands Free forward 'call teardown or otherwise Free the alloc'; demands Xyz forward;
procedure Setup; demands Free, Xyz forward; // OPTIONAL allow bundling of forwards, same as giving both individually
procedure Teardown; provides Free forward;
There can be multiple forwards. Setup could forward Free and maybe also ReleaseHandle and others.
Also, if the above signifies that "Setup" can contain a call to "Alloc" that is not freed inside setup, then the question is how many (or should count be kept)?
procedure Setup; demands Free forward 3; // has 3 calls that it does not resolve, if there are more, then they must be matched within setup / if there are less that is a warning too
A forward can be matched by an unforwarded call too. E.g. you could call "Alloc" direct, and then Teardown to release it. Or call Setup and directly Unalloc. (that could be controlled by the "as" below)
Optional, but probably would be more a source of errors than help, rename and bundle
procedure Setup; demands Free, Release forward as teardown // contains 1 call that requires Free and 1 that requires Release, and wants them fulfilled by a provision of "teardown"
// This does not work with a number after the "forward", so either tokens need to be repeated, or the number must be after each token
So if the caller of that "setup" calls UnAlloc directly, that will not fulfil it. It needs to call
procedure Teardown; provides Free, Release forward as teardown; // The list of provides must match that of the require alias. Otherwise its not valid
// That means either "forward as FOO" can have overloaded FOO for different combinations. Or giving a diff combination should be an error.
This would differ from the below, which also demands teardown, but the 2 calls could be resolved elsewhere. (teardown might not provide them)
procedure Setup; demands Free forward; demands Release forward; demands teardown;
There is on checking on matching arguments of the paired calls. They just need to be present (and present in the correct number)
The compiler would always keep count of the demands for each token, and at the end of each routine it would check that they equal the amount of forwards. (or zero if no forwards are declared).
It doesn't care which call matches with other, if there are multiple calls (as variables can have had their content changed...)
Note that any top level code (main begin /end) needs to check finalize sections. (and initialization sections may have had something too).