Клуб чтения Rust Source | Выпуск 1: Разрешение имен

задняя часть Rust Примечания

Примечание: это не письменный отчет онлайн-салона, а мой собственный отчет об исследовании после встречи.

Введение

Недавно Rust официально запустил Клуб чтения исходного кода Rust, см.Rust Code Reading Club.

Цель этого мероприятия — привлечь больше людей к участию в разработке компилятора Rust. Исходный код компилятора Rust поясняет сам руководитель языковой рабочей группы Нико.

В этом событии не будет повторов, только участие в реальном времени.

Условия участия:

  1. понимать английский
  2. Некоторое знание Rust, не обязательно все уголки Rust
  3. Базовые знания компилятора не обязательно требуются, но чтениеRustc Dev guide (Китайская версия также нуждается в вашем вкладе:GitHub.com/ржавчина C/…)

Действия Этапа 1:

Подготовка перед участием:

  1. Прочитать книгу "мозг программистов"
  2. Прочитайте руководство разработчика Rustc, чтобы понять процесс компиляции Rust.
  3. читатьcompiler/rustc_resolve/srcДокументация, связанная с библиотекой

Rust Feishu Group Клуб чтения Rust Source

В ответ на официальное мероприятие я также планирую организовывать онлайн-салон чтения исходного кода каждую субботу или воскресенье в китайском сообществе Feishu Rust.

Содержание заключается в том, чтобы следить за официальным содержанием и учиться вместе.

И будет записывать еженедельное исследование исходного кода, выводить текст поведения и делиться им со всеми.

Как присоединиться к китайскому сообществу Feishu Rust?

Готов к работе

Книга-рекомендация "Мозг программиста"

Rust официально рекомендует книгу «Мозг программиста» вофициальный сайт МэннингаЭлектронная версия книги доступна бесплатно. Если вы хотите перевести название книги, думаю, подойдет название «Программирование мозга»? Я слышал, что эта книга была представлена ​​отечественными издательствами.

Книга разделена на четыре части:

  1. Как лучше читать код
  2. Думая о коде
  3. написать лучший код
  4. О совместной работе над кодом

Что делает эту книгу особенной, так это то, что в ней представлены связи между различными типами кодов и когнитивным мышлением мозга, а не просто список методов.

Поскольку Rust официально рекомендует эту книгу, это означает, что ее содержание по-прежнему имеет некоторую ценность, и вы можете пойти и прочитать ее, если вам интересно.

О трех видах неприятностей в процессе программирования

В этом выпуске представлены три типа проблем, с которыми сталкиваются разработчики в процессе программирования, упомянутые в первой главе этой книги:

  1. Недостаток знаний (Недостаток знаний, соответствующий долговременной памяти, LTM). Относится к проблемам, вызванным незнанием базовых языков программирования и неспособностью разработчиков использовать или понимать базовый синтаксис.
  2. Недостаток информации (Недостаток информации, соответствующий кратковременной памяти, кратковременной памяти, СТМ). Относится к проблеме, вызванной незнанием информации о проблемном домене, с которой имеет дело программа.
  3. Недостаток вычислительной мощности (Недостаток вычислительной мощности, соответствующей оперативной памяти, оперативной памяти, WM). Это относится к проблемам, вызванным отсутствием у разработчиков возможности обрабатывать весь процесс выполнения программирования.

Эти три типа проблем не только мешают разработчикам писать новые программы, но также мешают разработчикам читать существующие коды.

Поэтому, когда мы читаем исходный код, написанный другими, мы должны убедиться, что у нас есть предварительное дополнение к знаниям, недостающим для этих трех типов задач.

Мои привычки чтения исходного кода

Я читаю исходный код так же, как и читал, от общей структуры к деталям.

