salsa-rs / salsa

A generic framework for on-demand, incrementalized computation. Inspired by adapton, glimmer, and rustc's query system.
https://salsa-rs.netlify.app/
Apache License 2.0
2.15k stars 155 forks source link

Idea: salsa::supertype and upcasting on `Id` #578

Open nikomatsakis opened 2 months ago

nikomatsakis commented 2 months ago

I've noticed that I often have an enum whose variants are all different salsa structs:

enum Item<'db> {
    Struct(Struct<'db>),
    Function(Function<'db>),
    ...
}

I was thinking about how this enum will use 2 words and not 1 and I realized that, given that each variant is just storing an Id, and that we can go from an Id to an IngredientIndex (it's stored on the page), we could have a proc-macro that takes such an enum and returns a special struct that just stores a single Id. It could then be converted into an enum readily enough. You also downcast it.

I'm imagining the following:

First, add two methods to salsa::Id, fn is<S>(self, db: &dyn Database) -> bool and fn downcast<S>(self, db: &dyn Database) -> Option<S>, where S: SalsaStructInDb<'_>. These methods can lookup the id in db.table, identify the ingredient index on the from the Page, compare that to the ingredient index for S, and then do the appropriate thing. (We might also want to tweak the FromId or other traits in a similar-ish way.)

Next, create a proc macro named supertype (bikeshed) which expects an enum with one lifetime argument and where each variant has a single argument:

#[salsa::supertype]
enum Foo<'db> {
    A(Bar<'db>),
    ...
    Z(Baz<'db>),
}

we generate the following:

// A struct to store the id itself
#[derive(Copy, Clone, ...)]
struct Foo<'db> { id: Id }

// Original enum, but with a tweaked name (should be configurable)
enum FooVariants<'db> {
    A(Bar<'db>),
    ...
    Z(Baz<'db>),
}

// From impls to "upcast" into the struct; these just take the `id`
impl<'db> From<Bar<'db>> for Foo<'db> { ... }

// From impls to "upcast" into the enum; these pick the appropriate variant
impl<'db> From<Bar<'db>> for FooVariants<'db> { ... }

// From impls from the variants struct
impl<'db> From<FooVariants<'db>> for Foo<'db> { ... }

impl Foo<'db> {
    // A method to convert from the struct into the variants 
    fn variants(self, db: &dyn salsa::Database) -> FooVariants<'db> {
        if let Some(v) = self.id.downcast::<Foo<'db>>(db) {
            return v.into();
        }

        // ...

        if let Some(v) = self.id.downcast::<Bar<'db>>(db) {
            return v.into();
        }

        unreachable!()
    }

    // Convenient downcast methods for each of the options
    fn a(self) -> Option<Foo<'db>> {
        self.id.downcast()
    }

    fn z(self) -> Option<Bar<'db>> {
        self.id.downcast()
    }
}
nikomatsakis commented 2 months ago

Mentoring instructions:

Methods on id

Proc macro

davidbarsky commented 4 days ago

I've assigned this issue to myself; I'll need this functionality in Salsa in order to migrate rust-analyzer.