Clause
← Уся документація

Перекладено з англійського оригіналу · 1e3a39c8-dirty · 2026-10-26 · Читати англійською

Семантичний аналіз

Виконується після синтаксичного аналізу в типовій компіляції та в --print-types; дії, що працюють лише із синтаксисом, його пропускають. Помилки зупиняють уражений пакет до LLVM; діагностика зберігає походження з макросів і включень, а наступні вхідні дані все одно діагностуються.

Перевірки модулів і викликів

Поведінки

Слідує поведінкам (behaviours) OTP, erl_internal:add_predefined_functions і check_behaviour з erl_lint.

Імпорти

Слідує правилам імпорту з erl_lint.

Попередньо визначені функції

Слідує модулям OTP і erl_internal:add_predefined_functions. Фронтенд додає module_info/0,1 до кожного скомпільованого модуля (після згенерованої behaviour_info/1) як функції Erlang, що повертають літеральні дані (driver/predefined, semantic/module_info); Module::predefined_ рахує ці кінцеві форми, які завжди експортовані й не потрапляють у --print-types.

Зв'язування

Кожне зв'язування має ідентичність відносно функції clause[N].local[M]. Входження — це визначення, читання або перевірки точної рівності, позначені контекстом голови/guard/тіла. Аналіз детермінований.

Обходи ітеративні з бюджетом модуля в 1 000 000 одиниць роботи або 32 на вираз, якщо це більше (згенерованим парсерам, як-от erl_parse з OTP, потрібно близько 10 на вираз). Розв'язання служб має бюджет того самого розміру; воно переглядає форми -compile і -import модуля щодо стану автоімпорту імені один раз на ім'я, а не один раз на виклик (кожна з тисяч клауз erl_parse викликає hd/1). Вичерпання або будь-яка семантична помилка очищує таблиці зв'язувань і нормалізації модуля.

Типи та специфікації

Приватний граф типів представляє всі розібрані форми типів незалежно від розкладки в runtime: одиничні значення (singletons), діапазони, контейнери, ролі полів map, добутки функцій і нерозв'язані застосування. Об'єднання сплощуються й позбуваються дублікатів; term() — верхній елемент, а none() — нижній. Значення за замовчуванням: 1 048 576 вузлів, 256 членів об'єднання, 200 000 елементів роботи на обхід оголошеного типу (розв'язання, переклад, розгортання) і 8 000 000 на прохід виведення по всій програмі; налагоджувальне збирання clau (без NDEBUG) дозволяє 16 000 і 1 000 000, щоб великі програми все одно швидко компілювалися, і може розширювати більше. Вичерпання розширює до term() з видимим прапорцем і ніколи не звужує представлення.

Оголошені метадані (-type, -opaque, -nominal, -export_type, -spec, -callback, -optional_callbacks) слідують довіднику з typespec OTP і зафіксованій поведінці erl_lint/erl_internal/erl_types:

Виведення типів

Виведення типів (inference) відокремлене від оголошених типів і ніколи не довіряє специфікаціям.

Залежні факти

semantic/types/dependent. case, if або try ... of поводиться як fun від змінних, які звужують його клаузи, застосований до них: його значення є залежним фактом, по одному функціональному типу на кожну можливу клаузу.

Домен виведення

semantic/types/lattice. Факт — це множина значень, які може мати змінна чи результат. Факти об'єднуються там, де сходиться потік керування (клаузи, гілки), і розширюються між раундами рекурсивної компоненти; кожен бюджет нижче коректно розширюється до більшої множини й ніколи не відхиляє програму. Факти друкуються як типи Erlang, категорії — за своїми вбудованими іменами.

