Clause
← Toda la documentación

Traducido del original en inglés · 1e3a39c8-dirty · 2026-10-26 · Leer en inglés

Análisis semántico

Se ejecuta después del análisis sintáctico en la compilación por defecto y en --print-types; las acciones solo sintácticas lo omiten. Los errores detienen el lote afectado antes de LLVM; los diagnósticos conservan los orígenes de macros/inclusiones, y las entradas posteriores se siguen diagnosticando.

Comprobaciones de módulo y de llamadas

Behaviours

Sigue los behaviours de OTP, erl_internal:add_predefined_functions y el check_behaviour de erl_lint.

Importaciones

Sigue las reglas de importación de erl_lint.

Funciones predefinidas

Sigue los módulos de OTP y erl_internal:add_predefined_functions. El frontend añade module_info/0,1 a cada módulo compilado (después de un behaviour_info/1 generado) como funciones Erlang que devuelven datos literales (driver/predefined, semantic/module_info); Module::predefined_ cuenta estas formas finales, que siempre se exportan y se omiten de --print-types.

Vinculaciones

Cada vinculación tiene una identidad relativa a la función clause[N].local[M]. Las apariciones son definiciones, lecturas o comprobaciones de igualdad exacta, etiquetadas con el contexto de cabecera/guard/cuerpo. El análisis es determinista.

Los recorridos son iterativos con un presupuesto por módulo de 1.000.000 de unidades de trabajo, o 32 por expresión cuando eso es más (los analizadores sintácticos generados, como erl_parse de OTP, necesitan unas 10 por expresión). La resolución de servicios tiene un presupuesto del mismo tamaño; examina las formas -compile e -import del módulo para conocer el estado de autoimportación de un nombre una vez por nombre, no una vez por llamada (cada una de las miles de cláusulas de erl_parse llama a hd/1). El agotamiento o cualquier error semántico vacía las tablas de vinculación y normalización del módulo.

Tipos y especificaciones

Un grafo de tipos privado representa todas las formas de tipo analizadas con independencia de la disposición en el runtime: singletons, rangos, contenedores, roles de campos de map, productos de función y aplicaciones no resueltas. Las uniones se aplanan y eliminan duplicados; term() es el máximo y none() el mínimo. Valores por defecto: 1.048.576 nodos, 256 miembros de unión, 200.000 elementos de trabajo por recorrido de un tipo declarado (resolución, traducción, expansión) y 8.000.000 por pasada de inferencia de todo el programa; una compilación de depuración de clau (sin NDEBUG) permite 16.000 y 1.000.000 para que los programas grandes sigan compilándose rápido, y puede ampliar más. El agotamiento amplía a term() con un indicador visible y nunca estrecha una representación.

Los metadatos declarados (-type, -opaque, -nominal, -export_type, -spec, -callback, -optional_callbacks) siguen la referencia de typespecs de OTP y el comportamiento fijado de erl_lint/erl_internal/erl_types:

Inferencia

La inferencia es independiente de los tipos declarados y nunca confía en las specs.

Hechos dependientes

semantic/types/dependent. Un case, if o try ... of se comporta como un fun de las variables que estrechan sus cláusulas, aplicado a ellas: su valor es un hecho dependiente, un tipo de función por cada cláusula posible.

Dominio de inferencia

semantic/types/lattice. Un hecho es un conjunto de valores que puede tener una variable o un resultado. Los hechos se unen donde confluye el flujo de control (cláusulas, ramas) y se amplían entre las rondas de una componente recursiva; cada presupuesto de abajo amplía de forma segura a un conjunto mayor, nunca rechaza un programa. Los hechos se imprimen como tipos de Erlang, y las categorías por sus nombres integrados.

