Skip to content

Commit

Permalink
Made the enriched member access be cached.
Browse files Browse the repository at this point in the history
  • Loading branch information
orizi committed Sep 4, 2024
1 parent 74c1bdc commit 5eae2a8
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 79 deletions.
173 changes: 96 additions & 77 deletions crates/cairo-lang-semantic/src/expr/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::{
Expand Down Expand Up @@ -2744,23 +2747,23 @@ 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 },
));
};
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),
Expand All @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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<SmolStr, (semantic::Member, usize)>,
/// 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<SmolStr>,
) -> Maybe<EnrichedMembers> {
// 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<TypeId> = OrderedHashSet::default();
// Add direct members.
let (_, mut long_ty) = peel_snapshots(ctx.db, ty);
accessed_member_name: &str,
) -> Maybe<Option<EnrichedTypeMemberAccess>> {
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<TypeId> = 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);
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -2942,29 +2965,25 @@ 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
// member access.
break;
}
}
Ok(EnrichedMembers { members: res, deref_functions })
Ok(())
}

/// Peels snapshots from a type and making sure it is fully not a variable type.
Expand Down
65 changes: 63 additions & 2 deletions crates/cairo-lang-semantic/src/resolve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<SmolStr, (Member, usize)>,
/// 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<ExprId>,
}
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<EnrichedTypeMemberAccess, EnrichedTypeMemberAccessFailure> {
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 {
Expand All @@ -115,6 +172,8 @@ pub struct ResolverData {
generic_param_by_name: OrderedHashMap<SmolStr, GenericParamId>,
/// All generic parameters accessible to the resolver.
pub generic_params: Vec<GenericParamId>,
/// 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.
Expand All @@ -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,
Expand All @@ -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,
Expand Down

0 comments on commit 5eae2a8

Please sign in to comment.