From 3ece6061b4af39c331b8c2768d278af04a62ab97 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Sun, 18 Apr 2021 15:04:59 -0700 Subject: [PATCH 1/3] DRAFT: coverage of async function bodies should match non-async The initial commit demonstrates the issue, but the fix is not yet implemented. Once corrected... Fixes: #83985 --- .../expected_show_coverage.async.txt | 2 +- .../expected_show_coverage.async2.txt | 124 ++++++++++++++++++ src/test/run-make-fulldeps/coverage/async.rs | 2 +- src/test/run-make-fulldeps/coverage/async2.rs | 78 +++++++++++ 4 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async2.txt create mode 100644 src/test/run-make-fulldeps/coverage/async2.rs diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async.txt index e0a5937c24686..ae9487473d0ca 100644 --- a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async.txt +++ b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async.txt @@ -1,6 +1,6 @@ 1| |#![allow(unused_assignments, dead_code)] 2| | - 3| |// compile-flags: --edition=2018 -C opt-level=1 # fix in rustc_mir/monomorphize/partitioning/mod.rs + 3| |// compile-flags: --edition=2018 -C opt-level=1 4| | 5| 1|async fn c(x: u8) -> u8 { 6| 1| if x == 8 { diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async2.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async2.txt new file mode 100644 index 0000000000000..d1adabe8ebc6d --- /dev/null +++ b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async2.txt @@ -0,0 +1,124 @@ + 1| |// compile-flags: --edition=2018 + 2| | + 3| |use core::{ + 4| | future::Future, + 5| | marker::Send, + 6| | pin::Pin, + 7| |}; + 8| | + 9| 1|fn non_async_func() { + 10| 1| println!("non_async_func was covered"); + 11| 1| let b = true; + 12| 1| if b { + 13| 1| println!("non_async_func println in block"); + 14| 1| } + 15| 1|} + 16| | + 17| |// FIXME(#83985): The auto-generated closure in an async function is failing to include + 18| |// the println!() and `let` assignment lines in the coverage code region(s), as it does in the + 19| |// non-async function above, unless the `println!()` is inside a covered block. + 20| 1|async fn async_func() { + 21| | println!("async_func was covered"); + 22| | let b = true; + 23| 1| if b { + 24| 1| println!("async_func println in block"); + 25| 1| } + ^0 + 26| 1|} + 27| | + 28| |// FIXME(#83985): As above, this async function only has the `println!()` macro call, which is not + 29| |// showing coverage, so the entire async closure _appears_ uncovered; but this is not exactly true. + 30| |// It's only certain kinds of lines and/or their context that results in missing coverage. + 31| 1|async fn async_func_just_println() { + 32| | println!("async_func_just_println was covered"); + 33| |} + 34| | + 35| 1|fn main() { + 36| 1| println!("codecovsample::main"); + 37| 1| + 38| 1| non_async_func(); + 39| 1| + 40| 1| executor::block_on(async_func()); + 41| 1| executor::block_on(async_func_just_println()); + 42| 1| + 43| 1| // let mut future = Box::pin(async_func()); + 44| 1| // executor::block_on(future.as_mut()); + 45| 1| + 46| 1| // let mut future = Box::pin(async_func()); + 47| 1| // executor::block_on(future.as_mut()); + 48| 1| + 49| 1| // let mut future = Box::pin(async_func_just_println()); + 50| 1| // executor::block_on(future.as_mut()); + 51| 1|} + 52| | + 53| |mod executor { + 54| | use core::{ + 55| | future::Future, + 56| | pin::Pin, + 57| | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + 58| | }; + 59| | + 60| 2| pub fn block_on(mut future: F) -> F::Output { + 61| 2| let mut future = unsafe { Pin::new_unchecked(&mut future) }; + 62| 2| use std::hint::unreachable_unchecked; + 63| 2| static VTABLE: RawWakerVTable = RawWakerVTable::new( + 64| 2| |_| unsafe { unreachable_unchecked() }, // clone + ^0 + 65| 2| |_| unsafe { unreachable_unchecked() }, // wake + ^0 + 66| 2| |_| unsafe { unreachable_unchecked() }, // wake_by_ref + ^0 + 67| 2| |_| (), + 68| 2| ); + 69| 2| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + 70| 2| let mut context = Context::from_waker(&waker); + 71| | + 72| | loop { + 73| 2| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + 74| 2| break val; + 75| 0| } + 76| | } + 77| 2| } + ------------------ + | async2::executor::block_on::>: + | 60| 1| pub fn block_on(mut future: F) -> F::Output { + | 61| 1| let mut future = unsafe { Pin::new_unchecked(&mut future) }; + | 62| 1| use std::hint::unreachable_unchecked; + | 63| 1| static VTABLE: RawWakerVTable = RawWakerVTable::new( + | 64| 1| |_| unsafe { unreachable_unchecked() }, // clone + | 65| 1| |_| unsafe { unreachable_unchecked() }, // wake + | 66| 1| |_| unsafe { unreachable_unchecked() }, // wake_by_ref + | 67| 1| |_| (), + | 68| 1| ); + | 69| 1| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + | 70| 1| let mut context = Context::from_waker(&waker); + | 71| | + | 72| | loop { + | 73| 1| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + | 74| 1| break val; + | 75| 0| } + | 76| | } + | 77| 1| } + ------------------ + | async2::executor::block_on::>: + | 60| 1| pub fn block_on(mut future: F) -> F::Output { + | 61| 1| let mut future = unsafe { Pin::new_unchecked(&mut future) }; + | 62| 1| use std::hint::unreachable_unchecked; + | 63| 1| static VTABLE: RawWakerVTable = RawWakerVTable::new( + | 64| 1| |_| unsafe { unreachable_unchecked() }, // clone + | 65| 1| |_| unsafe { unreachable_unchecked() }, // wake + | 66| 1| |_| unsafe { unreachable_unchecked() }, // wake_by_ref + | 67| 1| |_| (), + | 68| 1| ); + | 69| 1| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + | 70| 1| let mut context = Context::from_waker(&waker); + | 71| | + | 72| | loop { + | 73| 1| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + | 74| 1| break val; + | 75| 0| } + | 76| | } + | 77| 1| } + ------------------ + 78| |} + diff --git a/src/test/run-make-fulldeps/coverage/async.rs b/src/test/run-make-fulldeps/coverage/async.rs index 67bf696d0729f..a6e387747068a 100644 --- a/src/test/run-make-fulldeps/coverage/async.rs +++ b/src/test/run-make-fulldeps/coverage/async.rs @@ -1,6 +1,6 @@ #![allow(unused_assignments, dead_code)] -// compile-flags: --edition=2018 -C opt-level=1 # fix in rustc_mir/monomorphize/partitioning/mod.rs +// compile-flags: --edition=2018 -C opt-level=1 async fn c(x: u8) -> u8 { if x == 8 { diff --git a/src/test/run-make-fulldeps/coverage/async2.rs b/src/test/run-make-fulldeps/coverage/async2.rs new file mode 100644 index 0000000000000..0ba7872b35b53 --- /dev/null +++ b/src/test/run-make-fulldeps/coverage/async2.rs @@ -0,0 +1,78 @@ +// compile-flags: --edition=2018 + +use core::{ + future::Future, + marker::Send, + pin::Pin, +}; + +fn non_async_func() { + println!("non_async_func was covered"); + let b = true; + if b { + println!("non_async_func println in block"); + } +} + +// FIXME(#83985): The auto-generated closure in an async function is failing to include +// the println!() and `let` assignment lines in the coverage code region(s), as it does in the +// non-async function above, unless the `println!()` is inside a covered block. +async fn async_func() { + println!("async_func was covered"); + let b = true; + if b { + println!("async_func println in block"); + } +} + +// FIXME(#83985): As above, this async function only has the `println!()` macro call, which is not +// showing coverage, so the entire async closure _appears_ uncovered; but this is not exactly true. +// It's only certain kinds of lines and/or their context that results in missing coverage. +async fn async_func_just_println() { + println!("async_func_just_println was covered"); +} + +fn main() { + println!("codecovsample::main"); + + non_async_func(); + + executor::block_on(async_func()); + executor::block_on(async_func_just_println()); + + // let mut future = Box::pin(async_func()); + // executor::block_on(future.as_mut()); + + // let mut future = Box::pin(async_func()); + // executor::block_on(future.as_mut()); + + // let mut future = Box::pin(async_func_just_println()); + // executor::block_on(future.as_mut()); +} + +mod executor { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + }; + + pub fn block_on(mut future: F) -> F::Output { + let mut future = unsafe { Pin::new_unchecked(&mut future) }; + use std::hint::unreachable_unchecked; + static VTABLE: RawWakerVTable = RawWakerVTable::new( + |_| unsafe { unreachable_unchecked() }, // clone + |_| unsafe { unreachable_unchecked() }, // wake + |_| unsafe { unreachable_unchecked() }, // wake_by_ref + |_| (), + ); + let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + let mut context = Context::from_waker(&waker); + + loop { + if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + break val; + } + } + } +} From 1893721ec4412b4f039426b013c4b298371e6e5a Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Sun, 18 Apr 2021 16:26:18 -0700 Subject: [PATCH 2/3] Fixes the issue with uncovered source in async function bodies The body_span was assumed to be in the Span root context, but this was not the case for async function bodies. --- .../rustc_mir/src/transform/coverage/spans.rs | 4 +- .../expected_show_coverage.async2.txt | 145 ++++++++---------- .../expected_show_coverage.partial_eq.txt | 2 +- src/test/run-make-fulldeps/coverage/async2.rs | 9 -- 4 files changed, 71 insertions(+), 89 deletions(-) diff --git a/compiler/rustc_mir/src/transform/coverage/spans.rs b/compiler/rustc_mir/src/transform/coverage/spans.rs index 324d826b375c1..6b140296a8e76 100644 --- a/compiler/rustc_mir/src/transform/coverage/spans.rs +++ b/compiler/rustc_mir/src/transform/coverage/spans.rs @@ -246,8 +246,8 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> { ) -> Vec { let mut coverage_spans = CoverageSpans { mir_body, - fn_sig_span, - body_span, + fn_sig_span: fn_sig_span.with_ctxt(SyntaxContext::root()), + body_span: body_span.with_ctxt(SyntaxContext::root()), basic_coverage_blocks, sorted_spans_iter: None, refined_spans: Vec::with_capacity(basic_coverage_blocks.num_nodes() * 2), diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async2.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async2.txt index d1adabe8ebc6d..8a445433ab65f 100644 --- a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async2.txt +++ b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async2.txt @@ -18,8 +18,8 @@ 18| |// the println!() and `let` assignment lines in the coverage code region(s), as it does in the 19| |// non-async function above, unless the `println!()` is inside a covered block. 20| 1|async fn async_func() { - 21| | println!("async_func was covered"); - 22| | let b = true; + 21| 1| println!("async_func was covered"); + 22| 1| let b = true; 23| 1| if b { 24| 1| println!("async_func println in block"); 25| 1| } @@ -30,8 +30,8 @@ 29| |// showing coverage, so the entire async closure _appears_ uncovered; but this is not exactly true. 30| |// It's only certain kinds of lines and/or their context that results in missing coverage. 31| 1|async fn async_func_just_println() { - 32| | println!("async_func_just_println was covered"); - 33| |} + 32| 1| println!("async_func_just_println was covered"); + 33| 1|} 34| | 35| 1|fn main() { 36| 1| println!("codecovsample::main"); @@ -40,85 +40,76 @@ 39| 1| 40| 1| executor::block_on(async_func()); 41| 1| executor::block_on(async_func_just_println()); - 42| 1| - 43| 1| // let mut future = Box::pin(async_func()); - 44| 1| // executor::block_on(future.as_mut()); - 45| 1| - 46| 1| // let mut future = Box::pin(async_func()); - 47| 1| // executor::block_on(future.as_mut()); - 48| 1| - 49| 1| // let mut future = Box::pin(async_func_just_println()); - 50| 1| // executor::block_on(future.as_mut()); - 51| 1|} - 52| | - 53| |mod executor { - 54| | use core::{ - 55| | future::Future, - 56| | pin::Pin, - 57| | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, - 58| | }; - 59| | - 60| 2| pub fn block_on(mut future: F) -> F::Output { - 61| 2| let mut future = unsafe { Pin::new_unchecked(&mut future) }; - 62| 2| use std::hint::unreachable_unchecked; - 63| 2| static VTABLE: RawWakerVTable = RawWakerVTable::new( - 64| 2| |_| unsafe { unreachable_unchecked() }, // clone + 42| 1|} + 43| | + 44| |mod executor { + 45| | use core::{ + 46| | future::Future, + 47| | pin::Pin, + 48| | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + 49| | }; + 50| | + 51| 2| pub fn block_on(mut future: F) -> F::Output { + 52| 2| let mut future = unsafe { Pin::new_unchecked(&mut future) }; + 53| 2| use std::hint::unreachable_unchecked; + 54| 2| static VTABLE: RawWakerVTable = RawWakerVTable::new( + 55| 2| |_| unsafe { unreachable_unchecked() }, // clone ^0 - 65| 2| |_| unsafe { unreachable_unchecked() }, // wake + 56| 2| |_| unsafe { unreachable_unchecked() }, // wake ^0 - 66| 2| |_| unsafe { unreachable_unchecked() }, // wake_by_ref + 57| 2| |_| unsafe { unreachable_unchecked() }, // wake_by_ref ^0 - 67| 2| |_| (), - 68| 2| ); - 69| 2| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; - 70| 2| let mut context = Context::from_waker(&waker); - 71| | - 72| | loop { - 73| 2| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { - 74| 2| break val; - 75| 0| } - 76| | } - 77| 2| } + 58| 2| |_| (), + 59| 2| ); + 60| 2| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + 61| 2| let mut context = Context::from_waker(&waker); + 62| | + 63| | loop { + 64| 2| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + 65| 2| break val; + 66| 0| } + 67| | } + 68| 2| } ------------------ | async2::executor::block_on::>: - | 60| 1| pub fn block_on(mut future: F) -> F::Output { - | 61| 1| let mut future = unsafe { Pin::new_unchecked(&mut future) }; - | 62| 1| use std::hint::unreachable_unchecked; - | 63| 1| static VTABLE: RawWakerVTable = RawWakerVTable::new( - | 64| 1| |_| unsafe { unreachable_unchecked() }, // clone - | 65| 1| |_| unsafe { unreachable_unchecked() }, // wake - | 66| 1| |_| unsafe { unreachable_unchecked() }, // wake_by_ref - | 67| 1| |_| (), - | 68| 1| ); - | 69| 1| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; - | 70| 1| let mut context = Context::from_waker(&waker); - | 71| | - | 72| | loop { - | 73| 1| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { - | 74| 1| break val; - | 75| 0| } - | 76| | } - | 77| 1| } + | 51| 1| pub fn block_on(mut future: F) -> F::Output { + | 52| 1| let mut future = unsafe { Pin::new_unchecked(&mut future) }; + | 53| 1| use std::hint::unreachable_unchecked; + | 54| 1| static VTABLE: RawWakerVTable = RawWakerVTable::new( + | 55| 1| |_| unsafe { unreachable_unchecked() }, // clone + | 56| 1| |_| unsafe { unreachable_unchecked() }, // wake + | 57| 1| |_| unsafe { unreachable_unchecked() }, // wake_by_ref + | 58| 1| |_| (), + | 59| 1| ); + | 60| 1| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + | 61| 1| let mut context = Context::from_waker(&waker); + | 62| | + | 63| | loop { + | 64| 1| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + | 65| 1| break val; + | 66| 0| } + | 67| | } + | 68| 1| } ------------------ | async2::executor::block_on::>: - | 60| 1| pub fn block_on(mut future: F) -> F::Output { - | 61| 1| let mut future = unsafe { Pin::new_unchecked(&mut future) }; - | 62| 1| use std::hint::unreachable_unchecked; - | 63| 1| static VTABLE: RawWakerVTable = RawWakerVTable::new( - | 64| 1| |_| unsafe { unreachable_unchecked() }, // clone - | 65| 1| |_| unsafe { unreachable_unchecked() }, // wake - | 66| 1| |_| unsafe { unreachable_unchecked() }, // wake_by_ref - | 67| 1| |_| (), - | 68| 1| ); - | 69| 1| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; - | 70| 1| let mut context = Context::from_waker(&waker); - | 71| | - | 72| | loop { - | 73| 1| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { - | 74| 1| break val; - | 75| 0| } - | 76| | } - | 77| 1| } + | 51| 1| pub fn block_on(mut future: F) -> F::Output { + | 52| 1| let mut future = unsafe { Pin::new_unchecked(&mut future) }; + | 53| 1| use std::hint::unreachable_unchecked; + | 54| 1| static VTABLE: RawWakerVTable = RawWakerVTable::new( + | 55| 1| |_| unsafe { unreachable_unchecked() }, // clone + | 56| 1| |_| unsafe { unreachable_unchecked() }, // wake + | 57| 1| |_| unsafe { unreachable_unchecked() }, // wake_by_ref + | 58| 1| |_| (), + | 59| 1| ); + | 60| 1| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + | 61| 1| let mut context = Context::from_waker(&waker); + | 62| | + | 63| | loop { + | 64| 1| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + | 65| 1| break val; + | 66| 0| } + | 67| | } + | 68| 1| } ------------------ - 78| |} + 69| |} diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt index 4e4dde46b344b..9d3600822c531 100644 --- a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt +++ b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt @@ -2,7 +2,7 @@ 2| |// structure of this test. 3| | 4| 2|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] - ^0 ^0 ^0 ^0 ^1 ^1 ^0^0 + ^0 ^0 ^0 ^1 ^0 ------------------ | Unexecuted instantiation: ::ne ------------------ diff --git a/src/test/run-make-fulldeps/coverage/async2.rs b/src/test/run-make-fulldeps/coverage/async2.rs index 0ba7872b35b53..6171d95ff5543 100644 --- a/src/test/run-make-fulldeps/coverage/async2.rs +++ b/src/test/run-make-fulldeps/coverage/async2.rs @@ -39,15 +39,6 @@ fn main() { executor::block_on(async_func()); executor::block_on(async_func_just_println()); - - // let mut future = Box::pin(async_func()); - // executor::block_on(future.as_mut()); - - // let mut future = Box::pin(async_func()); - // executor::block_on(future.as_mut()); - - // let mut future = Box::pin(async_func_just_println()); - // executor::block_on(future.as_mut()); } mod executor { From 5d8d67f746a2955635de8c2168c972b4d6e7eb58 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Mon, 19 Apr 2021 12:30:55 -0700 Subject: [PATCH 3/3] compute fn_sig span from body call_site, and use body ctxt, not root --- compiler/rustc_mir/src/transform/coverage/spans.rs | 13 +++++++++---- .../expected_show_coverage.partial_eq.txt | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_mir/src/transform/coverage/spans.rs b/compiler/rustc_mir/src/transform/coverage/spans.rs index 6b140296a8e76..067e1001def88 100644 --- a/compiler/rustc_mir/src/transform/coverage/spans.rs +++ b/compiler/rustc_mir/src/transform/coverage/spans.rs @@ -11,7 +11,7 @@ use rustc_middle::mir::{ use rustc_middle::ty::TyCtxt; use rustc_span::source_map::original_sp; -use rustc_span::{BytePos, Span, SyntaxContext}; +use rustc_span::{BytePos, Span}; use std::cmp::Ordering; @@ -246,8 +246,8 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> { ) -> Vec { let mut coverage_spans = CoverageSpans { mir_body, - fn_sig_span: fn_sig_span.with_ctxt(SyntaxContext::root()), - body_span: body_span.with_ctxt(SyntaxContext::root()), + fn_sig_span: fn_sig_source_span(fn_sig_span, body_span), + body_span, basic_coverage_blocks, sorted_spans_iter: None, refined_spans: Vec::with_capacity(basic_coverage_blocks.num_nodes() * 2), @@ -731,8 +731,13 @@ pub(super) fn filtered_terminator_span( } } +#[inline] +fn fn_sig_source_span(fn_sig_span: Span, body_span: Span) -> Span { + original_sp(fn_sig_span, body_span).with_ctxt(body_span.ctxt()) +} + #[inline] fn function_source_span(span: Span, body_span: Span) -> Span { - let span = original_sp(span, body_span).with_ctxt(SyntaxContext::root()); + let span = original_sp(span, body_span).with_ctxt(body_span.ctxt()); if body_span.contains(span) { span } else { body_span } } diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt index 9d3600822c531..4e4dde46b344b 100644 --- a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt +++ b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt @@ -2,7 +2,7 @@ 2| |// structure of this test. 3| | 4| 2|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] - ^0 ^0 ^0 ^1 ^0 + ^0 ^0 ^0 ^0 ^1 ^1 ^0^0 ------------------ | Unexecuted instantiation: ::ne ------------------