Sitelet https://github.com/starkware-libs/cairo/commit/0aa64af17b78c2d0ef92602ca7b466b8710abc88
Skip to content

Commit 0aa64af

Browse files
feat(semantic): support tuple element access via t.0 syntax
Adds positional tuple element access by index using dot notation (e.g. `t.0`, `t.1`), resolved in the semantic model. Member access is generalized via a new `MemberAccessKind` enum (`Struct{..}` | `Index{..}`) threaded through `ExprMemberAccess`, `ExprVarMemberPath` and `usage::MemberPath`, so a member is identified either by a struct member id or by a numeric tuple index. The index must be a plain non-negative decimal literal (no base prefix or type suffix); out-of-bounds and non-tuple accesses are reported. Lowering of tuple index access is not yet implemented and currently reports an "Unsupported" error; it will be added in a follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2304744 commit 0aa64af

17 files changed

Lines changed: 519 additions & 90 deletions

File tree

‎crates/cairo-lang-lowering/src/lower/block_builder.rs‎

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use cairo_lang_syntax::node::TypedStablePtr;
1010
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
1111
use cairo_lang_utils::{Intern, require};
1212
use itertools::{Itertools, chain, zip_eq};
13-
use semantic::{ConcreteTypeId, ExprVarMemberPath, TypeLongId};
13+
use semantic::{ConcreteTypeId, ExprVarMemberPath, MemberAccessKind, TypeLongId};
1414

