Skip to content

Commit

Permalink
Enrich members caching. (#6343)
Browse files Browse the repository at this point in the history
  • Loading branch information
orizi authored Sep 5, 2024
1 parent 26ba445 commit 3f92254
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 80 deletions.
181 changes: 103 additions & 78 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,9 @@ 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, ResolvedConcreteItem, ResolvedGenericItem, Resolver,
};
use crate::semantic::{self, Binding, FunctionId, LocalVariable, TypeId, TypeLongId};
use crate::substitution::SemanticRewriter;
use crate::types::{
Expand Down Expand Up @@ -2741,23 +2743,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 @@ -2770,7 +2772,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 @@ -2790,7 +2792,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 @@ -2831,56 +2833,90 @@ 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.get_member(accessed_member_name) {
Some(value) => return Ok(Some(value)),
None => {
if e.exploration_tail.is_none() {
// There's no further exploration to be done, and member was not found.
return Ok(None);
}
}
}
// 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![], exploration_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.get_member(accessed_member_name))
}

/// 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, exploration_tail } = enriched_members;
let mut visited_types: OrderedHashSet<TypeId> = OrderedHashSet::default();

let expr_id =
exploration_tail.expect("`enrich_members` should be called with a `calc_tail` value.");
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 @@ -2900,27 +2936,22 @@ 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();

// This function either finds a member and sets `exploration_tail` or finishes the exploration
// and leaves that exploration tail as `None`.
*exploration_tail = None;

// 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 @@ -2929,8 +2960,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 @@ -2939,20 +2969,15 @@ 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.
enriched.entry(member_name.clone()).or_insert_with(|| (member.clone(), n_deref));
}
// If member is contained we can stop the calculation post the lookup.
if members.contains_key(accessed_member_name) {
// Found member, so exploration isn't done - setting up the tail.
*exploration_tail = Some(expr.id);
break;
}
}
if !visited_types.insert(long_ty.intern(ctx.db)) {
Expand All @@ -2961,7 +2986,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.
Expand Down
44 changes: 42 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,42 @@ 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 of deref chain explored so far. The search for additional members will continue
/// from this point.
/// Useful for partial computation of enriching members where a member was already previously
/// found.
pub exploration_tail: Option<ExprId>,
}
impl EnrichedMembers {
/// Returns `EnrichedTypeMemberAccess` for a single member if exists.
pub fn get_member(&self, name: &str) -> Option<EnrichedTypeMemberAccess> {
let (member, n_derefs) = self.members.get(name)?;
Some(EnrichedTypeMemberAccess {
member: member.clone(),
deref_functions: self.deref_functions[..*n_derefs].to_vec(),
})
}
}

/// 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)>,
}

#[derive(Debug, PartialEq, Eq, DebugWithDb)]
#[debug_db(dyn SemanticGroup + 'static)]
pub struct ResolverData {
Expand All @@ -115,6 +151,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 +170,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 +187,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 3f92254

Please sign in to comment.