7

Imagine that I have the following files in my (ridiculous) package:

File test1.el:

;;; test1.el ---                                   

;;; Code:

(defvar test-var1)

(defun test-fun1 (test)
  nil)

(require 'test2 "./test2.el)

(provide 'test1)
;;; test1.el ends here

File test2.el:

;;; test2.el ---  

;;; Code:

(defun test-fun2 ()
  (let ((test test-var1))
    (test-fun1 test)))

(provide 'test2)
;;; test2.el ends here

If then I run:

emacs -batch -f batch-byte-compile *.el

I get the following result:

Compiling .../test1.el...
Wrote .../test1.elc
Compiling .../test2.el...

In test-fun2:
test2.el:9:15:Warning: reference to free variable `test-var1'

In end of data:
test2.el:14:1:Warning: the function `test-fun1' is not known to be defined.
Wrote .../test2.elc

I understand why these warnings appear, and I understand that they are only warnings. However, it would be easy to miss a typo in a function name by dismissing all warnings of this kind.

I somehow thought that adding a (require 'test2) line in test2.el should fix it. However, in this case I get:

Compiling .../test1.el...

In toplevel form:
test1.el:10:1:Error: Recursive `require' for feature `test2'
Compiling .../test2.el...

In toplevel form:
test2.el:5:1:Error: Recursive `require' for feature `test1'

This is cryptic, because I thought that the point of require was precisely to avoid recursive loading. I assume that require is behaving like load during compilation time.

What is a good (and safe) way to get rid of these warnings?

The manual gives a work-around (I post it as a better-than-nothing answer below), but ultimately, I'd like the solution to be rather automatic (not requiring me to list all functions and variables that I will need in every file).

The ideal solution would be built-in in emacs or provided with Cask. If it doesn't exist, I will take what is available of course.

3 Answers 3

8

About require

require is not meant to avoid recusive loading, it is meant to avoid repetitive loading. So no, it does not solve you problem here.

About the problem

The right way to approach this (in my opinion) would be to avoid the mutual dependency.

The test1 file in your example has no reason to require test2. Even if that's not true for your actual package, maybe you can redesign how you're delegating code between the files. It is in general possible to avoid mutual dependency between your files.

Work arounds

  1. If the mutual dependency cannot be avoided, the manual mentions a solution. You'll have to add lines like the following for each function/variable you need.

    (declare-function test-fun1 "./test1.el")
    (defvar test-var1)
    
  2. Another option is to require the files only conditionally. Add something like this to file 1:

    (defvar test1-is-loading t)
    (unless (and (boundp 'test2-is-loading)
                 test2-is-loading)
      (require 'test2))
    

    And something like this to file 2:

    (defvar test2-is-loading t)
    (unless (and (boundp 'test1-is-loading)
                 test1-is-loading)
      (require 'test1))
    
4
  • The realistic situation is a package where the main file declares some stuff (variables such as a mode map, executable paths, customize group), requires other files providing a bunch of functions for the package, and assembles it all together (at least into a -mode function). The example is a toy example, having it use test-fun2 would not change the problem at all.
    – T. Verron
    Commented Nov 7, 2014 at 20:46
  • 1
    @T.Verron Yes, I worked on the assumption that your example wasn't completely accurate, and I offered workarounds. Still, I stand by my statement that the package can be better designed. In your realistic example, there's no reason why the file who assembles everything together should also be the one to define variables and groups. Make an extra file which contains these definitions (such as test-variables), and this file won't have to require any of the others.
    – Malabarba
    Commented Nov 7, 2014 at 21:01
  • As I mentioned in my initial answer, your first workaround works fine, but it rather tedious. The second one sounds promising, but it will be a bit more complicated: the statements will need to be wrapped in eval-when-compile's, and the packages will need to set their variable to nil at the end of the file (because all the files are compiled in a single session). It also has the benefit of showing me why exactly recursive loading is more complicated to avoid than repeated loading.
    – T. Verron
    Commented Nov 7, 2014 at 21:07
  • And thanks for the refactoring suggestion, this indeed would work.
    – T. Verron
    Commented Nov 7, 2014 at 21:08
4

Your example is weird:

  • You require test2 at the end of test1, whereas require should "always" be at the beginning of a file.
  • Your test1 does not call any test2 functions, so it doesn't need test2 to work (hence it requires it, unnecessarily), and OTOH your test2 does call test1 functions, so it does need test1, yet it fails to require it.

IOW, you have your requires backward.

4
  • In a real example, test1 would of course use the functions defined by test2, and the package would only be loaded through test1 (through autoload). Shouldn't this answer be a comment instead? It is just pointing out that my example is rather poorly chosen, and does not otherwise provide an answer to the question.
    – T. Verron
    Commented Nov 7, 2014 at 20:51
  • @T.Verron This answer answered the specific case you offered. The fact that it was a poorly chosen example is not his fault. ;-)
    – Malabarba
    Commented Nov 7, 2014 at 20:57
  • Since both answers address this refactoring point, I must assume that it is a valid point given the question. Yours had the benefit of addressing the general problems, plus the pointers in the comment for my specific case. Is this one really likely to be useful to anybody with a real usecase?
    – T. Verron
    Commented Nov 7, 2014 at 21:15
  • And with a more short-term view, had it been posted as a comment, I could have editted the question to make it (slightly) more realistic. But it is posted as an answer, so I cannot edit the question without making it a different question (different because a former answer would no longer be applicable). Imo, this answer falls precisely in the realm of comments: asking for clarifications (in this case a confirmation that the minimal example is really representative of the problem) from the asker.
    – T. Verron
    Commented Nov 7, 2014 at 21:18
3

The manual suggests adding declare-function and defvar lines.

The resulting test2 file is:

;;; test2.el ---  

;;; Code:

(declare-function test-fun1 "./test1.el")
(defvar test-var1)

(defun test-fun2 ()
  (let ((test test-var1))
    (test-fun1 test)))

(provide 'test2)
;;; test2.el ends here

However, this needs to be done for all functions and all variables defined in "parent" files.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.