about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRémy Rakic <remy.rakic+github@gmail.com>2025-06-26 15:29:03 +0000
committerRémy Rakic <remy.rakic+github@gmail.com>2025-08-08 15:14:51 +0000
commit9badbdf5f9272bf42699edabc14735a1ab42e3d7 (patch)
tree7539d64dee868fb8e10e8982f3bdae40fc203dc9
parent48ebae9cef6fdb1af495e692a0811634f308e447 (diff)
downloadrust-9badbdf5f9272bf42699edabc14735a1ab42e3d7.tar.gz
rust-9badbdf5f9272bf42699edabc14735a1ab42e3d7.zip
add cursor-like example that works
This is an example similar to the linked-list cursor examples
where the alpha shows the same imprecision as NLLs, but that can work due to
the loans not being live after the loop, or the constraint graph being
simple enough that the cfg/subset relationships are the same for
reachability and liveness.
-rw-r--r--tests/ui/nll/polonius/iterating-updating-mutref.nll.stderr15
-rw-r--r--tests/ui/nll/polonius/iterating-updating-mutref.rs87
2 files changed, 102 insertions, 0 deletions
diff --git a/tests/ui/nll/polonius/iterating-updating-mutref.nll.stderr b/tests/ui/nll/polonius/iterating-updating-mutref.nll.stderr
new file mode 100644
index 00000000000..941c736d8d2
--- /dev/null
+++ b/tests/ui/nll/polonius/iterating-updating-mutref.nll.stderr
@@ -0,0 +1,15 @@
+error[E0499]: cannot borrow `self.buf_read` as mutable more than once at a time
+  --> $DIR/iterating-updating-mutref.rs:61:23
+   |
+LL |     pub fn next<'a>(&'a mut self) -> &'a str {
+   |                 -- lifetime `'a` defined here
+LL |         loop {
+LL |             let buf = self.buf_read.fill_buf();
+   |                       ^^^^^^^^^^^^^ `self.buf_read` was mutably borrowed here in the previous iteration of the loop
+LL |             if let Some(s) = decode(buf) {
+LL |                 return s;
+   |                        - returning this value requires that `self.buf_read` is borrowed for `'a`
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0499`.
diff --git a/tests/ui/nll/polonius/iterating-updating-mutref.rs b/tests/ui/nll/polonius/iterating-updating-mutref.rs
new file mode 100644
index 00000000000..a315bf66279
--- /dev/null
+++ b/tests/ui/nll/polonius/iterating-updating-mutref.rs
@@ -0,0 +1,87 @@
+// These are some examples of iterating through and updating a mutable ref, similar in spirit to the
+// linked-list-like pattern of #46859/#48001 where the polonius alpha analysis shows imprecision,
+// unlike the datalog implementation.
+//
+// They differ in that after the loans prior to the loop are either not live after the loop, or with
+// control flow and outlives relationships that are simple enough for the reachability
+// approximation. They're thus accepted by the alpha analysis, like NLLs did for the simplest cases
+// of flow-sensitivity.
+
+//@ ignore-compare-mode-polonius (explicit revisions)
+//@ revisions: nll polonius legacy
+//@ [nll] known-bug: #46859
+//@ [polonius] check-pass
+//@ [polonius] compile-flags: -Z polonius=next
+//@ [legacy] check-pass
+//@ [legacy] compile-flags: -Z polonius=legacy
+
+// The #46859 OP
+struct List<T> {
+    value: T,
+    next: Option<Box<List<T>>>,
+}
+
+fn to_refs<T>(mut list: &mut List<T>) -> Vec<&mut T> {
+    let mut result = vec![];
+    loop {
+        result.push(&mut list.value);
+        if let Some(n) = list.next.as_mut() {
+            list = n;
+        } else {
+            return result;
+        }
+    }
+}
+
+// A similar construction, where paths in the constraint graph are also clearly terminating, so it's
+// fine even for NLLs.
+fn to_refs2<T>(mut list: &mut List<T>) -> Vec<&mut T> {
+    let mut result = vec![];
+    loop {
+        result.push(&mut list.value);
+        if let Some(n) = list.next.as_mut() {
+            list = n;
+        } else {
+            break;
+        }
+    }
+
+    result
+}
+
+// Another MCVE from the same issue, but was rejected by NLLs.
+pub struct Decoder {
+    buf_read: BufRead,
+}
+
+impl Decoder {
+    // NLLs fail here
+    pub fn next<'a>(&'a mut self) -> &'a str {
+        loop {
+            let buf = self.buf_read.fill_buf();
+            if let Some(s) = decode(buf) {
+                return s;
+            }
+            // loop to get more input data
+
+            // At this point `buf` is not used anymore.
+            // With NLL I would expect the borrow to end here,
+            // such that `self.buf_read` is not borrowed anymore
+            // by the time we start the next loop iteration.
+        }
+    }
+}
+
+struct BufRead;
+
+impl BufRead {
+    fn fill_buf(&mut self) -> &[u8] {
+        unimplemented!()
+    }
+}
+
+fn decode(_: &[u8]) -> Option<&str> {
+    unimplemented!()
+}
+
+fn main() {}