code-moniker-workspace 0.4.0

Workspace model, ports, snapshots, linkage, and change analysis for code-moniker.
Documentation
use code_moniker_core::core::moniker::Segment;
use code_moniker_core::core::moniker::query::bare_callable_name;
use code_moniker_core::lang::kinds;

use crate::linkage::catalog::LinkageCandidate;
use crate::linkage::catalog::LinkageQuery;
use crate::linkage::language::LanguageLinkageStrategy;

pub(super) struct RustLanguageLinkageStrategy;

impl LanguageLinkageStrategy for RustLanguageLinkageStrategy {
	fn matches(&self, query: &LinkageQuery<'_>, candidate: &LinkageCandidate<'_>) -> bool {
		candidate.moniker.bind_match(query.target)
			|| query.target.bind_match(candidate.moniker)
			|| rust_path_target_matches_def(query, candidate)
			|| rust_contextual_name_matches_def(query, candidate)
	}
}

fn rust_path_target_matches_def(
	query: &LinkageQuery<'_>,
	candidate: &LinkageCandidate<'_>,
) -> bool {
	if query.confidence != Some(confidence(kinds::CONF_NAME_MATCH))
		&& query.confidence != Some(confidence(kinds::CONF_IMPORTED))
	{
		return false;
	}
	let target_segments = normalized_rust_segments(query.target_segments());
	let candidate_segments = normalized_rust_segments(candidate.moniker.as_view().segments());
	if target_segments.len() != candidate_segments.len() || target_segments.is_empty() {
		return false;
	}
	target_segments
		.iter()
		.zip(candidate_segments.iter())
		.all(|(target, candidate_segment)| rust_path_segment_matches(*target, *candidate_segment))
}

#[derive(Clone, Copy)]
struct NormalizedSegment<'a> {
	kind: &'a [u8],
	name: &'a [u8],
}

fn normalized_rust_segments<'a>(
	segments: impl IntoIterator<Item = Segment<'a>>,
) -> Vec<NormalizedSegment<'a>> {
	let segments = segments.into_iter().collect::<Vec<_>>();
	let mut normalized = Vec::with_capacity(segments.len());
	let mut idx = 0;
	while idx < segments.len() {
		if is_implicit_rust_crate_root_module(&segments, idx) {
			idx += 1;
			continue;
		}
		if idx + 1 < segments.len()
			&& segments[idx].kind == kinds::DIR
			&& segments[idx + 1].kind == kinds::MODULE
			&& segments[idx + 1].name == b"mod"
		{
			normalized.push(NormalizedSegment {
				kind: kinds::MODULE,
				name: segments[idx].name,
			});
			idx += 2;
			continue;
		}
		normalized.push(NormalizedSegment {
			kind: segments[idx].kind,
			name: segments[idx].name,
		});
		idx += 1;
	}
	normalized
}