Сначала убедитесь, что выrustc_resolveКонтекстная информация этой библиотеки понимается, то есть второй тип проблем в трех типах проблем в процессе программирования, упомянутых выше, необходимо дополнить информацией. Новичкам, не знакомым с Rust, следует избегать проблем первого и третьего типа. Наиболее распространенной проблемой при чтении исходного кода Rust в целом является второй тип проблем, непонимание информации, с которой программа имеет дело в предметной области.

Официально рекомендуемый метод чтения

существуетОфициальный исходный код Rustc читайте в первом выпуске Slides, рекомендуется использовать широкий-глубокий-широкий (Broad - deep - broad) метода чтения по трем абзацам.

Конкретно:

  1. широкий(Broad): понять модуль в целом.
  2. глубокий(Deep): сосредоточьтесь на функции или небольшой области (которая вас интересует или вызывает вопросы).
  3. широкий(Broad): вернуться ко всему модулю.

Выполните несколько раундов в соответствии с тремя вышеуказанными методами чтения абзаца, и весь код будет прочитан.

Архитектура компилятора Rustc

существуетRustc Dev GuideОбщая архитектура компилятора Rust (Rustc) описана в .

Архитектура компилятора Rustc отличается от традиционной архитектуры компилятора. Традиционная архитектура компиляторана основе обхода (pass-based), архитектура компилятора RustПо запросу(demand-driven) и разработан.

Архитектура компилятора на основе обхода

Так называемый обход (Pass), то есть для кода /AST Сканировать и обрабатывать.

Ранние компиляторы обычноSingle PassДа и позжеMulti Pass, и делится на интерфейс компиляции и серверную часть. Интерфейс отвечает за генерациюAST, а серверная часть используется для генерации машинного кода. Каждый шаг процесса компиляции абстрагируется какPass, имя впервые было даноLLVMПринято, а затем распространено на всю область принципов компиляции.

Траверсы делятся на две категории:

  • Обход анализа, отвечающий за сбор информации для использования другими проходами, помощь в отладке или визуализации программ.
  • Преобразование (transform) обход, используется для изменения потока данных или потока управления программой, например оптимизация и т.п.

Эти два типа процессов обхода также соответствуют двум основным этапам компилятора: этапу анализа и этапу синтеза. Первый создает промежуточное представление из данного исходного текста, а второй создает эквивалентную объектную программу из промежуточного представления.

Интерфейс компилятора обычно соответствует фазе анализа, а серверная часть компилятора соответствует фазе синтеза.

Внешний интерфейс компилятора включает следующие части:

  1. лексический анализатор
  2. парсер
  3. Семантический анализатор
  4. Генератор промежуточного кода
  5. оптимизатор кода

Генерация объектного кода выполняется серверной частью.

На этапах лексического анализа, синтаксического анализа и семантического анализа компилятор будет создавать и поддерживать важную структуру данных для отслеживания семантики переменных, то есть он будет хранить связанную информацию и информацию о связывании имен и т. д., называемую таблицей символов (Symbol Table). Он используется во время генерации промежуточного кода и генерации объектного кода.

Традиционные архитектуры компиляторов на основе обхода, вероятно, таковы.

Архитектура компилятора, управляемая по запросу

