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

Type mismatch in salsa-generated code #9990

Closed
lnicola opened this issue Aug 22, 2021 · 11 comments · Fixed by #11970
Closed

Type mismatch in salsa-generated code #9990

lnicola opened this issue Aug 22, 2021 · 11 comments · Fixed by #11970
Labels
A-ty type system / type inference / traits / method resolution C-bug Category: bug S-actionable Someone could pick this issue up and work on it right now

Comments

@lnicola
Copy link
Member

lnicola commented Aug 22, 2021

CC #8961

pub trait Database {}

pub trait QueryGroup: Sized {
    type DynDb: ?Sized + Database + HasQueryGroup<Self>;
}

pub trait HasQueryGroup<G>: Database
where
    G: QueryGroup,
{
}

trait HelloWorld: HasQueryGroup<HelloWorldStorage> {
    fn length(&self);
}

struct HelloWorldStorage;
impl QueryGroup for HelloWorldStorage {
    type DynDb = dyn HelloWorld;
}

fn __shim(_db: &(dyn HelloWorld + '_)) {}

impl<DB> HelloWorld for DB
where
    DB: Database,
    DB: HasQueryGroup<HelloWorldStorage>,
{
    fn length(&self) {
        __shim(self); // Expected &dyn HelloWorld, got &DB
    }
}
@lnicola lnicola added A-macro macro expansion A-ty type system / type inference / traits / method resolution S-actionable Someone could pick this issue up and work on it right now labels Aug 22, 2021
@lnicola lnicola removed the A-macro macro expansion label Aug 22, 2021
@lnicola lnicola changed the title Type mismatch with salsa Type mismatch in salsa-generated code Aug 22, 2021
@lnicola
Copy link
Member Author

lnicola commented Aug 22, 2021

Updated with a self-contained repro, but it's still pretty convoluted. And I could swear I've done this before 😕.

@flodiebold
Copy link
Member

Thanks for the minimization -- I looked into this a bit and it looks like a Chalk problem where it on the one hand correctly determines that DB: Database from the environment, but then it also tries to use QueryGroup::DynDb: Database to prove the same thing and gets an ambiguous result from that, so the whole thing is ambiguous. In another case I looked at (one of the few that weren't inside Salsa-generated code) it was similar, but while trying to prove DB: Sized. We'll probably need a Chalk reproduction.

@lnicola lnicola added the C-bug Category: bug label Sep 13, 2021
@iDawer
Copy link
Contributor

iDawer commented Sep 15, 2021

I did log with CHALK_DEBUG=info CHALK_PRINT=1 issue_9990.log but it does not give the full program dump 🤔 (Is this possible?)

So I wrote it by myself, not sure it reflects ra's vision of the example precisely:

#[test]
fn coerce_ra_issue_9990() {
    test!(
        program {
            #[object_safe]
            #[lang(sized)]
            trait Sized {}
            #[object_safe]
            #[lang(unsize)]
            trait Unsize<T> {}
            #[object_safe]
            #[lang(coerce_unsized)]
            trait CoerceUnsized<T> {}

            impl<T, U> CoerceUnsized<&'static U> for &'static T where T: Unsize<U> {}

            #[object_safe]
            trait Database {}

            #[object_safe]
            trait QueryGroup
            where
                Self: Sized,
            {
                type DynDb: Database + HasQueryGroup<Self>;
            }

            #[object_safe]
            trait HasQueryGroup<G>
            where
                Self: Database,
                G: QueryGroup,
                G: Sized,
            { }

            #[object_safe]
            trait HelloWorld
            where
                Self: HasQueryGroup<HelloWorldStorage>,
            { }

            struct HelloWorldStorage {}

            impl QueryGroup for HelloWorldStorage {
                type DynDb = dyn HelloWorld + 'static;
            }
            impl<DB> HelloWorld for DB
            where
                DB: Database,
                DB: HasQueryGroup<HelloWorldStorage>,
                DB: Sized,
            { }
        }

        goal {
            exists<T> {
                if (FromEnv(T: Database); FromEnv(T: HasQueryGroup<HelloWorldStorage>); FromEnv(T: Sized)) {
                    &'static T: CoerceUnsized<&'static dyn HelloWorld + 'static>
                }
            }
        } yields {
            "Unique; substitution [], lifetime constraints [InEnvironment { environment: Env([]), goal: !1_0: 'static }]"
        }
    );
}

(Note: ra marks all traits #[object_safe], seems not relevant here.)

This test fails with Ambiguous; no inference guidance. Changing the quantifier to forall fixes it.

Hmm, after some playing I'm even more in doubt of it's precision: removing DB: Database bound from the original rust example makes ra stop complaining, but removing that plus FromEnv(T: Database) from the Chalk program has no effect, the test is still failing.

@lnicola
Copy link
Member Author

lnicola commented Sep 16, 2021

Note: ra marks all traits #[object_safe], seems not relevant here.

Well, that's not really surprising: https://github.com/rust-analyzer/rust-analyzer/blob/ae36af2bd43906ddb1eeff96d76754f012c0a2c7/crates/hir_ty/src/chalk_db.rs#L288-L291

@flodiebold
Copy link
Member

I think the example can be simplified further by getting rid of CoerceUnsized; just trying to solve T: HelloWorld should be enough to reproduce it.

@iDawer
Copy link
Contributor

iDawer commented Sep 23, 2021

just trying to solve T: HelloWorld should be enough to reproduce it.

simplified
test!(
    program {
        #[upstream]
        #[non_enumerable]
        #[lang(sized)]
        trait Sized {}

        #[non_enumerable]
        #[object_safe]
        trait Database {}

        #[non_enumerable]
        trait QueryGroup
        where
            Self: Sized,
        {
            type DynDb: Database + HasQueryGroup<Self>;
        }

        #[non_enumerable]
        #[object_safe]
        trait HasQueryGroup<G>
        where
            Self: Database,
            G: QueryGroup,
            G: Sized,
        { }

        #[non_enumerable]
        #[object_safe]
        trait HelloWorld
        where
            Self: HasQueryGroup<HelloWorldStorage>,
        { }

        struct HelloWorldStorage {}

        impl QueryGroup for HelloWorldStorage {
            type DynDb = dyn HelloWorld + 'static;
        }
        impl<DB> HelloWorld for DB
        where
            DB: Database,
            DB: HasQueryGroup<HelloWorldStorage>,
            DB: Sized,
        { }
    }

    goal {
        exists<T> {
            if (FromEnv(T: Database); FromEnv(T: HasQueryGroup<HelloWorldStorage>); FromEnv(T: Sized)) {
                T: HelloWorld
            }
        }
    } yields[SolverChoice::slg_default()] { // fails: "Ambiguous; no inference guidance"
        "Unique"
    } yields[SolverChoice::recursive_default()] { // fails: "Ambiguous; no inference guidance"
        "Unique"
    }

    goal {
        exists<T> {
            if (FromEnv(T: Database); FromEnv(T: HasQueryGroup<HelloWorldStorage>); FromEnv(T: Sized)) {
                T: Database
            }
        }
    } yields[SolverChoice::slg_default()] { // ok
        "Unique"
    } yields[SolverChoice::recursive_default()] { // fails: "Ambiguous; no inference guidance"
        "Unique"
    }

    goal {
        exists<T> {
            if (FromEnv(T: Database); FromEnv(T: HasQueryGroup<HelloWorldStorage>); FromEnv(T: Sized)) {
                HelloWorldStorage: Sized
            }
        }
    } yields[SolverChoice::slg_default()] { // ok
        "Unique"
    } yields[SolverChoice::recursive_default()] { // fails: "Ambiguous; no inference guidance"
        "Unique"
    }
);

@iDawer
Copy link
Contributor

iDawer commented Sep 23, 2021

Also the queries differ. Chalk test's query looks like this:

[tests/test/mod.rs:266] &peeled_goal = UCanonical {
    canonical: Canonical {
        value: InEnvironment {
            environment: Env([
                for<> FromEnv(^1.0: Database),
                for<> FromEnv(^1.0: HasQueryGroup<HelloWorldStorage>),
                for<> FromEnv(^1.0: Sized)
            ]),
            goal: Implemented(^0.0: HelloWorld),
        },
        binders: [U0 with kind type],
    },
    universes: 1,
}

But the RA query uses !0_1:

UCanonical {
    canonical: Canonical {
        value: InEnvironment { 
            environment: Env([
                for<> FromEnv(!0_1: Database), 
                for<> FromEnv(!0_1: HasQueryGroup<HelloWorldStorage<[]>>), 
                for<> FromEnv(!0_1: Sized)
            ]), 
            goal: Implemented((&'static !0_1): CoerceUnsized<(&'static dyn for<type> [for<> Implemented(^1.0: HelloWorld)] + 'static)>) 
        }, 
        binders: [],
    }, 
    universes: 1,
}

Is this query correct? In for<> FromEnv(!0_1: Database) the !0_1 placeholder is not defined in the innermost binder.

@flodiebold
Copy link
Member

flodiebold commented Sep 23, 2021

Yes, the test doesn't match the Rust code -- the equivalent of a type parameter DB would be a forall<DB>, not exists.

You can simplify the Rust version further as well by turning

fn __shim(_db: &(dyn HelloWorld + '_)) {}

into something like

fn __shim<T: HelloWorld>(_db: &T) {}

. I think it should still fail, and get rid of the CoerceUnsized involvement.

@iDawer
Copy link
Contributor

iDawer commented Sep 23, 2021

fn __shim<T: HelloWorld>(_db: &T) {}

analysis-stats shows no mismatch with that 🤔 .

Ok, this is closer now:

test!(
    program {
        #[upstream] #[non_enumerable] #[lang(sized)]
        trait Sized {}

        #[non_enumerable] #[object_safe]
        trait Database {}

        #[non_enumerable]
        trait QueryGroup
        where
            Self: Sized,
        {
            type DynDb: Database + HasQueryGroup<Self>;
        }

        #[non_enumerable] #[object_safe]
        trait HasQueryGroup<G>
        where
            Self: Database,
            G: QueryGroup,
            G: Sized,
        { }

        #[non_enumerable] #[object_safe]
        trait HelloWorld
        where
            Self: HasQueryGroup<HelloWorldStorage>,
        { }

        struct HelloWorldStorage {}

        impl QueryGroup for HelloWorldStorage {
            type DynDb = dyn HelloWorld + 'static;
        }
        impl<DB> HelloWorld for DB
        where
            DB: Database,
            DB: HasQueryGroup<HelloWorldStorage>,
            DB: Sized,
        { }
    }

    goal {
        forall<T> {
            if (FromEnv(T: Database); FromEnv(T: HasQueryGroup<HelloWorldStorage>); FromEnv(T: Sized)) {
                T: HelloWorld
            }
        }
    } yields[SolverChoice::slg_default()] { // ok
        "Unique"
    } yields[SolverChoice::recursive_default()] { // fails: "Ambiguous; no inference guidance"
        "Unique"
    }
);

The query lowers to this:

[tests/test/mod.rs:266] &peeled_goal = UCanonical {
    canonical: Canonical {
        value: InEnvironment {
            environment: Env([
                for<> FromEnv(!1_0: Database),
                for<> FromEnv(!1_0: HasQueryGroup<HelloWorldStorage>),
                for<> FromEnv(!1_0: Sized)
            ]),
            goal: Implemented(!1_0: HelloWorld),
        },
        binders: [],
    },
    universes: 2,
}

Removing DB: Database, bound and FromEnv(T: Database); makes the test pass just like the Rust code. The same behavior shows &'static T: CoerceUnsized<&'static dyn HelloWorld + 'static> query.

In logs both solvers report cycles but SLG does the job. Do we hit limitations/bug in recursive solver here? As a workaround we could switch to the SLG solver when the recursive failed.

@flodiebold
Copy link
Member

analysis-stats shows no mismatch with that

Ah well, failed obligations from type parameters don't lead to mismatches...

@flodiebold
Copy link
Member

flodiebold commented Sep 23, 2021

In logs both solvers report cycles but SLG does the job. Do we hit limitations/bug in recursive solver here? As a workaround we could switch to the SLG solver when the recursive failed.

Bug yes, I don't see any reason why it should be a limitation. I would rather investigate the actual problem than involve the SLG solver, which would just lead to failures in even more obscure cases (and also probably greatly slow down inference). I think the next step would be to report this to the Chalk team so they're aware of it (and maybe someone will look into it).

@lnicola lnicola mentioned this issue Apr 12, 2022
bors bot added a commit that referenced this issue Apr 12, 2022
11970: Bump chalk r=lnicola a=lnicola

Closes #9990, but not #6418, #10653

bors r+

Co-authored-by: Laurențiu Nicola <lnicola@dend.ro>
@bors bors bot closed this as completed in #11970 Apr 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ty type system / type inference / traits / method resolution C-bug Category: bug S-actionable Someone could pick this issue up and work on it right now
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants