Clause
← Toda a documentação

Traduzido do original em inglês · 1e3a39c8-dirty · 2026-10-26 · Ler em inglês

Análise semântica

Executa-se depois da análise sintática na compilação por omissão e em --print-types; as ações só de sintaxe saltam-na. Os erros param o lote afetado antes do LLVM; os diagnósticos mantêm as origens de macros/includes, e as entradas seguintes continuam a ser diagnosticadas.

Verificações de módulos e chamadas

Behaviours

Segue os behaviours do OTP, erl_internal:add_predefined_functions e o check_behaviour do erl_lint.

Imports

Segue as regras de importação do erl_lint.

Funções predefinidas

Segue os módulos do OTP e erl_internal:add_predefined_functions. O frontend acrescenta module_info/0,1 a cada módulo compilado (depois de um behaviour_info/1 gerado) como funções Erlang que devolvem dados literais (driver/predefined, semantic/module_info); Module::predefined_ conta estas formas finais, que são sempre exportadas e ficam fora de --print-types.

Vinculações

Cada vinculação tem uma identidade relativa à função clause[N].local[M]. As ocorrências são definições, leituras ou verificações de igualdade exata, etiquetadas com o contexto cabeça/guard/corpo. A análise é determinística.

Os percursos são iterativos, com um orçamento por módulo de 1 000 000 unidades de trabalho, ou 32 por expressão quando isso for mais (os parsers gerados, como o erl_parse do OTP, precisam de cerca de 10 por expressão). A resolução de serviços tem um orçamento do mesmo tamanho; percorre as formas -compile e -import do módulo para o estado de auto-importação de um nome uma vez por nome, não uma vez por chamada (cada uma dos milhares de cláusulas do erl_parse chama hd/1). O esgotamento ou qualquer erro semântico limpa as tabelas de vinculações e de normalização do módulo.

Tipos e especificações

Um grafo de tipos privado representa todas as formas de tipo analisadas, independentemente da disposição no runtime: singletons, intervalos, contentores, papéis dos campos de maps, produtos de funções e aplicações não resolvidas. As uniões são achatadas e sem duplicados; term() é o topo e none() o fundo. Valores por omissão: 1 048 576 nós, 256 membros por união, 200 000 itens de trabalho por percurso de um tipo declarado (resolução, tradução, expansão) e 8 000 000 por passagem de inferência sobre todo o programa; uma compilação debug de clau (sem NDEBUG) permite 16 000 e 1 000 000, para que os programas grandes continuem a compilar depressa, e pode alargar mais. O esgotamento alarga para term() com uma flag visível e nunca estreita uma representação.

Os metadados declarados (-type, -opaque, -nominal, -export_type, -spec, -callback, -optional_callbacks) seguem a referência de typespecs do OTP e o comportamento fixado de erl_lint/erl_internal/erl_types:

Inferência

A inferência é separada dos tipos declarados e nunca confia nas specs.

Factos dependentes

semantic/types/dependent. Um case, if ou try ... of comporta-se como uma fun das variáveis que as suas cláusulas estreitam, aplicada a elas: o seu valor é um facto dependente, um tipo de função por cada cláusula possível.

Domínio de inferência

semantic/types/lattice. Um facto é um conjunto de valores que uma variável ou um resultado pode ter. Os factos juntam-se onde o fluxo de controlo se encontra (cláusulas, ramos) e alargam-se entre as rondas de uma componente recursiva; cada orçamento abaixo alarga de forma correta para um conjunto maior, nunca rejeita um programa. Os factos imprimem-se como tipos Erlang, as categorias pelos seus nomes built-in.