1515
use super::context::{LoweredExpr, LoweringContext, LoweringFlowError, LoweringResult, VarRequest};
1616
use super::generators;
@@ -70,6 +70,14 @@ impl<'db> BlockBuilder<'db> {
7070
var: VariableId,
7171
location: LocationId<'db>,
7272
) {
73+
// TODO(TomerStarkware): Support lowering of tuple index access (`t.0`).
74+
if member_path_is_tuple_index(&member_path) {
75+
ctx.diagnostics.report_by_location(
76+
location.long(ctx.db).clone(),
77+
LoweringDiagnosticKind::Unsupported,
78+
);
79+
return;
80+
}
7381
self.semantics.update(
7482
&mut BlockStructRecomposer { statements: &mut self.statements, ctx, location },
7583
&member_path,
@@ -129,6 +137,14 @@ impl<'db> BlockBuilder<'db> {
129137
location: LocationId<'db>,
130138
expected_ty: Option<semantic::TypeId<'db>>,
131139
) -> Option<VarUsage<'db>> {
140+
// TODO(TomerStarkware): Support lowering of tuple index access (`t.0`).
141+
if member_path_is_tuple_index(member_path) {
142+
ctx.diagnostics.report_by_location(
143+
location.long(ctx.db).clone(),
144+
LoweringDiagnosticKind::Unsupported,
145+
);
146+
return None;
147+
}
132148
// Fetch the variable from the semantics.
133149
let res = self.semantics.get(
134150
BlockStructRecomposer { statements: &mut self.statements, ctx, location },
@@ -222,8 +238,11 @@ impl<'db> BlockBuilder<'db> {
222238
if let Some(var_id) = ctx.snapped_semantics.get::<MemberPath<'_>>(&member_path.into()) {
223239
return Some(VarUsage { var_id: *var_id, location });
224240
}
225-
let ExprVarMemberPath::Member { parent, member_id, concrete_struct_id, .. } = member_path
226-
else {
241+
let ExprVarMemberPath::Member { parent, kind, .. } = member_path else {
242+
return None;
243+
};
244+
// TODO(TomerStarkware): Support lowering of tuple index access (`t.0`).
245+
let MemberAccessKind::Struct { concrete_struct_id, member_id } = kind else {
227246
return None;
228247
};
229248
let parent_var = self.get_snap_ref(ctx, parent)?;
@@ -420,8 +439,30 @@ fn get_ty<'db>(
420439
) -> semantic::TypeId<'db> {
421440
match member_path {
422441
MemberPath::Var(var) => ctx.semantic_defs[var].ty(),
423-
MemberPath::Member { member_id, concrete_struct_id, .. } => {
424-
ctx.db.concrete_struct_members(*concrete_struct_id).unwrap()[&member_id.name(ctx.db)].ty
442+
MemberPath::Member { parent, kind } => match kind {
443+
MemberAccessKind::Struct { concrete_struct_id, member_id } => {
444+
ctx.db.concrete_struct_members(*concrete_struct_id).unwrap()
445+
[&member_id.name(ctx.db)]
446+
.ty
447+
}
448+
MemberAccessKind::Index { index } => {
449+
let (n_snapshots, long_ty) = peel_snapshots(ctx.db, get_ty(ctx, parent));
450+
let TypeLongId::Tuple(tys) = long_ty else {
451+
unreachable!("Tuple index access on a non-tuple type.");
452+
};
453+
wrap_in_snapshots(ctx.db, tys[*index], n_snapshots)
454+
}
455+
},
456+
}
457+
}
458+
459+
/// Returns whether the member path accesses a tuple element by index at any level (`t.0`).
460+
/// Such accesses are not yet lowered.
461+
fn member_path_is_tuple_index(member_path: &MemberPath<'_>) -> bool {
462+
match member_path {
463+
MemberPath::Var(_) => false,
464+
MemberPath::Member { parent, kind } => {
465+
matches!(kind, MemberAccessKind::Index { .. }) || member_path_is_tuple_index(parent)
425466
}
426467
}
427468
}

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

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use semantic::items::constant::ConstValue;
4444
use semantic::types::wrap_in_snapshots;
4545
use semantic::{
4646
ExprFunctionCallArg, ExprId, ExprPropagateError, ExprVarMemberPath, GenericArgumentId,
47-
MatchArmSelector, SemanticDiagnostic, TypeLongId,
47+
MatchArmSelector, MemberAccessKind, SemanticDiagnostic, TypeLongId,
4848
};
4949

5050
use self::block_builder::{BlockBuilder, SealedBlockBuilder, SealedGotoCallsite};
@@ -1534,12 +1534,11 @@ fn lower_expr_loop<'db>(
15341534
ExprVarMemberPath::Var(var) => {
15351535
ExprVarMemberPath::Var(ExprVar { ty: wrap_in_snapshots(db, var.ty, 1), ..*var })
15361536
}
1537-
ExprVarMemberPath::Member { parent, member_id, stable_ptr, concrete_struct_id, ty } => {
1537+
ExprVarMemberPath::Member { parent, kind, stable_ptr, ty } => {
15381538
ExprVarMemberPath::Member {
15391539
parent: parent.clone(),
1540-
member_id: *member_id,
1540+
kind: kind.clone(),
15411541
stable_ptr: *stable_ptr,
1542-
concrete_struct_id: *concrete_struct_id,
15431542
ty: wrap_in_snapshots(db, *ty, 1),
15441543
}
15451544
}
@@ -1804,19 +1803,23 @@ fn lower_expr_member_access<'db>(
18041803
builder: &mut BlockBuilder<'db>,
18051804
) -> LoweringResult<'db, LoweredExpr<'db>> {
18061805
log::trace!("Lowering a member-access expression: {:?}", expr.debug(&ctx.expr_formatter));
1806+
// TODO(TomerStarkware): Support lowering of tuple index access (`t.0`).
1807+
let MemberAccessKind::Struct { concrete_struct_id, member_id } = expr.kind else {
1808+
return Err(LoweringFlowError::Failed(
1809+
ctx.diagnostics.report(expr.stable_ptr.untyped(), Unsupported),
1810+
));
1811+
};
18071812
if let Some(member_path) = &expr.member_path {
18081813
return Ok(LoweredExpr::MemberPath(
18091814
member_path.clone(),
18101815
ctx.get_location(expr.stable_ptr.untyped()),
18111816
));
18121817
}
18131818
let location = ctx.get_location(expr.stable_ptr.untyped());
1814-
let members = ctx
1815-
.db
1816-
.concrete_struct_members(expr.concrete_struct_id)
1817-
.map_err(LoweringFlowError::Failed)?;
1819+
let members =
1820+
ctx.db.concrete_struct_members(concrete_struct_id).map_err(LoweringFlowError::Failed)?;
18181821
let member_idx =
1819-
members.iter().position(|(_, member)| member.id == expr.member).ok_or_else(|| {
1822+
members.iter().position(|(_, member)| member.id == member_id).ok_or_else(|| {
18201823
LoweringFlowError::Failed(
18211824
ctx.diagnostics.report(expr.stable_ptr.untyped(), UnexpectedError),
18221825
)
@@ -1862,9 +1865,11 @@ fn lower_expr_struct_ctor<'db>(
18621865
};
18631866
let member_path = ExprVarMemberPath::Member {
18641867
parent: Box::new(path.clone()),
1865-
member_id: member.id,
1868+
kind: MemberAccessKind::Struct {
1869+
concrete_struct_id: expr.concrete_struct_id,
1870+
member_id: member.id,
1871+
},
18661872
stable_ptr: path.stable_ptr(),
1867-
concrete_struct_id: expr.concrete_struct_id,
18681873
ty: member.ty,
18691874
};
18701875
entry

‎crates/cairo-lang-lowering/src/lower/refs.rs‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use cairo_lang_semantic::expr::fmt::ExprFormatter;
44
use cairo_lang_semantic::expr::inference::InferenceError;
55
use cairo_lang_semantic::items::structure::StructSemantic;
66
use cairo_lang_semantic::usage::MemberPath;
7-
use cairo_lang_semantic::{self as semantic, ConcreteTypeId, TypeLongId};
7+
use cairo_lang_semantic::{self as semantic, ConcreteTypeId, MemberAccessKind, TypeLongId};
88
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
99
use cairo_lang_utils::{Intern, extract_matches, try_extract_matches};
1010
use itertools::{Itertools, chain};
@@ -156,7 +156,11 @@ impl<'db> SemanticLoweringMapping<'db> {
156156
return self.scattered.get_mut(path);
157157
}
158158

159-
let MemberPath::Member { parent, member_id, concrete_struct_id, .. } = path else {
159+
let MemberPath::Member { parent, kind } = path else {
160+
return None;
161+
};
162+
// TODO(TomerStarkware): Support lowering of tuple index access (`t.0`).
163+
let MemberAccessKind::Struct { member_id, concrete_struct_id } = kind else {
160164
return None;
161165
};
162166

@@ -324,8 +328,7 @@ fn compute_remapped_variables<'db>(
324328
.map(|member_id| {
325329
let member_path = MemberPath::Member {
326330
parent: parent_path.clone().into(),
327-
member_id: *member_id,
328-
concrete_struct_id,
331+
kind: MemberAccessKind::Struct { concrete_struct_id, member_id: *member_id },
329332
};
330333
// Call `compute_remapped_variables` recursively on the scattered values.
331334
// If there is a [Value::Var], `require_remapping` will be set to `true` to account

‎crates/cairo-lang-semantic/src/cache/mod.rs‎

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ use crate::{
5959
ConcreteEnumId, ConcreteExternTypeId, ConcreteFunction, ConcreteFunctionWithBodyId,
6060
ConcreteImplId, ConcreteImplLongId, ConcreteStructId, ConcreteTraitId, ConcreteTraitLongId,
6161
ConcreteTypeId, ConcreteVariant, ExprVar, ExprVarMemberPath, FunctionId, FunctionLongId,
62-
GenericArgumentId, GenericParam, MatchArmSelector, TypeId, TypeLongId, ValueSelectorArm,
62+
GenericArgumentId, GenericParam, MatchArmSelector, MemberAccessKind, TypeId, TypeLongId,
63+
ValueSelectorArm,
6364
};
6465

6566
type SemanticCache<'db> = (CrateSemanticCache, SemanticCacheLookups);
@@ -477,13 +478,52 @@ impl FeatureKindCached {
477478
}
478479
}
479480

481+
#[derive(Serialize, Deserialize)]
482+
pub enum MemberAccessKindCached {
483+
Struct { concrete_struct_id: ConcreteStructCached, member_id: LanguageElementCached },
484+
Index { index: usize },
485+
}
486+
impl MemberAccessKindCached {
487+
pub fn new<'db>(
488+
kind: MemberAccessKind<'db>,
489+
ctx: &mut SemanticCacheSavingContext<'db>,
490+
) -> Self {
491+
match kind {
492+
MemberAccessKind::Struct { concrete_struct_id, member_id } => {
493+
MemberAccessKindCached::Struct {
494+
concrete_struct_id: ConcreteStructCached::new(concrete_struct_id, ctx),
495+
member_id: LanguageElementCached::new(member_id, &mut ctx.defs_ctx),
496+
}
497+
}
498+
MemberAccessKind::Index { index } => MemberAccessKindCached::Index { index },
499+
}
500+
}
501+
pub fn get_embedded<'db>(
502+
self,
503+
data: &Arc<SemanticCacheLoadingData<'db>>,
504+
db: &'db dyn Database,
505+
) -> MemberAccessKind<'db> {
506+
match self {
507+
MemberAccessKindCached::Struct { concrete_struct_id, member_id } => {
508+
let (module_id, member_stable_ptr) =
509+
member_id.get_embedded(&data.defs_loading_data);
510+
let member_id = MemberLongId(module_id, MemberPtr(member_stable_ptr)).intern(db);
511+
MemberAccessKind::Struct {
512+
concrete_struct_id: concrete_struct_id.get_embedded(data, db),
513+
member_id,
514+
}
515+
}
516+
MemberAccessKindCached::Index { index } => MemberAccessKind::Index { index },
517+
}
518+
}
519+
}
520+
480521
#[derive(Serialize, Deserialize)]
481522
pub enum ExprVarMemberPathCached {
482523
Var(ExprVarCached),
483524
Member {
484525
parent: Box<ExprVarMemberPathCached>,
485-
member_id: LanguageElementCached,
486-
concrete_struct_id: ConcreteStructCached,
526+
kind: MemberAccessKindCached,
487527
stable_ptr: SyntaxStablePtrIdCached,
488528
ty: TypeIdCached,
489529
},
@@ -497,11 +537,10 @@ impl ExprVarMemberPathCached {
497537
ExprVarMemberPath::Var(var) => {
498538
ExprVarMemberPathCached::Var(ExprVarCached::new(var, ctx))
499539
}
500-
ExprVarMemberPath::Member { parent, member_id, concrete_struct_id, stable_ptr, ty } => {
540+
ExprVarMemberPath::Member { parent, kind, stable_ptr, ty } => {
501541
ExprVarMemberPathCached::Member {
502542
parent: Box::new(ExprVarMemberPathCached::new(*parent, ctx)),
503-
member_id: LanguageElementCached::new(member_id, &mut ctx.defs_ctx),
504-
concrete_struct_id: ConcreteStructCached::new(concrete_struct_id, ctx),
543+
kind: MemberAccessKindCached::new(kind, ctx),
505544
stable_ptr: SyntaxStablePtrIdCached::new(
506545
stable_ptr.untyped(),
507546
&mut ctx.defs_ctx,
@@ -518,21 +557,11 @@ impl ExprVarMemberPathCached {
518557
) -> ExprVarMemberPath<'db> {
519558
match self {
520559
ExprVarMemberPathCached::Var(var) => ExprVarMemberPath::Var(var.get_embedded(data, db)),
521-
ExprVarMemberPathCached::Member {
522-
parent,
523-
member_id,
524-
concrete_struct_id,
525-
stable_ptr,
526-
ty,
527-
} => {
560+
ExprVarMemberPathCached::Member { parent, kind, stable_ptr, ty } => {
528561
let parent = Box::new(parent.get_embedded(data, db));
529-
let (module_id, member_stable_ptr) =
530-
member_id.get_embedded(&data.defs_loading_data);
531-
let member_id = MemberLongId(module_id, MemberPtr(member_stable_ptr)).intern(db);
532562
ExprVarMemberPath::Member {
533563
parent,
534-
member_id,
535-
concrete_struct_id: concrete_struct_id.get_embedded(data, db),
564+
kind: kind.get_embedded(data, db),
536565
stable_ptr: ExprPtr(stable_ptr.get_embedded(&data.defs_loading_data)),
537566
ty: ty.get_embedded(data),
538567
}

‎crates/cairo-lang-semantic/src/diagnostic.rs‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,14 @@ impl<'db> DiagnosticEntry<'db> for SemanticDiagnostic<'db> {
538538
SemanticDiagnosticKind::NoSuchTypeMember { ty, member_name } => {
539539
format!(r#"Type "{}" has no member "{}""#, ty.format(db), member_name.long(db))
540540
}
541+
SemanticDiagnosticKind::TupleIndexOutOfBounds { ty, index, len } => {
542+
format!(
543+
r#"Index {} is out of bounds for tuple "{}" of length {}."#,
544+
index,
545+
ty.format(db),
546+
len
547+
)
548+
}
541549
SemanticDiagnosticKind::MemberNotVisible(member_name) => {
542550
format!(r#"Member "{}" is not visible in this context."#, member_name.long(db))
543551
}
@@ -1245,6 +1253,7 @@ impl<'db> DiagnosticEntry<'db> for SemanticDiagnostic<'db> {
12451253
SemanticDiagnosticKind::ModuleFileNotFound(_) => error_code!(E0005),
12461254
SemanticDiagnosticKind::PathNotFound(_) => error_code!(E0006),
12471255
SemanticDiagnosticKind::NoSuchTypeMember { .. } => error_code!(E0007),
1256+
SemanticDiagnosticKind::TupleIndexOutOfBounds { .. } => error_code!(E2315),
12481257
SemanticDiagnosticKind::Unsupported => error_code!(E2000),
12491258
SemanticDiagnosticKind::UnknownLiteral => error_code!(E2001),
12501259
SemanticDiagnosticKind::UnknownBinaryOperator => error_code!(E2002),
@@ -1653,6 +1662,11 @@ pub enum SemanticDiagnosticKind<'db> {
16531662
ty: semantic::TypeId<'db>,
16541663
member_name: SmolStrId<'db>,
16551664
},
1665+
TupleIndexOutOfBounds {
1666+
ty: semantic::TypeId<'db>,
1667+
index: usize,
1668+
len: usize,
1669+
},
16561670
MemberNotVisible(SmolStrId<'db>),
16571671
NoSuchVariant {
16581672
enum_id: EnumId<'db>,

0 commit comments

Comments
 (0)