diff --git a/components/salsa-entity-macros/src/entity.rs b/components/salsa-entity-macros/src/entity.rs index 28103cde..01519af6 100644 --- a/components/salsa-entity-macros/src/entity.rs +++ b/components/salsa-entity-macros/src/entity.rs @@ -45,13 +45,7 @@ impl EntityLike { } fn validate_entity(&self) -> syn::Result<()> { - // Require that entities are structs for now. - if !self.has_named_fields() { - return Err(syn::Error::new( - self.id_ident().span(), - "entities must be structs with named fields", - )); - } + self.require_named_fields("entity")?; Ok(()) } diff --git a/components/salsa-entity-macros/src/entity_like.rs b/components/salsa-entity-macros/src/entity_like.rs index e26bfc8f..9b3a4a04 100644 --- a/components/salsa-entity-macros/src/entity_like.rs +++ b/components/salsa-entity-macros/src/entity_like.rs @@ -201,6 +201,42 @@ impl EntityLike { } } + + /// Return an error unless this is a struct with named fields. + /// + /// # Parameters + /// + /// * `kind`, the attribute name (e.g., `input` or `interned`) + pub(crate) fn require_named_fields(&self, kind: &str) -> syn::Result<()> { + if !self.has_named_fields() { + return Err(syn::Error::new( + self.id_ident().span(), + "`#[salsa::{kind}]` can only be applied to a struct with named fields", + )); + } + + Ok(()) + } + + /// Disallow `#[id]` attributes on the fields of this struct. + /// + /// If an `#[id]` field is found, return an error. + /// + /// # Parameters + /// + /// * `kind`, the attribute name (e.g., `input` or `interned`) + pub(crate) fn disallow_id_fields(&self, kind: &str) -> syn::Result<()> { + for ef in self.all_entity_fields() { + if ef.has_id_attr { + return Err(syn::Error::new( + ef.name().span(), + "`#[id]` cannot be used with `#[salsa::{kind}]`", + )); + } + } + + Ok(()) + } } pub(crate) const FIELD_OPTION_ATTRIBUTES: &[(&str, fn(&syn::Attribute, &mut EntityField))] = &[ diff --git a/components/salsa-entity-macros/src/interned.rs b/components/salsa-entity-macros/src/interned.rs index 989c4f23..e5e2d548 100644 --- a/components/salsa-entity-macros/src/interned.rs +++ b/components/salsa-entity-macros/src/interned.rs @@ -41,20 +41,7 @@ impl EntityLike { } fn validate_interned(&self) -> syn::Result<()> { - // Disallow `#[value]` attributes on interned things. - // - // They don't really make sense -- we intern all the fields of something - // to create its id. If multiple queries were to intern the same thing with - // distinct values for the value field, what would happen? - for ef in self.all_entity_fields() { - if ef.has_id_attr { - return Err(syn::Error::new( - ef.name().span(), - "`#[id]` not required in interned structs", - )); - } - } - + self.disallow_id_fields("interned")?; Ok(()) }