Процесс выполнения компилятора Rust:

  • rustcкоманда для выполнения компиляции
  • rustc_driverдля разбора аргументов командной строки соответствующая конфигурация сборки задокументирована вrustc_interface::Config
  • rustc_lexerИспользуется для лексического анализа для вывода текста исходного кода в виде лексического потока (Token Stream )
  • rustc_parseПодготовьтесь к следующему этапу процесса компиляции. Содержит часть лексического анализа, через встроенный StringBufferСтруктура проверяет текстовую строку и символизирует строку. Символизация – этоString interningметод для хранения неизменяемой копии значения строки.
  • rustc_parseДругая часть - синтаксический анализ, который использует метод рекурсивного спуска (сверху вниз) для анализа, преобразуя лексический поток в абстрактное синтаксическое дерево (AST). Точка входаrustc_parse::parser::ParserструктурныйParser::parse_crate_mod()иParser::parse_mod()Ассоциативный метод. Точка входа разрешения внешнего модуляrustc_expand::module::parse_external_mod. Точка входа парсера макросовParser::parse_nonterminal().
  • расширение макросов,ASTПроверка правильности, разрешение имен и ранняя проверка выполняются на этапах лексического анализа и синтаксического анализа процесса компиляции.
  • После этогоAST Преобразовать в HIR, использовать HIRВывод типа](как stc-dev-expensive.rust-wolf.org/type-infer E…impl `процесс в паре с каждой ссылкой на трейт) ипроверка типов(процесс преобразования типов).
  • Впоследствии,будетHIRПонижен до промежуточного промежуточного представителя (MIR). В процессе также построенTHIR, который является более обессахаренным HIR.THIR (Typed HIR)Используется для шаблонной и исчерпывающей проверки. Перевести в MIRчем HIRболее удобный.
  • MIRиспользуется дляодолжить чек, который в основном представляет собой граф потока управления (CFG). также ,MIRТакже используется для оптимизации, инкрементной компиляции, проверки Unsafe Rust UB и т. д.
  • Наконец, генерация кода (Codegen). будетMIRПеревести вLLVM IR,ПотомLLVM IRПерейти к LLVMСгенерируйте целевой машинный код.

Еще одна вещь, о которой следует знать, это то, что многие значения в компилятореinternиз. Это оптимизация производительности и памяти, которую мы называемArenaЗначение размещается в специальном распределителе.

В компиляторе Rust основные этапы описанного выше процесса организованы в виде набора запросов, которые вызывают друг друга.

Компилятор Rust использует систему запросов (Query System), а не компилятор обхода (архитектура компилятора на основе обхода) большинства учебников по принципам компиляции. Rust использует систему запросов для реализации инкрементной компиляции, то есть компиляции по требованию.

Компилятор Rust изначально не был реализован на основе системы запросов, поэтому весь компилятор все еще находится в процессе преобразования в систему запросов, а весь описанный выше процесс компиляции будет преобразован в систему запросов. Но по состоянию на ноябрь 2021 года в настоящее время только HIRприбыть LLVM IRЭтот процесс основан на запросе.

Структура исходного кода компилятора

Сам языковой проект Rust состоит из трех основных каталогов:

  • compiler/, включая исходный кодrustc. он состоит из многихcrateсостав, этиcrateВместе они составляют компилятор.
  • library/, который содержит стандартную библиотеку (core, alloc, std, proc_macro, test) и среда выполнения Rust (backtrace, rtstartup, lang_start).
  • src/,Включатьrustdoc,clippy,cargo, исходный код систем сборки, языковая документация и т. д.

Долженcompiler/Все названия упаковочной коробки начинаются сrustc_*. Это около 50 взаимозависимыхcrateколлекции разного размера. иrustc crateявляется фактическим двоичным файлом (т.е.mainфункция); в дополнение к вызовуrustc_driver crateКроме того, он фактически ничего не делает.

Почему компилятор Rust так сильно отличаетсяcrate, в основном с учетом следующих двух факторов:

  1. Легко организовать код. Компилятор представляет собой огромную кодовую базу, разделенную на несколько частей.crate, больше способствует организации.
  2. Ускорить время компиляции. несколькоcrateПодходит для инкрементной и параллельной компиляции.

Но поскольку система запросовrustc_middleопределено в , и многие другиеcrateвсе зависит от него, и он огромен, что приводит к длительному времени компиляции. Но работа по его разделению не так проста.

Вершина всего дерева зависимостей компилятораrustc_interfaceиrustc_driverящик.rustc_interfaceпредставляет собой нестабильную оболочку системы запросов, которая помогает управлять различными этапами компиляции.

Запрос: компиляция драйвера по запросу

Что такое запрос? Например, есть запрос под названиемtype_of(def_id), пока определеннаяItemизdef-id(значение индекса определяется идентификаторомrustc_middle/src/hir/def_id.rs), вы можете получитьItemтип. Выполнение запроса кэшируется, что также является механизмом инкрементной компиляции.

let ty = tcx.type_of(some_def_id);

Однако если запроса нет в кеше, компилятор попытается найти подходящийпровайдер. Поставщик — это функция, которая была определена и связана где-то в компиляторе и содержит код для вычисления результата запроса.

Общая структура инкрементных вычислений по требованию также является производной от системы запросов компилятора Rust.Salsa. ты можешь пройтиSalsa BOOKУзнайте больше о том, как работает система запросов.

Чтение исходного кода: компонент разрешения именrustc_resolve

Содержание первого выпуска чтения исходного кода сосредоточено наrustc_resolveБиблиотеки, связанные с разрешением имен.

После предыдущего понимания архитектуры компилятора Rust мы знаем, чтоrustc_resolveРазрешение имен происходит на этапе синтаксического анализа и служит для создания окончательного абстрактного синтаксического дерева, поэтому эта библиотека не использует систему запросов.

Это также является причиной того, что эта библиотека указана в первом выпуске чтения исходного кода, Если она не появится, это будет связано с относительно сложной системой запросов.

crateЗдесь строятся модули, пути к макросам, импорты модулей, выражения, типы, паттерны, метки (label) и время жизни решаются здесь

Разрешение имен в зависимости от типа (методы, поля, ассоциации) происходит вrustc_typeckначальство.

Разрешение имен в Rust

Изучив информацию, связанную с разрешением имен, я узнал, что компилятор Rust был представлен в 2016 году.RFC 1560для улучшения процесса разрешения имен.

До этого разрешение имен обрабатывалось компилятором на ранней стадии, после того как AST был понижен до HIR. AST будет пройден три раза, первый проход используется для построения简化图(reduce_graph), второй проход для разрешения имен и третий проход для проверки неиспользуемых имен. Упрощенная диаграмма — это запись всех определений и импортов в программе.

RFC 1560 делит разрешение имен на две фазы: первая фаза происходит одновременно с расширением макроса и разрешает импорт для определения сопоставления имени с определением в области действия. Второй этап — поиск определения по имени на всей карте. Целью этого является развязка.

В настоящее время реализован RFC 1560, при расширении макроса не выполняется разрешение полного имени, разрешаются только импорт и макросы. При построении всего AST выполняется разрешение полного имени, чтобы разрешить все имена во всем ящике.

Давайте посмотрим пример:


#![allow(unused)]
fn main() {
    type x = u32;
    let x: x = 1;
    let y: x = 2;
}

Вышеприведенный код допустим для компиляции. вxТо есть имя типа, а также имя переменной. Как Rust выполняет разрешение имен, чтобы разрешить сосуществование двух идентификаторов с одинаковыми именами?

Потому что у Rust разные пространства имен. Различные типы символов существуют в разных пространствах имен, например, типы и переменные не конфликтуют. Каждое пространство имен будет иметь свое собственное независимоеrib(Концепции абстрактных областей, введенные внутри компилятора, такие как привязка let, область определения фигурных скобок, область определения макроса и т. д., являются стеком rib ).

Теперь мы используем официально рекомендуемый метод чтения из трех абзацев для чтения исходного кода этой библиотеки.

Общая модульная структура rustc_resolve

включено в чтениеrustc_resolveКогда дело доходит до этой библиотеки, я начинаю с ее документации. ОдинcrateДокументация может очень четко показать этоcrateобщая структура.

doc.rust-wolf.org/stable/вы достаточно жестоки…

модуль

  • build_reduced_graphПосле получения фрагмента AST из макроса код в этом модуле помогает интегрировать этот фрагмент в уже частично построенную структуру модуля.
  • check_unused, как следует из названия, обнаруживает неиспользуемые структуры, перечисления и функции.
  • def_collector, создайте DefId (идентификационный идентификатор определения) для узлов AST
  • diagnostics, диагностическая информация об отказе
  • imports, методы и структуры, связанные с импортом пакетов и разборов
  • late, «позднее разрешение» — это процесс разрешения по большинству имен, кроме импорта и макроса. Он запускается, когда крейт полностью развернут и модульная структура полностью построена. Таким образом, он просто проходит через крейт и анализирует все выражения, типы и т. д. Почему нет соответсвияearly, потому что он рассеивается наbuild_reduced_graph.rs,macros.rsиimports.rsсередина.
  • macros, пакет методов и структур, связанных с разбором макросов

структура

тип ошибки

  • AmbiguityError, ошибка неоднозначности
  • BindingError, ошибка привязки
  • PrivacyError, ошибка видимости
  • UseError, используйте ошибку

тип данных

  • DeriveData, Получить связанные данные
  • ExpandHasher, развернуть Хашер
  • ModuleData, данные узла в дереве модулей
  • ExternPreludeEnty, работа с Extern, связанными с Prelude
  • NameBinding, записывает значения, типы или определения модулей, которые могут быть закрытыми
  • UsePlacementFinder, использовать связанные

пространство имен и область действия

  • PerNS, отдельные структуры для каждого пространства имен, вспомогательные типы
  • ParentScope, который записывает происхождение посетителя области
  • Segment, сегмент пути отображается минимально

Связанный с парсером

  • ResolverОсновной тип парсера
  • ResolverArenas, предоставляя память для других частей ящика, модель арены

перечислить

Не перечисленные здесь, некоторые типы перечисления похожи на структурную классификацию.

Traits

  • ToNameBinding, используемый для преобразования ссылок areans в ссылки NameBinding

функция

некоторые вспомогательные функции

введите псевдоним

Некоторые псевдонимы типов задокументированы

зависит от ящика

существуетrustc_resolveизCargo.tomlНекоторые зависимости можно увидеть вcrate:

  • rustc_ast , который определяет структуру данных AST, используемую внутри Rust.
  • rustc_arean, внутренний глобальный пул памяти компилятора, который используется для выделения памяти, и жизненный цикл выделенной памяти'tcx
  • rustc_middle, основная библиотека компилятора Rust, содержит общие определения типов, используемые в других библиотеках.
  • rustc_attr, что связано со встроенными свойствами компилятора
  • rustc_data_structures, который определяет многие структуры данных, используемые внутри компилятора, включая некоторые потокобезопасные структуры данных, необходимые для параллельной компиляции.
  • rustc_errors, который определяет утилиты, обычно используемые компиляторами для сообщения об ошибках.
  • rustc_expand, для расширения макроса.
  • rustc_feature, который определяет ворота функций в компиляторе
  • rustc_hir, который определяет типы данных, связанные с HIR.
  • rustc_index, правильноusizeОболочка NewType для внутренней индексации компилятора.
  • rustc_metadata, некоторые ссылки на метаинформацию о статических и динамических библиотеках Rust
  • rustc_query_system, система запросов Rust
  • rustc_session, обработка ошибок в процессе компиляции компилятора связана со встроенным lint
  • rustc_span, который определяет типы данных, связанные с расположением исходного кода, а также информацию, связанную с гигиеной макросов.

Выше приведено лишь перечисление некоторых основных зависимостей. На сегодняшний день (2021.11.13) библиотека разрешения имен также присоединилась к системе запросов.

Далее мы смотрим наlib.rsЧто определено в .

Видно, что вlib.rsВ основном те, которые определены в приведенной выше документации, используются для структур или типов перечисления, используемых в процессе разрешения имен.

Вот несколько наиболее простых для понимания типов:

ScopeТип перечисления:

// 用于查找名称的特定作用域,只能用于 early 解析过程,比如 导入 和 宏,而不能用于 late 解析。
/// A specific scope in which a name can be looked up.
/// This enum is currently used only for early resolution (imports and macros),
/// but not for late resolution yet.
#[derive(Clone, Copy)]
enum Scope<'a> {
    DeriveHelpers(LocalExpnId),
    DeriveHelpersCompat,
    MacroRules(MacroRulesScopeRef<'a>),
    CrateRoot,
    // The node ID is for reporting the `PROC_MACRO_DERIVE_RESOLUTION_FALLBACK`
    // lint if it should be reported.
    Module(Module<'a>, Option<NodeId>),
    RegisteredAttrs,
    MacroUsePrelude,
    BuiltinAttrs,
    ExternPrelude,
    ToolPrelude,
    StdLibPrelude,
    BuiltinTypes,
}

SegmentСтруктура:

// path 的最小化呈现 : 段
// 比如  std::sync::Arc  这就是一个 path,其中 `::` 分开的就是段
/// A minimal representation of a path segment. We use this in resolve because we synthesize 'path
/// segments' which don't have the rest of an AST or HIR `PathSegment`.
#[derive(Clone, Copy, Debug)]
pub struct Segment {
    ident: Ident,
    id: Option<NodeId>,
    /// Signals whether this `PathSegment` has generic arguments. Used to avoid providing
    /// nonsensical suggestions.
    has_generic_args: bool,
}

**LexicalScopeBindingперечислить:**

// Item,整个块中可见
// Res,只在定义的地方可见
/// An intermediate resolution result.
///
/// This refers to the thing referred by a name. The difference between `Res` and `Item` is that
/// items are visible in their whole block, while `Res`es only from the place they are defined
/// forward.
#[derive(Debug)]
enum LexicalScopeBinding<'a> {
    Item(&'a NameBinding<'a>),
    Res(Res),
}

ModuleKindперечислить

#[derive(Debug)]
enum ModuleKind {
    // 比较有意思的是,我们发现内部模块的分类,还有一种是 匿名模块,一个 block 就是一个匿名模块
    /// An anonymous module; e.g., just a block.
    ///
    /// ```
    /// fn main() {
    ///     fn f() {} // (1)
    ///     { // This is an anonymous module
    ///         f(); // This resolves to (2) as we are inside the block.
    ///         fn f() {} // (2)
    ///     }
    ///     f(); // Resolves to (1)
    /// }
    /// ```
    Block(NodeId),
    /// Any module with a name.
    ///
    /// This could be:
    ///
    /// * A normal module – either `mod from_file;` or `mod from_block { }` –
    ///   or the crate root (which is conceptually a top-level module).
    ///   Note that the crate root's [name][Self::name] will be [`kw::Empty`].
    /// * A trait or an enum (it implicitly contains associated types, methods and variant
    ///   constructors).
    Def(DefKind, DefId, Symbol),
}

