Skip to content

Commit

Permalink
Rollup merge of #72540 - davidtwco:issue-67552-mono-collector-compari…
Browse files Browse the repository at this point in the history
…son, r=varkor

mir: adjust conditional in recursion limit check

Fixes #67552.

This PR adjusts the condition used in the recursion limit check of
the monomorphization collector, from `>` to `>=`.

In #67552, the test case had infinite indirect recursion, repeating a
handful of functions (from the perspective of the monomorphization
collector): `rec` -> `identity` -> `Iterator::count` -> `Iterator::fold`
-> `Iterator::next` -> `rec`.

During this process, `resolve_associated_item` was invoked for
`Iterator::fold` (during the construction of an `Instance`), and
ICE'd due to substitutions needing inference. However, previous
iterations of this recursion would have called this function for
`Iterator::fold` - and did! - and succeeded in doing so (trivially
checkable from debug logging, `()` is present where `_` is in the substs
of the failing execution).

The expected outcome of this test case would be a recursion limit error
(which is present when the `identity` fn indirection is removed), and
the recursion depth of `rec` is increasing (other functions finish
collecting their neighbours and thus have their recursion depths reset).

When the ICE occurs, the recursion depth of `rec` is 256 (which matches
the recursion limit), which suggests perhaps that a different part of
the compiler is using a `>=` comparison and returning a different result
on this recursion rather than what it returned in every previous
recursion, thus stopping the monomorphization collector from reporting
an error on the next recursion, where `recursion_depth_of_rec > 256`
would have been true.

With grep and some educated guesses, we can determine that
the recursion limit check at line 818 in
`src/librustc_trait_selection/traits/project.rs` is the other check that
is using a different comparison. Modifying either comparison to be `>` or
`>=` respectively will fix the error, but changing the monomorphization
collector produces the nicer error.
  • Loading branch information
RalfJung authored May 30, 2020
2 parents 43ae54d + a54ed87 commit f96e3e2
Show file tree
Hide file tree
Showing 30 changed files with 147 additions and 55 deletions.
2 changes: 1 addition & 1 deletion src/librustc_error_codes/error_codes/E0055.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ recursion limit (which can be set via the `recursion_limit` attribute).
For a somewhat artificial example:

