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

feature: add #[salsa::supertype] #677

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

davidbarsky
Copy link
Contributor

This PR implements #578 and is (currently) stacked atop of #676. All authorship/credit for this work goes to @ChayimFriedman2 for the implementation, I largely renamed some stuff.

Two notes on this PR:

  1. There's no corresponding macro_rules! macro for #[salsa::supertype]. We can add it, need be.
  2. I've commented out some hand-written PartialEq implementations because they caused a bunch of test failures in rust-analyzer. Those test failures stemmed from rust-analyzer's native, non-Salsa powered interner, which persists even after we changed databases in tests! For more details, see what Chayim wrote on Zulip. Instead of commenting things out, I'll make these options on the attribute macro.

Copy link

netlify bot commented Feb 6, 2025

Deploy Preview for salsa-rs canceled.

Name Link
🔨 Latest commit 2864e76
🔍 Latest deploy log https://app.netlify.com/sites/salsa-rs/deploys/67a67aa41d0dfa0008f24d76

Copy link

codspeed-hq bot commented Feb 6, 2025

CodSpeed Performance Report

Merging #677 will not alter performance

Comparing davidbarsky:davidbarsky/supertypes (2864e76) with master (c02959d)

Summary

✅ 9 untouched benchmarks

@@ -66,6 +67,11 @@ pub fn interned(args: TokenStream, input: TokenStream) -> TokenStream {
interned::interned(args, input)
}

#[proc_macro_derive(supertype)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[proc_macro_derive(supertype)]
#[proc_macro_derive(Supertype)]

Comment on lines +4 to +8
/// For an entity struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
///
/// * the "id struct" `struct Foo(salsa::Id)`
/// * the entity ingredient, which maps the id fields to the `Id`
/// * for each value field, a function ingredient
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems wrong (given it was copied verbatim from another one attr/derive)

src/zalsa.rs Outdated Show resolved Hide resolved
/// This method is used to create ingredient indices. Note, it does *not* create the ingredients
/// themselves, that is the job of [`Zalsa::add_or_lookup_jar_by_type()`]. This method only creates
/// or lookup the indices. Naturally, implementors may call `add_or_lookup_jar_by_type()` to
/// create the ingredient, but they do not must, e.g. enums recursively call
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// create the ingredient, but they do not must, e.g. enums recursively call
/// create the ingredient, but they aren't required to, e.g. enums recursively call

Comment on lines +15 to +55
/// This method is used to support nested Salsa enums, a.k.a.:
/// ```ignore
/// #[salsa::input]
/// struct Input {}
///
/// #[salsa::interned]
/// struct Interned1 {}
///
/// #[salsa::interned]
/// struct Interned2 {}
///
/// #[derive(Debug, salsa::Enum)]
/// enum InnerEnum {
/// Input(Input),
/// Interned1(Interned1),
/// }
///
/// #[derive(Debug, salsa::Enum)]
/// enum OuterEnum {
/// InnerEnum(InnerEnum),
/// Interned2(Interned2),
/// }
/// ```
/// Imagine `OuterEnum` got a [`salsa::Id`][Id] and it wants to know which variant it belongs to.
/// It cannot ask each variant "what is your ingredient index?" and compare, because `InnerEnum`
/// has multiple possible ingredient indices.
///
/// It could ask each variant "is this value yours?" and then invoke [`FromId`][crate::id::FromId]
/// with the correct variant, but that will duplicate the work: now `InnerEnum` will have to do
/// the same thing for its variants.
///
/// Instead, we keep track of the [`TypeId`] of the ID struct, and ask each variant to "cast" it. If
/// it succeeds, we return that value; if not, we go to the next variant.
///
/// Why `TypeId` and not `IngredientIndex`? Because it's cheaper and easier. The `TypeId` is readily
/// available at compile time, while the `IngredientIndex` requires a runtime lookup.
fn cast(id: Id, type_id: TypeId) -> Option<Self>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the implication of this if you have nested enums where some of the variants are of the same salsa struct? I presume it will just always cast to the "first" of the duplicated variants being walked right? Might be good to note that down somewhere (in that we do not preserve variant identity when faced with duplicate variants)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I thought this won't be a problem, because the use-case seems dubious.

Copy link
Member

@Veykril Veykril Feb 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I don't think there is any point in having a differing behavior here, just wanted to clarify. So your enums being sets analogy makes sense.

src/memo_ingredient_indices.rs Outdated Show resolved Hide resolved
@Veykril
Copy link
Member

Veykril commented Feb 7, 2025

Of note is that this implements a different design of #578, this design here having the benefit of retaining the enum being an enum (where as the issue would've transformed it into just another id that you'd need to access variants via methods).

@Veykril
Copy link
Member

Veykril commented Feb 7, 2025

I've commented out some hand-written PartialEq implementations because they caused a bunch of test failures in rust-analyzer. Those test failures stemmed from rust-analyzer's native, non-Salsa powered interner, which persists even after we changed databases in tests! For more details, see what Chayim wrote on Zulip. Instead of commenting things out, I'll make these options on the attribute macro.

Hmm that sounds problematic, I don't see how adding an option to the macro fixes this? Are you saying we'd want to opt out of those impls in rust-analyzer until we replaced the type interner with salsa (by using the new trait solver)?

@davidbarsky
Copy link
Contributor Author

Are you saying we'd want to opt out of those impls in rust-analyzer until we replaced the type interner with salsa (by using the new trait solver)?

Yes. In the meantime, Jack has also asked us to avoid making changes to the interner, as his changes depend on it being in place until Salsa's interned structs can natively handle/represent a TyKind.

/// atomicity), so other ingredients could not be created.
///
/// Only tracked fns use this.
fn create_dependencies(_zalsa: &Zalsa) -> IngredientIndices
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like calling this dependencies as it may cause confusion with query (tracked function) dependencies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait, what'd you call this instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well they are dependencies, but a different kind, unsure about a better name though

@Veykril
Copy link
Member

Veykril commented Feb 7, 2025

Are you saying we'd want to opt out of those impls in rust-analyzer until we replaced the type interner with salsa (by using the new trait solver)?

Yes. In the meantime, Jack has also asked us to avoid making changes to the interner, as his changes depend on it being in place until Salsa's interned structs can natively handle/represent a TyKind.

Understandable but won't this have effects on the incrementality of rust-analyzer?

@Veykril
Copy link
Member

Veykril commented Feb 7, 2025

Propopsing a different store scheme davidbarsky#1 that wastes a tiny bit of memory for faster IngredientIndex to MemoIngredientIndex mapping. It does introduce pointer chasing but I don't think that will be problematic in comparison. only for the enum case.

Edit: It also skips the mapping entirely now for structs, so you only pay perf / memory usage for enums you use now.

@davidbarsky
Copy link
Contributor Author

I don't think so, or at the very least, I haven't noticed any issues while running rust-analyzer under new Salsa, as I have been for the past month.

For what it's worth, I think these test failures go away if we run the test suite under nextest, as we don't really create new database instances under normal IDE conditions.

ChayimFriedman2 and others added 5 commits February 7, 2025 16:26
Or "supertypes" as they are called in salsa-rs#578.

They are of the form:
```
enum Enum {
    Input(Input),
    Interned(Interned),
    ...
}
```
Drop unnecesary ingredient- and memo-index mapping for structs
@davidbarsky davidbarsky force-pushed the davidbarsky/supertypes branch from ce8ca57 to 2864e76 Compare February 7, 2025 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants