Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Certain Polonius loops do not work with polonius_loop! #11

Open
JohnScience opened this issue Oct 11, 2023 · 2 comments
Open

Certain Polonius loops do not work with polonius_loop! #11

JohnScience opened this issue Oct 11, 2023 · 2 comments
Labels
question Further information is requested

Comments

@JohnScience
Copy link

JohnScience commented Oct 11, 2023

First of all, hi Yandros!

Getting back the the problem, ...

Code:

fn response<'a, 'b>(responses_list: &'a mut Vec<Vec<Response>>) -> Option<&'b mut Response>
where
    'a: 'b,
{
    loop {
        if let Some(response) = responses_list.last_mut()?.last_mut() {
            return Some(response);
        }
        let _ = responses_list.pop();
    }
}

I try to rewrite it to make it more amicable to the polonius_loop! macro but it has the problem that responses_list has to be mutable.

Latest attempt

fn response<'a, 'b>(responses_list: &'a mut Vec<Vec<Response>>) -> Option<&'b mut Response>
where
    'a: 'b,
{
    polonius_loop!(|responses_list| -> Option<&'polonius mut Response> {
        match responses_list.last_mut() {
            Some(responses) => {
                if let Some(response) = responses.last_mut() {
                    polonius_return!(Some(response));
                }
                let _ = responses_list.pop();
            }
            None => polonius_return!(None),
        }
    });
    unreachable!();
}

Deeper look

I also tried to uncover what's the problem myself by looking at the expansion, ...

Before the cleanup:

fn response<'a, 'b>(responses_list: &'a mut Vec<Vec<Response>>) -> Option<&'b mut Response>
where
    'a: 'b,
{
   loop {
       match $crate::polonius::<
           dyn for<'polonius> $crate::WithLifetime<
               'polonius,
               T = $crate::::Dependent<Option<&'polonius mut Response>>,
           >,
           _,
           _,
           _,
       >(&mut *responses_list, |mut responses_list: &mut _| {
           #[allow(clippy::self_assignment)]
           {
               responses_list = responses_list;
           }
           let () = if true {
               match responses_list.last_mut() {
                   Some(responses) => {
                       if let Some(response) = responses.last_mut() {
                           return $crate::::core::result::Result::Ok(
                               $crate::::Dependent::Return((Some(response))),
                           );
                       }
                       let _ = responses_list.pop();
                   }
                   None => {
                       return $crate::::core::result::Result::Ok($crate::::Dependent::Return(
                           None,
                       ))
                   }
               }
           } else {
               $crate::::core::option::Option::None.unwrap()
           };
           return $crate::::core::result::Result::Err(
               $crate::::core::ops::ControlFlow::<_>::Continue(()),
           );
       }) {
           $crate::::core::result::Result::Ok(dependent) => match dependent {
               $crate::::Dependent::Return(return_value) => return return_value,
               $crate::::Dependent::Break(break_value) => {
                   let _: $crate::::dependent_break_without_break_ty_annotation = break_value;
                   match break_value {}
               }
           },
           $crate::::core::result::Result::Err((give_input_back, bail_value)) => {
               responses_list = give_input_back;
               match bail_value {
                   $crate::::core::ops::ControlFlow::Break(value) => {
                       break if false { loop {} } else { value };
                   }
                   $crate::::core::ops::ControlFlow::Continue(()) => continue,
               }
           }
       }
   }

    unreachable!();
}

After the cleanup:

fn response<'a, 'b>(responses_list: &'a mut Vec<Vec<Response>>) -> Option<&'b mut Response>
where
    'a: 'b,
{
    loop {
        match polonius::<
            dyn for<'polonius> polonius_the_crab::WithLifetime<
                'polonius,
                T = polonius_the_crab::::Dependent<Option<&'polonius mut Response>>,
            >,
            _,
            _,
            _,
        >(&mut *responses_list, |mut responses_list: &mut _| {
            #[allow(clippy::self_assignment)]
            {
                responses_list = responses_list;
            }
            let () = if true {
                match responses_list.last_mut() {
                    Some(responses) => {
                        if let Some(response) = responses.last_mut() {
                            return polonius_the_crab::::core::result::Result::Ok(
                                polonius_the_crab::::Dependent::Return((Some(response))),
                            );
                        }
                        let _ = responses_list.pop();
                    }
                    None => {
                        return core::result::Result::Ok(polonius_the_crab::::Dependent::Return(
                            None,
                        ))
                    }
                }
            } else {
                core::option::Option::None.unwrap()
            };
            return core::result::Result::Err(core::ops::ControlFlow::<_>::Continue(()));
        }) {
            core::result::Result::Ok(dependent) => match dependent {
                polonius_the_crab::::Dependent::Return(return_value) => return return_value,
                polonius_the_crab::::Dependent::Break(break_value) => {
                    let _: polonius_the_crab::::dependent_break_without_break_ty_annotation =
                        break_value;
                    match break_value {}
                }
            },
            polonius_the_crab::::core::result::Result::Err((give_input_back, bail_value)) => {
                responses_list = give_input_back;
                match bail_value {
                    polonius_the_crab::::core::ops::ControlFlow::Break(value) => {
                        break if false { loop {} } else { value };
                    }
                    polonius_the_crab::::core::ops::ControlFlow::Continue(()) => continue,
                }
            }
        }
    }

    unreachable!();
}

But so far it was fruitless.

Code that (almost) worked

Pie rewrote my code this way but it's actually not equivalent.

fn response<'a, 'b>(mut responses_list: &'a mut Vec<Vec<Response>>) -> Option<&'b mut Response>
where
    'a: 'b,
{
    polonius_loop!(|responses_list| -> Option<&'polonius mut Response> {
        match responses_list.last_mut() {
            Some(responses) => {
                polonius_return!(if let Some(response) = responses.last_mut() {
                    Some(response)
                } else {
                    polonius_continue!();
                })
            }
            None => polonius_return!(None),
        }
    });
    unreachable!();
}

This code misses let _ = responses_list.pop(); part, which is quite important for the algorithm.

@danielhenrymantilla
Copy link
Owner

danielhenrymantilla commented Oct 16, 2023

Hey @JohnScience, thanks for the question: you are right there is a rough edge in the crate's API, insofar it may lure you into using polonius_loop! here, when it's actually a case of "basic polonius!" being the one to be used, inside a loop.

  • Bonus issue of the current API, polonius_try! does not support unwrapping Options, only Results

First attempt:

use ::polonius_the_crab::prelude::*;

fn response<'a, Response>(
    mut responses_list: &'a mut Vec<Vec<Response>>,
 // ^^^
 // `mut` on the binding is indeed needed by the macro
 // (you could always manually do a `let mut responses_list = responses_list;`
 // before calling the macro)
) -> Option<&'a mut Response>
{
    loop {
        polonius!(|responses_list| -> Option<&'polonius mut Response> {
            if let Some(response) = responses_list.last_mut()?.last_mut() {
                polonius_return!(Some(response));
            }
        });
        let _ = responses_list.pop();
    }
}

fails because of the ? inside polonius!


Second attempt:

use ::polonius_the_crab::prelude::*;

fn response<'a, Response>(
    mut responses_list: &'a mut Vec<Vec<Response>>,
) -> Option<&'a mut Response>
{
    loop {
        polonius!(|responses_list| -> Option<&'polonius mut Response> {
            match responses_list.last_mut().map(|it| it.last_mut()) {
                // 1st last_mut failed:
                None => polonius_return!(None),
                // 2nd last_mut failed:
                Some(None) => {}, // <- we cannot access `responses_list` yet (still inside the macro)
                // both succeeded:
                Some(Some(response)) => polonius_return!(Some(response)),
            }
        }); // <- at the end of the macro we get access to `responses_list` back 🙂
        let _ = responses_list.pop();
    }
}

