Implement super let.

This commit is contained in:
Mara Bos 2025-03-27 18:29:58 +01:00
parent 9e14530c7c
commit 3123df8ef0
13 changed files with 126 additions and 48 deletions

View File

@ -1169,6 +1169,7 @@ pub enum MacStmtStyle {
#[derive(Clone, Encodable, Decodable, Debug)]
pub struct Local {
pub id: NodeId,
pub super_: Option<Span>,
pub pat: P<Pat>,
pub ty: Option<P<Ty>>,
pub kind: LocalKind,
@ -3926,7 +3927,7 @@ mod size_asserts {
static_assert_size!(Item, 144);
static_assert_size!(ItemKind, 80);
static_assert_size!(LitKind, 24);
static_assert_size!(Local, 80);
static_assert_size!(Local, 96);
static_assert_size!(MetaItemLit, 40);
static_assert_size!(Param, 40);
static_assert_size!(Pat, 72);

View File

@ -704,7 +704,8 @@ fn walk_parenthesized_parameter_data<T: MutVisitor>(vis: &mut T, args: &mut Pare
}
fn walk_local<T: MutVisitor>(vis: &mut T, local: &mut P<Local>) {
let Local { id, pat, ty, kind, span, colon_sp, attrs, tokens } = local.deref_mut();
let Local { id, super_, pat, ty, kind, span, colon_sp, attrs, tokens } = local.deref_mut();
visit_opt(super_, |sp| vis.visit_span(sp));
vis.visit_id(id);
visit_attrs(vis, attrs);
vis.visit_pat(pat);

View File

@ -323,7 +323,7 @@ pub fn walk_crate<'a, V: Visitor<'a>>(visitor: &mut V, krate: &'a Crate) -> V::R
}
pub fn walk_local<'a, V: Visitor<'a>>(visitor: &mut V, local: &'a Local) -> V::Result {
let Local { id: _, pat, ty, kind, span: _, colon_sp: _, attrs, tokens: _ } = local;
let Local { id: _, super_: _, pat, ty, kind, span: _, colon_sp: _, attrs, tokens: _ } = local;
walk_list!(visitor, visit_attribute, attrs);
try_visit!(visitor.visit_pat(pat));
visit_opt!(visitor, visit_ty, ty);

View File

@ -95,6 +95,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
fn lower_local(&mut self, l: &Local) -> &'hir hir::LetStmt<'hir> {
// Let statements are allowed to have impl trait in bindings.
let super_ = l.super_;
let ty = l.ty.as_ref().map(|t| {
self.lower_ty(t, self.impl_trait_in_bindings_ctxt(ImplTraitPosition::Variable))
});
@ -109,7 +110,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
let span = self.lower_span(l.span);
let source = hir::LocalSource::Normal;
self.lower_attrs(hir_id, &l.attrs, l.span);
self.arena.alloc(hir::LetStmt { hir_id, ty, pat, init, els, span, source })
self.arena.alloc(hir::LetStmt { hir_id, super_, ty, pat, init, els, span, source })
}
fn lower_block_check_mode(&mut self, b: &BlockCheckMode) -> hir::BlockCheckMode {

View File

@ -2223,6 +2223,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
self.attrs.insert(hir_id.local_id, a);
}
let local = hir::LetStmt {
super_: None,
hir_id,
init,
pat,

View File

@ -1336,6 +1336,9 @@ impl<'a> State<'a> {
self.print_outer_attributes(&loc.attrs);
self.space_if_not_bol();
self.ibox(INDENT_UNIT);
if loc.super_.is_some() {
self.word_nbsp("let");
}
self.word_nbsp("let");
self.ibox(INDENT_UNIT);

View File

@ -230,6 +230,7 @@ impl<'a> ExtCtxt<'a> {
self.pat_ident(sp, ident)
};
let local = P(ast::Local {
super_: None,
pat,
ty,
id: ast::DUMMY_NODE_ID,
@ -245,6 +246,7 @@ impl<'a> ExtCtxt<'a> {
/// Generates `let _: Type;`, which is usually used for type assertions.
pub fn stmt_let_type_only(&self, span: Span, ty: P<ast::Ty>) -> ast::Stmt {
let local = P(ast::Local {
super_: None,
pat: self.pat_wild(span),
ty: Some(ty),
id: ast::DUMMY_NODE_ID,

View File

@ -1817,6 +1817,8 @@ pub enum StmtKind<'hir> {
/// Represents a `let` statement (i.e., `let <pat>:<ty> = <init>;`).
#[derive(Debug, Clone, Copy, HashStable_Generic)]
pub struct LetStmt<'hir> {
/// Span of `super` in `super let`.
pub super_: Option<Span>,
pub pat: &'hir Pat<'hir>,
/// Type annotation, if any (otherwise the type will be inferred).
pub ty: Option<&'hir Ty<'hir>>,
@ -4850,7 +4852,7 @@ mod size_asserts {
static_assert_size!(ImplItemKind<'_>, 40);
static_assert_size!(Item<'_>, 88);
static_assert_size!(ItemKind<'_>, 64);
static_assert_size!(LetStmt<'_>, 64);
static_assert_size!(LetStmt<'_>, 72);
static_assert_size!(Param<'_>, 32);
static_assert_size!(Pat<'_>, 72);
static_assert_size!(Path<'_>, 40);

View File

@ -8,6 +8,7 @@
use std::mem;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{self, Visitor};
@ -44,6 +45,8 @@ struct ScopeResolutionVisitor<'tcx> {
scope_tree: ScopeTree,
cx: Context,
extended_super_lets: FxHashMap<hir::ItemLocalId, Option<Scope>>,
}
/// Records the lifetime of a local variable as `cx.var_parent`
@ -214,18 +217,29 @@ fn resolve_stmt<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, stmt: &'tcx hi
let stmt_id = stmt.hir_id.local_id;
debug!("resolve_stmt(stmt.id={:?})", stmt_id);
// Every statement will clean up the temporaries created during
// execution of that statement. Therefore each statement has an
// associated destruction scope that represents the scope of the
// statement plus its destructors, and thus the scope for which
// regions referenced by the destructors need to survive.
if let hir::StmtKind::Let(LetStmt { super_: Some(_), .. }) = stmt.kind {
// `super let` statement does not start a new scope, such that
//
// { super let x = identity(&temp()); &x }.method();
//
// behaves exactly as
//
// (&identity(&temp()).method();
intravisit::walk_stmt(visitor, stmt);
} else {
// Every statement will clean up the temporaries created during
// execution of that statement. Therefore each statement has an
// associated destruction scope that represents the scope of the
// statement plus its destructors, and thus the scope for which
// regions referenced by the destructors need to survive.
let prev_parent = visitor.cx.parent;
visitor.enter_node_scope_with_dtor(stmt_id, true);
let prev_parent = visitor.cx.parent;
visitor.enter_node_scope_with_dtor(stmt_id, true);
intravisit::walk_stmt(visitor, stmt);
intravisit::walk_stmt(visitor, stmt);
visitor.cx.parent = prev_parent;
visitor.cx.parent = prev_parent;
}
}
fn resolve_expr<'tcx>(
@ -485,10 +499,9 @@ fn resolve_local<'tcx>(
visitor: &mut ScopeResolutionVisitor<'tcx>,
pat: Option<&'tcx hir::Pat<'tcx>>,
init: Option<&'tcx hir::Expr<'tcx>>,
super_let: bool,
) {
debug!("resolve_local(pat={:?}, init={:?})", pat, init);
let blk_scope = visitor.cx.var_parent;
debug!("resolve_local(pat={:?}, init={:?}, super_let={:?})", pat, init, super_let);
// As an exception to the normal rules governing temporary
// lifetimes, initializers in a let have a temporary lifetime
@ -546,14 +559,50 @@ fn resolve_local<'tcx>(
// A, but the inner rvalues `a()` and `b()` have an extended lifetime
// due to rule C.
if super_let {
if let Some(scope) = visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) {
// This expression was lifetime-extended by a parent let binding. E.g.
//
// let a = {
// super let b = temp();
// &b
// };
//
// (Which needs to behave exactly as: let a = &temp();)
//
// Processing of `let a` will have already decided to extend the lifetime of this
// `super let` to its own var_scope. We use that scope.
visitor.cx.var_parent = scope;
} else {
// This `super let` is not subject to lifetime extension from a parent let binding. E.g.
//
// identity({ super let x = temp(); &x }).method();
//
// (Which needs to behave exactly as: identity(&temp()).method();)
//
// Iterate up to the enclosing destruction scope to find the same scope that will also
// be used for the result of the block itself.
while let Some(s) = visitor.cx.var_parent {
let parent = visitor.scope_tree.parent_map.get(&s).cloned();
if let Some(Scope { data: ScopeData::Destruction, .. }) = parent {
break;
}
visitor.cx.var_parent = parent;
}
}
}
if let Some(expr) = init {
record_rvalue_scope_if_borrow_expr(visitor, expr, blk_scope);
record_rvalue_scope_if_borrow_expr(visitor, expr, visitor.cx.var_parent);
if let Some(pat) = pat {
if is_binding_pat(pat) {
visitor.scope_tree.record_rvalue_candidate(
expr.hir_id,
RvalueCandidate { target: expr.hir_id.local_id, lifetime: blk_scope },
RvalueCandidate {
target: expr.hir_id.local_id,
lifetime: visitor.cx.var_parent,
},
);
}
}
@ -565,6 +614,7 @@ fn resolve_local<'tcx>(
if let Some(expr) = init {
visitor.visit_expr(expr);
}
if let Some(pat) = pat {
visitor.visit_pat(pat);
}
@ -642,6 +692,7 @@ fn resolve_local<'tcx>(
/// | [ ..., E&, ... ]
/// | ( ..., E&, ... )
/// | {...; E&}
/// | { super let ... = E&; ... }
/// | if _ { ...; E& } else { ...; E& }
/// | match _ { ..., _ => E&, ... }
/// | box E&
@ -678,6 +729,13 @@ fn resolve_local<'tcx>(
if let Some(subexpr) = block.expr {
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
}
for stmt in block.stmts {
if let hir::StmtKind::Let(local) = stmt.kind
&& let Some(_) = local.super_
{
visitor.extended_super_lets.insert(local.pat.hir_id.local_id, blk_id);
}
}
}
hir::ExprKind::If(_, then_block, else_block) => {
record_rvalue_scope_if_borrow_expr(visitor, then_block, blk_id);
@ -803,7 +861,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> {
local_id: body.value.hir_id.local_id,
data: ScopeData::Destruction,
});
resolve_local(this, None, Some(body.value));
resolve_local(this, None, Some(body.value), false);
}
})
}
@ -821,7 +879,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> {
resolve_expr(self, ex, false);
}
fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) {
resolve_local(self, Some(l.pat), l.init)
resolve_local(self, Some(l.pat), l.init, l.super_.is_some());
}
fn visit_inline_const(&mut self, c: &'tcx hir::ConstBlock) {
let body = self.tcx.hir_body(c.body);
@ -850,6 +908,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree {
cx: Context { parent: None, var_parent: None },
pessimistic_yield: false,
fixup_scopes: vec![],
extended_super_lets: Default::default(),
};
visitor.scope_tree.root_body = Some(body.value.hir_id);

View File

@ -960,12 +960,16 @@ impl<'a> State<'a> {
fn print_local(
&mut self,
super_: bool,
init: Option<&hir::Expr<'_>>,
els: Option<&hir::Block<'_>>,
decl: impl Fn(&mut Self),
) {
self.space_if_not_bol();
self.ibox(INDENT_UNIT);
if super_ {
self.word_nbsp("super");
}
self.word_nbsp("let");
self.ibox(INDENT_UNIT);
@ -995,7 +999,9 @@ impl<'a> State<'a> {
self.maybe_print_comment(st.span.lo());
match st.kind {
hir::StmtKind::Let(loc) => {
self.print_local(loc.init, loc.els, |this| this.print_local_decl(loc));
self.print_local(loc.super_.is_some(), loc.init, loc.els, |this| {
this.print_local_decl(loc)
});
}
hir::StmtKind::Item(item) => self.ann.nested(self, Nested::Item(item)),
hir::StmtKind::Expr(expr) => {
@ -1488,7 +1494,7 @@ impl<'a> State<'a> {
// Print `let _t = $init;`:
let temp = Ident::from_str("_t");
self.print_local(Some(init), None, |this| this.print_ident(temp));
self.print_local(false, Some(init), None, |this| this.print_ident(temp));
self.word(";");
// Print `_t`:

View File

@ -43,7 +43,7 @@ pub(super) struct Declaration<'a> {
impl<'a> From<&'a hir::LetStmt<'a>> for Declaration<'a> {
fn from(local: &'a hir::LetStmt<'a>) -> Self {
let hir::LetStmt { hir_id, pat, ty, span, init, els, source: _ } = *local;
let hir::LetStmt { hir_id, super_: _, pat, ty, span, init, els, source: _ } = *local;
Declaration { hir_id, pat, ty, span, init, origin: DeclOrigin::LocalDecl { els } }
}
}

View File

@ -75,10 +75,11 @@ impl<'a> Parser<'a> {
let stmt = if self.token.is_keyword(kw::Super) && self.is_keyword_ahead(1, &[kw::Let]) {
self.collect_tokens(None, attrs, force_collect, |this, attrs| {
let super_span = this.token.span;
this.expect_keyword(exp!(Super))?;
this.psess.gated_spans.gate(sym::super_let, this.prev_token.span);
this.expect_keyword(exp!(Let))?;
let local = this.parse_local(attrs)?; // FIXME(mara): implement super let
this.psess.gated_spans.gate(sym::super_let, super_span);
let local = this.parse_local(Some(super_span), attrs)?;
let trailing = Trailing::from(capture_semi && this.token == token::Semi);
Ok((
this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Let(local)),
@ -89,7 +90,7 @@ impl<'a> Parser<'a> {
} else if self.token.is_keyword(kw::Let) {
self.collect_tokens(None, attrs, force_collect, |this, attrs| {
this.expect_keyword(exp!(Let))?;
let local = this.parse_local(attrs)?;
let local = this.parse_local(None, attrs)?;
let trailing = Trailing::from(capture_semi && this.token == token::Semi);
Ok((
this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Let(local)),
@ -294,7 +295,7 @@ impl<'a> Parser<'a> {
force_collect: ForceCollect,
) -> PResult<'a, Stmt> {
let stmt = self.collect_tokens(None, attrs, force_collect, |this, attrs| {
let local = this.parse_local(attrs)?;
let local = this.parse_local(None, attrs)?;
// FIXME - maybe capture semicolon in recovery?
Ok((
this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Let(local)),
@ -308,8 +309,8 @@ impl<'a> Parser<'a> {
}
/// Parses a local variable declaration.
fn parse_local(&mut self, attrs: AttrVec) -> PResult<'a, P<Local>> {
let lo = self.prev_token.span;
fn parse_local(&mut self, super_: Option<Span>, attrs: AttrVec) -> PResult<'a, P<Local>> {
let lo = super_.unwrap_or(self.prev_token.span);
if self.token.is_keyword(kw::Const) && self.look_ahead(1, |t| t.is_ident()) {
self.dcx().emit_err(errors::ConstLetMutuallyExclusive { span: lo.to(self.token.span) });
@ -411,6 +412,7 @@ impl<'a> Parser<'a> {
};
let hi = if self.token == token::Semi { self.token.span } else { self.prev_token.span };
Ok(P(ast::Local {
super_,
ty,
pat,
kind,

View File

@ -5,25 +5,25 @@ ast-stats-1 Crate 40 ( 0.6%) 1 40
ast-stats-1 GenericArgs 40 ( 0.6%) 1 40
ast-stats-1 - AngleBracketed 40 ( 0.6%) 1
ast-stats-1 ExprField 48 ( 0.7%) 1 48
ast-stats-1 Attribute 64 ( 1.0%) 2 32
ast-stats-1 Attribute 64 ( 0.9%) 2 32
ast-stats-1 - DocComment 32 ( 0.5%) 1
ast-stats-1 - Normal 32 ( 0.5%) 1
ast-stats-1 WherePredicate 72 ( 1.1%) 1 72
ast-stats-1 - BoundPredicate 72 ( 1.1%) 1
ast-stats-1 ForeignItem 80 ( 1.2%) 1 80
ast-stats-1 - Fn 80 ( 1.2%) 1
ast-stats-1 Local 80 ( 1.2%) 1 80
ast-stats-1 Arm 96 ( 1.4%) 2 48
ast-stats-1 Local 96 ( 1.4%) 1 96
ast-stats-1 FnDecl 120 ( 1.8%) 5 24
ast-stats-1 Param 160 ( 2.4%) 4 40
ast-stats-1 Stmt 160 ( 2.4%) 5 32
ast-stats-1 - Let 32 ( 0.5%) 1
ast-stats-1 - MacCall 32 ( 0.5%) 1
ast-stats-1 - Expr 96 ( 1.4%) 3
ast-stats-1 Block 192 ( 2.9%) 6 32
ast-stats-1 Block 192 ( 2.8%) 6 32
ast-stats-1 FieldDef 208 ( 3.1%) 2 104
ast-stats-1 Variant 208 ( 3.1%) 2 104
ast-stats-1 AssocItem 320 ( 4.8%) 4 80
ast-stats-1 AssocItem 320 ( 4.7%) 4 80
ast-stats-1 - Fn 160 ( 2.4%) 2
ast-stats-1 - Type 160 ( 2.4%) 2
ast-stats-1 GenericBound 352 ( 5.2%) 4 88
@ -33,7 +33,7 @@ ast-stats-1 Pat 504 ( 7.5%) 7 72
ast-stats-1 - Struct 72 ( 1.1%) 1
ast-stats-1 - Wild 72 ( 1.1%) 1
ast-stats-1 - Ident 360 ( 5.3%) 5
ast-stats-1 Expr 576 ( 8.6%) 8 72
ast-stats-1 Expr 576 ( 8.5%) 8 72
ast-stats-1 - Match 72 ( 1.1%) 1
ast-stats-1 - Path 72 ( 1.1%) 1
ast-stats-1 - Struct 72 ( 1.1%) 1
@ -41,8 +41,8 @@ ast-stats-1 - Lit 144 ( 2.1%) 2
ast-stats-1 - Block 216 ( 3.2%) 3
ast-stats-1 PathSegment 744 (11.0%) 31 24
ast-stats-1 Ty 896 (13.3%) 14 64
ast-stats-1 - Ptr 64 ( 1.0%) 1
ast-stats-1 - Ref 64 ( 1.0%) 1
ast-stats-1 - Ptr 64 ( 0.9%) 1
ast-stats-1 - Ref 64 ( 0.9%) 1
ast-stats-1 - ImplicitSelf 128 ( 1.9%) 2
ast-stats-1 - Path 640 ( 9.5%) 10
ast-stats-1 Item 1_296 (19.2%) 9 144
@ -53,7 +53,7 @@ ast-stats-1 - Trait 144 ( 2.1%) 1
ast-stats-1 - Fn 288 ( 4.3%) 2
ast-stats-1 - Use 432 ( 6.4%) 3
ast-stats-1 ----------------------------------------------------------------
ast-stats-1 Total 6_736 116
ast-stats-1 Total 6_752 116
ast-stats-1
ast-stats-2 POST EXPANSION AST STATS
ast-stats-2 Name Accumulated Size Count Item Size
@ -66,8 +66,8 @@ ast-stats-2 WherePredicate 72 ( 1.0%) 1 72
ast-stats-2 - BoundPredicate 72 ( 1.0%) 1
ast-stats-2 ForeignItem 80 ( 1.1%) 1 80
ast-stats-2 - Fn 80 ( 1.1%) 1
ast-stats-2 Local 80 ( 1.1%) 1 80
ast-stats-2 Arm 96 ( 1.3%) 2 48
ast-stats-2 Local 96 ( 1.3%) 1 96
ast-stats-2 FnDecl 120 ( 1.6%) 5 24
ast-stats-2 InlineAsm 120 ( 1.6%) 1 120
ast-stats-2 Attribute 128 ( 1.7%) 4 32
@ -84,14 +84,14 @@ ast-stats-2 Variant 208 ( 2.8%) 2 104
ast-stats-2 AssocItem 320 ( 4.3%) 4 80
ast-stats-2 - Fn 160 ( 2.2%) 2
ast-stats-2 - Type 160 ( 2.2%) 2
ast-stats-2 GenericBound 352 ( 4.8%) 4 88
ast-stats-2 - Trait 352 ( 4.8%) 4
ast-stats-2 GenericBound 352 ( 4.7%) 4 88
ast-stats-2 - Trait 352 ( 4.7%) 4
ast-stats-2 GenericParam 480 ( 6.5%) 5 96
ast-stats-2 Pat 504 ( 6.8%) 7 72
ast-stats-2 - Struct 72 ( 1.0%) 1
ast-stats-2 - Wild 72 ( 1.0%) 1
ast-stats-2 - Ident 360 ( 4.9%) 5
ast-stats-2 Expr 648 ( 8.8%) 9 72
ast-stats-2 Expr 648 ( 8.7%) 9 72
ast-stats-2 - InlineAsm 72 ( 1.0%) 1
ast-stats-2 - Match 72 ( 1.0%) 1
ast-stats-2 - Path 72 ( 1.0%) 1
@ -113,7 +113,7 @@ ast-stats-2 - Trait 144 ( 1.9%) 1
ast-stats-2 - Fn 288 ( 3.9%) 2
ast-stats-2 - Use 576 ( 7.8%) 4
ast-stats-2 ----------------------------------------------------------------
ast-stats-2 Total 7_400 127
ast-stats-2 Total 7_416 127
ast-stats-2
hir-stats HIR STATS
hir-stats Name Accumulated Size Count Item Size
@ -126,11 +126,11 @@ hir-stats TraitItemRef 56 ( 0.6%) 2 28
hir-stats GenericArg 64 ( 0.7%) 4 16
hir-stats - Type 16 ( 0.2%) 1
hir-stats - Lifetime 48 ( 0.5%) 3
hir-stats Local 64 ( 0.7%) 1 64
hir-stats Param 64 ( 0.7%) 2 32
hir-stats Body 72 ( 0.8%) 3 24
hir-stats ImplItemRef 72 ( 0.8%) 2 36
hir-stats InlineAsm 72 ( 0.8%) 1 72
hir-stats Local 72 ( 0.8%) 1 72
hir-stats WherePredicate 72 ( 0.8%) 3 24
hir-stats - BoundPredicate 72 ( 0.8%) 3
hir-stats Arm 80 ( 0.9%) 2 40
@ -143,8 +143,8 @@ hir-stats Attribute 128 ( 1.4%) 4 32
hir-stats FieldDef 128 ( 1.4%) 2 64
hir-stats GenericArgs 144 ( 1.6%) 3 48
hir-stats Variant 144 ( 1.6%) 2 72
hir-stats GenericBound 256 ( 2.9%) 4 64
hir-stats - Trait 256 ( 2.9%) 4
hir-stats GenericBound 256 ( 2.8%) 4 64
hir-stats - Trait 256 ( 2.8%) 4
hir-stats Block 288 ( 3.2%) 6 48
hir-stats Pat 360 ( 4.0%) 5 72
hir-stats - Struct 72 ( 0.8%) 1
@ -156,7 +156,7 @@ hir-stats Ty 720 ( 8.0%) 15 48
hir-stats - Ptr 48 ( 0.5%) 1
hir-stats - Ref 48 ( 0.5%) 1
hir-stats - Path 624 ( 6.9%) 13
hir-stats Expr 768 ( 8.6%) 12 64
hir-stats Expr 768 ( 8.5%) 12 64
hir-stats - InlineAsm 64 ( 0.7%) 1
hir-stats - Match 64 ( 0.7%) 1
hir-stats - Path 64 ( 0.7%) 1
@ -174,5 +174,5 @@ hir-stats - Use 352 ( 3.9%) 4
hir-stats Path 1_240 (13.8%) 31 40
hir-stats PathSegment 1_920 (21.4%) 40 48
hir-stats ----------------------------------------------------------------
hir-stats Total 8_980 180
hir-stats Total 8_988 180
hir-stats