Skip to content

Commit 3574eae

Browse files
committed
feat(semantic): support tuple member access via v.0 syntax (#7608)
1 parent 22810ca commit 3574eae

12 files changed

Lines changed: 256 additions & 133 deletions

File tree

crates/cairo-lang-lowering/src/lower/mod.rs

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ use cairo_lang_semantic::items::functions::{
1515
FunctionsSemantic, GenericFunctionId, ImplGenericFunctionId,
1616
};
1717
use cairo_lang_semantic::items::imp::ImplLongId;
18-
use cairo_lang_semantic::items::structure::StructSemantic;
18+
use cairo_lang_semantic::items::structure::{StructSemantic, TypeMemberKind};
1919
use cairo_lang_semantic::items::trt::TraitSemantic;
20+
use cairo_lang_semantic::types::peel_snapshots;
2021
use cairo_lang_semantic::usage::MemberPath;
2122
use cairo_lang_semantic::{
2223
ConcreteFunction, ConcreteTraitLongId, ExprVar, LocalVariable, VarId, corelib,
@@ -1808,23 +1809,42 @@ fn lower_expr_member_access<'db>(
18081809
));
18091810
}
18101811
let location = ctx.get_location(expr.stable_ptr.untyped());
1811-
let members = ctx
1812-
.db
1813-
.concrete_struct_members(expr.concrete_struct_id)
1814-
.map_err(LoweringFlowError::Failed)?;
1815-
let member_idx =
1816-
members.iter().position(|(_, member)| member.id == expr.member).ok_or_else(|| {
1817-
LoweringFlowError::Failed(
1818-
ctx.diagnostics.report(expr.stable_ptr.untyped(), UnexpectedError),
1819-
)
1820-
})?;
1812+
let container_ty = ctx.function_body.arenas.exprs[expr.expr].ty();
1813+
let (_, container_long_ty) = peel_snapshots(ctx.db, container_ty);
1814+
let (member_tys, member_idx) = match &expr.type_member.kind {
1815+
TypeMemberKind::StructMember(member_id) => {
1816+
let semantic::ConcreteTypeId::Struct(concrete_struct_id) =
1817+
extract_matches!(container_long_ty, TypeLongId::Concrete)
1818+
else {
1819+
unreachable!("Container must be a struct for StructMember access")
1820+
};
1821+
let members = ctx
1822+
.db
1823+
.concrete_struct_members(concrete_struct_id)
1824+
.map_err(LoweringFlowError::Failed)?;
1825+
let member_idx =
1826+
members.iter().position(|(_, m)| m.id == *member_id).ok_or_else(|| {
1827+
LoweringFlowError::Failed(
1828+
ctx.diagnostics.report(expr.stable_ptr.untyped(), UnexpectedError),
1829+
)
1830+
})?;
1831+
let member_tys = members
1832+
.iter()
1833+
.map(|(_, m)| wrap_in_snapshots(ctx.db, m.ty, expr.n_snapshots))
1834+
.collect();
1835+
(member_tys, member_idx)
1836+
}
1837+
TypeMemberKind::TupleElement(idx) => {
1838+
let tys = extract_matches!(container_long_ty, TypeLongId::Tuple);
1839+
let member_tys =
1840+
tys.iter().map(|&ty| wrap_in_snapshots(ctx.db, ty, expr.n_snapshots)).collect();
1841+
(member_tys, *idx)
1842+
}
1843+
};
18211844
Ok(LoweredExpr::AtVariable(
18221845
generators::StructMemberAccess {
18231846
input: lower_expr_to_var_usage(ctx, builder, expr.expr)?,
1824-
member_tys: members
1825-
.iter()
1826-
.map(|(_, member)| wrap_in_snapshots(ctx.db, member.ty, expr.n_snapshots))
1827-
.collect(),
1847+
member_tys,
18281848
member_idx,
18291849
location,
18301850
}

crates/cairo-lang-semantic/src/expr/compute.rs

Lines changed: 77 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3760,35 +3760,48 @@ fn try_extract_identifier_from_path<'a>(
37603760
}
37613761
}
37623762