FactoImpressoJunçãoOrçamento e alargamento
Nadanone()IdentidadeUma função que nunca regressa fica none()
Qualquer coisaterm()Absorve qualquer factodynamic() e any() são term()
Inteiros42, 1 | 3 | 7União de singletonsMais de 8 singletons tornam-se o seu intervalo
Intervalo de inteiros1..10, 0..255O menor intervalo que contém ambosUm limite que se moveu entre rondas passa para o limiar seguinte: um inferior para 1, depois 0, depois ilimitado; um superior para -1, depois ilimitado. Os limites dentro de contentores também alargam, quando o facto anterior tem um tuplo (desse tamanho e com essa etiqueta), map (com essas chaves) ou lista na mesma posição
Inteiros ilimitadospos_integer() (1 e acima), non_neg_integer() (0 e acima), neg_integer() (-1 e abaixo), integer()O menor intervalo que contém ambos, impresso pela sua categoria—
Floatsfloat()——
Númerosnumber()Um intervalo ou categoria de inteiros junto com float()Inteiros singleton com float() ficam 1 | float()
Átomosok, error | ok, boolean()União de singletons; exatamente false e true imprimem boolean()Mais de 8 singletons tornam-se atom()
Identificadorespid(), port(), reference()——
Tuplos{ok, 1}, tuple(), #point{x :: 0, y :: _}Os tuplos do mesmo tamanho cujos primeiros elementos não são dois átomos diferentes (a sua etiqueta) juntam-se elemento a elemento; os outros ficam membros separadosMais de 16 elementos tornam-se tuple(), a menos que todos os elementos sejam conhecidos; mais de 8 formas separadas tornam-se tuple(). Um tuplo com o nome e o tamanho de um record em tuplo visível imprime-se como o record
Listas[], [T], [T, ...], nonempty_improper_list(H, T), [1, a], [a, b | T]Os elementos juntam-se; [] com uma lista não vazia dá uma possivelmente vazia; as listas impróprias juntam cabeças e caudas. As listas de dois ou mais elementos conhecidos mantêm as suas posições (uma notação do Clause, a linguagem de tipos não tem nenhuma, com os elementos impressos com | entre parênteses): as listas posicionais de um mesmo comprimento juntam-se posição a posição, caso contrário juntam-se como listas simplesUma lista de 0..1114111 (char()) imprime string() ou nonempty_string(); uma lista possivelmente vazia de _ imprime list()
Maps#{}, #{a := 1}, #{1..17 => a}, map()Os maps com as mesmas chaves juntam-se valor a valor; os maps de outras chaves juntam-se numa associação das suas chaves e valores juntos (=>: qualquer chave pode faltar)Mais de 16 chaves juntam-se numa associação
Funsfun((term()) -> 1), fun()As funs de uma mesma aridade juntam os seus resultados; outras aridades dão fun()—
Bitstrings<<_:16>>, <<_:3, _:_*2>>, binary()O tamanho menor mais cada diferença de tamanhos como unidadeBase e unidade 0/8, 8/8, 0/1, 1/1 imprimem binary(), nonempty_binary(), bitstring(), nonempty_bitstring()

Em -O2 com especialização de tipos, o rebaixamento usa estes factos para remover verificações (especialização); as especificações nunca se tornam factos.

Cache de inferência

Os módulos nos diretórios da cache de inferência podem começar uma compilação com os resultados que uma compilação anterior guardou: resumos, entradas locais e factos de expressões das suas funções. infer mantém-nos fixos em todas as passagens, nunca percorre uma componente cujos membros os reutilizam todos, e percorre todas as outras funções como de costume, lendo os resumos guardados como quaisquer outros.

--print-types

Imprime cada módulo do lote (pela ordem das entradas/alvos, com os módulos de biblioteca depois deles) como código-fonte Erlang (impressão do código-fonte) com o que a inferência de tipos encontrou. A saída vai para stdout e é legível por humanos, não é Erlang nem um formato de intercâmbio. Os avisos ficam em 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 inferência

tests/fixtures/inference/*.erl registam o que a inferência deve encontrar para cada função e o que encontra hoje. Cada módulo torna-se o CTest inference_<module> (tests/compiler/inference/expectations.py):

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

Impressão de tipos

semantic::types::type_source(graph, type) (semantic/types/printing) apresenta um tipo do grafo de tipos na sintaxe de tipos Erlang: _ para qualquer termo (term(), escrito por TERM_SOURCE por brevidade; a sintaxe de tipos lê _ como any()), none(), átomos e inteiros, 1..5, {ok, T}, tuple(), [T], [T, ...], #{K => V, K := V}, #r{f :: T}, <<_:B, _:_*U>>, fun((A) -> R), A | B. Uma fun com vários tipos de função imprime-os numa notação do Clause, fun((1) -> one; (_) -> other): a sintaxe de tipos Erlang não tem um tipo fun sobrecarregado, e uma união de tipos fun significa outra coisa. Os inteiros de uma união imprimem-se por ordem de valor onde está o seu primeiro inteiro, os consecutivos como um intervalo (1 | 2 | 3 | 5 imprime 1..3 | 5; o facto mantém os singletons). Os tipos predefinidos de erlang perdem o seu módulo; as referências a tipos declarados ficam com nome. Um orçamento de nós limita o texto; além dele, e abaixo de 32 níveis de aninhamento, ... toma o seu lugar.

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