Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 33 additions & 17 deletions crates/cairo-lang-semantic/src/expr/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -3949,14 +3949,23 @@ 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(
rhs_syntax.stable_ptr(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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
82 changes: 80 additions & 2 deletions crates/cairo-lang-semantic/src/items/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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<Option<OrderedHashMap<SmolStrId<'db>, 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>(
Expand Down Expand Up @@ -296,6 +366,14 @@ pub trait StructSemantic<'db>: Database {
) -> Maybe<&'db OrderedHashMap<SmolStrId<'db>, 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<Option<&'db OrderedHashMap<SmolStrId<'db>, 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 {}
10 changes: 5 additions & 5 deletions crates/cairo-lang-semantic/src/resolve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<SmolStrId<'db>, (Member<'db>, usize)>,
pub members: OrderedHashMap<SmolStrId<'db>, (TypeMember<'db>, usize)>,
/// The sequence of deref needed to access the members.
pub deref_chain: Arc<Vec<DerefInfo<'db>>>,
// The number of derefs that were explored.
Expand All @@ -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<EnrichedTypeMemberAccess<'db>> {
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()
Expand All @@ -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)>,
}
Expand Down
2 changes: 1 addition & 1 deletion crates/cairo-lang-semantic/src/semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading