7

What type of variable binding or variable or symbol is setq creating, when the variable hasn't been declared prior setq?

Given following source code:

;; -*- lexical-binding: t; -*-

(defun setq-x ()
  (setq x 10))

(defvar y 20)

(setq-x)

(message " variable values: x: %i, y %i\n special-var status: x: %s, y: %s"
         x
         y
         (special-variable-p 'x)
         (special-variable-p 'y))

The source code above outputs:

 variable values: x: 10, y 20
 special-var status: x: nil, y: t

I did not expect this output, and I could not find an explanation in the documentation[1].
Since setq is used so often in Elisp without prior declaration of a variable, I regard this question as somewhat important.

[1] mentioned documentation:
https://www.gnu.org/software/emacs/manual/html_node/elisp/Setting-Variables.html#index-setq
https://www.gnu.org/software/emacs/manual/html_node/elisp/Variable-Scoping.html
https://www.emacswiki.org/emacs/DynamicBindingVsLexicalBinding

3
  • See also CLTL2 about scope & extent. The rules are essentially the same for Common Lisp and Elisp (with non-nil lexical-binding), except for how specialness (aka dynamicness) is declared.
    – Drew
    Commented Apr 15, 2021 at 15:04
  • @Drew, thanks for the hint, I'm not always sure, when rules of CL apply to elisp and vice versa.
    – jue
    Commented Apr 15, 2021 at 16:27
  • @jue The rules of CL don't apply to Elisp, except where they coincide ;).
    – Basil
    Commented Apr 15, 2021 at 17:28

1 Answer 1

6

Quoth the docstring of special-variable-p:

special-variable-p is a built-in function in `src/eval.c'.

(special-variable-p SYMBOL)

  Probably introduced at or before Emacs version 24.1.

Return non-nil if SYMBOL's global binding has been declared special.
A special variable is one that will be bound dynamically, even in a
context where binding is lexical by default.

Note the subtlety in the last sentence, about how a special variable would be treated in a lexical context (such as your lexical-binding buffer).

Quoth the manual entry for special-variable-p:

 -- Function: special-variable-p symbol
     This function returns non-‘nil’ if SYMBOL is a special variable
     (i.e., it has a ‘defvar’, ‘defcustom’, or ‘defconst’ variable
     definition).  Otherwise, the return value is ‘nil’.

     Note that since this is a function, it can only return non-‘nil’
     for variables which are permanently special, but not for those that
     are only special in the current lexical scope.

This tells us that only variables which have been declared as special count as special. So, while setq will change the global value of x so long as it is a free variable, special-variable-p will return non-nil only after x is declared special.

Modifying your example a bit may help to highlight why this distinction is important:

;; -*- lexical-binding: t -*-

(defun setq-x ()
  (setq x 10))

(defvar y 20)

(setq-x)
(let ((x 20))
  (setq-x)

  (message "values   x: %s, y: %s\nspecial  x: %s, y: %s"
           x y
           (special-variable-p 'x)
           (special-variable-p 'y)))

When byte-compiled, this gives:

values   x: 20, y: 20
special  x: nil, y: t

If the first setq-x made x special, then x would have to be 10 after the second call to setq-x. Instead, x is let-bound like a local lexical variable that's in a different scope to that of setq-x.


The conclusion is that it's fine for users to set undeclared variables (usually user options defined in packages that are yet to be loaded). But Elisp libraries ought to declare their variables or those from other libraries appropriately.


What type of variable binding or variable or symbol is setq creating, when the variable hasn't been declared prior setq?

setq considers variables without a local lexical value as effectively being dynamic. So in your example, (setq x 10) is the same as (set 'x 10) (which wouldn't be the case if x were a lexical variable). You can see this towards the end of the definition of setq:

DEFUN ("setq", Fsetq, Ssetq, 0, UNEVALLED, 0,
       doc: /* Set each SYM to the value of its VAL.
The symbols SYM are variables; they are literal (not evaluated).
The values VAL are expressions; they are evaluated.
Thus, (setq x (1+ y)) sets `x' to the value of `(1+ y)'.
The second VAL is not computed until after the first SYM is set, and so on;
each VAL can use the new value of variables set earlier in the `setq'.
The return value of the `setq' form is the value of the last VAL.
usage: (setq [SYM VAL]...)  */)
  (Lisp_Object args)
{
  Lisp_Object val = args, tail = args;

  for (EMACS_INT nargs = 0; CONSP (tail); nargs += 2)
    {
      Lisp_Object sym = XCAR (tail);
      tail = XCDR (tail);
      if (!CONSP (tail))
        xsignal2 (Qwrong_number_of_arguments, Qsetq, make_fixnum (nargs + 1));
      Lisp_Object arg = XCAR (tail);
      tail = XCDR (tail);
      val = eval_sub (arg);
      /* Like for eval_sub, we do not check declared_special here since
         it's been done when let-binding.  */
      Lisp_Object lex_binding
        = ((!NILP (Vinternal_interpreter_environment) /* Mere optimization!  */
            && SYMBOLP (sym))
           ? Fassq (sym, Vinternal_interpreter_environment)
           : Qnil);
      if (!NILP (lex_binding))
        XSETCDR (lex_binding, val); /* SYM is lexically bound.  */
      else
        Fset (sym, val);        /* SYM is dynamically bound.  */
    }

  return val;
}
2
  • Thanks for the detailed explanation! So TL;DR is: x is a special var, but hasn't been declared to be one, therefore special-variable-p can not reflect that. Btw: output of (symbol-value 'x) supports that explanation.
    – jue
    Commented Apr 15, 2021 at 16:24
  • 1
    @jue Almost: x is set as if it were a special variable, but that doesn't make it special. This is why when you call (setq-x) within a context where x is let-bound, it has no effect. If x were a special variable it would have been updated by the call to (setq-x).
    – Basil
    Commented Apr 15, 2021 at 17:27

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.