AmbiguityKindперечислить

// 歧义类型
#[derive(Clone, Copy, PartialEq, Debug)]
enum AmbiguityKind {
    Import,  //  多个导入源
    BuiltinAttr, // 内建属性命名冲突
    DeriveHelper, //  derive 内命名冲突
    MacroRulesVsModularized, //   宏名和非宏名冲突
    GlobVsOuter, 
    GlobVsGlob,
    GlobVsExpanded,
    MoreExpandedVsOuter,
}

Resolver<'a'>структура

// 这是主要用于解析的结构体,这是一个很大的结构体,包含了名称解析过程需要的数据信息
/// The main resolver class.
///
/// This is the visitor that walks the whole crate.
pub struct Resolver<'a> {
    session: &'a Session,

    definitions: Definitions,

    graph_root: Module<'a>,

    prelude: Option<Module<'a>>,
    extern_prelude: FxHashMap<Ident, ExternPreludeEntry<'a>>,
    // ...
}

// 用于 Resolver 库里的内存分配
pub struct ResolverArenas<'a> {
    modules: TypedArena<ModuleData<'a>>,
    local_modules: RefCell<Vec<Module<'a>>>,
    imports: TypedArena<Import<'a>>,
    name_resolutions: TypedArena<RefCell<NameResolution<'a>>>,
    ast_paths: TypedArena<ast::Path>,
    dropless: DroplessArena,
}

