diff --git a/crates/cairo-lang-semantic/src/expr/compute.rs b/crates/cairo-lang-semantic/src/expr/compute.rs index f6560d1d2aa..bb7623939fd 100644 --- a/crates/cairo-lang-semantic/src/expr/compute.rs +++ b/crates/cairo-lang-semantic/src/expr/compute.rs @@ -27,7 +27,7 @@ use cairo_lang_syntax::node::helpers::{GetIdentifier, PathSegmentEx}; use cairo_lang_syntax::node::ids::SyntaxStablePtrId; use cairo_lang_syntax::node::{ast, Terminal, TypedStablePtr, TypedSyntaxNode}; use cairo_lang_utils as utils; -use cairo_lang_utils::ordered_hash_map::OrderedHashMap; +use cairo_lang_utils::ordered_hash_map::{Entry, OrderedHashMap}; use cairo_lang_utils::ordered_hash_set::OrderedHashSet; use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; use cairo_lang_utils::unordered_hash_set::UnorderedHashSet; @@ -68,7 +68,10 @@ use crate::items::imp::{filter_candidate_traits, infer_impl_by_self}; use crate::items::modifiers::compute_mutability; use crate::items::visibility; use crate::literals::try_extract_minus_literal; -use crate::resolve::{ResolvedConcreteItem, ResolvedGenericItem, Resolver}; +use crate::resolve::{ + EnrichedMembers, EnrichedTypeMemberAccess, EnrichedTypeMemberAccessFailure, + ResolvedConcreteItem, ResolvedGenericItem, Resolver, +}; use crate::semantic::{self, Binding, FunctionId, LocalVariable, TypeId, TypeLongId}; use crate::substitution::SemanticRewriter; use crate::types::{ @@ -2744,9 +2747,9 @@ fn member_access_expr( match &long_ty { TypeLongId::Concrete(_) | TypeLongId::Tuple(_) | TypeLongId::FixedSizeArray { .. } => { - let EnrichedMembers { members, deref_functions } = - enriched_members(ctx, lexpr.clone(), stable_ptr, Some(member_name.clone()))?; - let Some((member, n_derefs)) = members.get(&member_name) else { + let Some(EnrichedTypeMemberAccess { member, deref_functions }) = + get_enriched_type_member_access(ctx, lexpr.clone(), stable_ptr, &member_name)? + else { return Err(ctx.diagnostics.report( &rhs_syntax, NoSuchTypeMember { ty: long_ty.intern(ctx.db), member_name }, @@ -2754,13 +2757,13 @@ fn member_access_expr( }; check_struct_member_is_visible( ctx, - member, + &member, rhs_syntax.stable_ptr().untyped(), &member_name, ); let member_path = match &long_ty { TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id)) - if n_snapshots == 0 && *n_derefs == 0 => + if n_snapshots == 0 && deref_functions.is_empty() => { lexpr.as_member_path().map(|parent| ExprVarMemberPath::Member { parent: Box::new(parent), @@ -2773,7 +2776,7 @@ fn member_access_expr( _ => None, }; let mut derefed_expr: ExprAndId = lexpr; - for (deref_function, mutability) in deref_functions.iter().take(*n_derefs) { + for (deref_function, mutability) in &deref_functions { let cur_expr = expr_function_call( ctx, *deref_function, @@ -2793,7 +2796,7 @@ fn member_access_expr( } _ => unreachable!(), }; - let ty = if *n_derefs != 0 { + let ty = if !deref_functions.is_empty() { member.ty } else { wrap_in_snapshots(ctx.db, member.ty, n_snapshots) @@ -2834,56 +2837,86 @@ fn member_access_expr( } } -/// The result of enriched_members lookup. -struct EnrichedMembers { - /// A map from member names to their semantic representation and the number of deref operations - /// needed to access them. - members: OrderedHashMap, - /// The sequence of deref functions needed to access the members. - deref_functions: Vec<(FunctionId, Mutability)>, -} - +/// Returns the member and the deref operations needed for its access. +/// /// Enriched members include both direct members (in case of a struct), and members of derefed types -/// if the type implements the Deref trait into a struct. Returns a map from member names to the -/// semantic representation, and the number of deref operations needed for each member. -/// `accessed_member_name` is an optional parameter that can be used to stop the search for members -/// once the desired member is found. A `None` value means that all members should be returned, this -/// is useful for completions in the language server. -fn enriched_members( +/// if the type implements the Deref trait into a struct. +fn get_enriched_type_member_access( ctx: &mut ComputationContext<'_>, - mut expr: ExprAndId, + expr: ExprAndId, stable_ptr: ast::ExprPtr, - accessed_member_name: Option, -) -> Maybe { - // TODO(Gil): Use this function for LS completions. - let mut ty = expr.ty(); - let mut res = OrderedHashMap::default(); - let mut deref_functions = vec![]; - let mut visited_types: OrderedHashSet = OrderedHashSet::default(); - // Add direct members. - let (_, mut long_ty) = peel_snapshots(ctx.db, ty); + accessed_member_name: &str, +) -> Maybe> { + let (_, mut long_ty) = peel_snapshots(ctx.db, expr.ty()); if matches!(long_ty, TypeLongId::Var(_)) { // Save some work. ignore the result. The error, if any, will be reported later. ctx.resolver.inference().solve().ok(); long_ty = ctx.resolver.inference().rewrite(long_ty).no_err(); } let (_, long_ty) = peel_snapshots_ex(ctx.db, long_ty); - - if let TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id)) = long_ty { - let members = ctx.db.concrete_struct_members(concrete_struct_id)?; - if let Some(accessed_member_name) = &accessed_member_name { - if let Some(member) = members.get(accessed_member_name) { - return Ok(EnrichedMembers { - members: [(accessed_member_name.clone(), (member.clone(), 0))].into(), - deref_functions, - }); - } - } else { - for (member_name, member) in members.iter() { - res.insert(member_name.clone(), (member.clone(), 0)); + let base_var = match &expr.expr { + Expr::Var(expr_var) => Some(expr_var.var), + Expr::MemberAccess(ExprMemberAccess { member_path: Some(member_path), .. }) => { + Some(member_path.base_var()) + } + _ => None, + }; + let is_mut_var = base_var + .filter(|var_id| matches!(ctx.semantic_defs.get(var_id), Some(var) if var.is_mut())) + .is_some(); + let ty = long_ty.clone().intern(ctx.db); + let key = (ty, is_mut_var); + let mut enriched_members = match ctx.resolver.type_enriched_members.entry(key) { + Entry::Occupied(entry) => { + let e = entry.get(); + match e.accessed(accessed_member_name) { + Ok(value) => return Ok(Some(value)), + Err(EnrichedTypeMemberAccessFailure::NotFound) => return Ok(None), + Err(EnrichedTypeMemberAccessFailure::NotFinal) => {} } + // Moving out of the map to call `enrich_members` and insert back with updated value. + entry.swap_remove() + } + Entry::Vacant(_) => { + 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.clone(), (v.clone(), 0))).collect() + } else { + Default::default() + }; + EnrichedMembers { members, deref_functions: vec![], calc_tail: Some(expr.id) } } - } + }; + enrich_members(ctx, &mut enriched_members, is_mut_var, stable_ptr, accessed_member_name)?; + let e = ctx.resolver.type_enriched_members.entry(key).or_insert(enriched_members); + Ok(e.accessed(accessed_member_name).ok()) +} + +/// Enriches the `enriched_members` with members from "deref"s of the current type. +/// +/// The function will stop enriching if it encounters a cycle in the deref chain, or if the +/// requested member is found. +fn enrich_members( + ctx: &mut ComputationContext<'_>, + enriched_members: &mut EnrichedMembers, + is_mut_var: bool, + stable_ptr: ast::ExprPtr, + accessed_member_name: &str, +) -> Maybe<()> { + let EnrichedMembers { members: enriched, deref_functions, calc_tail } = enriched_members; + let mut visited_types: OrderedHashSet = OrderedHashSet::default(); + + let expr_id = calc_tail.expect("`enrich_members` should be called with a `calc_tail` value."); + *calc_tail = None; + let mut expr = ExprAndId { expr: ctx.arenas.exprs[expr_id].clone(), id: expr_id }; let deref_mut_trait_id = deref_mut_trait(ctx.db); let deref_trait_id = deref_trait(ctx.db); @@ -2903,27 +2936,18 @@ fn enriched_members( ) }; - // Add members of derefed types. - let mut n_deref = 0; - let base_var = match &expr.expr { - Expr::Var(expr_var) => Some(expr_var.var), - Expr::MemberAccess(ExprMemberAccess { member_path: Some(member_path), .. }) => { - Some(member_path.base_var()) - } - _ => None, - }; // If the variable is mutable, and implements DerefMut, we use DerefMut in the first iteration. - let mut use_deref_mut = base_var - .filter(|var_id| matches!(ctx.semantic_defs.get(var_id), Some(var) if var.is_mut())) - .is_some() + let mut use_deref_mut = deref_functions.is_empty() + && is_mut_var && compute_deref_method_function_call_data(ctx, expr.clone(), true).is_ok(); + // Add members of derefed types. while let Ok((function_id, _, cur_expr, mutability)) = compute_deref_method_function_call_data(ctx, expr, use_deref_mut) { deref_functions.push((function_id, mutability)); use_deref_mut = false; - n_deref += 1; + let n_deref = deref_functions.len(); expr = cur_expr; let derefed_expr = expr_function_call( ctx, @@ -2932,8 +2956,7 @@ fn enriched_members( stable_ptr, stable_ptr, )?; - ty = derefed_expr.ty(); - ty = ctx.reduce_ty(ty); + let ty = ctx.reduce_ty(derefed_expr.ty()); let (_, long_ty) = finalized_snapshot_peeled_ty(ctx, ty, stable_ptr)?; // If the type is still a variable we stop looking for derefed members. if let TypeLongId::Var(_) = long_ty { @@ -2942,21 +2965,17 @@ fn enriched_members( expr = ExprAndId { expr: derefed_expr.clone(), id: ctx.arenas.exprs.alloc(derefed_expr) }; if let TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id)) = long_ty { let members = ctx.db.concrete_struct_members(concrete_struct_id)?; - if let Some(accessed_member_name) = &accessed_member_name { - if let Some(member) = members.get(accessed_member_name) { - return Ok(EnrichedMembers { - members: [(accessed_member_name.clone(), (member.clone(), n_deref))].into(), - deref_functions, - }); - } - } else { - for (member_name, member) in members.iter() { - // Insert member if there is not already a member with the same name. - if !res.contains_key(member_name) { - res.insert(member_name.clone(), (member.clone(), n_deref)); - } + for (member_name, member) in members.iter() { + // Insert member if there is not already a member with the same name. + if !enriched.contains_key(member_name) { + enriched.insert(member_name.clone(), (member.clone(), n_deref)); } } + // If member is contained we can stop the calculation post the lookup. + if members.contains_key(accessed_member_name) { + *calc_tail = Some(expr.id); + break; + } } if !visited_types.insert(long_ty.intern(ctx.db)) { // Break if we have a cycle. A diagnostic will be reported from the impl and not from @@ -2964,7 +2983,7 @@ fn enriched_members( break; } } - Ok(EnrichedMembers { members: res, deref_functions }) + Ok(()) } /// Peels snapshots from a type and making sure it is fully not a variable type. diff --git a/crates/cairo-lang-semantic/src/resolve/mod.rs b/crates/cairo-lang-semantic/src/resolve/mod.rs index fbda3382b5f..67c51b42f5b 100644 --- a/crates/cairo-lang-semantic/src/resolve/mod.rs +++ b/crates/cairo-lang-semantic/src/resolve/mod.rs @@ -53,8 +53,8 @@ use crate::items::{visibility, TraitOrImplContext}; use crate::substitution::{GenericSubstitution, SemanticRewriter, SubstitutionRewriter}; use crate::types::{are_coupons_enabled, resolve_type, ImplTypeId}; use crate::{ - ConcreteFunction, ConcreteTypeId, FunctionId, FunctionLongId, GenericArgumentId, GenericParam, - TypeId, TypeLongId, + ConcreteFunction, ConcreteTypeId, ExprId, FunctionId, FunctionLongId, GenericArgumentId, + GenericParam, Member, Mutability, TypeId, TypeLongId, }; #[cfg(test)] @@ -106,6 +106,63 @@ impl ResolvedItems { } } +/// The enriched members of a type, including direct members of structs, as well as members of +/// targets of `Deref` and `DerefMut` of the type. +#[derive(Debug, PartialEq, Eq, DebugWithDb, Clone)] +#[debug_db(dyn SemanticGroup + 'static)] +pub struct EnrichedMembers { + /// A map from member names to their semantic representation and the number of deref operations + /// needed to access them. + pub members: OrderedHashMap, + /// The sequence of deref functions needed to access the members. + pub deref_functions: Vec<(FunctionId, Mutability)>, + /// The tail remaining for required computation - the current expression `Deref`/`DerefMut` + /// traits lookup. Useful for partial computation of enriching members where a member was + /// already previously found. + pub calc_tail: Option, +} +impl EnrichedMembers { + /// Returns `EnrichedTypeMemberAccess` for a single member if exists. + /// + /// If it does not exist and the enriched type info is final, returns + /// `EnrichedTypeMemberAccessFailure::NotFound` otherwise returns + /// `EnrichedTypeMemberAccessFailure::NotFinal`. + pub fn accessed( + &self, + name: &str, + ) -> Result { + if let Some((member, n_derefs)) = self.members.get(name) { + Ok(EnrichedTypeMemberAccess { + member: member.clone(), + deref_functions: self.deref_functions[..*n_derefs].to_vec(), + }) + } else { + Err(if self.calc_tail.is_none() { + EnrichedTypeMemberAccessFailure::NotFound + } else { + EnrichedTypeMemberAccessFailure::NotFinal + }) + } + } +} + +/// The enriched member of a type, including the member itself and the deref functions needed to +/// access it. +pub struct EnrichedTypeMemberAccess { + /// The member itself. + pub member: Member, + /// The sequence of deref functions needed to access the member. + pub deref_functions: Vec<(FunctionId, Mutability)>, +} + +/// The failure cases for enriched member access. +pub enum EnrichedTypeMemberAccessFailure { + /// The member was not found. + NotFound, + /// The member was not found, but the enriched type info is not final. + NotFinal, +} + #[derive(Debug, PartialEq, Eq, DebugWithDb)] #[debug_db(dyn SemanticGroup + 'static)] pub struct ResolverData { @@ -115,6 +172,8 @@ pub struct ResolverData { generic_param_by_name: OrderedHashMap, /// All generic parameters accessible to the resolver. pub generic_params: Vec, + /// The enriched members per type and its mutability in the resolver context. + pub type_enriched_members: OrderedHashMap<(TypeId, bool), EnrichedMembers>, /// Lookback map for resolved identifiers in path. Used in "Go to definition". pub resolved_items: ResolvedItems, /// Inference data for the resolver. @@ -132,6 +191,7 @@ impl ResolverData { module_file_id, generic_param_by_name: Default::default(), generic_params: Default::default(), + type_enriched_members: Default::default(), resolved_items: Default::default(), inference_data: InferenceData::new(inference_id), trait_or_impl_ctx: TraitOrImplContext::None, @@ -148,6 +208,7 @@ impl ResolverData { module_file_id: self.module_file_id, generic_param_by_name: self.generic_param_by_name.clone(), generic_params: self.generic_params.clone(), + type_enriched_members: self.type_enriched_members.clone(), resolved_items: self.resolved_items.clone(), inference_data: self.inference_data.clone_with_inference_id(db, inference_id), trait_or_impl_ctx: self.trait_or_impl_ctx,