```compile_fail,E0055
#![recursion_limit="5"]
#![recursion_limit="4"]
struct Foo;
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_expand/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::sync::{self, Lrc};
use rustc_errors::{DiagnosticBuilder, ErrorReported};
use rustc_parse::{self, parser, MACRO_ARGUMENTS};
use rustc_session::parse::ParseSess;
use rustc_session::{parse::ParseSess, Limit};
use rustc_span::def_id::DefId;
use rustc_span::edition::Edition;
use rustc_span::hygiene::{AstPass, ExpnData, ExpnId, ExpnKind};
Expand Down Expand Up @@ -941,7 +941,7 @@ pub struct ExpansionData {
pub struct ExtCtxt<'a> {
pub parse_sess: &'a ParseSess,
pub ecfg: expand::ExpansionConfig<'a>,
pub reduced_recursion_limit: Option<usize>,
pub reduced_recursion_limit: Option<Limit>,
pub root_path: PathBuf,
pub resolver: &'a mut dyn Resolver,
pub current_expansion: ExpansionData,
Expand Down
7 changes: 4 additions & 3 deletions src/librustc_expand/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use rustc_parse::validate_attr;
use rustc_session::lint::builtin::UNUSED_DOC_COMMENTS;
use rustc_session::lint::BuiltinLintDiagnostics;
use rustc_session::parse::{feature_err, ParseSess};
use rustc_session::Limit;
use rustc_span::source_map::respan;
use rustc_span::symbol::{sym, Ident, Symbol};
use rustc_span::{FileName, Span, DUMMY_SP};
Expand Down Expand Up @@ -664,7 +665,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
) -> ExpandResult<AstFragment, Invocation> {
let recursion_limit =
self.cx.reduced_recursion_limit.unwrap_or(self.cx.ecfg.recursion_limit);
if self.cx.current_expansion.depth > recursion_limit {
if !recursion_limit.value_within_limit(self.cx.current_expansion.depth) {
if self.cx.reduced_recursion_limit.is_none() {
self.error_recursion_limit_reached();
}
Expand Down Expand Up @@ -1784,7 +1785,7 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
pub struct ExpansionConfig<'feat> {
pub crate_name: String,
pub features: Option<&'feat Features>,
pub recursion_limit: usize,
pub recursion_limit: Limit,
pub trace_mac: bool,
pub should_test: bool, // If false, strip `#[test]` nodes
pub keep_macs: bool,
Expand All @@ -1795,7 +1796,7 @@ impl<'feat> ExpansionConfig<'feat> {
ExpansionConfig {
crate_name,
features: None,
recursion_limit: 1024,
recursion_limit: Limit::new(1024),
trace_mac: false,
should_test: false,
keep_macs: false,
Expand Down
8 changes: 4 additions & 4 deletions src/librustc_middle/middle/limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use crate::bug;
use rustc_ast::ast;
use rustc_data_structures::sync::OnceCell;
use rustc_session::Session;
use rustc_session::{Limit, Session};
use rustc_span::symbol::{sym, Symbol};

use std::num::IntErrorKind;
Expand All @@ -22,7 +22,7 @@ pub fn update_limits(sess: &Session, krate: &ast::Crate) {
fn update_limit(
sess: &Session,
krate: &ast::Crate,
limit: &OnceCell<usize>,
limit: &OnceCell<Limit>,
name: Symbol,
default: usize,
) {
Expand All @@ -34,7 +34,7 @@ fn update_limit(
if let Some(s) = attr.value_str() {
match s.as_str().parse() {
Ok(n) => {
limit.set(n).unwrap();
limit.set(Limit::new(n)).unwrap();
return;
}
Err(e) => {
Expand Down Expand Up @@ -62,5 +62,5 @@ fn update_limit(
}
}
}
limit.set(default).unwrap();
limit.set(Limit::new(default)).unwrap();
}
3 changes: 1 addition & 2 deletions src/librustc_middle/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,9 @@ fn layout_raw<'tcx>(
query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>,
) -> Result<&'tcx Layout, LayoutError<'tcx>> {
ty::tls::with_related_context(tcx, move |icx| {
let rec_limit = tcx.sess.recursion_limit.get().copied().unwrap();
let (param_env, ty) = query.into_parts();

if icx.layout_depth > rec_limit {
if !tcx.sess.recursion_limit().value_within_limit(icx.layout_depth) {
tcx.sess.fatal(&format!("overflow representing the type `{}`", ty));
}

Expand Down
5 changes: 3 additions & 2 deletions src/librustc_mir/const_eval/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use rustc_data_structures::fx::FxHashMap;
use rustc_ast::ast::Mutability;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::AssertMessage;
use rustc_session::Limit;
use rustc_span::symbol::Symbol;

use crate::interpret::{
Expand Down Expand Up @@ -109,8 +110,8 @@ pub struct MemoryExtra {
}

impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> {
pub(super) fn new(const_eval_limit: usize) -> Self {
CompileTimeInterpreter { steps_remaining: const_eval_limit, stack: Vec::new() }
pub(super) fn new(const_eval_limit: Limit) -> Self {
CompileTimeInterpreter { steps_remaining: const_eval_limit.0, stack: Vec::new() }
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/interpret/eval_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
M::after_stack_push(self)?;
info!("ENTERING({}) {}", self.frame_idx(), self.frame().instance);

if self.stack().len() > self.tcx.sess.recursion_limit() {
if !self.tcx.sess.recursion_limit().value_within_limit(self.stack().len()) {
throw_exhaust!(StackFrameLimitReached)
} else {
Ok(())
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_mir/monomorphize/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ fn check_recursion_limit<'tcx>(
// Code that needs to instantiate the same function recursively
// more than the recursion limit is assumed to be causing an
// infinite expansion.
if adjusted_recursion_depth > tcx.sess.recursion_limit() {
if !tcx.sess.recursion_limit().value_within_limit(adjusted_recursion_depth) {
let error = format!("reached the recursion limit while instantiating `{}`", instance);
if let Some(def_id) = def_id.as_local() {
let hir_id = tcx.hir().as_local_hir_id(def_id);
Expand Down Expand Up @@ -463,7 +463,7 @@ fn check_type_length_limit<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
// which means that rustc basically hangs.
//
// Bail out in these cases to avoid that bad user experience.
if type_length > tcx.sess.type_length_limit() {
if !tcx.sess.type_length_limit().value_within_limit(type_length) {
// The instance name is already known to be too long for rustc.
// Show only the first and last 32 characters to avoid blasting
// the user's terminal with thousands of lines of type-name.
Expand Down
54 changes: 48 additions & 6 deletions src/librustc_session/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ use rustc_target::spec::{Target, TargetTriple, TlsModel};

use std::cell::{self, RefCell};
use std::env;
use std::fmt;
use std::io::Write;
use std::num::NonZeroU32;
use std::ops::{Div, Mul};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
Expand All @@ -55,6 +57,46 @@ pub enum CtfeBacktrace {
Immediate,
}

/// New-type wrapper around `usize` for representing limits. Ensures that comparisons against
/// limits are consistent throughout the compiler.
#[derive(Clone, Copy, Debug)]
pub struct Limit(pub usize);

impl Limit {
/// Create a new limit from a `usize`.
pub fn new(value: usize) -> Self {
Limit(value)
}

/// Check that `value` is within the limit. Ensures that the same comparisons are used
/// throughout the compiler, as mismatches can cause ICEs, see #72540.
pub fn value_within_limit(&self, value: usize) -> bool {
value <= self.0
}
}

impl fmt::Display for Limit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

impl Div<usize> for Limit {
type Output = Limit;

fn div(self, rhs: usize) -> Self::Output {
Limit::new(self.0 / rhs)
}
}

impl Mul<usize> for Limit {
type Output = Limit;

fn mul(self, rhs: usize) -> Self::Output {
Limit::new(self.0 * rhs)
}
}

/// Represents the data associated with a compilation
/// session for a single crate.
pub struct Session {
Expand Down Expand Up @@ -89,13 +131,13 @@ pub struct Session {

/// The maximum recursion limit for potentially infinitely recursive
/// operations such as auto-dereference and monomorphization.
pub recursion_limit: OnceCell<usize>,
pub recursion_limit: OnceCell<Limit>,

/// The maximum length of types during monomorphization.
pub type_length_limit: OnceCell<usize>,
pub type_length_limit: OnceCell<Limit>,

/// The maximum blocks a const expression can evaluate.
pub const_eval_limit: OnceCell<usize>,
pub const_eval_limit: OnceCell<Limit>,

incr_comp_session: OneThread<RefCell<IncrCompSession>>,
/// Used for incremental compilation tests. Will only be populated if
Expand Down Expand Up @@ -255,15 +297,15 @@ impl Session {
self.crate_types.set(crate_types).expect("`crate_types` was initialized twice")
}

pub fn recursion_limit(&self) -> usize {
pub fn recursion_limit(&self) -> Limit {
self.recursion_limit.get().copied().unwrap()
}

pub fn type_length_limit(&self) -> usize {
pub fn type_length_limit(&self) -> Limit {
self.type_length_limit.get().copied().unwrap()
}

pub fn const_eval_limit(&self) -> usize {
pub fn const_eval_limit(&self) -> Limit {
self.const_eval_limit.get().copied().unwrap()
}

Expand Down
9 changes: 4 additions & 5 deletions src/librustc_trait_selection/traits/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,10 +333,10 @@ impl<'a, 'b, 'tcx> TypeFolder<'tcx> for AssocTypeNormalizer<'a, 'b, 'tcx> {

Reveal::All => {
let recursion_limit = self.tcx().sess.recursion_limit();
if self.depth >= recursion_limit {
if !recursion_limit.value_within_limit(self.depth) {
let obligation = Obligation::with_depth(
self.cause.clone(),
recursion_limit,
recursion_limit.0,
self.param_env,
ty,
);
Expand Down Expand Up @@ -522,7 +522,7 @@ fn opt_normalize_projection_type<'a, 'b, 'tcx>(
// But for now, let's classify this as an overflow:
let recursion_limit = selcx.tcx().sess.recursion_limit();
let obligation =
Obligation::with_depth(cause, recursion_limit, param_env, projection_ty);
Obligation::with_depth(cause, recursion_limit.0, param_env, projection_ty);
selcx.infcx().report_overflow_error(&obligation, false);
}
Err(ProjectionCacheEntry::NormalizedTy(ty)) => {
Expand Down Expand Up @@ -814,8 +814,7 @@ fn project_type<'cx, 'tcx>(
) -> Result<ProjectedTy<'tcx>, ProjectionTyError<'tcx>> {
debug!("project(obligation={:?})", obligation);

let recursion_limit = selcx.tcx().sess.recursion_limit();
if obligation.recursion_depth >= recursion_limit {
if !selcx.tcx().sess.recursion_limit().value_within_limit(obligation.recursion_depth) {
debug!("project: overflow!");
return Err(ProjectionTyError::TraitSelectionError(SelectionError::Overflow));
}
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_trait_selection/traits/query/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ impl<'cx, 'tcx> TypeFolder<'tcx> for QueryNormalizer<'cx, 'tcx> {

Reveal::All => {
let recursion_limit = self.tcx().sess.recursion_limit();
if self.anon_depth >= recursion_limit {
if !recursion_limit.value_within_limit(self.anon_depth) {
let obligation = Obligation::with_depth(
self.cause.clone(),
recursion_limit,
recursion_limit.0,
self.param_env,
ty,
);
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_trait_selection/traits/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
obligation: &Obligation<'tcx, T>,
error_obligation: &Obligation<'tcx, V>,
) -> Result<(), OverflowError> {
if obligation.recursion_depth >= self.infcx.tcx.sess.recursion_limit() {
if !self.infcx.tcx.sess.recursion_limit().value_within_limit(obligation.recursion_depth) {
match self.query_mode {
TraitQueryMode::Standard => {
self.infcx().report_overflow_error(error_obligation, true);
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_traits/dropck_outlives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ fn dtorck_constraint_for_ty<'tcx>(
) -> Result<(), NoSolution> {
debug!("dtorck_constraint_for_ty({:?}, {:?}, {:?}, {:?})", span, for_ty, depth, ty);

if depth >= tcx.sess.recursion_limit() {
if !tcx.sess.recursion_limit().value_within_limit(depth) {
constraints.overflows.push(ty);
return Ok(());
}
Expand Down
5 changes: 3 additions & 2 deletions src/librustc_ty/needs_drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use rustc_hir::def_id::DefId;
use rustc_middle::ty::subst::Subst;
use rustc_middle::ty::util::{needs_drop_components, AlwaysRequiresDrop};
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_session::Limit;
use rustc_span::DUMMY_SP;

type NeedsDropResult<T> = Result<T, AlwaysRequiresDrop>;
Expand All @@ -30,7 +31,7 @@ struct NeedsDropTypes<'tcx, F> {
/// if it needs drop. If the result depends on whether some other types
/// need drop we push them onto the stack.
unchecked_tys: Vec<(Ty<'tcx>, usize)>,
recursion_limit: usize,
recursion_limit: Limit,
adt_components: F,
}

Expand Down Expand Up @@ -66,7 +67,7 @@ where
let tcx = self.tcx;

while let Some((ty, level)) = self.unchecked_tys.pop() {
if level > self.recursion_limit {
if !self.recursion_limit.value_within_limit(level) {
// Not having a `Span` isn't great. But there's hopefully some other
// recursion limit error as well.
tcx.sess.span_err(
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_typeck/check/autoderef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl<'a, 'tcx> Iterator for Autoderef<'a, 'tcx> {
return Some((self.cur_ty, 0));
}

if self.steps.len() >= tcx.sess.recursion_limit() {
if !tcx.sess.recursion_limit().value_within_limit(self.steps.len()) {
if !self.silence_errors {
report_autoderef_recursion_limit_error(tcx, self.span, self.cur_ty);
}
Expand Down
3 changes: 2 additions & 1 deletion src/test/ui/did_you_mean/recursion_limit.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error[E0275]: overflow evaluating the requirement `J: std::marker::Send`
error[E0275]: overflow evaluating the requirement `K: std::marker::Send`
--> $DIR/recursion_limit.rs:34:5
|
LL | fn is_send<T:Send>() { }
Expand All @@ -8,6 +8,7 @@ LL | is_send::<A>();
| ^^^^^^^^^^^^
|
= help: consider adding a `#![recursion_limit="20"]` attribute to your crate (`recursion_limit`)
= note: required because it appears within the type `J`
= note: required because it appears within the type `I`
= note: required because it appears within the type `H`
= note: required because it appears within the type `G`
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/did_you_mean/recursion_limit_deref.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error[E0055]: reached the recursion limit while auto-dereferencing `I`
error[E0055]: reached the recursion limit while auto-dereferencing `J`
--> $DIR/recursion_limit_deref.rs:50:22
|
LL | let x: &Bottom = &t;
Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/dropck/dropck_no_diverge_on_nonregular_1.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ error[E0320]: overflow while adding drop-check rules for FingerTree<i32>
LL | let ft =
| ^^
|
= note: overflowed on FingerTree<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<i32>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
= note: overflowed on FingerTree<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<i32>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

error[E0320]: overflow while adding drop-check rules for FingerTree<i32>
--> $DIR/dropck_no_diverge_on_nonregular_1.rs:25:9
|
LL | FingerTree::Single(1);
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: overflowed on FingerTree<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<i32>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
= note: overflowed on FingerTree<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<Node<i32>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

error: aborting due to 2 previous errors

Loading

0 comments on commit f96e3e2

Please sign in to comment.