code-moniker-workspace 0.6.1

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

use crate::linkage::binding::ReferenceLinkageDecision;
use crate::linkage::catalog::{CandidateCatalog, SymbolSet};
use crate::linkage::language::{BindingTarget, PythonBindings};
use crate::linkage::resolve::{
	DecisionSelection, LinkageRefiner, MethodCallReference, ReceiverFieldTables,
	resolve_method_through_supers,
};
use crate::snapshot::{DynamicReason, RecordTable, ReferenceRecord};

pub(in crate::linkage) fn refine(
	bindings: &PythonBindings,
	linkage: &LinkageRefiner<'_>,
	tables: &ReceiverFieldTables,
	decisions: &mut [ReferenceLinkageDecision],
	references: &RecordTable<ReferenceRecord>,
	selection: DecisionSelection<'_>,
) {
	for &decision_idx in selection.indices() {
		let decision = &mut decisions[decision_idx];
		let Some(reference_idx) = decision.refinement_pending_reference_idx() else {
			continue;
		};
		if !selection.includes(decision.reference()) {
			continue;
		}
		let reference = &references[reference_idx];
		if let Some(resolved) =
			resolve_reference(bindings, linkage, tables, reference_idx, reference)
		{
			*decision = resolved;
		}
	}
}

fn resolve_reference(
	bindings: &PythonBindings,
	linkage: &LinkageRefiner<'_>,
	tables: &ReceiverFieldTables,
	reference_idx: usize,
	reference: &ReferenceRecord,
) -> Option<ReferenceLinkageDecision> {
	let (raw_owner, name) = PythonBindings::target_key(linkage.material, reference)?;
	let owner = tables
		.type_aliases
		.get(&raw_owner)
		.cloned()
		.unwrap_or_else(|| raw_owner.clone());
	let requested_target = linkage.material.reference_target(&reference.id);
	if let Some(resolved) = decision(
		bindings,
		&raw_owner,
		&name,
		reference_idx,
		reference,
		requested_target,
	) {
		return Some(resolved);
	}
	if owner != raw_owner
		&& let Some(resolved) = decision(
			bindings,
			&owner,
			&name,
			reference_idx,
			reference,
			requested_target,
		) {
		return Some(resolved);
	}
	let bound_owner = canonical_workspace_owner(bindings, &owner, linkage.candidates)?;
	if let Some(resolved) = decision(
		bindings,
		&bound_owner,
		&name,
		reference_idx,
		reference,
		requested_target,
	) {
		return Some(resolved);
	}
	let method_call = MethodCallReference::new(reference_idx, reference)?;
	resolve_method_through_supers(linkage, tables, &bound_owner, method_call)
}

fn decision(
	bindings: &PythonBindings,
	owner: &Moniker,
	name: &[u8],
	reference_idx: usize,
	reference: &ReferenceRecord,
	requested_target: Option<&Moniker>,
) -> Option<ReferenceLinkageDecision> {
	if bindings.has_dynamic_wildcards(owner) {
		let candidates = bindings
			.alias(owner, name)
			.map_or_else(SymbolSet::new, BindingTarget::workspace_candidates);
		return Some(ReferenceLinkageDecision::dynamic(
			DynamicReason::RuntimeImport,
			reference_idx,
			reference.id,
			candidates,
		));
	}
	let external = bindings.external_wildcards(owner).collect::<Vec<_>>();
	if let Some(target) = bindings.alias(owner, name) {
		return Some(binding_decision(
			target,
			!external.is_empty(),
			reference_idx,
			reference,
			requested_target,
			name,
		));
	}
	if let Some(binding) = owner_binding(bindings, owner)
		&& (!external.is_empty() || !matches!(binding, BindingTarget::Workspace { .. }))
	{
		return Some(binding_decision(
			binding,
			!external.is_empty(),
			reference_idx,
			reference,
			requested_target,
			name,
		));
	}
	match external.as_slice() {
		[(origin, target)] => Some(ReferenceLinkageDecision::external_target(
			*origin,
			reference_idx,
			reference.id,
			external_wildcard_target(target, requested_target, name),
		)),
		[] => None,
		_ => Some(ReferenceLinkageDecision::dynamic(
			DynamicReason::RuntimeImport,
			reference_idx,
			reference.id,
			SymbolSet::new(),
		)),
	}
}

fn owner_binding<'a>(bindings: &'a PythonBindings, owner: &Moniker) -> Option<&'a BindingTarget> {
	let segment = owner.as_view().segments().last()?;
	bindings.alias(&owner.parent()?, bare_callable_name(segment.name))
}

fn canonical_workspace_owner(
	bindings: &PythonBindings,
	owner: &Moniker,
	candidates: &CandidateCatalog,
) -> Option<Moniker> {
	let binding = owner_binding(bindings, owner)?;
	let BindingTarget::Workspace {
		targets,
		candidate_reason: None,
		..
	} = binding
	else {
		return None;
	};
	let symbol = targets.single()?;
	Some(candidates.candidate(symbol)?.moniker.clone())
}

fn binding_decision(
	binding: &BindingTarget,
	external_present: bool,
	reference_idx: usize,
	reference: &ReferenceRecord,
	requested_target: Option<&Moniker>,
	name: &[u8],
) -> ReferenceLinkageDecision {
	if external_present {
		return ReferenceLinkageDecision::dynamic(
			DynamicReason::RuntimeImport,
			reference_idx,
			reference.id,
			binding.workspace_candidates(),
		);
	}
	match binding {
		BindingTarget::External { origin, target } => ReferenceLinkageDecision::external_target(
			*origin,
			reference_idx,
			reference.id,
			external_wildcard_target(target, requested_target, name),
		),
		BindingTarget::Dynamic { candidates } => ReferenceLinkageDecision::dynamic(
			DynamicReason::RuntimeImport,
			reference_idx,
			reference.id,
			candidates.clone(),
		),
		BindingTarget::Workspace { .. } => {
			binding.to_decision(reference_idx, reference, requested_target)
		}
	}
}

fn external_wildcard_target(
	module: &Moniker,
	requested_target: Option<&Moniker>,
	name: &[u8],
) -> Moniker {
	let (kind, target_name) = requested_target
		.and_then(|target| target.as_view().segments().last())
		.map_or((kinds::PATH, name), |segment| (segment.kind, segment.name));
	MonikerBuilder::from_view(module.as_view())
		.segment(kind, target_name)
		.build()
}

#[cfg(test)]
mod tests {
	use super::*;

	use crate::linkage::binding::ExternalOrigin;
	use crate::snapshot::{ReferenceId, SourceId, SymbolId};

	#[test]
	fn external_wildcard_keeps_its_original_provenance() {
		let owner = MonikerBuilder::new()
			.project(b".")
			.segment(kinds::LANG, b"python")
			.segment(kinds::MODULE, b"facade")
			.build();
		let external = MonikerBuilder::new()
			.project(b".")
			.segment(kinds::EXTERNAL_PKG, b"generated")
			.build();
		let bindings = PythonBindings::with_external_wildcard(
			owner.clone(),
			external,
			ExternalOrigin::Injected,
		);
		let reference = ReferenceRecord::new(
			ReferenceId::at(0, 0),
			SourceId::at(0),
			SymbolId::at(0, 0),
			"code+moniker://./lang:python/module:facade/function:Client",
			"calls",
			None,
		);

		let decision = decision(&bindings, &owner, b"Client", 0, &reference, None)
			.expect("external wildcard decision");

		assert!(matches!(
			decision,
			ReferenceLinkageDecision::External {
				origin: ExternalOrigin::Injected,
				..
			}
		));
	}
}