fn is_implicit_rust_crate_root_module(segments: &[Segment<'_>], idx: usize) -> bool {
	idx > 0
		&& segments[idx - 1].kind == kinds::DIR
		&& segments[idx - 1].name == b"src"
		&& segments[idx].kind == kinds::MODULE
		&& matches!(segments[idx].name, b"lib" | b"main")
}

fn rust_path_segment_matches(
	target: NormalizedSegment<'_>,
	candidate: NormalizedSegment<'_>,
) -> bool {
	if target.kind == candidate.kind {
		return bare_callable_name(target.name) == bare_callable_name(candidate.name);
	}
	if target.kind == kinds::MODULE && candidate.kind == kinds::DIR {
		return bare_callable_name(target.name) == bare_callable_name(candidate.name);
	}
	if is_rust_callable_kind(target.kind) && is_rust_callable_kind(candidate.kind) {
		return bare_callable_name(target.name) == bare_callable_name(candidate.name);
	}
	target.kind == kinds::PATH
		&& is_rust_path_target_kind(candidate.kind)
		&& bare_callable_name(target.name) == bare_callable_name(candidate.name)
}

fn rust_contextual_name_matches_def(
	query: &LinkageQuery<'_>,
	candidate: &LinkageCandidate<'_>,
) -> bool {
	if !can_use_contextual_name_match(query) {
		return false;
	}
	let Some(target) = query.target_last else {
		return false;
	};
	let Some(candidate_segment) = candidate.last_segment else {
		return false;
	};
	if !rust_name_matches(query, candidate, target, candidate_segment) {
		return false;
	}
	rust_kind_can_satisfy(query, target.kind, candidate_segment.kind)
}

fn can_use_contextual_name_match(query: &LinkageQuery<'_>) -> bool {
	if is_qualified_local_rust_call(query) {
		return false;
	}
	if is_rust_call_ref(query.reference_kind.as_bytes()) {
		return true;
	}
	if query.confidence == Some(confidence(kinds::CONF_NAME_MATCH))
		|| query.confidence == Some(confidence(kinds::CONF_IMPORTED))
	{
		return true;
	}
	query.confidence == Some(confidence(kinds::CONF_EXTERNAL))
		&& external_root(query).is_some_and(|root| !is_builtin_external_root(root))
}

fn is_qualified_local_rust_call(query: &LinkageQuery<'_>) -> bool {
	query.reference_kind.as_bytes() == kinds::CALLS
		&& query.target_segment_count > 1
		&& query
			.target_first
			.is_some_and(|first| first.kind != kinds::EXTERNAL_PKG)
}

fn rust_name_matches(
	query: &LinkageQuery<'_>,
	candidate: &LinkageCandidate<'_>,
	target: Segment<'_>,
	candidate_segment: Segment<'_>,
) -> bool {
	if is_rust_call_ref(query.reference_kind.as_bytes()) {
		return query
			.call_name
			.is_some_and(|name| Some(name.as_bytes()) == candidate.call_name)
			&& query.call_arity == candidate.call_arity;
	}
	bare_callable_name(target.name) == candidate_name(candidate, candidate_segment)
}

fn rust_kind_can_satisfy(
	query: &LinkageQuery<'_>,
	target_kind: &[u8],
	candidate_kind: &[u8],
) -> bool {
	if is_rust_call_ref(query.reference_kind.as_bytes()) {
		return is_rust_callable_kind(candidate_kind);
	}
	if target_kind == kinds::PATH {
		return is_rust_path_target_kind(candidate_kind);
	}
	target_kind == candidate_kind
}

fn candidate_name<'a>(
	candidate: &'a LinkageCandidate<'a>,
	candidate_segment: Segment<'a>,
) -> &'a [u8] {
	candidate
		.call_name
		.unwrap_or_else(|| bare_callable_name(candidate_segment.name))
}

fn external_root<'a>(query: &'a LinkageQuery<'_>) -> Option<&'a [u8]> {
	query
		.target
		.as_view()
		.segments()
		.next()
		.and_then(|head| (head.kind == kinds::EXTERNAL_PKG).then_some(head.name))
}

fn is_builtin_external_root(root: &[u8]) -> bool {
	matches!(root, b"std" | b"core" | b"alloc" | b"proc_macro")
}

pub(super) fn builtin_external_root(root: &str) -> bool {
	matches!(root, "std" | "core" | "alloc" | "proc_macro" | "test")
}

pub(super) fn proc_macro_annotation(query: &LinkageQuery<'_>) -> bool {
	query
		.material
		.files
		.get(query.source_file)
		.is_some_and(|file| file.lang == code_moniker_core::lang::Lang::Rs)
		&& query.reference_kind.as_bytes() == kinds::ANNOTATES
		&& query.confidence == Some(confidence(kinds::CONF_NAME_MATCH))
}

fn is_rust_exportable_kind(kind: &[u8]) -> bool {
	matches!(
		kind,
		kinds::MODULE
			| kinds::STRUCT
			| kinds::ENUM
			| kinds::TRAIT
			| kinds::TYPE
			| kinds::FN
			| kinds::CONST
			| kinds::STATIC
			| kinds::ENUM_CONSTANT
	)
}

fn is_rust_callable_kind(kind: &[u8]) -> bool {
	kind == kinds::FN || kind == kinds::METHOD
}

fn is_rust_call_ref(kind: &[u8]) -> bool {
	kind == kinds::CALLS || kind == kinds::METHOD_CALL
}

fn is_rust_path_target_kind(kind: &[u8]) -> bool {
	kind == kinds::PATH || is_rust_exportable_kind(kind) || is_rust_callable_kind(kind)
}

fn confidence(value: &[u8]) -> &str {
	std::str::from_utf8(value)
		.unwrap_or_else(|err| panic!("confidence constants must be utf-8: {err}"))
}