In SNAKES, one cannot use Expression objects as input arcs annotations, moreover, all the variables used in a transition guard and output arcs should be bound through (at least) one of the input arcs. These restrictions can be explained.

When calling method t.modes(), the available modes are computed by matching the input arcs annotations with respect to the available tokens in the corresponding input places. This is a simple task when input arcs are only annotated with values, variables or tuples of such objects. But it may become impossible if expressions would be used. Indeed, imagine a place p and an arc from p to t labelled by Expression("f(x)") where f is a Python function. For each token value v in p, t.modes() would need to solve f(x)==v wrt x in order to find all possible bindings of x. This is simply not possible in general because SNAKES allow f to be an arbitrary Python function. (There's not magic: there must be a price to pay for such a level of generality.) Fortunately, this is not a limitation in practice.

So, SNAKES simply forbids expressions on input arcs. This could be relaxed a bit when, for instance, x can be known from another input arc. But this would lead to complex code and usually, there is another way to achieve the same effect. In this instance, we can bind tokens from p using an annotation Variable("y") and add "y == f(x)" in the guard of t.

Now, given a Transition instance t and a Substitution instance m, the execution of can be decomposed into several steps:

(The first and third steps are actually also performed by t.modes() to ensure it returns a mode that actually allows firing and not just a correct binding of the input variables.)

Considering that m is a dict-like object, the evaluations above are roughly equivalent to Python's eval(expr, m). If m was discovered using t.modes(), it only binds variables that appear in at least one input arc. So, if more variables are used (for instance in the guard) a NameError will occur.

This explains why it is recommended that all variables are bound through input arcs. But this is not a requirement when modes are user-provided instead of being computed using t.modes().