Validating a function call during compilation and aborting if a certain condition is not met

Hello everyone, I’m trying to achieve the following through:

Given a certain module with some implemented public functions (let’s call it API), I want to be able to check — during compilation — 1) what are the arguments being passed and 2) if the arguments don’t meet certain conditions, then I want to abort the compilation and inform the user of the problem:

  1. Checking the arguments: I’ve been looking at some compile-time hooks like @before_compile, @after_compile and so on. My best bet right now is @on_definition, but this hook allows the validation of certain functions defined inside a module and doesn’t check function calls. Basically I want to do something like this:
def client_app_function, do: API.some_function([a: 1, b: 2])

### During Compilation ###   
Check if the arguments are valid, obeying several rules (if there's any missing, if a certain one is not valid for that config, ...)
If yes, then abort compilation and inform of the error and reason
If not, proceed

In a certain way this would be like getting some kind of static checking applied to a given project domain. I want the compiler to be aware of this domain and to check if the calls are valid when compared against certain rules.

I’ve thought about adding a compiler to the mix.exs compilers list, based on this topic. However, I’m not sure if a Mix.Task could be used to check every function call from AP that’s being made by a given application that uses API as its dependency? Maybe using Property Checking?

  1. Aborting the compilation. I’ve been trying to find if there’s any specific function to achieve this, but I can’t seem to find anything. I’ve experimented with throwing exceptions and this seems to be okayish from what I’ve gathered.

I’m not sure if I’m explaining things well enough, my concept is still not set in stone.

First lets be sure I do understand you correctly. You maintain the module Foo, and another guy has the module Bar which uses functions from Foo, und you want to check if he calls your functions correctly, whenever he calls something from your module?

The only thing I am aware of, that could do this at least at a certain amount is to use dialyzer. Nothing else.

If you really want to use a hook, the only thing that were slightly usable is to register @before_compile in Bar to Foo. Your “checker” will then get the full module Bar and as such has the most possible context it will ever able to get without parsing every single file and the all the dependencies itself.

And now let me explain, why your rule checking is not possible easily (and will need to reimplement dialyzer at some point). Lets say your rule is, that Foo.do/1 needs to be called with an :ok tuple everytime. And now you see the following code:

bar = Map.fetch(%{a: :b}, :a)
Foo.do(bar)

Would your checker accept this? What would it do in case of Map.fetch(%{a: :b}, :b)? What would it do, if the map is not statically known at compile time?

Personally I would get rid of your module as soon as it fails compilation the first time, but I (or dialyzer) do know it better.

1 Like

Sorry for the late answer @NobbZ, your post told me what I later came to understand :slight_smile: Indeed what I was trying to achieve was not possible or simply to much effort