Далее идут некоторые функции, в том числеreport_errors / report_conflict / add_suggestion_for_rename_of_useИ некоторые другие функции для диагностической информации компилятора.

сосредоточиться на проблеме

Теперь у нас есть достаточное и систематическое понимание предыстории функции разрешения имен. Давайте посмотрим на некоторые детали кода.

Согласно официальному предложению прочитать исходный код, этот шаг должен бытьDeep, сосредоточив внимание на некоторых интересных или сомнительных функциях.

Меня больше интересует, как Rustc проверяет неиспользуемые переменные, так что давайте сосредоточимсяcheck_unused.rsсоответствующие функции в модуле.

В комментарии к модулю говорится, что проверка неиспользуемого импорта — это трехэтапный процесс:

первый шаг:UnusedImportCheckVisitorпройти AST, чтобы найтиUseTreeвсе неиспользованные импорты внутри и регистрируйте ихuseгруппировка иNodeIdИнформация.

Для неиспользуемых методов признаков, то вrustc_typeck/check_unused.rsрегистрироваться.

Мы уже знаем из предыдущей справочной информации,check_unusedПроисходит при третьем обходе AST.После первых двух обходовUseTree, просто пройдиUnused NodeIdТолько что:

struct UnusedImport<'a> {
    use_tree: &'a ast::UseTree,
    use_tree_id: ast::NodeId,
    item_span: Span,
    unused: FxHashSet<ast::NodeId>,  // 内部的 快速 HashSet 存储 NodeId 信息
}

