code-moniker-workspace 0.6.1

Workspace model, ports, snapshots, linkage, and change analysis for code-moniker.
Documentation
use code_moniker_core::core::moniker::{Moniker, MonikerBuilder, Segment};
use code_moniker_core::lang::{Lang, kinds};
use rustc_hash::FxHashMap;

use crate::linkage::catalog::{CandidateCatalog, LinkageCandidate, LinkageQuery, SymbolSet};
use crate::linkage::resolve::resolve_global_scope;
use crate::source::CodeIndexMaterial;

#[derive(Default)]
pub(in crate::linkage) struct JavaOnDemandImports {
	by_source: FxHashMap<usize, Vec<OnDemandImport>>,
}

#[derive(Clone)]
enum OnDemandImport {
	Package(Moniker),
	StaticType(Moniker),
}

struct JavaImportedTarget {
	target: Moniker,
	requires_static: bool,
}

impl JavaOnDemandImports {
	pub(in crate::linkage) fn build(material: &CodeIndexMaterial) -> Self {
		let mut imports = Self::default();
		for (file_idx, file) in material.files.iter().enumerate() {
			if file.lang != Lang::Java {
				continue;
			}
			for reference in file.graph.refs() {
				if reference.kind != kinds::IMPORTS_MODULE || reference.alias.as_ref() != b"*" {
					continue;
				}
				let Some(last) = reference.target.as_view().segments().last() else {
					continue;
				};
				let import = if last.kind == kinds::MODULE {
					OnDemandImport::StaticType(reference.target.clone())
				} else if last.kind == kinds::PACKAGE {
					OnDemandImport::Package(reference.target.clone())
				} else {
					continue;
				};
				imports.by_source.entry(file_idx).or_default().push(import);
			}
		}
		imports
	}

	pub(in crate::linkage) fn matching_targets(
		&self,
		query: &LinkageQuery<'_>,
		candidates: &CandidateCatalog,
	) -> SymbolSet {
		let mut targets = SymbolSet::new();
		if query
			.material
			.files
			.get(query.source_file)
			.is_none_or(|source| source.lang != Lang::Java)
		{
			return targets;
		}
		if query.confidence != Some("name_match") {
			return targets;
		}
		let Some(last) = query.target_last else {
			return targets;
		};
		let forwarded = self
			.by_source
			.get(&query.source_file)
			.into_iter()
			.flatten()
			.filter_map(|import| import.forward(query.reference_kind, last))
			.collect::<Vec<_>>();
		for imported in &forwarded {
			let forwarded_query = query.with_target(&imported.target);
			for symbol in resolve_global_scope(&forwarded_query, candidates).iter() {
				if imported.requires_static
					&& !candidates
						.candidate(symbol)
						.is_some_and(|candidate| candidate_is_static(&candidate))
				{
					continue;
				}
				targets.insert(symbol);
			}
		}
		targets
	}
}

impl OnDemandImport {
	fn forward(&self, reference_kind: &str, last: Segment<'_>) -> Option<JavaImportedTarget> {
		match self {
			Self::Package(package) if is_type_reference(reference_kind) => {
				let mut builder = MonikerBuilder::from_view(package.as_view());
				builder.segment(kinds::MODULE, last.name);
				builder.segment(kinds::PATH, last.name);
				Some(JavaImportedTarget {
					target: builder.build(),
					requires_static: false,
				})
			}
			Self::StaticType(owner) if is_static_member_reference(reference_kind) => {
				let owner_name = owner.as_view().segments().last()?.name;
				let mut builder = MonikerBuilder::from_view(owner.as_view());
				builder.segment(kinds::CLASS, owner_name);
				builder.segment(last.kind, last.name);
				Some(JavaImportedTarget {
					target: builder.build(),
					requires_static: true,
				})
			}
			_ => None,
		}
	}
}

fn is_type_reference(kind: &str) -> bool {
	matches!(
		kind.as_bytes(),
		kinds::ANNOTATES
			| kinds::EXTENDS
			| kinds::IMPLEMENTS
			| kinds::INSTANTIATES
			| kinds::RETURNS_TYPE
			| kinds::TYPED_AS
			| kinds::USES_TYPE
	)
}

fn is_static_member_reference(kind: &str) -> bool {
	matches!(kind.as_bytes(), kinds::CALLS | kinds::READS | kinds::WRITES)
}

fn candidate_is_static(candidate: &LinkageCandidate<'_>) -> bool {
	if candidate.signature == b"static" || candidate.signature.starts_with(b"static ") {
		return true;
	}
	let segments = candidate.moniker.as_view().segments().collect::<Vec<_>>();
	let [.., owner, member] = segments.as_slice() else {
		return false;
	};
	member.kind == kinds::ENUM_CONSTANT
		|| (member.kind == kinds::FIELD
			&& matches!(owner.kind, kinds::INTERFACE | kinds::ANNOTATION_TYPE))
}