ФактДрукОб'єднанняБюджет і розширення
Ніщоnone()Нейтральний елементФункція, яка ніколи не повертається, залишається none()
Будь-щоterm()Поглинає кожен фактdynamic() і any() — це term()
Цілі числа42, 1 | 3 | 7Об'єднання одиничних значеньПонад 8 одиничних значень стають своїм діапазоном
Діапазон цілих чисел1..10, 0..255Найменший діапазон, що містить обидваМежа, що зсунулася між раундами, переходить до наступного порогу: нижня — до 1, потім 0, потім без обмеження; верхня — до -1, потім без обмеження. Межі всередині контейнерів також розширюються там, де попередній факт має кортеж (того самого розміру й тега), map (із тими самими ключами) або список у тому самому місці
Необмежені цілі числаpos_integer() (1 і більше), non_neg_integer() (0 і більше), neg_integer() (-1 і менше), integer()Найменший інтервал, що містить обидва, друкується своєю категорією—
Числа з плаваючою комоюfloat()——
Числаnumber()Діапазон або категорія цілих чисел, об'єднані з float()Одиничні цілі числа з float() залишаються 1 | float()
Атомиok, error | ok, boolean()Об'єднання одиничних значень; рівно false і true друкуються як boolean()Понад 8 одиничних значень стають atom()
Ідентифікаториpid(), port(), reference()——
Кортежі{ok, 1}, tuple(), #point{x :: 0, y :: _}Кортежі однакового розміру, чиї перші елементи не є двома різними атомами (їхнім тегом), об'єднуються поелементно; інші залишаються окремими членамиПонад 16 елементів стають tuple(), якщо не кожен елемент відомий; понад 8 окремих форм стають tuple(). Кортеж з іменем і розміром видимого record на кортежі друкується як record
Списки[], [T], [T, ...], nonempty_improper_list(H, T), [1, a], [a, b | T]Елементи об'єднуються; [] з непорожнім списком дає, можливо, порожній; неправильні списки об'єднують голови й хвости. Списки з двох або більше відомих елементів зберігають свої позиції (нотація Clause, мова типів такої не має, елементи друкуються через | у дужках): позиційні списки однієї довжини об'єднуються позиція за позицією, інакше вони об'єднуються як прості спискиСписок із 0..1114111 (char()) друкується як string() або nonempty_string(); можливо, порожній список із _ друкується як list()
Maps#{}, #{a := 1}, #{1..17 => a}, map()Maps з однаковими ключами об'єднуються значення за значенням; maps з іншими ключами об'єднуються в одну асоціацію своїх об'єднаних ключів і значень (=>: будь-який ключ може бути відсутнім)Понад 16 ключів об'єднуються в одну асоціацію
Funsfun((term()) -> 1), fun()Funs однієї арності об'єднують свої результати; інші арності дають fun()—
Bitstrings<<_:16>>, <<_:3, _:_*2>>, binary()Менший розмір плюс кожна різниця розмірів як одиницяОснова й одиниця 0/8, 8/8, 0/1, 1/1 друкуються як binary(), nonempty_binary(), bitstring(), nonempty_bitstring()

На -O2 зі спеціалізацією типів пониження використовує ці факти для вилучення перевірок (спеціалізація); специфікації ніколи не стають фактами.

Кеш виведення

Модулі в каталогах кешу виведення можуть почати компіляцію з результатами, збереженими попередньою компіляцією: підсумками, локальними вхідними даними та фактами виразів своїх функцій. infer тримає їх незмінними в кожному проході, ніколи не обходить компоненту, всі члени якої їх повторно використовують, і обходить кожну іншу функцію як зазвичай, читаючи збережені підсумки, як і будь-які інші.

--print-types

Друкує кожен модуль пакета (у порядку вхідних даних/цілей, бібліотечні модулі після них) як вихідний код Erlang (друк вихідного коду) з тим, що знайшло виведення типів. Вивід іде в stdout і призначений для читання людиною, це не Erlang і не формат обміну. Попередження залишаються в 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

Очікування виведення

tests/fixtures/inference/*.erl записують, що виведення має знайти для кожної функції і що воно знаходить сьогодні. Кожен модуль стає CTest inference_<module> (tests/compiler/inference/expectations.py):

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

Друк типів

semantic::types::type_source(graph, type) (semantic/types/printing) подає тип із графа типів у синтаксисі типів Erlang: _ для будь-якого терму (term(), записується через TERM_SOURCE для стислості; синтаксис типів читає _ як any()), none(), атоми й цілі числа, 1..5, {ok, T}, tuple(), [T], [T, ...], #{K => V, K := V}, #r{f :: T}, <<_:B, _:_*U>>, fun((A) -> R), A | B. Fun з кількома функціональними типами друкує їх у нотації Clause, fun((1) -> one; (_) -> other): синтаксис типів Erlang не має перевантаженого типу fun, а об'єднання типів fun означає інше. Цілі числа об'єднання друкуються в порядку значень там, де стоїть його перше ціле число, послідовні — як діапазон (1 | 2 | 3 | 5 друкується як 1..3 | 5; факт зберігає одиничні значення). Попередньо визначені типи erlang втрачають свій модуль; посилання на оголошені типи залишаються іменованими. Бюджет вузлів обмежує текст; понад нього, а також глибше за 32 рівні вкладеності, замість тексту стоїть ....

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