code-moniker-workspace 0.6.1

Workspace model, ports, snapshots, linkage, and change analysis for code-moniker.
Documentation
use super::*;

#[derive(Clone, Copy)]
pub(in crate::linkage) struct MethodCallReference<'a> {
	pub(super) reference_idx: usize,
	pub(super) reference: &'a ReferenceRecord,
	pub(super) call_name: &'a str,
}

impl<'a> MethodCallReference<'a> {
	pub(in crate::linkage) fn new(
		reference_idx: usize,
		reference: &'a ReferenceRecord,
	) -> Option<Self> {
		if reference.kind != "method_call" && reference.kind != "calls" {
			return None;
		}
		Some(Self {
			reference_idx,
			reference,
			call_name: reference.call_name.as_deref()?,
		})
	}

	pub(super) fn call_name(&self) -> &str {
		self.call_name
	}

	pub(super) fn call_arity(&self) -> Option<usize> {
		self.reference.call_arity
	}

	pub(super) fn external_decision_with_origin(
		&self,
		origin: ExternalOrigin,
		target: Moniker,
	) -> ReferenceLinkageDecision {
		ReferenceLinkageDecision::external_target(
			origin,
			self.reference_idx,
			self.reference.id,
			target,
		)
	}

	pub(super) fn resolved_decision(
		&self,
		scope: ResolutionScope,
		targets: SymbolSet,
	) -> ReferenceLinkageDecision {
		ReferenceLinkageDecision::resolved(ResolutionDecision::new(
			scope,
			ResolutionEvidence::TypeConstraint,
			self.reference.id,
			self.reference_idx,
			targets,
		))
	}
}

#[derive(Default)]
pub(super) struct ReceiverCallIndex {
	pub(super) by_reference: FxHashMap<usize, usize>,
}

impl ReceiverCallIndex {
	pub(super) fn get(&self, reference_idx: usize) -> Option<usize> {
		self.by_reference.get(&reference_idx).copied()
	}
}

type MethodKey = (Moniker, Vec<u8>, usize);

#[derive(Default)]
pub(in crate::linkage) struct MethodTable {
	by_owner_name_arity: FxHashMap<MethodKey, Vec<SymbolOrdinal>>,
	by_owner_name: FxHashMap<(Moniker, Vec<u8>), Vec<SymbolOrdinal>>,
	owners_by_name_arity: FxHashMap<(Vec<u8>, usize), SymbolSet>,
}

impl MethodTable {
	pub(in crate::linkage) fn build(
		material: &CodeIndexMaterial,
		candidates: &CandidateCatalog,
	) -> Self {
		let mut index = Self::default();
		for file_idx in 0..material.files.len() {
			index.insert_file(material, candidates, file_idx);
		}
		index
	}

	fn insert_file(
		&mut self,
		material: &CodeIndexMaterial,
		candidates: &CandidateCatalog,
		file_idx: usize,
	) {
		let Some(file) = material.files.get(file_idx) else {
			return;
		};
		for (def_idx, def) in file.graph.defs().enumerate() {
			let Some(arity) = def.call_arity else {
				continue;
			};
			if def.call_name.is_empty() {
				continue;
			}
			let Some(parent_idx) = def.parent else {
				continue;
			};
			let owner = file.graph.def_at(parent_idx).moniker.clone();
			let Some(symbol) = candidates.symbol_at(file_idx, def_idx) else {
				continue;
			};
			let owner_symbol = candidates.indexes().symbol_by_moniker(&owner);
			let key = (owner, def.call_name.to_vec(), arity);
			insert_method_key(self, key, symbol, owner_symbol);
		}
	}

	pub(super) fn resolve_by_name(
		&self,
		owner: &Moniker,
		call_name: &str,
		call_arity: Option<usize>,
	) -> Option<SymbolSet> {
		let targets = match call_arity {
			Some(arity) => self.by_owner_name_arity.get(&(
				owner.clone(),
				call_name.as_bytes().to_vec(),
				arity,
			))?,
			None => self
				.by_owner_name
				.get(&(owner.clone(), call_name.as_bytes().to_vec()))?,
		};
		(targets.len() == 1).then(|| SymbolSet::from_symbol(targets[0]))
	}

	pub(super) fn structural_owners(
		&self,
		call_name: &str,
		call_arity: usize,
	) -> Option<&SymbolSet> {
		self.owners_by_name_arity
			.get(&(call_name.as_bytes().to_vec(), call_arity))
	}

	pub(super) fn methods_for_owners(
		&self,
		candidates: &CandidateCatalog,
		owners: &SymbolSet,
		call_name: &str,
		call_arity: usize,
	) -> SymbolSet {
		let mut methods = SymbolSet::new();
		for owner in owners.iter() {
			let Some(owner) = candidates
				.candidate(owner)
				.map(|candidate| candidate.moniker)
			else {
				continue;
			};
			if let Some(targets) = self.by_owner_name_arity.get(&(
				owner.clone(),
				call_name.as_bytes().to_vec(),
				call_arity,
			)) {
				for target in targets {
					methods.insert(*target);
				}
			}
		}
		methods
	}
}

fn insert_method_key(
	table: &mut MethodTable,
	key: MethodKey,
	symbol: SymbolOrdinal,
	owner_symbol: Option<SymbolOrdinal>,
) {
	let (owner, name, arity) = key;
	if let Some(owner) = owner_symbol {
		table
			.owners_by_name_arity
			.entry((name.clone(), arity))
			.or_default()
			.insert(owner);
	}
	table
		.by_owner_name
		.entry((owner.clone(), name.clone()))
		.or_default()
		.push(symbol);
	table
		.by_owner_name_arity
		.entry((owner, name, arity))
		.or_default()
		.push(symbol);
}