3763-
/// Given an expression syntax, if it's an identifier, returns it. Otherwise, returns the proper
3764-
/// error.
3765-
fn expr_as_identifier<'db>(
3766-
ctx: &mut ComputationContext<'db, '_>,
3767-
path: &ast::ExprPath<'db>,
3768-
db: &'db dyn Database,
3769-
) -> Maybe<SmolStrId<'db>> {
3770-
let segments_var = path.segments(db);
3771-
let mut segments = segments_var.elements(db);
3772-
if segments.len() == 1 {
3773-
Ok(segments.next().unwrap().identifier(db))
3774-
} else {
3775-
Err(ctx.diagnostics.report(path.stable_ptr(db), InvalidMemberExpression))
3776-
}
3777-
}
3778-
3779-
// TODO(spapini): Consider moving some checks here to the responsibility of the parser.
37803763
/// Computes the semantic expression for a dot expression.
37813764
fn dot_expr<'db>(
37823765
ctx: &mut ComputationContext<'db, '_>,
37833766
lexpr: ExprAndId<'db>,
37843767
rhs_syntax: ast::Expr<'db>,
37853768
stable_ptr: ast::ExprPtr<'db>,
37863769
) -> Maybe<Expr<'db>> {
3787-
// Find MemberId.
3788-
match rhs_syntax {
3789-
ast::Expr::Path(expr) => member_access_expr(ctx, lexpr, expr, stable_ptr),
3790-
ast::Expr::FunctionCall(expr) => method_call_expr(ctx, lexpr, expr, stable_ptr),
3791-
_ => Err(ctx.diagnostics.report(rhs_syntax.stable_ptr(ctx.db), InvalidMemberExpression)),
3770+
if let Some(member_name) = match rhs_syntax {
3771+
ast::Expr::FunctionCall(expr) => return method_call_expr(ctx, lexpr, expr, stable_ptr),
3772+
ast::Expr::Path(ref expr) => path_as_identifier(ctx.db, &expr),
3773+
ast::Expr::Literal(ref literal) => literal_as_identifier(ctx.db, &literal),
3774+
_ => None,
3775+
} {
3776+
member_access_expr_by_name(
3777+
ctx,
3778+
lexpr,
3779+
member_name,
3780+
rhs_syntax.stable_ptr(ctx.db).untyped(),
3781+
stable_ptr,
3782+
)
3783+
} else {
3784+
Err(ctx.diagnostics.report(rhs_syntax.stable_ptr(ctx.db), InvalidMemberExpression))
3785+
}
3786+
}
3787+
3788+
/// Given a path syntax, return it as an identifier if possible, otherwise, return None.
3789+
fn path_as_identifier<'db>(
3790+
db: &'db dyn Database,
3791+
path: &ast::ExprPath<'db>,
3792+
) -> Option<SmolStrId<'db>> {
3793+
Some(path.segments(db).elements(db).exactly_one().ok()?.identifier(db))
3794+
}
3795+
3796+
/// Given a literal syntax, return it as an identifier if possible, otherwise, return None.
3797+
fn literal_as_identifier<'db>(
3798+
db: &'db dyn Database,
3799+
literal: &ast::TerminalLiteralNumber<'db>,
3800+
) -> Option<SmolStrId<'db>> {
3801+
if let (value, None) = literal.numeric_value_and_suffix(db)? {
3802+
Some(SmolStrId::from(db, value.to_usize()?.to_string()))
3803+
} else {
3804+
None
37923805
}
37933806
}
37943807

@@ -3933,55 +3946,46 @@ fn method_call_expr<'db>(
39333946
expr_function_call(ctx, function_id, named_args, expr.stable_ptr(db), stable_ptr)
39343947
}
39353948

3936-
/// Computes the semantic model of a member access expression (e.g. "expr.member").
3937-
fn member_access_expr<'db>(
3949+
/// Computes the semantic model of a member access expression for a given member name.
3950+
fn member_access_expr_by_name<'db>(
39383951
ctx: &mut ComputationContext<'db, '_>,
39393952
lexpr: ExprAndId<'db>,
3940-
rhs_syntax: ast::ExprPath<'db>,
3953+
member_name: SmolStrId<'db>,
3954+
name_stable_ptr: SyntaxStablePtrId<'db>,
39413955
stable_ptr: ast::ExprPtr<'db>,
39423956
) -> Maybe<Expr<'db>> {
39433957
let db = ctx.db;
3944-
3945-
// Find MemberId.
3946-
let member_name = expr_as_identifier(ctx, &rhs_syntax, db)?;
3947-
let (n_snapshots, long_ty) =
3948-
finalized_snapshot_peeled_ty(ctx, lexpr.ty(), rhs_syntax.stable_ptr(db))?;
3958+
let (n_snapshots, long_ty) = finalized_snapshot_peeled_ty(ctx, lexpr.ty(), name_stable_ptr)?;
39493959

