polonius: add generation of liveneness-related facts
Notably contains an ugly hack to generate initialization information for variables that will go away when we have that functionality in Polonius.
This commit is contained in:
parent
e775bf33c0
commit
ac0a3d106b
@ -9,6 +9,7 @@ use crate::hir::def_id::DefId;
|
|||||||
use crate::hir::{self, InlineAsm as HirInlineAsm};
|
use crate::hir::{self, InlineAsm as HirInlineAsm};
|
||||||
use crate::mir::interpret::{ConstValue, InterpError, Scalar};
|
use crate::mir::interpret::{ConstValue, InterpError, Scalar};
|
||||||
use crate::mir::visit::MirVisitable;
|
use crate::mir::visit::MirVisitable;
|
||||||
|
use polonius_engine::Atom;
|
||||||
use rustc_data_structures::bit_set::BitMatrix;
|
use rustc_data_structures::bit_set::BitMatrix;
|
||||||
use rustc_data_structures::fx::FxHashSet;
|
use rustc_data_structures::fx::FxHashSet;
|
||||||
use rustc_data_structures::graph::dominators::{dominators, Dominators};
|
use rustc_data_structures::graph::dominators::{dominators, Dominators};
|
||||||
@ -600,6 +601,12 @@ newtype_index! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Atom for Local {
|
||||||
|
fn index(self) -> usize {
|
||||||
|
Idx::index(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Classifies locals into categories. See `Body::local_kind`.
|
/// Classifies locals into categories. See `Body::local_kind`.
|
||||||
#[derive(PartialEq, Eq, Debug, HashStable)]
|
#[derive(PartialEq, Eq, Debug, HashStable)]
|
||||||
pub enum LocalKind {
|
pub enum LocalKind {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
//! FIXME: this might be better as a "generic" fixed-point combinator,
|
//! FIXME: this might be better as a "generic" fixed-point combinator,
|
||||||
//! but is not as ugly as it is right now.
|
//! but is not as ugly as it is right now.
|
||||||
|
|
||||||
use rustc::mir::{BasicBlock, Location};
|
use rustc::mir::{BasicBlock, Local, Location};
|
||||||
use rustc::ty::RegionVid;
|
use rustc::ty::RegionVid;
|
||||||
use rustc_data_structures::bit_set::BitIter;
|
use rustc_data_structures::bit_set::BitIter;
|
||||||
|
|
||||||
@ -21,6 +21,8 @@ use either::Either;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
crate type PoloniusOutput = Output<RegionVid, BorrowIndex, LocationIndex, Local>;
|
||||||
|
|
||||||
// (forced to be `pub` due to its use as an associated type below.)
|
// (forced to be `pub` due to its use as an associated type below.)
|
||||||
crate struct Flows<'b, 'tcx> {
|
crate struct Flows<'b, 'tcx> {
|
||||||
borrows: FlowAtLocation<'tcx, Borrows<'b, 'tcx>>,
|
borrows: FlowAtLocation<'tcx, Borrows<'b, 'tcx>>,
|
||||||
@ -28,7 +30,7 @@ crate struct Flows<'b, 'tcx> {
|
|||||||
pub ever_inits: FlowAtLocation<'tcx, EverInitializedPlaces<'b, 'tcx>>,
|
pub ever_inits: FlowAtLocation<'tcx, EverInitializedPlaces<'b, 'tcx>>,
|
||||||
|
|
||||||
/// Polonius Output
|
/// Polonius Output
|
||||||
pub polonius_output: Option<Rc<Output<RegionVid, BorrowIndex, LocationIndex>>>,
|
pub polonius_output: Option<Rc<PoloniusOutput>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'b, 'tcx> Flows<'b, 'tcx> {
|
impl<'b, 'tcx> Flows<'b, 'tcx> {
|
||||||
@ -36,7 +38,7 @@ impl<'b, 'tcx> Flows<'b, 'tcx> {
|
|||||||
borrows: FlowAtLocation<'tcx, Borrows<'b, 'tcx>>,
|
borrows: FlowAtLocation<'tcx, Borrows<'b, 'tcx>>,
|
||||||
uninits: FlowAtLocation<'tcx, MaybeUninitializedPlaces<'b, 'tcx>>,
|
uninits: FlowAtLocation<'tcx, MaybeUninitializedPlaces<'b, 'tcx>>,
|
||||||
ever_inits: FlowAtLocation<'tcx, EverInitializedPlaces<'b, 'tcx>>,
|
ever_inits: FlowAtLocation<'tcx, EverInitializedPlaces<'b, 'tcx>>,
|
||||||
polonius_output: Option<Rc<Output<RegionVid, BorrowIndex, LocationIndex>>>,
|
polonius_output: Option<Rc<PoloniusOutput>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Flows {
|
Flows {
|
||||||
borrows,
|
borrows,
|
||||||
|
@ -2,6 +2,7 @@ use crate::borrow_check::location::{LocationIndex, LocationTable};
|
|||||||
use crate::dataflow::indexes::BorrowIndex;
|
use crate::dataflow::indexes::BorrowIndex;
|
||||||
use polonius_engine::AllFacts as PoloniusAllFacts;
|
use polonius_engine::AllFacts as PoloniusAllFacts;
|
||||||
use polonius_engine::Atom;
|
use polonius_engine::Atom;
|
||||||
|
use rustc::mir::Local;
|
||||||
use rustc::ty::{RegionVid, TyCtxt};
|
use rustc::ty::{RegionVid, TyCtxt};
|
||||||
use rustc_data_structures::indexed_vec::Idx;
|
use rustc_data_structures::indexed_vec::Idx;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
@ -10,7 +11,7 @@ use std::fs::{self, File};
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
crate type AllFacts = PoloniusAllFacts<RegionVid, BorrowIndex, LocationIndex>;
|
crate type AllFacts = PoloniusAllFacts<RegionVid, BorrowIndex, LocationIndex, Local>;
|
||||||
|
|
||||||
crate trait AllFactsExt {
|
crate trait AllFactsExt {
|
||||||
/// Returns `true` if there is a need to gather `AllFacts` given the
|
/// Returns `true` if there is a need to gather `AllFacts` given the
|
||||||
@ -60,6 +61,12 @@ impl AllFactsExt for AllFacts {
|
|||||||
outlives,
|
outlives,
|
||||||
region_live_at,
|
region_live_at,
|
||||||
invalidates,
|
invalidates,
|
||||||
|
var_used,
|
||||||
|
var_defined,
|
||||||
|
var_drop_used,
|
||||||
|
var_uses_region,
|
||||||
|
var_drops_region,
|
||||||
|
var_initialized_on_exit,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -11,7 +11,7 @@ use crate::transform::MirSource;
|
|||||||
use crate::borrow_check::Upvar;
|
use crate::borrow_check::Upvar;
|
||||||
use rustc::hir::def_id::DefId;
|
use rustc::hir::def_id::DefId;
|
||||||
use rustc::infer::InferCtxt;
|
use rustc::infer::InferCtxt;
|
||||||
use rustc::mir::{ClosureOutlivesSubject, ClosureRegionRequirements, Body};
|
use rustc::mir::{ClosureOutlivesSubject, ClosureRegionRequirements, Local, Body};
|
||||||
use rustc::ty::{self, RegionKind, RegionVid};
|
use rustc::ty::{self, RegionKind, RegionVid};
|
||||||
use rustc_errors::Diagnostic;
|
use rustc_errors::Diagnostic;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@ -84,7 +84,7 @@ pub(in crate::borrow_check) fn compute_regions<'cx, 'tcx>(
|
|||||||
errors_buffer: &mut Vec<Diagnostic>,
|
errors_buffer: &mut Vec<Diagnostic>,
|
||||||
) -> (
|
) -> (
|
||||||
RegionInferenceContext<'tcx>,
|
RegionInferenceContext<'tcx>,
|
||||||
Option<Rc<Output<RegionVid, BorrowIndex, LocationIndex>>>,
|
Option<Rc<Output<RegionVid, BorrowIndex, LocationIndex, Local>>>,
|
||||||
Option<ClosureRegionRequirements<'tcx>>,
|
Option<ClosureRegionRequirements<'tcx>>,
|
||||||
) {
|
) {
|
||||||
let mut all_facts = if AllFacts::enabled(infcx.tcx) {
|
let mut all_facts = if AllFacts::enabled(infcx.tcx) {
|
||||||
|
@ -15,6 +15,7 @@ use std::rc::Rc;
|
|||||||
use super::TypeChecker;
|
use super::TypeChecker;
|
||||||
|
|
||||||
mod local_use_map;
|
mod local_use_map;
|
||||||
|
mod polonius;
|
||||||
mod trace;
|
mod trace;
|
||||||
|
|
||||||
/// Combines liveness analysis with initialization analysis to
|
/// Combines liveness analysis with initialization analysis to
|
||||||
@ -57,15 +58,9 @@ pub(super) fn generate<'tcx>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !live_locals.is_empty() {
|
if !live_locals.is_empty() {
|
||||||
trace::trace(
|
trace::trace(typeck, body, elements, flow_inits, move_data, live_locals, location_table);
|
||||||
typeck,
|
|
||||||
body,
|
polonius::populate_var_liveness_facts(typeck, body, location_table);
|
||||||
elements,
|
|
||||||
flow_inits,
|
|
||||||
move_data,
|
|
||||||
live_locals,
|
|
||||||
location_table,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
use crate::borrow_check::location::{LocationIndex, LocationTable};
|
||||||
|
use crate::util::liveness::{categorize, DefUse};
|
||||||
|
use rustc::mir::visit::{PlaceContext, Visitor};
|
||||||
|
use rustc::mir::{Body, Local, Location};
|
||||||
|
use rustc::ty::subst::Kind;
|
||||||
|
use rustc::ty::Ty;
|
||||||
|
|
||||||
|
use super::TypeChecker;
|
||||||
|
|
||||||
|
type VarPointRelations = Vec<(Local, LocationIndex)>;
|
||||||
|
|
||||||
|
struct LivenessPointFactsExtractor<'me> {
|
||||||
|
var_defined: &'me mut VarPointRelations,
|
||||||
|
var_used: &'me mut VarPointRelations,
|
||||||
|
location_table: &'me LocationTable,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Visitor to walk through the MIR and extract point-wise facts
|
||||||
|
impl LivenessPointFactsExtractor<'_> {
|
||||||
|
fn location_to_index(&self, location: Location) -> LocationIndex {
|
||||||
|
self.location_table.mid_index(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_def(&mut self, local: Local, location: Location) {
|
||||||
|
debug!("LivenessFactsExtractor::insert_def()");
|
||||||
|
self.var_defined.push((local, self.location_to_index(location)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_use(&mut self, local: Local, location: Location) {
|
||||||
|
debug!("LivenessFactsExtractor::insert_use()");
|
||||||
|
self.var_used.push((local, self.location_to_index(location)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visitor<'tcx> for LivenessPointFactsExtractor<'_> {
|
||||||
|
fn visit_local(&mut self, &local: &Local, context: PlaceContext, location: Location) {
|
||||||
|
match categorize(context) {
|
||||||
|
Some(DefUse::Def) => self.insert_def(local, location),
|
||||||
|
Some(DefUse::Use) => self.insert_use(local, location),
|
||||||
|
_ => (),
|
||||||
|
// NOTE: Drop handling is now done in trace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_var_uses_regions(typeck: &mut TypeChecker<'_, 'tcx>, local: Local, ty: Ty<'tcx>) {
|
||||||
|
debug!("add_regions(local={:?}, type={:?})", local, ty);
|
||||||
|
typeck.tcx().for_each_free_region(&ty, |region| {
|
||||||
|
let region_vid = typeck.borrowck_context.universal_regions.to_region_vid(region);
|
||||||
|
debug!("add_regions for region {:?}", region_vid);
|
||||||
|
if let Some(facts) = typeck.borrowck_context.all_facts {
|
||||||
|
facts.var_uses_region.push((local, region_vid));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn populate_var_liveness_facts(
|
||||||
|
typeck: &mut TypeChecker<'_, 'tcx>,
|
||||||
|
mir: &Body<'tcx>,
|
||||||
|
location_table: &LocationTable,
|
||||||
|
) {
|
||||||
|
debug!("populate_var_liveness_facts()");
|
||||||
|
|
||||||
|
if let Some(facts) = typeck.borrowck_context.all_facts.as_mut() {
|
||||||
|
LivenessPointFactsExtractor {
|
||||||
|
var_defined: &mut facts.var_defined,
|
||||||
|
var_used: &mut facts.var_used,
|
||||||
|
location_table,
|
||||||
|
}
|
||||||
|
.visit_body(mir);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (local, local_decl) in mir.local_decls.iter_enumerated() {
|
||||||
|
add_var_uses_regions(typeck, local, local_decl.ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For every potentially drop()-touched region `region` in `local`'s type
|
||||||
|
// (`kind`), emit a Polonius `var_drops_region(local, region)` fact.
|
||||||
|
pub(super) fn add_var_drops_regions(
|
||||||
|
typeck: &mut TypeChecker<'_, 'tcx>,
|
||||||
|
local: Local,
|
||||||
|
kind: &Kind<'tcx>,
|
||||||
|
) {
|
||||||
|
debug!("add_var_drops_region(local={:?}, kind={:?}", local, kind);
|
||||||
|
let tcx = typeck.tcx();
|
||||||
|
|
||||||
|
tcx.for_each_free_region(kind, |drop_live_region| {
|
||||||
|
let region_vid = typeck.borrowck_context.universal_regions.to_region_vid(drop_live_region);
|
||||||
|
if let Some(facts) = typeck.borrowck_context.all_facts.as_mut() {
|
||||||
|
facts.var_drops_region.push((local, region_vid));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
@ -1,13 +1,14 @@
|
|||||||
use crate::borrow_check::location::LocationTable;
|
use crate::borrow_check::location::LocationTable;
|
||||||
use crate::borrow_check::nll::region_infer::values::{self, PointIndex, RegionValueElements};
|
use crate::borrow_check::nll::region_infer::values::{self, PointIndex, RegionValueElements};
|
||||||
use crate::borrow_check::nll::type_check::liveness::local_use_map::LocalUseMap;
|
use crate::borrow_check::nll::type_check::liveness::local_use_map::LocalUseMap;
|
||||||
|
use crate::borrow_check::nll::type_check::liveness::polonius;
|
||||||
use crate::borrow_check::nll::type_check::NormalizeLocation;
|
use crate::borrow_check::nll::type_check::NormalizeLocation;
|
||||||
use crate::borrow_check::nll::type_check::TypeChecker;
|
use crate::borrow_check::nll::type_check::TypeChecker;
|
||||||
use crate::dataflow::indexes::MovePathIndex;
|
use crate::dataflow::indexes::MovePathIndex;
|
||||||
use crate::dataflow::move_paths::MoveData;
|
use crate::dataflow::move_paths::MoveData;
|
||||||
use crate::dataflow::{FlowAtLocation, FlowsAtLocation, MaybeInitializedPlaces};
|
use crate::dataflow::{FlowAtLocation, FlowsAtLocation, MaybeInitializedPlaces};
|
||||||
use rustc::infer::canonical::QueryRegionConstraints;
|
use rustc::infer::canonical::QueryRegionConstraints;
|
||||||
use rustc::mir::{BasicBlock, ConstraintCategory, Local, Location, Body};
|
use rustc::mir::{BasicBlock, Body, ConstraintCategory, Local, Location};
|
||||||
use rustc::traits::query::dropck_outlives::DropckOutlivesResult;
|
use rustc::traits::query::dropck_outlives::DropckOutlivesResult;
|
||||||
use rustc::traits::query::type_op::outlives::DropckOutlives;
|
use rustc::traits::query::type_op::outlives::DropckOutlives;
|
||||||
use rustc::traits::query::type_op::TypeOp;
|
use rustc::traits::query::type_op::TypeOp;
|
||||||
@ -130,6 +131,12 @@ impl LivenessResults<'me, 'typeck, 'flow, 'tcx> {
|
|||||||
for local in live_locals {
|
for local in live_locals {
|
||||||
self.reset_local_state();
|
self.reset_local_state();
|
||||||
self.add_defs_for(local);
|
self.add_defs_for(local);
|
||||||
|
|
||||||
|
// FIXME: this is temporary until we can generate our own initialization
|
||||||
|
if self.cx.typeck.borrowck_context.all_facts.is_some() {
|
||||||
|
self.add_polonius_var_initialized_on_exit_for(local)
|
||||||
|
}
|
||||||
|
|
||||||
self.compute_use_live_points_for(local);
|
self.compute_use_live_points_for(local);
|
||||||
self.compute_drop_live_points_for(local);
|
self.compute_drop_live_points_for(local);
|
||||||
|
|
||||||
@ -150,6 +157,63 @@ impl LivenessResults<'me, 'typeck, 'flow, 'tcx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WARNING: panics if self.cx.typeck.borrowck_context.all_facts != None
|
||||||
|
//
|
||||||
|
// FIXME: this analysis (the initialization tracking) should be
|
||||||
|
// done in Polonius, but isn't yet.
|
||||||
|
fn add_polonius_var_initialized_on_exit_for(&mut self, local: Local) {
|
||||||
|
let move_path = self.cx.move_data.rev_lookup.find_local(local);
|
||||||
|
let facts = self.cx.typeck.borrowck_context.all_facts.as_mut().unwrap();
|
||||||
|
for block in self.cx.body.basic_blocks().indices() {
|
||||||
|
debug!("polonius: generating initialization facts for {:?} in {:?}", local, block);
|
||||||
|
|
||||||
|
// iterate through the block, applying the effects of each statement
|
||||||
|
// up to and including location, and populate `var_initialized_on_exit`
|
||||||
|
self.cx.flow_inits.reset_to_entry_of(block);
|
||||||
|
let start_location = Location { block, statement_index: 0 };
|
||||||
|
self.cx.flow_inits.apply_local_effect(start_location);
|
||||||
|
|
||||||
|
for statement_index in 0..self.cx.body[block].statements.len() {
|
||||||
|
let current_location = Location { block, statement_index };
|
||||||
|
|
||||||
|
self.cx.flow_inits.reconstruct_statement_effect(current_location);
|
||||||
|
|
||||||
|
// statement has not yet taken effect:
|
||||||
|
if self.cx.flow_inits.has_any_child_of(move_path).is_some() {
|
||||||
|
facts
|
||||||
|
.var_initialized_on_exit
|
||||||
|
.push((local, self.cx.location_table.start_index(current_location)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// statement has now taken effect
|
||||||
|
self.cx.flow_inits.apply_local_effect(current_location);
|
||||||
|
|
||||||
|
if self.cx.flow_inits.has_any_child_of(move_path).is_some() {
|
||||||
|
facts
|
||||||
|
.var_initialized_on_exit
|
||||||
|
.push((local, self.cx.location_table.mid_index(current_location)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let terminator_location = self.cx.body.terminator_loc(block);
|
||||||
|
|
||||||
|
if self.cx.flow_inits.has_any_child_of(move_path).is_some() {
|
||||||
|
facts
|
||||||
|
.var_initialized_on_exit
|
||||||
|
.push((local, self.cx.location_table.start_index(terminator_location)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the effects of the terminator and push it if needed
|
||||||
|
self.cx.flow_inits.reset_to_exit_of(block);
|
||||||
|
|
||||||
|
if self.cx.flow_inits.has_any_child_of(move_path).is_some() {
|
||||||
|
facts
|
||||||
|
.var_initialized_on_exit
|
||||||
|
.push((local, self.cx.location_table.mid_index(terminator_location)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Clear the value of fields that are "per local variable".
|
/// Clear the value of fields that are "per local variable".
|
||||||
fn reset_local_state(&mut self) {
|
fn reset_local_state(&mut self) {
|
||||||
self.defs.clear();
|
self.defs.clear();
|
||||||
@ -211,6 +275,11 @@ impl LivenessResults<'me, 'typeck, 'flow, 'tcx> {
|
|||||||
debug_assert_eq!(self.cx.body.terminator_loc(location.block), location,);
|
debug_assert_eq!(self.cx.body.terminator_loc(location.block), location,);
|
||||||
|
|
||||||
if self.cx.initialized_at_terminator(location.block, mpi) {
|
if self.cx.initialized_at_terminator(location.block, mpi) {
|
||||||
|
// FIXME: this analysis (the initialization tracking) should be
|
||||||
|
// done in Polonius, but isn't yet.
|
||||||
|
if let Some(facts) = self.cx.typeck.borrowck_context.all_facts {
|
||||||
|
facts.var_drop_used.push((local, self.cx.location_table.mid_index(location)));
|
||||||
|
}
|
||||||
if self.drop_live_at.insert(drop_point) {
|
if self.drop_live_at.insert(drop_point) {
|
||||||
self.drop_locations.push(location);
|
self.drop_locations.push(location);
|
||||||
self.stack.push(drop_point);
|
self.stack.push(drop_point);
|
||||||
@ -487,6 +556,8 @@ impl LivenessContext<'_, '_, '_, 'tcx> {
|
|||||||
live_at,
|
live_at,
|
||||||
self.location_table,
|
self.location_table,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
polonius::add_var_drops_regions(&mut self.typeck, dropped_local, &kind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,14 +576,15 @@ impl LivenessContext<'_, '_, '_, 'tcx> {
|
|||||||
|
|
||||||
let tcx = typeck.tcx();
|
let tcx = typeck.tcx();
|
||||||
tcx.for_each_free_region(&value, |live_region| {
|
tcx.for_each_free_region(&value, |live_region| {
|
||||||
let live_region_vid = typeck.borrowck_context
|
let live_region_vid =
|
||||||
.universal_regions
|
typeck.borrowck_context.universal_regions.to_region_vid(live_region);
|
||||||
.to_region_vid(live_region);
|
typeck
|
||||||
typeck.borrowck_context
|
.borrowck_context
|
||||||
.constraints
|
.constraints
|
||||||
.liveness_constraints
|
.liveness_constraints
|
||||||
.add_elements(live_region_vid, live_at);
|
.add_elements(live_region_vid, live_at);
|
||||||
|
|
||||||
|
// FIXME: remove this when we can generate our own region-live-at reliably
|
||||||
if let Some(facts) = typeck.borrowck_context.all_facts {
|
if let Some(facts) = typeck.borrowck_context.all_facts {
|
||||||
for point in live_at.iter() {
|
for point in live_at.iter() {
|
||||||
let loc = elements.to_location(point);
|
let loc = elements.to_location(point);
|
||||||
|
Loading…
Reference in New Issue
Block a user