3

How can eval-when-compile and eval-and-compile have different behavior if their definitions are identical?

From byte-run.el:

(defmacro eval-when-compile (&rest body)
  ;; ... documentation string omitted ...
  (declare (debug (&rest def-form)) (indent 0))
  (list 'quote (eval (cons 'progn body) lexical-binding)))

(defmacro eval-and-compile (&rest body)
  ;; ... documentation string omitted ...
  (declare (debug (&rest def-form)) (indent 0))
  (list 'quote (eval (cons 'progn body) lexical-binding)))
5

How can eval-when-compile and eval-and-compile have different behavior if their definitions are identical?

The hint is in the commentary in the definition of eval-and-compile:

;; When the byte-compiler expands code, this macro is not used, so we're
;; either about to run `body' (plain interpretation) or we're doing eager
;; macroexpansion.

Indeed, it's not the body of these macros that defines their semantics, but the environment in which they're expanded during byte-compilation. The two semantics are differentiated in byte-compile-initial-macro-environment:

byte-compile-initial-macro-environment is a variable defined in `bytecomp.el'.

Its value is shown below.

  This variable may be risky if used as a file-local variable.

The default macro-environment passed to macroexpand by the compiler.
Placing a macro here will cause a macro to have different semantics when
expanded by the compiler as when expanded by the interpreter.

Value:
((declare-function . byte-compile-macroexpand-declare-function)
 (eval-when-compile . #f(compiled-function
                         (&rest body)
                         #<bytecode 0x914a391b97fc68e>))
 (eval-and-compile . #f(compiled-function
                        (&rest body)
                        #<bytecode 0x1395261de4da407b>))
 (with-suppressed-warnings . #f(compiled-function
                                (warnings &rest body)
                                #<bytecode -0xadad30c834fbff0>)))

Here's the current environment for eval-when-compile:

(lambda (&rest body)
  (let ((result nil))
    (byte-compile-recurse-toplevel
     (macroexp-progn body)
     (lambda (form)
       ;; Insulate the following variables against changes made in the
       ;; subsidiary compilation.  This prevents spurious warning
       ;; messages: "not defined at runtime" etc.
       (let ((byte-compile-unresolved-functions
              byte-compile-unresolved-functions)
             (byte-compile-new-defuns
              byte-compile-new-defuns))
         (setf result
               (byte-compile-eval
                (byte-compile-top-level
                 (byte-compile-preprocess form)))))))
    (list 'quote result)))

And that for eval-and-compile:

(lambda (&rest body)
  (byte-compile-recurse-toplevel
   (macroexp-progn body)
   (lambda (form)
     ;; Don't compile here, since we don't know whether to compile as
     ;; byte-compile-form or byte-compile-file-form.
     (let ((expanded (macroexpand-all form macroexpand-all-environment)))
       (eval expanded lexical-binding)
       expanded))))

It is not entirely uncommon to have macros whose body does very little, but whose macroexpansion is treated specially. Another example in addition to the forms listed above is the declare form, whose body is nil but whose macroexpansion results in the setting of function properties.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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