works! 🥳


Playground

@danielhenrymantilla danielhenrymantilla added the question Further information is requested label Oct 16, 2023
@danielhenrymantilla
Copy link
Owner

danielhenrymantilla commented Oct 16, 2023

Basically polonius_loop! does not allow for further usage of the input borrow; which is an important limitation of the macro. I should try to see if I could generalize over this and make polonius_loop! make this one work too 🤔


FWIW, you could also try you luck without the polonius_...! macros, and directly calling into the API. I have just released version 0.4.0, to precisely make this a bit more readable:

use ::polonius::{polonius, ForLt, PoloniusResult, Placeholder};

fn response<'a, Response>(
    mut responses: &'a mut Vec<Vec<Response>>,
) -> Option<&'a mut Response>
{
    loop {
        let it = polonius::<_, _, ForLt!(/* type here what goes in the Borrowing variant */)>(|responses| {
                                      // answer:
                                      // Option<&mut Response>

                                                     // TODO: return `PoloniusResult::Borrowing(None)` here
                                                     // v
            if let Some(response) = responses.last_mut()?.last_mut() {
                PoloniusResult::Borrowing(Some(response))
            } else {
                PoloniusResult::Owned {
                    value: (), // <- we have nothing interesting to pass, here
                    input_borrow: Placeholder, // <- the `polonius()` function will put 
                                               //    the `responses` input back here
                }
            }
        });
        match it {
            PoloniusResult::Borrowing(item) => return item,
            PoloniusResult::Owned {
                value: (), // <- still as uninteresting as we left it
                input_borrow, // <- we get back our input `responses` here!
            } => {
                responses = input_borrow;
            },
        };
 
        let _ = responses.pop();
    }
}

@danielhenrymantilla danielhenrymantilla changed the title Help rewrite a Polonius loop using polonius_the_crab::polonius_loop Certain Polonius loops do not work with polonius_loop! Oct 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants