From 22810cad6a1b561b3adad5762a8c55aaf43d2c47 Mon Sep 17 00:00:00 2001 From: Ori Ziv Date: Sat, 25 Apr 2026 19:45:22 +0300 Subject: [PATCH] refactor(semantic): introduce TypeMember abstraction for unified struct/tuple member access --- .../cairo-lang-semantic/src/expr/compute.rs | 50 +++++++---- .../src/items/structure.rs | 82 ++++++++++++++++++- crates/cairo-lang-semantic/src/resolve/mod.rs | 10 +-- crates/cairo-lang-semantic/src/semantic.rs | 2 +- 4 files changed, 119 insertions(+), 25 deletions(-) diff --git a/crates/cairo-lang-semantic/src/expr/compute.rs b/crates/cairo-lang-semantic/src/expr/compute.rs index c7b80cb1ac8..0a4556270fb 100644 --- a/crates/cairo-lang-semantic/src/expr/compute.rs +++ b/crates/cairo-lang-semantic/src/expr/compute.rs @@ -81,7 +81,7 @@ use crate::items::macro_declaration::{ }; use crate::items::modifiers::compute_mutability; use crate::items::module::ModuleSemantic; -use crate::items::structure::StructSemantic; +use crate::items::structure::{StructSemantic, TypeMember, TypeMemberKind}; use crate::items::trt::TraitSemantic; use crate::items::visibility; use crate::keyword::MACRO_CALL_SITE; @@ -3949,7 +3949,7 @@ fn member_access_expr<'db>( match &long_ty { TypeLongId::Concrete(_) | TypeLongId::Tuple(_) | TypeLongId::FixedSizeArray { .. } => { - let Some(EnrichedTypeMemberAccess { member, deref_functions }) = + let Some(EnrichedTypeMemberAccess { type_member, deref_functions }) = get_enriched_type_member_access(ctx, lexpr.clone(), stable_ptr, member_name)? else { return Err(ctx.diagnostics.report( @@ -3957,6 +3957,15 @@ fn member_access_expr<'db>( NoSuchTypeMember { ty: long_ty.intern(ctx.db), member_name }, )); }; + // TODO(#7608): handle TypeMemberKind::TupleElement here. + let TypeMemberKind::StructMember(member_id) = type_member.kind else { + return Err(ctx.diagnostics.report(rhs_syntax.stable_ptr(db), Unsupported)); + }; + let member = semantic::Member { + id: member_id, + ty: type_member.ty, + visibility: type_member.visibility, + }; check_struct_member_is_visible( ctx, &member, @@ -4081,20 +4090,18 @@ fn get_enriched_type_member_access<'db>( } Entry::Vacant(_) => { let (_, long_ty) = finalized_snapshot_peeled_ty(ctx, ty, stable_ptr)?; - let members = - if let TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id)) = long_ty { - let members = ctx.db.concrete_struct_members(concrete_struct_id)?; - if let Some(member) = members.get(&accessed_member_name) { - // Found direct member access - so directly returning it. - return Ok(Some(EnrichedTypeMemberAccess { - member: member.clone(), - deref_functions: vec![], - })); - } - members.iter().map(|(k, v)| (*k, (v.clone(), 0))).collect() - } else { - Default::default() - }; + let members = if let Some(type_members_map) = ctx.db.type_members(long_ty.intern(ctx.db))? { + if let Some(type_member) = type_members_map.get(&accessed_member_name) { + // Found direct member access - so directly returning it. + return Ok(Some(EnrichedTypeMemberAccess { + type_member: type_member.clone(), + deref_functions: vec![], + })); + } + type_members_map.iter().map(|(k, v)| (*k, (v.clone(), 0))).collect() + } else { + Default::default() + }; EnrichedMembers { members, @@ -4132,7 +4139,16 @@ fn enrich_members<'db>( let members = ctx.db.concrete_struct_members(concrete_struct_id)?; for (member_name, member) in members.iter() { // Insert member if there is not already a member with the same name. - enriched.entry(*member_name).or_insert_with(|| (member.clone(), *explored_derefs)); + enriched.entry(*member_name).or_insert_with(|| { + ( + TypeMember { + ty: member.ty, + visibility: member.visibility, + kind: TypeMemberKind::StructMember(member.id), + }, + *explored_derefs, + ) + }); } // If member is contained we can stop the calculation post the lookup. if members.contains_key(&accessed_member_name) { diff --git a/crates/cairo-lang-semantic/src/items/structure.rs b/crates/cairo-lang-semantic/src/items/structure.rs index 89d56c0480c..e132852ff6b 100644 --- a/crates/cairo-lang-semantic/src/items/structure.rs +++ b/crates/cairo-lang-semantic/src/items/structure.rs @@ -22,8 +22,8 @@ use crate::expr::inference::InferenceId; use crate::expr::inference::canonic::ResultNoErrEx; use crate::resolve::{Resolver, ResolverData}; use crate::substitution::{GenericSubstitution, SemanticRewriter}; -use crate::types::{ConcreteStructId, add_type_based_diagnostics, resolve_type}; -use crate::{GenericParam, SemanticDiagnostic, semantic}; +use crate::types::{ConcreteStructId, ConcreteTypeId, add_type_based_diagnostics, resolve_type}; +use crate::{GenericParam, SemanticDiagnostic, TypeId, TypeLongId, semantic}; #[cfg(test)] #[path = "structure_test.rs"] @@ -121,6 +121,76 @@ pub struct Member<'db> { pub visibility: Visibility, } +/// A member or element of a type, abstracting over named struct members and positional tuple +/// elements, enabling unified member access across both. +#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, SemanticObject, salsa::Update)] +#[debug_db(dyn Database)] +pub struct TypeMember<'db> { + pub ty: TypeId<'db>, + #[dont_rewrite] + pub visibility: Visibility, + pub kind: TypeMemberKind<'db>, +} + +/// Distinguishes between named struct members and positional tuple elements. +#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, SemanticObject, salsa::Update)] +#[debug_db(dyn Database)] +pub enum TypeMemberKind<'db> { + /// A named struct member, identified by its `MemberId`. + StructMember(MemberId<'db>), + /// A positional tuple element at the given index. + TupleElement(#[dont_rewrite] usize), +} + +/// Returns the members of a type keyed by their access name, or `None` for types without +/// members. +/// +/// For struct types, members are keyed by their declared name. For tuple types, members are keyed +/// by their stringified index ("0", "1", ...). +#[salsa::tracked(returns(ref))] +fn type_members_query<'db>( + db: &'db dyn Database, + ty: TypeId<'db>, +) -> Maybe, TypeMember<'db>>>> { + match ty.long(db) { + TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id)) => { + let members = db.concrete_struct_members(*concrete_struct_id)?; + Ok(Some( + members + .iter() + .map(|(name, member)| { + ( + *name, + TypeMember { + ty: member.ty, + visibility: member.visibility, + kind: TypeMemberKind::StructMember(member.id), + }, + ) + }) + .collect(), + )) + } + TypeLongId::Tuple(tys) => Ok(Some( + tys.iter() + .enumerate() + .map(|(i, &ty)| { + let name = SmolStrId::from(db, i.to_string()); + ( + name, + TypeMember { + ty, + visibility: Visibility::Public, + kind: TypeMemberKind::TupleElement(i), + }, + ) + }) + .collect(), + )), + _ => Ok(None), + } +} + /// Returns the definition data of a struct. #[salsa::tracked(returns(ref))] fn struct_definition_data<'db>( @@ -296,6 +366,14 @@ pub trait StructSemantic<'db>: Database { ) -> Maybe<&'db OrderedHashMap, semantic::Member<'db>>> { concrete_struct_members(self.as_dyn_database(), concrete_struct_id).maybe_as_ref() } + /// Returns the members of a type keyed by their access name, or `None` for types without + /// members. Works for both struct types and tuples. + fn type_members( + &'db self, + ty: TypeId<'db>, + ) -> Maybe, TypeMember<'db>>>> { + type_members_query(self.as_dyn_database(), ty).maybe_as_ref().map(|opt| opt.as_ref()) + } } impl<'db, T: Database + ?Sized> StructSemantic<'db> for T {} diff --git a/crates/cairo-lang-semantic/src/resolve/mod.rs b/crates/cairo-lang-semantic/src/resolve/mod.rs index 8594d0174d6..3571a4c5175 100644 --- a/crates/cairo-lang-semantic/src/resolve/mod.rs +++ b/crates/cairo-lang-semantic/src/resolve/mod.rs @@ -77,7 +77,7 @@ use crate::types::{ }; use crate::{ ConcreteFunction, ConcreteTypeId, ConcreteVariant, FunctionId, FunctionLongId, - GenericArgumentId, GenericParam, Member, Mutability, TypeId, TypeLongId, + GenericArgumentId, GenericParam, Mutability, TypeId, TypeLongId, TypeMember, }; #[cfg(test)] @@ -134,7 +134,7 @@ impl<'db> ResolvedItems<'db> { pub struct EnrichedMembers<'db> { /// A map from member names to their semantic representation and the number of deref operations /// needed to access them. - pub members: OrderedHashMap, (Member<'db>, usize)>, + pub members: OrderedHashMap, (TypeMember<'db>, usize)>, /// The sequence of deref needed to access the members. pub deref_chain: Arc>>, // The number of derefs that were explored. @@ -143,9 +143,9 @@ pub struct EnrichedMembers<'db> { impl<'db> EnrichedMembers<'db> { /// Returns `EnrichedTypeMemberAccess` for a single member if exists. pub fn get_member(&self, name: SmolStrId<'db>) -> Option> { - let (member, n_derefs) = self.members.get(&name)?; + let (type_member, n_derefs) = self.members.get(&name)?; Some(EnrichedTypeMemberAccess { - member: member.clone(), + type_member: type_member.clone(), deref_functions: self .deref_chain .iter() @@ -160,7 +160,7 @@ impl<'db> EnrichedMembers<'db> { /// access it. pub struct EnrichedTypeMemberAccess<'db> { /// The member itself. - pub member: Member<'db>, + pub type_member: TypeMember<'db>, /// The sequence of deref functions needed to access the member. pub deref_functions: Vec<(FunctionId<'db>, Mutability)>, } diff --git a/crates/cairo-lang-semantic/src/semantic.rs b/crates/cairo-lang-semantic/src/semantic.rs index 4a0798a7060..3bb6c223f85 100644 --- a/crates/cairo-lang-semantic/src/semantic.rs +++ b/crates/cairo-lang-semantic/src/semantic.rs @@ -20,7 +20,7 @@ pub use crate::items::functions::{ }; pub use crate::items::generics::{GenericArgumentId, GenericParam}; pub use crate::items::imp::{ConcreteImplId, ConcreteImplLongId}; -pub use crate::items::structure::Member; +pub use crate::items::structure::{Member, TypeMember, TypeMemberKind}; pub use crate::items::trt::{ConcreteTraitId, ConcreteTraitLongId}; pub use crate::types::{ ConcreteEnumId, ConcreteExternTypeId, ConcreteStructId, ConcreteTypeId, TypeId, TypeLongId,