2019-01-24 12:28:50 +00:00
|
|
|
use std::marker::PhantomData;
|
|
|
|
|
2019-01-23 20:14:13 +00:00
|
|
|
use ra_db::{LocationIntener, FileId};
|
2019-01-23 16:49:11 +00:00
|
|
|
use ra_syntax::{TreeArc, SyntaxNode, SourceFile, AstNode, ast};
|
2019-01-04 13:15:50 +00:00
|
|
|
use ra_arena::{Arena, RawId, impl_arena_id};
|
2019-01-01 20:21:16 +00:00
|
|
|
|
2019-01-08 12:57:45 +00:00
|
|
|
use crate::{
|
2019-01-24 12:28:50 +00:00
|
|
|
HirDatabase, Def, Struct, Enum, EnumVariant, Crate,
|
2019-01-11 18:02:12 +00:00
|
|
|
Module, Trait, Type, Static, Const,
|
2019-01-08 12:57:45 +00:00
|
|
|
};
|
2019-01-06 12:16:21 +00:00
|
|
|
|
2019-01-24 09:41:08 +00:00
|
|
|
#[derive(Debug, Default)]
|
|
|
|
pub struct HirInterner {
|
|
|
|
defs: LocationIntener<DefLoc, DefId>,
|
|
|
|
macros: LocationIntener<MacroCallLoc, MacroCallId>,
|
2019-01-24 10:34:41 +00:00
|
|
|
fns: LocationIntener<FunctionLoc, FunctionId>,
|
2019-01-24 09:41:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl HirInterner {
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.defs.len() + self.macros.len()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-08 23:47:12 +00:00
|
|
|
/// hir makes heavy use of ids: integer (u32) handlers to various things. You
|
2019-01-01 19:47:10 +00:00
|
|
|
/// can think of id as a pointer (but without a lifetime) or a file descriptor
|
|
|
|
/// (but for hir objects).
|
|
|
|
///
|
|
|
|
/// This module defines a bunch of ids we are using. The most important ones are
|
|
|
|
/// probably `HirFileId` and `DefId`.
|
|
|
|
|
2019-01-08 23:47:12 +00:00
|
|
|
/// Input to the analyzer is a set of files, where each file is indentified by
|
2019-01-01 19:47:10 +00:00
|
|
|
/// `FileId` and contains source code. However, another source of source code in
|
|
|
|
/// Rust are macros: each macro can be thought of as producing a "temporary
|
2019-01-08 23:47:12 +00:00
|
|
|
/// file". To assign an id to such a file, we use the id of the macro call that
|
2019-01-01 19:47:10 +00:00
|
|
|
/// produced the file. So, a `HirFileId` is either a `FileId` (source code
|
|
|
|
/// written by user), or a `MacroCallId` (source code produced by macro).
|
|
|
|
///
|
|
|
|
/// What is a `MacroCallId`? Simplifying, it's a `HirFileId` of a file containin
|
|
|
|
/// the call plus the offset of the macro call in the file. Note that this is a
|
2019-01-08 23:47:12 +00:00
|
|
|
/// recursive definition! However, the size_of of `HirFileId` is finite
|
2019-01-01 19:47:10 +00:00
|
|
|
/// (because everything bottoms out at the real `FileId`) and small
|
2019-01-08 23:47:12 +00:00
|
|
|
/// (`MacroCallId` uses the location interner).
|
2019-01-01 19:47:10 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
2019-01-01 20:21:16 +00:00
|
|
|
pub struct HirFileId(HirFileIdRepr);
|
|
|
|
|
|
|
|
impl HirFileId {
|
2019-01-02 13:00:01 +00:00
|
|
|
/// For macro-expansion files, returns the file original source file the
|
|
|
|
/// expansionoriginated from.
|
2019-01-07 23:30:49 +00:00
|
|
|
pub fn original_file(self, db: &impl HirDatabase) -> FileId {
|
2019-01-01 20:21:16 +00:00
|
|
|
match self.0 {
|
|
|
|
HirFileIdRepr::File(file_id) => file_id,
|
|
|
|
HirFileIdRepr::Macro(macro_call_id) => {
|
|
|
|
let loc = macro_call_id.loc(db);
|
2019-01-02 13:00:35 +00:00
|
|
|
loc.source_item_id.file_id.original_file(db)
|
2019-01-01 20:21:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn as_original_file(self) -> FileId {
|
|
|
|
match self.0 {
|
|
|
|
HirFileIdRepr::File(file_id) => file_id,
|
|
|
|
HirFileIdRepr::Macro(_r) => panic!("macro generated file: {:?}", self),
|
|
|
|
}
|
|
|
|
}
|
2019-01-02 13:00:01 +00:00
|
|
|
|
2019-01-03 18:28:35 +00:00
|
|
|
pub(crate) fn as_macro_call_id(self) -> Option<MacroCallId> {
|
|
|
|
match self.0 {
|
|
|
|
HirFileIdRepr::Macro(it) => Some(it),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-08 08:28:42 +00:00
|
|
|
pub(crate) fn hir_source_file(
|
|
|
|
db: &impl HirDatabase,
|
|
|
|
file_id: HirFileId,
|
2019-01-11 16:59:06 +00:00
|
|
|
) -> TreeArc<SourceFile> {
|
2019-01-01 20:21:16 +00:00
|
|
|
match file_id.0 {
|
|
|
|
HirFileIdRepr::File(file_id) => db.source_file(file_id),
|
|
|
|
HirFileIdRepr::Macro(m) => {
|
|
|
|
if let Some(exp) = db.expand_macro_invocation(m) {
|
|
|
|
return exp.file();
|
|
|
|
}
|
|
|
|
// returning an empty string looks fishy...
|
2019-01-08 08:28:42 +00:00
|
|
|
SourceFile::parse("")
|
2019-01-01 20:21:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
enum HirFileIdRepr {
|
2019-01-01 19:47:10 +00:00
|
|
|
File(FileId),
|
|
|
|
Macro(MacroCallId),
|
|
|
|
}
|
|
|
|
|
2019-01-01 20:21:16 +00:00
|
|
|
impl From<FileId> for HirFileId {
|
|
|
|
fn from(file_id: FileId) -> HirFileId {
|
|
|
|
HirFileId(HirFileIdRepr::File(file_id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<MacroCallId> for HirFileId {
|
|
|
|
fn from(macro_call_id: MacroCallId) -> HirFileId {
|
|
|
|
HirFileId(HirFileIdRepr::Macro(macro_call_id))
|
2019-01-01 19:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-01 21:30:00 +00:00
|
|
|
|
2019-01-01 21:37:36 +00:00
|
|
|
/// `MacroCallId` identifies a particular macro invocation, like
|
|
|
|
/// `println!("Hello, {}", world)`.
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
2019-01-08 12:53:32 +00:00
|
|
|
pub struct MacroCallId(RawId);
|
|
|
|
impl_arena_id!(MacroCallId);
|
2019-01-01 21:37:36 +00:00
|
|
|
|
2019-01-23 20:14:13 +00:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
2019-01-01 21:37:36 +00:00
|
|
|
pub struct MacroCallLoc {
|
2019-01-23 20:14:13 +00:00
|
|
|
pub(crate) module: Module,
|
2019-01-01 21:37:36 +00:00
|
|
|
pub(crate) source_item_id: SourceItemId,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MacroCallId {
|
2019-01-24 09:41:08 +00:00
|
|
|
pub(crate) fn loc(self, db: &impl AsRef<HirInterner>) -> MacroCallLoc {
|
|
|
|
db.as_ref().macros.id2loc(self)
|
2019-01-01 21:37:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MacroCallLoc {
|
|
|
|
#[allow(unused)]
|
2019-01-24 09:41:08 +00:00
|
|
|
pub(crate) fn id(&self, db: &impl AsRef<HirInterner>) -> MacroCallId {
|
|
|
|
db.as_ref().macros.loc2id(&self)
|
2019-01-01 21:37:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-24 12:28:50 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
|
|
pub struct ItemLoc<N: AstNode> {
|
|
|
|
pub(crate) module: Module,
|
|
|
|
raw: SourceItemId,
|
|
|
|
_ty: PhantomData<N>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<N: AstNode> ItemLoc<N> {
|
|
|
|
pub(crate) fn from_ast(
|
|
|
|
db: &impl HirDatabase,
|
|
|
|
module: Module,
|
|
|
|
file_id: HirFileId,
|
|
|
|
ast: &N,
|
|
|
|
) -> ItemLoc<N> {
|
|
|
|
let items = db.file_items(file_id);
|
|
|
|
let raw = SourceItemId {
|
|
|
|
file_id,
|
|
|
|
item_id: Some(items.id_of(file_id, ast.syntax())),
|
|
|
|
};
|
|
|
|
ItemLoc {
|
|
|
|
module,
|
|
|
|
raw,
|
|
|
|
_ty: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn source(&self, db: &impl HirDatabase) -> (HirFileId, TreeArc<N>) {
|
|
|
|
let syntax = db.file_item(self.raw);
|
|
|
|
let ast = N::cast(&syntax)
|
|
|
|
.unwrap_or_else(|| panic!("invalid ItemLoc: {:?}", self.raw))
|
|
|
|
.to_owned();
|
|
|
|
(self.raw.file_id, ast)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<N: AstNode> Clone for ItemLoc<N> {
|
|
|
|
fn clone(&self) -> ItemLoc<N> {
|
|
|
|
ItemLoc {
|
|
|
|
module: self.module,
|
|
|
|
raw: self.raw,
|
|
|
|
_ty: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-24 10:34:41 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub struct FunctionId(RawId);
|
|
|
|
impl_arena_id!(FunctionId);
|
|
|
|
|
2019-01-24 12:28:50 +00:00
|
|
|
pub(crate) type FunctionLoc = ItemLoc<ast::FnDef>;
|
2019-01-24 10:34:41 +00:00
|
|
|
|
|
|
|
impl FunctionId {
|
|
|
|
pub(crate) fn loc(self, db: &impl AsRef<HirInterner>) -> FunctionLoc {
|
|
|
|
db.as_ref().fns.id2loc(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FunctionLoc {
|
|
|
|
pub(crate) fn id(&self, db: &impl AsRef<HirInterner>) -> FunctionId {
|
|
|
|
db.as_ref().fns.loc2id(&self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-01 21:30:00 +00:00
|
|
|
/// Def's are a core concept of hir. A `Def` is an Item (function, module, etc)
|
|
|
|
/// in a specific module.
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
2019-01-08 12:53:32 +00:00
|
|
|
pub struct DefId(RawId);
|
|
|
|
impl_arena_id!(DefId);
|
2019-01-01 21:30:00 +00:00
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
|
|
pub struct DefLoc {
|
|
|
|
pub(crate) kind: DefKind,
|
2019-01-23 20:14:13 +00:00
|
|
|
pub(crate) module: Module,
|
2019-01-01 21:30:00 +00:00
|
|
|
pub(crate) source_item_id: SourceItemId,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
|
|
pub(crate) enum DefKind {
|
|
|
|
Struct,
|
|
|
|
Enum,
|
2019-01-08 15:01:19 +00:00
|
|
|
EnumVariant,
|
2019-01-11 18:02:12 +00:00
|
|
|
Const,
|
|
|
|
Static,
|
|
|
|
Trait,
|
|
|
|
Type,
|
2019-01-01 21:30:00 +00:00
|
|
|
Item,
|
|
|
|
|
2019-01-13 10:58:41 +00:00
|
|
|
/// The constructor of a struct. E.g. if we have `struct Foo(usize)`, the
|
|
|
|
/// name `Foo` needs to resolve to different types depending on whether we
|
|
|
|
/// are in the types or values namespace: As a type, `Foo` of course refers
|
|
|
|
/// to the struct `Foo`; as a value, `Foo` is a callable type with signature
|
|
|
|
/// `(usize) -> Foo`. The cleanest approach to handle this seems to be to
|
|
|
|
/// have different defs in the two namespaces.
|
|
|
|
///
|
|
|
|
/// rustc does the same; note that it even creates a struct constructor if
|
|
|
|
/// the struct isn't a tuple struct (see `CtorKind::Fictive` in rustc).
|
2019-01-01 21:30:00 +00:00
|
|
|
StructCtor,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DefId {
|
2019-01-24 09:41:08 +00:00
|
|
|
pub(crate) fn loc(self, db: &impl AsRef<HirInterner>) -> DefLoc {
|
|
|
|
db.as_ref().defs.id2loc(self)
|
2019-01-01 21:30:00 +00:00
|
|
|
}
|
|
|
|
|
2019-01-15 15:50:16 +00:00
|
|
|
pub fn resolve(self, db: &impl HirDatabase) -> Def {
|
2019-01-01 21:30:00 +00:00
|
|
|
let loc = self.loc(db);
|
2019-01-15 15:50:16 +00:00
|
|
|
match loc.kind {
|
2019-01-01 21:30:00 +00:00
|
|
|
DefKind::Struct => {
|
|
|
|
let struct_def = Struct::new(self);
|
|
|
|
Def::Struct(struct_def)
|
|
|
|
}
|
2019-01-08 15:01:19 +00:00
|
|
|
DefKind::Enum => Def::Enum(Enum::new(self)),
|
|
|
|
DefKind::EnumVariant => Def::EnumVariant(EnumVariant::new(self)),
|
2019-01-11 18:02:12 +00:00
|
|
|
DefKind::Const => {
|
|
|
|
let def = Const::new(self);
|
|
|
|
Def::Const(def)
|
|
|
|
}
|
|
|
|
DefKind::Static => {
|
|
|
|
let def = Static::new(self);
|
|
|
|
Def::Static(def)
|
|
|
|
}
|
|
|
|
DefKind::Trait => {
|
|
|
|
let def = Trait::new(self);
|
|
|
|
Def::Trait(def)
|
|
|
|
}
|
|
|
|
DefKind::Type => {
|
|
|
|
let def = Type::new(self);
|
|
|
|
Def::Type(def)
|
|
|
|
}
|
|
|
|
|
2019-01-01 21:30:00 +00:00
|
|
|
DefKind::StructCtor => Def::Item,
|
|
|
|
DefKind::Item => Def::Item,
|
2019-01-15 15:50:16 +00:00
|
|
|
}
|
2019-01-01 21:30:00 +00:00
|
|
|
}
|
|
|
|
|
2019-01-11 16:59:06 +00:00
|
|
|
pub(crate) fn source(self, db: &impl HirDatabase) -> (HirFileId, TreeArc<SyntaxNode>) {
|
2019-01-07 23:30:49 +00:00
|
|
|
let loc = self.loc(db);
|
|
|
|
let syntax = db.file_item(loc.source_item_id);
|
|
|
|
(loc.source_item_id.file_id, syntax)
|
|
|
|
}
|
|
|
|
|
2019-01-01 21:30:00 +00:00
|
|
|
/// For a module, returns that module; for any other def, returns the containing module.
|
2019-01-15 16:18:52 +00:00
|
|
|
pub fn module(self, db: &impl HirDatabase) -> Module {
|
2019-01-23 20:14:13 +00:00
|
|
|
self.loc(db).module
|
2019-01-01 21:30:00 +00:00
|
|
|
}
|
2018-12-28 13:34:00 +00:00
|
|
|
|
|
|
|
/// Returns the containing crate.
|
2019-01-15 16:18:52 +00:00
|
|
|
pub fn krate(&self, db: &impl HirDatabase) -> Option<Crate> {
|
|
|
|
self.module(db).krate(db)
|
2018-12-28 13:34:00 +00:00
|
|
|
}
|
2019-01-01 21:30:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl DefLoc {
|
2019-01-24 09:41:08 +00:00
|
|
|
pub(crate) fn id(&self, db: &impl AsRef<HirInterner>) -> DefId {
|
|
|
|
db.as_ref().defs.loc2id(&self)
|
2019-01-01 21:30:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Identifier of item within a specific file. This is stable over reparses, so
|
|
|
|
/// it's OK to use it as a salsa key/value.
|
2019-01-04 13:15:50 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
pub struct SourceFileItemId(RawId);
|
|
|
|
impl_arena_id!(SourceFileItemId);
|
2019-01-01 21:30:00 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub struct SourceItemId {
|
|
|
|
pub(crate) file_id: HirFileId,
|
|
|
|
/// None for the whole file.
|
|
|
|
pub(crate) item_id: Option<SourceFileItemId>,
|
|
|
|
}
|
|
|
|
|
2019-01-08 23:47:12 +00:00
|
|
|
/// Maps items' `SyntaxNode`s to `SourceFileItemId`s and back.
|
2019-01-01 21:30:00 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
pub struct SourceFileItems {
|
|
|
|
file_id: HirFileId,
|
2019-01-11 16:59:06 +00:00
|
|
|
arena: Arena<SourceFileItemId, TreeArc<SyntaxNode>>,
|
2019-01-01 21:30:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SourceFileItems {
|
2019-01-08 08:28:42 +00:00
|
|
|
pub(crate) fn new(file_id: HirFileId, source_file: &SourceFile) -> SourceFileItems {
|
2019-01-01 21:30:00 +00:00
|
|
|
let mut res = SourceFileItems {
|
|
|
|
file_id,
|
|
|
|
arena: Arena::default(),
|
|
|
|
};
|
|
|
|
res.init(source_file);
|
|
|
|
res
|
|
|
|
}
|
|
|
|
|
2019-01-08 08:28:42 +00:00
|
|
|
fn init(&mut self, source_file: &SourceFile) {
|
2019-01-08 22:57:40 +00:00
|
|
|
// By walking the tree in bread-first order we make sure that parents
|
|
|
|
// get lower ids then children. That is, addding a new child does not
|
|
|
|
// change parent's id. This means that, say, adding a new function to a
|
|
|
|
// trait does not chage ids of top-level items, which helps caching.
|
|
|
|
bfs(source_file.syntax(), |it| {
|
2019-01-08 15:01:19 +00:00
|
|
|
if let Some(enum_variant) = ast::EnumVariant::cast(it) {
|
|
|
|
self.alloc(enum_variant.syntax().to_owned());
|
|
|
|
} else if let Some(module_item) = ast::ModuleItem::cast(it) {
|
2019-01-08 08:28:42 +00:00
|
|
|
self.alloc(module_item.syntax().to_owned());
|
2019-01-01 21:30:00 +00:00
|
|
|
} else if let Some(macro_call) = ast::MacroCall::cast(it) {
|
2019-01-08 08:28:42 +00:00
|
|
|
self.alloc(macro_call.syntax().to_owned());
|
2019-01-01 21:30:00 +00:00
|
|
|
}
|
2019-01-08 22:57:40 +00:00
|
|
|
})
|
2019-01-01 21:30:00 +00:00
|
|
|
}
|
|
|
|
|
2019-01-11 16:59:06 +00:00
|
|
|
fn alloc(&mut self, item: TreeArc<SyntaxNode>) -> SourceFileItemId {
|
2019-01-01 21:30:00 +00:00
|
|
|
self.arena.alloc(item)
|
|
|
|
}
|
2019-01-08 08:28:42 +00:00
|
|
|
pub(crate) fn id_of(&self, file_id: HirFileId, item: &SyntaxNode) -> SourceFileItemId {
|
2019-01-01 21:30:00 +00:00
|
|
|
assert_eq!(
|
|
|
|
self.file_id, file_id,
|
|
|
|
"SourceFileItems: wrong file, expected {:?}, got {:?}",
|
|
|
|
self.file_id, file_id
|
|
|
|
);
|
|
|
|
self.id_of_unchecked(item)
|
|
|
|
}
|
2019-01-08 08:28:42 +00:00
|
|
|
pub(crate) fn id_of_unchecked(&self, item: &SyntaxNode) -> SourceFileItemId {
|
|
|
|
if let Some((id, _)) = self.arena.iter().find(|(_id, i)| *i == item) {
|
2019-01-01 21:30:00 +00:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
// This should not happen. Let's try to give a sensible diagnostics.
|
|
|
|
if let Some((id, i)) = self.arena.iter().find(|(_id, i)| i.range() == item.range()) {
|
|
|
|
// FIXME(#288): whyyy are we getting here?
|
|
|
|
log::error!(
|
|
|
|
"unequal syntax nodes with the same range:\n{:?}\n{:?}",
|
|
|
|
item,
|
|
|
|
i
|
|
|
|
);
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
panic!(
|
|
|
|
"Can't find {:?} in SourceFileItems:\n{:?}",
|
|
|
|
item,
|
|
|
|
self.arena.iter().map(|(_id, i)| i).collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
pub fn id_of_source_file(&self) -> SourceFileItemId {
|
|
|
|
let (id, _syntax) = self.arena.iter().next().unwrap();
|
|
|
|
id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::Index<SourceFileItemId> for SourceFileItems {
|
|
|
|
type Output = SyntaxNode;
|
|
|
|
fn index(&self, idx: SourceFileItemId) -> &SyntaxNode {
|
|
|
|
&self.arena[idx]
|
|
|
|
}
|
|
|
|
}
|
2019-01-08 22:57:40 +00:00
|
|
|
|
|
|
|
/// Walks the subtree in bfs order, calling `f` for each node.
|
|
|
|
fn bfs(node: &SyntaxNode, mut f: impl FnMut(&SyntaxNode)) {
|
|
|
|
let mut curr_layer = vec![node];
|
|
|
|
let mut next_layer = vec![];
|
|
|
|
while !curr_layer.is_empty() {
|
|
|
|
curr_layer.drain(..).for_each(|node| {
|
|
|
|
next_layer.extend(node.children());
|
|
|
|
f(node);
|
|
|
|
});
|
|
|
|
std::mem::swap(&mut curr_layer, &mut next_layer);
|
|
|
|
}
|
|
|
|
}
|