Suggestion: Reporting unavailable modules

I think it’d be great to get a warning when referring to a module which isn’t available in the source.

E.g., adding a Phoenix route but mis-spelling the controller:

get("/hello", HeloController, :index)

I’m trying in vain to get some kind of compiler warning/error for this. mix xref isn’t doing it for me. Is there anything else?

(I think this would be a great addition to the compiler — this isn’t a static typing issue; it’s a linking issue. The compiler does return an error when a function isn’t available.)

“Aliases” (thats the proper term) are not necessarily module names. You have been told this in the other thread. Until you try to actually invoke a function from them or construct a struct, they are treatened as plain atoms.

This is a good thing.since you can use an Alias as a tag in a tagged tuple as well.

Also since you can use small caps atom for modules as well, do you want to warn for :ok as well? It probably does not exist as module.

The proper way to get such typos is through extensive testing.

xref can’t find this issue as well, because as already said, that alias is just an aotm, and only using a function in it will actually cause a reference to the other module. There is no linking in the BEAM at all. Modules are loaded when referenced for the first time at runtime. You can even unload or replace them at runtime. This is called it hot-code-reloading and one of the features that make zero-downtime-deploys possible.

3 Likes

However, in this specific case of the phoenix router the phoenix macro that uses that could indeed perform the test itself, I think that would be a worthwhile PR. :slight_smile:

2 Likes

Would that guarantee that the module is available at runtime?

1 Like

Entirely nope, the BEAM is too dynamic for that, but would help prevent the occasional misspelling (as long as it doesn’t access the module in such a way that a link is created). ^.^;

1 Like

Couldn’t compilation order cause spurious false errors/warnings?

1 Like

Only if they’d depend on each other then, which would be a cyclic dependency (I don’t think the controllers depending on the Router.Helpers makes a dependency on Router too, but it might, in which case more finagling would be needed).

1 Like

I don’t believe so. E.g., the compiler could handle this alternate API:

get("/hello", &HelloController.index/2)

As I said in another thread discussing this issue that would create compile-time dependency from router to the compiler. This is something you really don’t want to have. This is also why phoenix router has scopes - this way you don’t need to write MyApp.HelloController but just HelloController (with the MyApp part in scope declaration). This way does not create a compile-time dependency.

The Elixir compiler is very conservative when resolving inter-module dependencies and even having the module as a value inside the module body (outside of functions) will create a compile-time dependency.

Additionally controller are just plugs! The router calls just plugs, not controllers. It does not know the controller exports the index function. All the router will do, will be to call:

init_data = HelloController.init(:index)
HelloController.call(conn, init_data)

Just like for any other plug.

4 Likes

I’m skeptical! I think a compile-time dependency is appropriate here, because there’s an actual dependency between these two files. I want the compiler to do the work, not me, in every case it can.

(And this kind of dependency shouldn’t trigger a re-compile in any case because the compiled module’s public symbol table would still be up-to-date.)

A compile-time dependency is purely about compilation order and the fact that if one module is changed, the other needs to be recompiled. It has little to do with logic dependency.

And the primary reason is the second one I listed - right now the controller just calls plugs. It “knows” nothing about controllers. Making the router controller-specific would limit the features.

2 Likes