HechoImpresoUniónPresupuesto y ampliación
Nadanone()IdentidadUna función que nunca retorna sigue siendo none()
Cualquier cosaterm()Absorbe todo hechodynamic() y any() son term()
Enteros42, 1 | 3 | 7Unión de singletonsMás de 8 singletons se convierten en su rango
Rango de enteros1..10, 0..255El menor rango que contiene ambosUn límite que se ha movido entre rondas pasa al siguiente umbral: uno inferior a 1, después 0, después sin límite; uno superior a -1, después sin límite. Los límites dentro de contenedores también se amplían, cuando el hecho anterior tiene una tupla (de ese tamaño y etiqueta), un map (con esas claves) o una lista en el mismo lugar
Enteros sin límitepos_integer() (1 en adelante), non_neg_integer() (0 en adelante), neg_integer() (-1 hacia abajo), integer()El menor intervalo que contiene ambos, impreso por su categoría—
Flotantesfloat()——
Númerosnumber()Un rango o categoría de enteros unido con float()Los enteros singleton con float() quedan como 1 | float()
Átomosok, error | ok, boolean()Unión de singletons; exactamente false y true se imprimen como boolean()Más de 8 singletons se convierten en atom()
Identificadorespid(), port(), reference()——
Tuplas{ok, 1}, tuple(), #point{x :: 0, y :: _}Las tuplas del mismo tamaño cuyos primeros elementos no son dos átomos distintos (su etiqueta) se unen elemento a elemento; las demás quedan como miembros separadosMás de 16 elementos se convierten en tuple() salvo que se conozcan todos los elementos; más de 8 formas separadas se convierten en tuple(). Una tupla con el nombre y el tamaño de un record de tupla visible se imprime como el record
Listas[], [T], [T, ...], nonempty_improper_list(H, T), [1, a], [a, b | T]Los elementos se unen; [] con una lista no vacía da una posiblemente vacía; las listas impropias unen cabezas y colas. Las listas de dos o más elementos conocidos conservan sus posiciones (una notación de Clause, el lenguaje de tipos no tiene ninguna; los elementos se imprimen con | entre paréntesis): las listas posicionales de una misma longitud se unen posición a posición, y si no se unen como listas simplesUna lista de 0..1114111 (char()) se imprime como string() o nonempty_string(); una lista posiblemente vacía de _ se imprime como list()
Maps#{}, #{a := 1}, #{1..17 => a}, map()Los maps con las mismas claves se unen valor a valor; los maps de otras claves se unen en una asociación de sus claves y valores unidos (=>: cualquier clave puede faltar)Más de 16 claves se unen en una asociación
Funsfun((term()) -> 1), fun()Los funs de una misma aridad unen sus resultados; otras aridades dan fun()—
Bitstrings<<_:16>>, <<_:3, _:_*2>>, binary()El tamaño menor más cada diferencia de tamaños como unidadBase y unidad 0/8, 8/8, 0/1, 1/1 se imprimen como binary(), nonempty_binary(), bitstring(), nonempty_bitstring()

En -O2 con especialización por tipos, el lowering usa estos hechos para eliminar comprobaciones (especialización); las especificaciones nunca se convierten en hechos.

Caché de inferencia

Los módulos de los directorios de la caché de inferencia pueden empezar una compilación con los resultados que conservó una compilación anterior: resúmenes, entradas locales y hechos de expresiones de sus funciones. infer los mantiene fijos en cada pasada, nunca recorre una componente cuyos miembros los reutilizan todos, y recorre cualquier otra función como de costumbre, leyendo los resúmenes conservados como cualquier otro.

--print-types

Imprime cada módulo del lote (en orden de entrada/destino, con los módulos de biblioteca después) como código fuente Erlang (impresión del código fuente) con lo que encontró la inferencia de tipos. La salida va a stdout y es legible por humanos, no es Erlang ni un formato de intercambio. Las advertencias siguen yendo a stderr.

%% module "branches" source="branches.erl" target="" declared=complete inferred=complete
-module(branches).
-export([mixed/1]).

%% inferred: mixed(1) -> 1; (_) -> 2
mixed(X) ->
    case X of
        1 ->
            1;
        _ ->
            2
    end. % $case_of_operator(X :: 1) -> 1; (X :: _) -> 2

Expectativas de inferencia

tests/fixtures/inference/*.erl registran lo que la inferencia debería encontrar para cada función y lo que encuentra hoy. Cada módulo se convierte en la prueba de CTest inference_<module> (tests/compiler/inference/expectations.py):

%% expect: sum() -> 3
%% today: sum() -> _
sum() -> 1 + 2.

Impresión de tipos

semantic::types::type_source(graph, type) (semantic/types/printing) representa un tipo del grafo de tipos en la sintaxis de tipos de Erlang: _ para cualquier término (term(), escrito mediante TERM_SOURCE por brevedad; la sintaxis de tipos lee _ como any()), none(), átomos y enteros, 1..5, {ok, T}, tuple(), [T], [T, ...], #{K => V, K := V}, #r{f :: T}, <<_:B, _:_*U>>, fun((A) -> R), A | B. Un fun de varios tipos de función los imprime en una notación de Clause, fun((1) -> one; (_) -> other): la sintaxis de tipos de Erlang no tiene un tipo fun sobrecargado, y una unión de tipos fun significa otra cosa. Los enteros de una unión se imprimen en orden de valor donde está su primer entero, y los consecutivos como un rango (1 | 2 | 3 | 5 se imprime como 1..3 | 5; el hecho conserva los singletons). Los tipos predefinidos de erlang omiten su módulo; las referencias a tipos declarados conservan su nombre. Un presupuesto de nodos acota el texto; más allá de él, y a más de 32 niveles de anidamiento, aparece ... en su lugar.

clau --print-types answer.erl client.erl
clau --print-types --project project.toml --target demo --verbose