Skip to content

minijinja: type annotations needed #195

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

Closed
lcnr opened this issue May 5, 2025 · 4 comments
Closed

minijinja: type annotations needed #195

lcnr opened this issue May 5, 2025 · 4 comments
Assignees
Labels
from-crater A regression found via a crater run, not part of our test suite

Comments

@lcnr
Copy link
Contributor

lcnr commented May 5, 2025

[INFO] [stdout] error[E0283]: type annotations needed
[INFO] [stdout]    --> /opt/rustwide/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/minijinja-1.0.10/src/environment.rs:597:34
[INFO] [stdout]     |
[INFO] [stdout] 597 |             .insert(name.into(), filters::BoxedFilter::new(f));
[INFO] [stdout]     |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^ - type must be known at this point
[INFO] [stdout]     |                                  |
[INFO] [stdout]     |                                  cannot infer type of the type parameter `Rv` declared on the associated function `new`
[INFO] [stdout]     |
[INFO] [stdout]     = note: cannot satisfy `F: filters::Filter<_, _>`
[INFO] [stdout]     = help: the following types implement trait `filters::Filter<Rv, Args>`:
[INFO] [stdout]               `Func` implements `filters::Filter<Rv, ()>`
[INFO] [stdout]               `Func` implements `filters::Filter<Rv, (A, B)>`
[INFO] [stdout]               `Func` implements `filters::Filter<Rv, (A, B, C)>`
[INFO] [stdout]               `Func` implements `filters::Filter<Rv, (A, B, C, D)>`
[INFO] [stdout]               `Func` implements `filters::Filter<Rv, (A, B, C, D, E)>`
[INFO] [stdout]               `Func` implements `filters::Filter<Rv, (A,)>`
@lcnr lcnr added the from-crater A regression found via a crater run, not part of our test suite label May 5, 2025
@lcnr
Copy link
Contributor Author

lcnr commented May 5, 2025

Instance of #168

enum Value {
    String(String),
    U32(u32),
}

trait Arg<'a> {
    type Output;
    fn from_value(value: &'a Value) -> Option<Self::Output>;
}
impl<'a, 'b> Arg<'a> for &'b str {
    type Output = &'a str;
    fn from_value(value: &'a Value) -> Option<Self::Output> {
        match value {
            Value::String(s) => Some(s),
            Value::U32(_) => None,
        }
    }
}
impl<'a> Arg<'a> for u32 {
    type Output = u32;
    fn from_value(value: &'a Value) -> Option<Self::Output> {
        match value {
            Value::String(_) => None,
            Value::U32(x) => Some(*x),
        }
    }
}

fn get_values() -> Vec<Value> {
    vec![Value::String("string".into()), Value::U32(128)]
}

trait Filter<A> {
    fn filter(&mut self, arg: A);
}
impl<A, F: FnMut(A)> Filter<A> for F {
    fn filter(&mut self, arg: A) {
        self(arg)
    }
}

fn inspect_values<F, A>(mut f: F)
where
    A: for<'a> Arg<'a>,
    F: for<'a> Filter<<A as Arg<'a>>::Output> + Filter<A>,
{
    let values = get_values();
    for arg in values.iter().filter_map(A::from_value) {
        f.filter(arg);
    }
}

fn generic_caller<F, A>(f: F)
where
    A: for<'a> Arg<'a>,
    F: for<'a> Filter<<A as Arg<'a>>::Output> + Filter<A>,
{
    inspect_values(f)
}

fn main() {
    generic_caller(|x: u32| println!("{x}"));
    generic_caller(|x: &str| println!("'{}' has len {}", x, x.len()));
}

@lcnr
Copy link
Contributor Author

lcnr commented May 5, 2025

  • the reason we need to use <A as Arg<'a>>::Output is that we want fn inspect_value to be generic over the inspected value and have that value potentially be a local reference. i.e. we either call it with a function for<'a> fn(&'a ()) or fn(u32)
  • constraining A via a F: for<'a> Filter<<A as Arg<'a>>::Output> relies on incomplete alias-relate Inference hazard due to lazy alias relate: Break it in the old solver? #168
  • in the old solver, proving for<'a> Filter<<A as Arg<'a>>::Output> via a where-bound structurally relates aliases, so non-hr where-bounds don't apply and we use the for<'a> Filter<<A as Arg<'a>>::Output> where-bound as a unique candidate
  • the reason there's also a non-hr bound is to guide type inference when proving the where-bound via impls. The blanket impl of Filter applies for all ?a with F: FnMut(?A). Before proving F: FnMut(<?a as Arg<'!a>>::Output), we normalize this goal, resulting in an ambiguous Projecton(<?a as Arg<'!a>>::Output, rigid_ty) goal
  • both FnMut impls need to be hidden by an impl with an ambig self type when deducing the closure signature. We otherwise either fail to constrain A or require the argument to not be higher ranked, depending on which of the two we try first

Tragic :<

@lcnr
Copy link
Contributor Author

lcnr commented May 5, 2025

a rewrite which should fix this?

enum Value {
    String(String),
    U32(u32),
}

trait Arg<'a> {
    type Output;
    fn from_value(value: &'a Value) -> Option<Self::Output>;
}
impl<'a, 'b> Arg<'a> for &'b str {
    type Output = &'a str;
    fn from_value(value: &'a Value) -> Option<Self::Output> {
        match value {
            Value::String(s) => Some(s),
            Value::U32(_) => None,
        }
    }
}
impl<'a> Arg<'a> for u32 {
    type Output = u32;
    fn from_value(value: &'a Value) -> Option<Self::Output> {
        match value {
            Value::String(_) => None,
            Value::U32(x) => Some(*x),
        }
    }
}

fn get_values() -> Vec<Value> {
    vec![Value::String("string".into()), Value::U32(128)]
}

trait Filter<A: for<'a> Arg<'a>> {
    fn filter<'a>(&mut self, arg: <A as Arg<'a>>::Output);
}
// Move both `FnMut` bounds to the impl of `Filter` instead of having
// two generic bounds. This means we're now always choosing a unique
// where-bound in `generic_caller` and no longer rely on incomplete
// alias-relate.
impl<A: for<'a> Arg<'a>, F: for<'a> FnMut(<A as Arg<'a>>::Output) + FnMut(A)> Filter<A> for F {
    fn filter<'a>(&mut self, arg: <A as Arg<'a>>::Output) {
        self(arg)
    }
}

fn inspect_values<F, A>(mut f: F)
where
    A: for<'a> Arg<'a>,
    F: Filter<A>,
{
    let values = get_values();
    for arg in values.iter().filter_map(A::from_value) {
        f.filter(arg);
    }
}

fn generic_caller<F, A>(f: F)
where
    A: for<'a> Arg<'a>,
    F: Filter<A>,
{
    inspect_values(f)
}

fn foo(x: &str) {
    println!("this is '{x}'")
}

fn main() {
    generic_caller(|x: u32| println!("{x}"));
    generic_caller(|x: &str| println!("'{}' has len {}", x, x.len()));
    generic_caller(foo);
}

@lcnr
Copy link
Contributor Author

lcnr commented May 5, 2025

fixed by mitsuhiko/minijinja#787

@lcnr lcnr moved this from in progress to done in -Znext-solver=globally May 6, 2025
@lcnr lcnr closed this as completed by moving to done in -Znext-solver=globally May 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
from-crater A regression found via a crater run, not part of our test suite
Projects
Development

No branches or pull requests

1 participant