Skip to content

minijinja: type annotations needed #195

New issue

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

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

Already on GitHub? # 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
# for free to join this conversation on GitHub. Already have an account? # 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