Guards
Guard legality follows the pinned OTP 29 erl_internal/erl_lint rules; it is
never derived from runtime registration. Every operand is checked, including
unreachable ones. A guard succeeds only when the final value is atom true.
Control flow
,conjoins tests in order.;starts a new alternative afterfalse, a non-boolean or a reached semantic error; when all fail, the clause mismatches.- Head bindings are readable in every alternative; guards create no bindings.
- Infrastructure errors (OOM, limits, ownership) stop execution and never try another alternative.
| Operator | Semantics |
|---|---|
A andalso B | A must be boolean; false → false; otherwise returns B as any term |
A orelse B | A must be boolean; true → true; otherwise returns B |
and, or, xor | Both operands evaluated in order, both boolean |
not A | Boolean inverse |
true andalso 7 returns 7 in a body and fails as a final guard test;
(true andalso 7) =:= 7 succeeds. A reached error inside a nested expression
rejects the whole alternative: hd([]) orelse true fails, hd([]); true
succeeds. In bodies, bad strict operands raise badarg and a bad lazy left
operand raises {badarg, Value}, as in OTP.
Catalog
guards.tsv lists all 81 guard
signatures from the pinned guard_bif, new_type_test, old_type_test,
arith_op, bool_op and comp_op tables, and a test enforces exact set
equality with upstream. The audit manifest
maps each to resolver, lowering and runtime owner. All 81 are implemented (the
identity-dependent self/0, node/0,1 and native is_record/1 since plan
step 52): self() is the calling process's pid, node() and node/1 of a
pid, reference or port are nonode@nohost (there is one node; any other
node/1 argument fails the guard, badarg in a body), and is_record/1 tests
for a native record. is_record/2 with a local
native record name tests module and name; is_record/3 with an atom third
argument tests a native record's module and name; native field access fails
the guard on any mismatch.
- Type tests:
is_atom,is_integer,is_float,is_number,is_boolean,is_tuple,is_list,is_map,is_binary,is_bitstring,is_function/1,2(funs),is_pid,is_reference(pids and references);is_portis always false (no ports exist). is_integer(V, Lo, Hi)validates both bounds as integers first (elsebadarg), then tests the inclusive range; reversed bounds give false.- Comparisons
==,/=,=:=,=/=,<,=<,>,>=; arithmetic and bitwise operators; queries and conversions per terms. - Constructors and map updates in guards use the same rooted services as bodies.
- Record tests: see terms.
Resolution
- Unqualified guard BIFs honor local definitions, imports and global/selective
no_auto_import.erlang:F(...)anderlang:'op'(...)bypass shadowing but only for guard BIFs and operators. - Legacy tests (
integer/1,float/1,atom/1,record/2, ...) are valid only at the top level of a guard test (parentheses allowed). Therefloat(X)isis_float; nested or qualifiedfloat/1is conversion. Legacyrecord/2ignores suppression of its old name, but a localis_record/2blocks it. - Not allowed in guards: assignment, arbitrary local/remote/dynamic calls,
++,--,!, funs, comprehensions, control flow, record updates,record_info/2.
Oracle limitations
Installed OTP 29.1.1 crashes in SSA conversion for some unrelated modern imports behind legacy scalar aliases; Clause rejects that unauthorized owner explicitly. OTP's loader also rejects a guard with a huge literal record arity despite lint accepting it; that case has semantic evidence only.
Clause