Clause
← Toda a documentação

Traduzido do original em inglês · 06042fa · 2026-10-09 · Ler em inglês

Análise semântica

É executada após a análise sintática na compilação predefinida e em --print-types; as ações apenas sintáticas saltam-na. Os erros param o lote afetado antes do LLVM; os diagnósticos mantêm as origens de macros/inclusões, e as entradas seguintes continuam a ser diagnosticadas.

Verificações de módulos e chamadas

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 de 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. 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 tipos analisadas, independentemente da disposição em 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 deduplicadas; term() é o topo e none() o fundo. Predefinições: 16 384 nós, 16 membros por união, 100 000 itens de trabalho por tradução. 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.

Domínio de inferência

Decisão do plan 11 step 58A (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 são impressos como tipos Erlang, as categorias pelos seus nomes built-in.

FactoImpressoJunçãoOrçamento e alargamento
Nadanone()IdentidadeUma função que nunca retorna permanece none()
Qualquer coisaterm()Absorve todos os factosdynamic() 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
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() permanecem 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 sejam dois átomos diferentes (a sua etiqueta) juntam-se elemento a elemento; os outros permanecem 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 é impresso como o record (step 58J)
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 (step 58J; uma notação do Clause, a linguagem de tipos não tem nenhuma, elementos impressos com | entre parênteses): as listas posicionais do 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 única associação das suas chaves e valores juntos (=>: qualquer chave pode faltar, step 58J)Mais de 16 chaves juntam-se numa única associação
Funsfun((term()) -> 1), fun()As funs de uma 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()

O rebaixamento (lowering) consome estes factos. O IR gerado nunca converte um inteiro num ponteiro para o heap; cada resultado de serviço falível só é carregado no seu caminho de sucesso, e as verificações de forma dominam a extração. Política de especialização: specialization.md.

--print-types

Imprime cada módulo do lote (pela ordem das entradas/alvos, com os módulos de biblioteca a seguir) 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 o stdout e destina-se a ser lida por pessoas; não é Erlang nem um formato de intercâmbio. Os avisos permanecem no stderr.

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

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

Expectativas de inferência

tests/fixtures/inference/*.erl registam o que a inferência deveria 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 do 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 do Erlang não tem um tipo fun sobrecarregado, e uma união de tipos fun significa outra coisa. Os inteiros de uma união são impressos pela ordem dos valores, na posição do seu primeiro inteiro, e os consecutivos como intervalo (1 | 2 | 3 | 5 imprime 1..3 | 5; o facto mantém os singletons). Os tipos erlang predefinidos perdem o seu módulo; as referências a tipos declarados permanecem nomeadas. Um orçamento de nós limita o texto; para além dele, e abaixo de 32 níveis de aninhamento, ... ocupa o seu lugar.

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