39503960
match &long_ty {
39513961
TypeLongId::Concrete(_) | TypeLongId::Tuple(_) | TypeLongId::FixedSizeArray { .. } => {
39523962
let Some(EnrichedTypeMemberAccess { type_member, deref_functions }) =
39533963
get_enriched_type_member_access(ctx, lexpr.clone(), stable_ptr, member_name)?
39543964
else {
39553965
return Err(ctx.diagnostics.report(
3956-
rhs_syntax.stable_ptr(db),
3966+
name_stable_ptr,
39573967
NoSuchTypeMember { ty: long_ty.intern(ctx.db), member_name },
39583968
));
39593969
};
3960-
// TODO(#7608): handle TypeMemberKind::TupleElement here.
3961-
let TypeMemberKind::StructMember(member_id) = type_member.kind else {
3962-
return Err(ctx.diagnostics.report(rhs_syntax.stable_ptr(db), Unsupported));
3963-
};
3964-
let member = semantic::Member {
3965-
id: member_id,
3966-
ty: type_member.ty,
3967-
visibility: type_member.visibility,
3968-
};
3969-
check_struct_member_is_visible(
3970-
ctx,
3971-
&member,
3972-
rhs_syntax.stable_ptr(db).untyped(),
3973-
member_name,
3974-
);
3975-
let member_path = match &long_ty {
3976-
TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id))
3977-
if n_snapshots == 0 && deref_functions.is_empty() =>
3978-
{
3970+
if let TypeMemberKind::StructMember(member_id) = type_member.kind {
3971+
let member = semantic::Member {
3972+
id: member_id,
3973+
ty: type_member.ty,
3974+
visibility: type_member.visibility,
3975+
};
3976+
check_struct_member_is_visible(ctx, &member, name_stable_ptr, member_name);
3977+
}
3978+
let member_path = match (&type_member.kind, &long_ty) {
3979+
(
3980+
TypeMemberKind::StructMember(member_id),
3981+
TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id)),
3982+
) if n_snapshots == 0 && deref_functions.is_empty() => {
39793983
lexpr.as_member_path().map(|parent| ExprVarMemberPath::Member {
39803984
parent: Box::new(parent),
3981-
member_id: member.id,
3985+
member_id: *member_id,
39823986
stable_ptr,
39833987
concrete_struct_id: *concrete_struct_id,
3984-
ty: member.ty,
3988+
ty: type_member.ty,
39853989
})
39863990
}
39873991
_ => None,
@@ -4000,19 +4004,12 @@ fn member_access_expr<'db>(
40004004
derefed_expr =
40014005
ExprAndId { expr: cur_expr.clone(), id: ctx.arenas.exprs.alloc(cur_expr) };
40024006
}
4003-
let (n_snapshots, long_ty) =
4004-
finalized_snapshot_peeled_ty(ctx, derefed_expr.ty(), rhs_syntax.stable_ptr(db))?;
4005-
let derefed_expr_concrete_struct_id = match long_ty {
4006-
TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id)) => {
4007-
concrete_struct_id
4008-
}
4009-
_ => unreachable!(),
4010-
};
4011-
let ty = wrap_in_snapshots(ctx.db, member.ty, n_snapshots);
4007+
let (n_snapshots, _) =
4008+
finalized_snapshot_peeled_ty(ctx, derefed_expr.ty(), name_stable_ptr)?;
4009+
let ty = wrap_in_snapshots(ctx.db, type_member.ty, n_snapshots);
40124010
let mut final_expr = Expr::MemberAccess(ExprMemberAccess {
40134011
expr: derefed_expr.id,
4014-
concrete_struct_id: derefed_expr_concrete_struct_id,
4015-
member: member.id,
4012+
type_member,
40164013
ty,
40174014
member_path,
40184015
n_snapshots,
@@ -4031,20 +4028,15 @@ fn member_access_expr<'db>(
40314028
Ok(final_expr)
40324029
}
40334030

4034-
TypeLongId::Snapshot(_) => {
4035-
// TODO(spapini): Handle snapshot members.
4036-
Err(ctx.diagnostics.report(rhs_syntax.stable_ptr(db), Unsupported))
4037-
}
4038-
TypeLongId::Closure(_) => {
4039-
Err(ctx.diagnostics.report(rhs_syntax.stable_ptr(db), Unsupported))
4040-
}
4031+
TypeLongId::Snapshot(_) => Err(ctx.diagnostics.report(name_stable_ptr, Unsupported)),
4032+
TypeLongId::Closure(_) => Err(ctx.diagnostics.report(name_stable_ptr, Unsupported)),
40414033
TypeLongId::Var(_) => Err(ctx.diagnostics.report(
4042-
rhs_syntax.stable_ptr(db),
4034+
name_stable_ptr,
40434035
InternalInferenceError(InferenceError::TypeNotInferred(long_ty.intern(ctx.db))),
40444036
)),
40454037
TypeLongId::ImplType(_) | TypeLongId::GenericParameter(_) | TypeLongId::Coupon(_) => {
40464038
Err(ctx.diagnostics.report(
4047-
rhs_syntax.stable_ptr(db),
4039+
name_stable_ptr,
40484040
TypeHasNoMembers { ty: long_ty.intern(ctx.db), member_name },
40494041
))
40504042
}
@@ -4090,18 +4082,19 @@ fn get_enriched_type_member_access<'db>(
40904082
}
40914083
Entry::Vacant(_) => {
40924084
let (_, long_ty) = finalized_snapshot_peeled_ty(ctx, ty, stable_ptr)?;
4093-
let members = if let Some(type_members_map) = ctx.db.type_members(long_ty.intern(ctx.db))? {
4094-
if let Some(type_member) = type_members_map.get(&accessed_member_name) {
4095-
// Found direct member access - so directly returning it.
4096-
return Ok(Some(EnrichedTypeMemberAccess {
4097-
type_member: type_member.clone(),
4098-
deref_functions: vec![],
4099-
}));
4100-
}
4101-
type_members_map.iter().map(|(k, v)| (*k, (v.clone(), 0))).collect()
4102-
} else {
4103-
Default::default()
4104-
};
4085+
let members =
4086+
if let Some(type_members_map) = ctx.db.type_members(long_ty.intern(ctx.db))? {
4087+
if let Some(type_member) = type_members_map.get(&accessed_member_name) {
4088+
// Found direct member access - so directly returning it.
4089+
return Ok(Some(EnrichedTypeMemberAccess {
4090+
type_member: type_member.clone(),
4091+
deref_functions: vec![],
4092+
}));
4093+
}
4094+
type_members_map.iter().map(|(k, v)| (*k, (v.clone(), 0))).collect()
4095+
} else {
4096+
Default::default()
4097+
};
41054098

41064099
EnrichedMembers {
41074100
members,

crates/cairo-lang-semantic/src/expr/inference.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ use crate::{
5858
ConcreteEnumId, ConcreteExternTypeId, ConcreteFunction, ConcreteImplId, ConcreteImplLongId,
5959
ConcreteStructId, ConcreteTraitId, ConcreteTraitLongId, ConcreteTypeId, ConcreteVariant,
6060
FunctionId, FunctionLongId, GenericArgumentId, GenericParam, LocalVariable, MatchArmSelector,
61-
Member, Parameter, SemanticObject, Signature, TypeId, TypeLongId, ValueSelectorArm,
62-
add_basic_rewrites, add_expr_rewrites, add_rewrite, semantic_object_for_id,
61+
Member, Parameter, SemanticObject, Signature, TypeId, TypeLongId, TypeMember, TypeMemberKind,
62+
ValueSelectorArm, add_basic_rewrites, add_expr_rewrites, add_rewrite, semantic_object_for_id,
6363
};
6464

6565
pub mod canonic;

crates/cairo-lang-semantic/src/expr/objects.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use salsa::Database;
1010

1111
use super::fmt::ExprFormatter;
1212
use crate::items::constant::ConstValueId;
13+
use crate::items::structure::TypeMember;
1314
use crate::{ConcreteStructId, FunctionId, TypeId, semantic};
1415

1516
/// Defines an arena id type and its behavior for usage in an arena.
@@ -526,8 +527,7 @@ pub struct ExprStringLiteral<'db> {
526527
#[debug_db(ExprFormatter<'db>)]
527528
pub struct ExprMemberAccess<'db> {
528529
pub expr: semantic::ExprId,
529-
pub concrete_struct_id: ConcreteStructId<'db>,
530-
pub member: MemberId<'db>,
530+
pub type_member: TypeMember<'db>,
531531
pub ty: semantic::TypeId<'db>,
532532
#[hide_field_debug_with_db]
533533
pub member_path: Option<ExprVarMemberPath<'db>>,

crates/cairo-lang-semantic/src/expr/semantic_test_data/repr_ptr

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -537,13 +537,23 @@ FunctionCall(
537537
expr: Var(
538538
LocalVarId(test::p),
539539
),
540-
concrete_struct_id: test::PPoint,
541-
member: MemberId(test::PPoint::point),
540+
type_member: TypeMember {
541+
ty: test::Point,
542+
visibility: Private,
543+
kind: StructMember(
544+
MemberId(test::PPoint::point),
545+
),
546+
},
542547
ty: test::Point,
543548
},
544549
),
545-
concrete_struct_id: test::Point,
546-
member: MemberId(test::Point::x),
550+
type_member: TypeMember {
551+
ty: core::integer::u32,
552+
visibility: Private,
553+
kind: StructMember(
554+
MemberId(test::Point::x),
555+
),
556+
},
547557
ty: core::integer::u32,
548558
},
549559
),

0 commit comments

Comments
 (0)