Clean up block_ctxt representation
This commit is contained in:
parent
54d7bffbb8
commit
9f4206cdc4
2 changed files with 131 additions and 140 deletions
|
@ -2108,7 +2108,7 @@ fn trans_for(cx: @block_ctxt, local: @ast::local, seq: @ast::expr,
|
|||
body: ast::blk, outer_next_cx: @block_ctxt) -> @block_ctxt {
|
||||
let next_cx = new_sub_block_ctxt(bcx, "next");
|
||||
let scope_cx =
|
||||
new_loop_scope_block_ctxt(bcx, option::some(next_cx),
|
||||
new_loop_scope_block_ctxt(bcx, cont_other(next_cx),
|
||||
outer_next_cx, "for loop scope",
|
||||
body.span);
|
||||
Br(bcx, scope_cx.llbb);
|
||||
|
@ -2138,7 +2138,7 @@ fn trans_for(cx: @block_ctxt, local: @ast::local, seq: @ast::expr,
|
|||
fn trans_while(cx: @block_ctxt, cond: @ast::expr, body: ast::blk)
|
||||
-> @block_ctxt {
|
||||
let next_cx = new_sub_block_ctxt(cx, "while next");
|
||||
let cond_cx = new_loop_scope_block_ctxt(cx, none, next_cx,
|
||||
let cond_cx = new_loop_scope_block_ctxt(cx, cont_self, next_cx,
|
||||
"while cond", body.span);
|
||||
let body_cx = new_scope_block_ctxt(cond_cx, "while loop body");
|
||||
Br(cx, cond_cx.llbb);
|
||||
|
@ -2154,7 +2154,7 @@ fn trans_do_while(cx: @block_ctxt, body: ast::blk, cond: @ast::expr) ->
|
|||
@block_ctxt {
|
||||
let next_cx = new_sub_block_ctxt(cx, "next");
|
||||
let body_cx =
|
||||
new_loop_scope_block_ctxt(cx, option::none::<@block_ctxt>, next_cx,
|
||||
new_loop_scope_block_ctxt(cx, cont_self, next_cx,
|
||||
"do-while loop body", body.span);
|
||||
let body_end = trans_block(body_cx, body, ignore);
|
||||
let cond_cx = new_scope_block_ctxt(body_cx, "do-while cond");
|
||||
|
@ -2995,27 +2995,32 @@ fn invoke_(bcx: @block_ctxt, llfn: ValueRef, llargs: [ValueRef],
|
|||
}
|
||||
|
||||
fn get_landing_pad(bcx: @block_ctxt) -> BasicBlockRef {
|
||||
fn find_scope_for_lpad(bcx: @block_ctxt) -> @block_ctxt {
|
||||
let scope_bcx = bcx;
|
||||
fn in_lpad_scope_cx(bcx: @block_ctxt, f: fn(scope_info)) {
|
||||
let bcx = bcx;
|
||||
while true {
|
||||
if vec::is_not_empty(scope_bcx.cleanups) { break; }
|
||||
scope_bcx = alt scope_bcx.parent {
|
||||
parent_some(b) { b }
|
||||
parent_none { break; }
|
||||
};
|
||||
alt bcx.kind {
|
||||
scope_block(info) {
|
||||
if info.cleanups.len() > 0u || bcx.parent == parent_none {
|
||||
f(info); ret;
|
||||
}
|
||||
}
|
||||
_ {}
|
||||
}
|
||||
bcx = alt check bcx.parent { parent_some(b) { b } };
|
||||
}
|
||||
scope_bcx
|
||||
}
|
||||
|
||||
let scope_bcx = find_scope_for_lpad(bcx);
|
||||
// If there is a valid landing pad still around, use it
|
||||
alt scope_bcx.landing_pad {
|
||||
some(target) { ret target; }
|
||||
none {}
|
||||
let cached = none, pad_bcx = bcx; // Guaranteed to be set below
|
||||
in_lpad_scope_cx(bcx) {|info|
|
||||
// If there is a valid landing pad still around, use it
|
||||
alt info.landing_pad {
|
||||
some(target) { cached = some(target); ret; }
|
||||
none {}
|
||||
}
|
||||
pad_bcx = new_sub_block_ctxt(bcx, "unwind");
|
||||
info.landing_pad = some(pad_bcx.llbb);
|
||||
}
|
||||
|
||||
let pad_bcx = new_sub_block_ctxt(bcx, "unwind");
|
||||
scope_bcx.landing_pad = some(pad_bcx.llbb);
|
||||
alt cached { some(b) { ret b; } none {} } // Can't return from block above
|
||||
// The landing pad return type (the type being propagated). Not sure what
|
||||
// this represents but it's determined by the personality function and
|
||||
// this is what the EH proposal example uses.
|
||||
|
@ -3573,13 +3578,13 @@ fn trans_break_cont(bcx: @block_ctxt, to_end: bool)
|
|||
let unwind = bcx, target = bcx;
|
||||
while true {
|
||||
alt unwind.kind {
|
||||
LOOP_SCOPE_BLOCK(_cont, _break) {
|
||||
scope_block({is_loop: some({cnt, brk}), _}) {
|
||||
target = if to_end {
|
||||
_break
|
||||
brk
|
||||
} else {
|
||||
alt _cont {
|
||||
option::some(_cont) { _cont }
|
||||
_ { unwind }
|
||||
alt cnt {
|
||||
cont_other(o) { o }
|
||||
cont_self { unwind }
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
@ -3738,16 +3743,14 @@ fn new_block_ctxt(cx: @fn_ctxt, parent: block_parent, kind: block_kind,
|
|||
if cx.ccx.sess.opts.save_temps || cx.ccx.sess.opts.debuginfo {
|
||||
s = cx.ccx.names(name);
|
||||
}
|
||||
let llbb: BasicBlockRef =
|
||||
str::as_buf(s, {|buf| llvm::LLVMAppendBasicBlock(cx.llfn, buf) });
|
||||
let llbb: BasicBlockRef = str::as_buf(s, {|buf|
|
||||
llvm::LLVMAppendBasicBlock(cx.llfn, buf)
|
||||
});
|
||||
let bcx = @{llbb: llbb,
|
||||
mutable terminated: false,
|
||||
mutable unreachable: false,
|
||||
parent: parent,
|
||||
kind: kind,
|
||||
mutable cleanups: [],
|
||||
mutable cleanup_paths: [],
|
||||
mutable landing_pad: none,
|
||||
block_span: block_span,
|
||||
fcx: cx};
|
||||
alt parent {
|
||||
|
@ -3759,34 +3762,43 @@ fn new_block_ctxt(cx: @fn_ctxt, parent: block_parent, kind: block_kind,
|
|||
ret bcx;
|
||||
}
|
||||
|
||||
fn simple_scope_block() -> block_kind {
|
||||
scope_block({is_loop: none, mutable cleanups: [],
|
||||
mutable cleanup_paths: [], mutable landing_pad: none})
|
||||
}
|
||||
|
||||
// Use this when you're at the top block of a function or the like.
|
||||
fn new_top_block_ctxt(fcx: @fn_ctxt, sp: option<span>) -> @block_ctxt {
|
||||
ret new_block_ctxt(fcx, parent_none, SCOPE_BLOCK, "function top level",
|
||||
sp);
|
||||
ret new_block_ctxt(fcx, parent_none, simple_scope_block(),
|
||||
"function top level", sp);
|
||||
}
|
||||
|
||||
|
||||
// Use this when you're at a curly-brace or similar lexical scope.
|
||||
fn new_scope_block_ctxt(bcx: @block_ctxt, n: str) -> @block_ctxt {
|
||||
ret new_block_ctxt(bcx.fcx, parent_some(bcx), SCOPE_BLOCK, n, none);
|
||||
ret new_block_ctxt(bcx.fcx, parent_some(bcx), simple_scope_block(),
|
||||
n, none);
|
||||
}
|
||||
|
||||
fn new_real_block_ctxt(bcx: @block_ctxt, n: str, sp: span) -> @block_ctxt {
|
||||
ret new_block_ctxt(bcx.fcx, parent_some(bcx), SCOPE_BLOCK, n, some(sp));
|
||||
ret new_block_ctxt(bcx.fcx, parent_some(bcx), simple_scope_block(),
|
||||
n, some(sp));
|
||||
}
|
||||
|
||||
fn new_loop_scope_block_ctxt(bcx: @block_ctxt, _cont: option<@block_ctxt>,
|
||||
fn new_loop_scope_block_ctxt(bcx: @block_ctxt, _cont: loop_cont,
|
||||
_break: @block_ctxt, n: str, sp: span)
|
||||
-> @block_ctxt {
|
||||
ret new_block_ctxt(bcx.fcx, parent_some(bcx),
|
||||
LOOP_SCOPE_BLOCK(_cont, _break), n, some(sp));
|
||||
ret new_block_ctxt(bcx.fcx, parent_some(bcx), scope_block({
|
||||
is_loop: some({cnt: _cont, brk: _break}),
|
||||
mutable cleanups: [],
|
||||
mutable cleanup_paths: [],
|
||||
mutable landing_pad: none
|
||||
}), n, some(sp));
|
||||
}
|
||||
|
||||
|
||||
// Use this when you're making a general CFG BB within a scope.
|
||||
fn new_sub_block_ctxt(bcx: @block_ctxt, n: str) -> @block_ctxt {
|
||||
ret new_block_ctxt(bcx.fcx, parent_some(bcx), NON_SCOPE_BLOCK, n, none);
|
||||
ret new_block_ctxt(bcx.fcx, parent_some(bcx), non_scope_block, n, none);
|
||||
}
|
||||
|
||||
fn new_raw_block_ctxt(fcx: @fn_ctxt, llbb: BasicBlockRef) -> @block_ctxt {
|
||||
|
@ -3794,10 +3806,7 @@ fn new_raw_block_ctxt(fcx: @fn_ctxt, llbb: BasicBlockRef) -> @block_ctxt {
|
|||
mutable terminated: false,
|
||||
mutable unreachable: false,
|
||||
parent: parent_none,
|
||||
kind: NON_SCOPE_BLOCK,
|
||||
mutable cleanups: [],
|
||||
mutable cleanup_paths: [],
|
||||
mutable landing_pad: none,
|
||||
kind: non_scope_block,
|
||||
block_span: none,
|
||||
fcx: fcx};
|
||||
}
|
||||
|
@ -3813,13 +3822,13 @@ fn new_raw_block_ctxt(fcx: @fn_ctxt, llbb: BasicBlockRef) -> @block_ctxt {
|
|||
fn trans_block_cleanups(bcx: @block_ctxt, cleanup_cx: @block_ctxt) ->
|
||||
@block_ctxt {
|
||||
if bcx.unreachable { ret bcx; }
|
||||
let i = cleanup_cx.cleanups.len(), bcx = bcx;
|
||||
if cleanup_cx.kind == NON_SCOPE_BLOCK { assert i == 0u; }
|
||||
while i > 0u {
|
||||
i -= 1u;
|
||||
alt cleanup_cx.cleanups[i] {
|
||||
clean(cfn) | clean_temp(_, cfn) { bcx = cfn(bcx); }
|
||||
let bcx = bcx;
|
||||
alt check cleanup_cx.kind {
|
||||
scope_block({cleanups, _}) {
|
||||
vec::riter(cleanups) {|cu|
|
||||
alt cu { clean(cfn) | clean_temp(_, cfn) { bcx = cfn(bcx); } }
|
||||
}
|
||||
}
|
||||
}
|
||||
ret bcx;
|
||||
}
|
||||
|
@ -3831,9 +3840,9 @@ fn cleanup_and_leave(bcx: @block_ctxt, upto: option<BasicBlockRef>,
|
|||
leave: option<BasicBlockRef>) {
|
||||
let cur = bcx, bcx = bcx;
|
||||
while true {
|
||||
if cur.cleanups.len() > 0u {
|
||||
assert cur.kind != NON_SCOPE_BLOCK;
|
||||
for exists in cur.cleanup_paths {
|
||||
alt cur.kind {
|
||||
scope_block(info) if info.cleanups.len() > 0u {
|
||||
for exists in info.cleanup_paths {
|
||||
if exists.target == leave {
|
||||
Br(bcx, exists.dest);
|
||||
ret;
|
||||
|
@ -3841,8 +3850,10 @@ fn cleanup_and_leave(bcx: @block_ctxt, upto: option<BasicBlockRef>,
|
|||
}
|
||||
let sub_cx = new_sub_block_ctxt(bcx, "cleanup");
|
||||
Br(bcx, sub_cx.llbb);
|
||||
cur.cleanup_paths += [{target: leave, dest: sub_cx.llbb}];
|
||||
info.cleanup_paths += [{target: leave, dest: sub_cx.llbb}];
|
||||
bcx = trans_block_cleanups(sub_cx, cur);
|
||||
}
|
||||
_ {}
|
||||
}
|
||||
alt upto {
|
||||
some(bb) { if cur.llbb == bb { break; } }
|
||||
|
@ -3890,33 +3901,6 @@ fn block_locals(b: ast::blk, it: fn(@ast::local)) {
|
|||
}
|
||||
}
|
||||
|
||||
fn llstaticallocas_block_ctxt(fcx: @fn_ctxt) -> @block_ctxt {
|
||||
ret @{llbb: fcx.llstaticallocas,
|
||||
mutable terminated: false,
|
||||
mutable unreachable: false,
|
||||
parent: parent_none,
|
||||
kind: SCOPE_BLOCK,
|
||||
mutable cleanups: [],
|
||||
mutable cleanup_paths: [],
|
||||
mutable landing_pad: none,
|
||||
block_span: none,
|
||||
fcx: fcx};
|
||||
}
|
||||
|
||||
fn llderivedtydescs_block_ctxt(fcx: @fn_ctxt) -> @block_ctxt {
|
||||
ret @{llbb: fcx.llderivedtydescs,
|
||||
mutable terminated: false,
|
||||
mutable unreachable: false,
|
||||
parent: parent_none,
|
||||
kind: SCOPE_BLOCK,
|
||||
mutable cleanups: [],
|
||||
mutable cleanup_paths: [],
|
||||
mutable landing_pad: none,
|
||||
block_span: none,
|
||||
fcx: fcx};
|
||||
}
|
||||
|
||||
|
||||
fn alloc_ty(cx: @block_ctxt, t: ty::t) -> result {
|
||||
let bcx = cx, ccx = bcx_ccx(cx);
|
||||
let llty = type_of(ccx, t);
|
||||
|
@ -3927,7 +3911,8 @@ fn alloc_ty(cx: @block_ctxt, t: ty::t) -> result {
|
|||
// block_ctxt built on the llderivedtydescs block for the fn,
|
||||
// so that the size dominates the array_alloca that
|
||||
// comes next.
|
||||
let n = size_of(llderivedtydescs_block_ctxt(bcx.fcx), t);
|
||||
let n = size_of(new_raw_block_ctxt(cx.fcx, cx.fcx.llderivedtydescs),
|
||||
t);
|
||||
bcx.fcx.llderivedtydescs = n.bcx.llbb;
|
||||
PointerCast(bcx, dynastack_alloca(bcx, T_i8(), n.val, t), T_ptr(llty))
|
||||
};
|
||||
|
|
|
@ -233,16 +233,17 @@ enum cleanup {
|
|||
type cleanup_path = {target: option<BasicBlockRef>,
|
||||
dest: BasicBlockRef};
|
||||
|
||||
fn scope_clean_changed(cx: @block_ctxt) {
|
||||
cx.cleanup_paths = [];
|
||||
cx.landing_pad = none;
|
||||
fn scope_clean_changed(info: scope_info) {
|
||||
if info.cleanup_paths.len() > 0u { info.cleanup_paths = []; }
|
||||
info.landing_pad = none;
|
||||
}
|
||||
|
||||
fn add_clean(cx: @block_ctxt, val: ValueRef, ty: ty::t) {
|
||||
if !ty::type_needs_drop(bcx_tcx(cx), ty) { ret; }
|
||||
let scope_cx = find_scope_cx(cx);
|
||||
scope_cx.cleanups += [clean(bind drop_ty(_, val, ty))];
|
||||
scope_clean_changed(scope_cx);
|
||||
in_scope_cx(cx) {|info|
|
||||
info.cleanups += [clean(bind drop_ty(_, val, ty))];
|
||||
scope_clean_changed(info);
|
||||
}
|
||||
}
|
||||
fn add_clean_temp(cx: @block_ctxt, val: ValueRef, ty: ty::t) {
|
||||
if !ty::type_needs_drop(bcx_tcx(cx), ty) { ret; }
|
||||
|
@ -254,23 +255,25 @@ fn add_clean_temp(cx: @block_ctxt, val: ValueRef, ty: ty::t) {
|
|||
ret drop_ty(bcx, val, ty);
|
||||
}
|
||||
}
|
||||
let scope_cx = find_scope_cx(cx);
|
||||
scope_cx.cleanups +=
|
||||
[clean_temp(val, bind do_drop(_, val, ty))];
|
||||
scope_clean_changed(scope_cx);
|
||||
in_scope_cx(cx) {|info|
|
||||
info.cleanups += [clean_temp(val, bind do_drop(_, val, ty))];
|
||||
scope_clean_changed(info);
|
||||
}
|
||||
}
|
||||
fn add_clean_temp_mem(cx: @block_ctxt, val: ValueRef, ty: ty::t) {
|
||||
if !ty::type_needs_drop(bcx_tcx(cx), ty) { ret; }
|
||||
let scope_cx = find_scope_cx(cx);
|
||||
scope_cx.cleanups += [clean_temp(val, bind drop_ty(_, val, ty))];
|
||||
scope_clean_changed(scope_cx);
|
||||
in_scope_cx(cx) {|info|
|
||||
info.cleanups += [clean_temp(val, bind drop_ty(_, val, ty))];
|
||||
scope_clean_changed(info);
|
||||
}
|
||||
}
|
||||
fn add_clean_free(cx: @block_ctxt, ptr: ValueRef, shared: bool) {
|
||||
let scope_cx = find_scope_cx(cx);
|
||||
let free_fn = if shared { bind base::trans_shared_free(_, ptr) }
|
||||
else { bind base::trans_free(_, ptr) };
|
||||
scope_cx.cleanups += [clean_temp(ptr, free_fn)];
|
||||
scope_clean_changed(scope_cx);
|
||||
in_scope_cx(cx) {|info|
|
||||
info.cleanups += [clean_temp(ptr, free_fn)];
|
||||
scope_clean_changed(info);
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this only works for temporaries. We should, at some point, move
|
||||
|
@ -278,27 +281,22 @@ fn add_clean_free(cx: @block_ctxt, ptr: ValueRef, shared: bool) {
|
|||
// this will be more involved. For now, we simply zero out the local, and the
|
||||
// drop glue checks whether it is zero.
|
||||
fn revoke_clean(cx: @block_ctxt, val: ValueRef) {
|
||||
let sc_cx = find_scope_cx(cx);
|
||||
let found = -1;
|
||||
let i = 0;
|
||||
for c: cleanup in sc_cx.cleanups {
|
||||
alt c {
|
||||
clean_temp(v, _) {
|
||||
if v as uint == val as uint { found = i; break; }
|
||||
}
|
||||
_ { }
|
||||
in_scope_cx(cx) {|info|
|
||||
let i = 0u;
|
||||
for cu in info.cleanups {
|
||||
alt cu {
|
||||
clean_temp(v, _) if v == val {
|
||||
info.cleanups =
|
||||
vec::slice(info.cleanups, 0u, i) +
|
||||
vec::slice(info.cleanups, i + 1u, info.cleanups.len());
|
||||
scope_clean_changed(info);
|
||||
ret;
|
||||
}
|
||||
_ {}
|
||||
}
|
||||
i += 1u;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
// The value does not have a cleanup associated with it.
|
||||
if found == -1 { ret; }
|
||||
// We found the cleanup and remove it
|
||||
sc_cx.cleanups =
|
||||
vec::slice(sc_cx.cleanups, 0u, found as uint) +
|
||||
vec::slice(sc_cx.cleanups, (found as uint) + 1u,
|
||||
sc_cx.cleanups.len());
|
||||
scope_clean_changed(sc_cx);
|
||||
ret;
|
||||
}
|
||||
|
||||
fn get_res_dtor(ccx: @crate_ctxt, did: ast::def_id, inner_t: ty::t)
|
||||
|
@ -325,47 +323,53 @@ enum block_kind {
|
|||
// cleaned up. May correspond to an actual block in the language, but also
|
||||
// to an implicit scope, for example, calls introduce an implicit scope in
|
||||
// which the arguments are evaluated and cleaned up.
|
||||
SCOPE_BLOCK,
|
||||
// A basic block created from the body of a loop. Contains pointers to
|
||||
// which block to jump to in the case of "continue" or "break".
|
||||
LOOP_SCOPE_BLOCK(option<@block_ctxt>, @block_ctxt),
|
||||
scope_block(scope_info),
|
||||
// A non-scope block is a basic block created as a translation artifact
|
||||
// from translating code that expresses conditional logic rather than by
|
||||
// explicit { ... } block structure in the source language. It's called a
|
||||
// non-scope block because it doesn't introduce a new variable scope.
|
||||
NON_SCOPE_BLOCK
|
||||
non_scope_block,
|
||||
}
|
||||
|
||||
enum loop_cont { cont_self, cont_other(@block_ctxt), }
|
||||
|
||||
type scope_info = {
|
||||
is_loop: option<{cnt: loop_cont, brk: @block_ctxt}>,
|
||||
// A list of functions that must be run at when leaving this
|
||||
// block, cleaning up any variables that were introduced in the
|
||||
// block.
|
||||
mutable cleanups: [cleanup],
|
||||
// Existing cleanup paths that may be reused, indexed by destination and
|
||||
// cleared when the set of cleanups changes.
|
||||
mutable cleanup_paths: [cleanup_path],
|
||||
// Unwinding landing pad. Also cleared when cleanups change.
|
||||
mutable landing_pad: option<BasicBlockRef>,
|
||||
};
|
||||
|
||||
// Basic block context. We create a block context for each basic block
|
||||
// (single-entry, single-exit sequence of instructions) we generate from Rust
|
||||
// code. Each basic block we generate is attached to a function, typically
|
||||
// with many basic blocks per function. All the basic blocks attached to a
|
||||
// function are organized as a directed graph.
|
||||
type block_ctxt =
|
||||
type block_ctxt = {
|
||||
// The BasicBlockRef returned from a call to
|
||||
// llvm::LLVMAppendBasicBlock(llfn, name), which adds a basic
|
||||
// block to the function pointed to by llfn. We insert
|
||||
// instructions into that block by way of this block context.
|
||||
// The block pointing to this one in the function's digraph.
|
||||
llbb: BasicBlockRef,
|
||||
mutable terminated: bool,
|
||||
mutable unreachable: bool,
|
||||
parent: block_parent,
|
||||
// The 'kind' of basic block this is.
|
||||
// A list of functions that run at the end of translating this
|
||||
// block, cleaning up any variables that were introduced in the
|
||||
// block and need to go out of scope at the end of it.
|
||||
// The source span where this block comes from, for error
|
||||
// reporting. FIXME this is not currently reliable
|
||||
kind: block_kind,
|
||||
// The source span where the block came from, if it is a block that
|
||||
// actually appears in the source code.
|
||||
block_span: option<span>,
|
||||
// The function context for the function to which this block is
|
||||
// attached.
|
||||
{llbb: BasicBlockRef,
|
||||
mutable terminated: bool,
|
||||
mutable unreachable: bool,
|
||||
parent: block_parent,
|
||||
kind: block_kind,
|
||||
// FIXME the next five fields should probably only appear in scope blocks
|
||||
mutable cleanups: [cleanup],
|
||||
mutable cleanup_paths: [cleanup_path],
|
||||
mutable landing_pad: option<BasicBlockRef>,
|
||||
block_span: option<span>,
|
||||
fcx: @fn_ctxt};
|
||||
fcx: @fn_ctxt
|
||||
};
|
||||
|
||||
// FIXME: we should be able to use option<@block_parent> here but
|
||||
// the infinite-enum check in rustboot gets upset.
|
||||
|
@ -395,13 +399,15 @@ fn struct_elt(llstructty: TypeRef, n: uint) -> TypeRef unsafe {
|
|||
ret llvm::LLVMGetElementType(elt_tys[n]);
|
||||
}
|
||||
|
||||
fn find_scope_cx(cx: @block_ctxt) -> @block_ctxt {
|
||||
fn in_scope_cx(cx: @block_ctxt, f: fn(scope_info)) {
|
||||
let cur = cx;
|
||||
while true {
|
||||
if cur.kind != NON_SCOPE_BLOCK { break; }
|
||||
alt cur.kind {
|
||||
scope_block(info) { f(info); ret; }
|
||||
_ {}
|
||||
}
|
||||
cur = alt check cur.parent { parent_some(b) { b } };
|
||||
}
|
||||
cur
|
||||
}
|
||||
|
||||
// Accessors
|
||||
|
|
Loading…
Reference in a new issue