impl<'a> UnusedImport<'a> {
    fn add(&mut self, id: ast::NodeId) {
        self.unused.insert(id);
    }
}

struct UnusedImportCheckVisitor<'a, 'b> {
    r: &'a mut Resolver<'b>,
    /// All the (so far) unused imports, grouped path list
    unused_imports: NodeMap<UnusedImport<'a>>,
    base_use_tree: Option<&'a ast::UseTree>,
    base_id: ast::NodeId,
    item_span: Span,
}

impl<'a, 'b> UnusedImportCheckVisitor<'a, 'b> {
    // We have information about whether `use` (import) items are actually
    // used now. If an import is not used at all, we signal a lint error.
    fn check_import(&mut self, id: ast::NodeId) {
        /* do something */
    }
    
}

// 实现 rustc_ast 中 定义 的 Visitor trait, 这是访问者模式在 Rust 编译器中的应用
// Visitor trait 中定义了 AST Node的访问钩子方法,这样具体的访问者就可以实现 Visitor 的特定方法来进行具体的访问
// 这里具体的访问者就是 UnusedImportCheckVisitor
impl<'a, 'b> Visitor<'a> for UnusedImportCheckVisitor<'a, 'b> {
      fn visit_item(&mut self, item: &'a ast::Item) { /* do something */ }
      fn visit_use_tree(&mut self, use_tree: &'a ast::UseTree, id: ast::NodeId, nested: bool) { /* do something */ }
}

Шаг 2:calc_unused_spans, просмотрите собранные данные на предыдущем шагеNodeIdСвязанныйSpan

fn calc_unused_spans(
    unused_import: &UnusedImport<'_>,
    use_tree: &ast::UseTree,
    use_tree_id: ast::NodeId,
) -> UnusedSpanResult {
    /* do something */
    match use_tree.kind {
        ast::UseTreeKind::Simple(..) | ast::UseTreeKind::Glob => { /* do something */ }
        ast::UseTreeKind::Nested(ref nested) => {/* do something */}
    }
    /* do something */
}

третий шаг:check_crate, который выдает диагностику на основе сгенерированных данных

impl Resolver<'_> {
    // 为 Resolver 实现 check_unused 方法
    crate fn check_unused(&mut self, krate: &ast::Crate) {
        /* do something */
        // 检查导入源
        for import in self.potentially_unused_imports.iter() {
            match import.kind {
                ImportKind::MacroUse => { /* do something */ }
                ImportKind::ExternCrate { .. } =>  { /* do something */ }
            }
        }
        let mut visitor = UnusedImportCheckVisitor {
            r: self,
            unused_imports: Default::default(),
            base_use_tree: None,
            base_id: ast::DUMMY_NODE_ID,
            item_span: DUMMY_SP,
        };
        visit::walk_crate(&mut visitor, krate);
        for unused in visitor.unused_imports.values() {
             let mut fixes = Vec::new(); // 为 cargo fix 记录
             /* do something */
             // 计算 unused 位置信息
             let mut spans = match calc_unused_spans(unused, unused.use_tree, unused.use_tree_id) {
             	/* do something */
             }
             /* do something */
             // 发出诊断消息
             visitor.r.lint_buffer.buffer_lint_with_diagnostic(
                UNUSED_IMPORTS,
                unused.use_tree_id,
                ms,
                &msg,
                BuiltinLintDiagnostics::UnusedImports(fix_msg.into(), fixes),
            );
        }
    }
}

Читая эту часть кода, мы, вероятно, понимаемrustc_resolveОрганизация библиотеки:

  • lib.rsопределить основныеResolverСвязанные типы и методы
  • в различныхResolverРеализуйте специальные методы синтаксического анализа в функциональных модулях, таких какcheck_unused

вернуться к общему модулю

Затем мы возвращаемся к общему модулю, чтобы понять другие части кода.

Мы знаем, что первый обход AST строит сокращенный граф (reduced graph), то этот процесс должен соответствовать build_reduced_graph.rs модуль.

Мы видим, что модуль импортируетrustc_ast / rustc_expand/ rustc_data_structures::sync::Lrc (等价于 Arc)/ rustc_hir::def_idи другие связанные компоненты, возможно, что это связано с расширением макросов, а также поддерживает параллельную компиляцию.

impl<'a> Resolver<'a> {
    crate fn define<T>(&mut self, parent: Module<'a>, ident: Ident, ns: Namespace, def: T) where
        T: ToNameBinding<'a>,
    {
        let binding = def.to_name_binding(self.arenas);
        let key = self.new_key(ident, ns);
        // https://github.com/rust-lang/rust/blob/master/compiler/rustc_resolve/src/imports.rs#L490
        // try_define 定义于 imports 模块,解析导入的时候用于检查绑定的名称
        if let Err(old_binding) = self.try_define(parent, key, binding) {
            // 如果命名有冲突,这里会调用 report_conflict 来发出错误报告
            self.report_conflict(parent, ident, ns, old_binding, &binding);
        }
    }
    fn get_nearest_non_block_module(&mut self, mut def_id: DefId) -> Module<'a>  {/* do something */}
    crate fn get_module(&mut self, def_id: DefId) -> Option<Module<'a>>  {/* do something */}
    crate fn expn_def_scope(&mut self, expn_id: ExpnId) -> Module<'a>  {/* do something */}
    crate fn build_reduced_graph(
        &mut self,
        fragment: &AstFragment,
        parent_scope: ParentScope<'a>,
    ) -> MacroRulesScopeRef<'a>  {/* do something */}
    
}

Реализовано то, что нужно для построения упрощенной схемыResolverсопутствующие методы. Мы не будем рассматривать конкретные детали, просто поймем общий процесс.

Суммировать

Деятельность официального клуба по чтению исходного кода направлена ​​на то, чтобы побудить разработчиков компилятора Rust активно вносить свой вклад в компилятор Rust. Впрочем, для конкретного процесса чтения исходного кода догадка будет не слишком подробной, и есть еще много вещей, которые нужно понять в частном порядке.

Эта статья распространяется как отчет о чтении и изучении исходного кода и направлена ​​​​на привлечение других.Если в тексте есть ошибки, обратная связь приветствуется.

Ссылка на ссылку