use {
crate::{
core::{Ident, SymbolPath, SymbolVisibility, Value, Visibility},
partitions::SymbolEntry,
},
std::hash::Hash,
};
fn ident_text<I: Ident>(
ident: &I,
resolver: &dyn laburnum::SourceResolver,
) -> String {
ident
.span()
.and_then(|span| resolver.try_resolve_span(&span))
.or_else(|| ident.ident().and_then(|i| resolver.resolve_ident(i)))
.unwrap_or_else(|| "<unresolved>".to_string())
}
pub enum Resolution<V, I, P, S = SymbolVisibility>
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
Resolved(SymbolEntry<V, I, P, S>),
Deferred,
Phantom(PhantomSymbol<V, I, P, S>),
WrongKind {
found: SymbolEntry<V, I, P, S>,
expected_kinds: Vec<String>,
},
Unresolved,
Inaccessible(SymbolEntry<V, I, P, S>),
}
impl<V, I, P, S> Clone for Resolution<V, I, P, S>
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
fn clone(&self) -> Self {
match self {
| Resolution::Resolved(id) => Resolution::Resolved(id.clone()),
| Resolution::Deferred => Resolution::Deferred,
| Resolution::Phantom(p) => Resolution::Phantom(p.clone()),
| Resolution::WrongKind {
found,
expected_kinds,
} => Resolution::WrongKind {
found: found.clone(),
expected_kinds: expected_kinds.clone(),
},
| Resolution::Unresolved => Resolution::Unresolved,
| Resolution::Inaccessible(id) => Resolution::Inaccessible(id.clone()),
}
}
}
impl<V, I, P, S> PartialEq for Resolution<V, I, P, S>
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
| (Resolution::Resolved(a), Resolution::Resolved(b)) => a == b,
| (Resolution::Deferred, Resolution::Deferred) => true,
| (Resolution::Phantom(a), Resolution::Phantom(b)) => a == b,
| (
Resolution::WrongKind {
found: f1,
expected_kinds: e1,
},
Resolution::WrongKind {
found: f2,
expected_kinds: e2,
},
) => f1 == f2 && e1 == e2,
| (Resolution::Unresolved, Resolution::Unresolved) => true,
| (Resolution::Inaccessible(a), Resolution::Inaccessible(b)) => a == b,
| _ => false,
}
}
}
impl<V, I, P, S> Resolution<V, I, P, S>
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
pub fn is_resolved(&self) -> bool {
matches!(self, Resolution::Resolved(_))
}
pub fn is_failed(&self) -> bool {
matches!(self, Resolution::Unresolved | Resolution::Inaccessible(_))
}
pub fn is_unresolved(&self) -> bool {
matches!(self, Resolution::Unresolved)
}
pub fn is_inaccessible(&self) -> bool {
matches!(self, Resolution::Inaccessible(_))
}
pub fn is_deferred(&self) -> bool {
matches!(self, Resolution::Deferred)
}
pub fn is_phantom(&self) -> bool {
matches!(self, Resolution::Phantom(_))
}
pub fn is_wrong_kind(&self) -> bool {
matches!(self, Resolution::WrongKind { .. })
}
pub fn qualified_symbol_id(&self) -> Option<&SymbolEntry<V, I, P, S>> {
match self {
| Resolution::Resolved(qid) => Some(qid),
| Resolution::WrongKind { found, .. } => Some(found),
| _ => None,
}
}
pub fn symbol_id(&self) -> Option<&SymbolEntry<V, I, P, S>> {
self.qualified_symbol_id()
}
pub fn description(&self, resolver: &dyn laburnum::SourceResolver) -> String {
match self {
| Resolution::Resolved(qid) => {
format!(
"Resolved to symbol {}",
laburnum::PartitionSortKey::resolve(&qid.path, resolver)
)
},
| Resolution::Deferred => "Deferred to next pass".to_string(),
| Resolution::Phantom(phantom) => {
format!(
"Expected {} '{}': {}",
phantom.expected_kind,
ident_text(&phantom.name, resolver),
phantom.reason
)
},
| Resolution::WrongKind { expected_kinds, .. } => {
format!("Wrong type, expected: {}", expected_kinds.join(" or "))
},
| Resolution::Unresolved => "Unresolved: no such definition".to_string(),
| Resolution::Inaccessible(_) => {
"Inaccessible: definition is not visible from here".to_string()
},
}
}
pub fn into_result(
self,
resolver: &dyn laburnum::SourceResolver,
) -> Result<SymbolEntry<V, I, P, S>, String> {
match self {
| Resolution::Resolved(qid) => Ok(qid),
| other => Err(other.description(resolver)),
}
}
}
pub struct PhantomSymbol<V, I, P, S = SymbolVisibility>
where
V: Value<I>,
I: Ident,
P: SymbolPath,
S: Visibility,
{
pub expected_kind: String,
pub name: I,
pub reason: String,
pub expected_in_scope: SymbolEntry<V, I, P, S>,
pub hints: Vec<String>,
}
impl<V, I, P, S> Clone for PhantomSymbol<V, I, P, S>
where
V: Value<I>,
I: Ident,
P: SymbolPath,
S: Visibility,
{
fn clone(&self) -> Self {
Self {
expected_kind: self.expected_kind.clone(),
name: self.name.clone(),
reason: self.reason.clone(),
expected_in_scope: self.expected_in_scope.clone(),
hints: self.hints.clone(),
}
}
}
impl<V, I, P, S> PartialEq for PhantomSymbol<V, I, P, S>
where
V: Value<I>,
I: Ident,
P: SymbolPath,
S: Visibility,
{
fn eq(&self, other: &Self) -> bool {
self.expected_kind == other.expected_kind
&& self.name == other.name
&& self.reason == other.reason
&& self.expected_in_scope == other.expected_in_scope
&& self.hints == other.hints
}
}
impl<V, I, P, S> PhantomSymbol<V, I, P, S>
where
V: Value<I>,
I: Ident,
P: SymbolPath,
S: Visibility,
{
pub fn new(
expected_kind: String,
name: I,
reason: String,
expected_in_scope: SymbolEntry<V, I, P, S>,
) -> Self {
Self {
expected_kind,
name,
reason,
expected_in_scope,
hints: Vec::new(),
}
}
pub fn with_hint(mut self, hint: String) -> Self {
self.hints.push(hint);
self
}
pub fn with_hints(mut self, hints: Vec<String>) -> Self {
self.hints.extend(hints);
self
}
pub fn has_hints(&self) -> bool {
!self.hints.is_empty()
}
pub fn display_name(&self, resolver: &dyn laburnum::SourceResolver) -> String {
ident_text(&self.name, resolver)
}
}
pub struct ResolutionStep<V, I, P, S = SymbolVisibility>
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
pub segment: I,
pub span: laburnum::Span,
pub resolution: Resolution<V, I, P, S>,
pub path_so_far: Vec<I>,
}
impl<V, I, P, S> Clone for ResolutionStep<V, I, P, S>
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
fn clone(&self) -> Self {
Self {
segment: self.segment.clone(),
span: self.span,
resolution: self.resolution.clone(),
path_so_far: self.path_so_far.clone(),
}
}
}
impl<V, I, P, S> ResolutionStep<V, I, P, S>
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
pub fn new(
segment: I,
span: laburnum::Span,
resolution: Resolution<V, I, P, S>,
path_so_far: Vec<I>,
) -> Self {
Self {
segment,
span,
resolution,
path_so_far,
}
}
pub fn is_successful(&self) -> bool {
self.resolution.is_resolved()
}
pub fn description(&self, resolver: &dyn laburnum::SourceResolver) -> String {
format!(
"Resolving '{}': {}",
ident_text(&self.segment, resolver),
self.resolution.description(resolver)
)
}
}
pub struct ResolutionResult<V, I, P, S = SymbolVisibility>
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
pub steps: Vec<ResolutionStep<V, I, P, S>>,
pub final_resolution: Resolution<V, I, P, S>,
}
impl<V, I, P, S> Clone for ResolutionResult<V, I, P, S>
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
fn clone(&self) -> Self {
Self {
steps: self.steps.clone(),
final_resolution: self.final_resolution.clone(),
}
}
}
impl<V, I, P, S> ResolutionResult<V, I, P, S>
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
pub fn new(
steps: Vec<ResolutionStep<V, I, P, S>>,
final_resolution: Resolution<V, I, P, S>,
) -> Self {
Self {
steps,
final_resolution,
}
}
pub fn simple(resolution: Resolution<V, I, P, S>) -> Self {
Self {
steps: Vec::new(),
final_resolution: resolution,
}
}
pub fn is_successful(&self) -> bool {
self.final_resolution.is_resolved()
}
pub fn step_count(&self) -> usize {
self.steps.len()
}
pub fn is_qualified_path(&self) -> bool {
self.step_count() > 1
}
pub fn symbol_id(&self) -> Option<&SymbolEntry<V, I, P, S>> {
self.final_resolution.symbol_id()
}
pub fn report(&self, resolver: &dyn laburnum::SourceResolver) -> String {
if self.steps.is_empty() {
format!(
"Simple resolution: {}",
self.final_resolution.description(resolver)
)
} else {
let mut report =
format!("Multi-step resolution ({} steps):\n", self.steps.len());
for (i, step) in self.steps.iter().enumerate() {
report.push_str(&format!(
" {}: {}\n",
i + 1,
step.description(resolver)
));
}
report.push_str(&format!(
"Final: {}",
self.final_resolution.description(resolver)
));
report
}
}
}
pub trait ResolveCtx<V, I, P, S = SymbolVisibility>
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
fn lookup(&self, path: &P) -> Option<SymbolEntry<V, I, P, S>>;
fn visibility_of(&self, entry: &SymbolEntry<V, I, P, S>) -> Option<S>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EmptyResolveCtx;
impl<V, I, P, S> ResolveCtx<V, I, P, S> for EmptyResolveCtx
where
V: Value<I>,
I: Ident + Hash,
P: SymbolPath,
S: Visibility,
{
fn lookup(&self, _path: &P) -> Option<SymbolEntry<V, I, P, S>> {
None
}
fn visibility_of(&self, _entry: &SymbolEntry<V, I, P, S>) -> Option<S> {
None
}
}
pub trait SymboliqueResolver<
V: Value<I>,
I: Ident + Hash,
P: SymbolPath = String,
S: Visibility = SymbolVisibility,
>
{
fn resolve(
&self,
ctx: &dyn ResolveCtx<V, I, P, S>,
path: &P,
from_path: &P,
from_scope: SymbolEntry<V, I, P, S>,
) -> Resolution<V, I, P, S>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DefaultResolver;
impl<V: Value<I>, I: Ident + Hash, P: SymbolPath, S: Visibility>
SymboliqueResolver<V, I, P, S> for DefaultResolver
{
fn resolve(
&self,
_ctx: &dyn ResolveCtx<V, I, P, S>,
_path: &P,
_from_path: &P,
_from_scope: SymbolEntry<V, I, P, S>,
) -> Resolution<V, I, P, S> {
Resolution::Unresolved
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::*;
#[test]
fn resolved_is_resolved() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let resolution = Resolution::<DV, SI, TP>::Resolved(entry);
assert!(resolution.is_resolved());
}
#[test]
fn failed_is_failed() {
let resolution = Resolution::<DV, SI, TP>::Unresolved;
assert!(resolution.is_failed());
}
#[test]
fn deferred_is_deferred() {
let resolution = Resolution::<DV, SI, TP>::Deferred;
assert!(resolution.is_deferred());
}
#[test]
fn phantom_is_phantom() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let phantom = PhantomSymbol::<DV, SI, TP>::new(
"function".to_string(),
SI::new("foo"),
"not found".to_string(),
entry,
);
let resolution = Resolution::<DV, SI, TP>::Phantom(phantom);
assert!(resolution.is_phantom());
}
#[test]
fn wrong_kind_is_wrong_kind() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let resolution = Resolution::<DV, SI, TP>::WrongKind {
found: entry,
expected_kinds: vec!["function".to_string()],
};
assert!(resolution.is_wrong_kind());
}
#[test]
fn resolved_symbol_id() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let resolution = Resolution::<DV, SI, TP>::Resolved(entry);
assert!(resolution.symbol_id().is_some());
}
#[test]
fn failed_symbol_id_none() {
let resolution = Resolution::<DV, SI, TP>::Unresolved;
assert!(resolution.symbol_id().is_none());
}
#[test]
fn deferred_symbol_id_none() {
let resolution = Resolution::<DV, SI, TP>::Deferred;
assert!(resolution.symbol_id().is_none());
}
#[test]
fn wrong_kind_symbol_id() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let resolution = Resolution::<DV, SI, TP>::WrongKind {
found: entry,
expected_kinds: vec!["function".to_string()],
};
assert!(resolution.symbol_id().is_some());
}
#[test]
fn resolved_into_result_ok() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let resolution = Resolution::<DV, SI, TP>::Resolved(entry);
let resolver = test_source_resolver();
assert!(resolution.into_result(&resolver).is_ok());
}
#[test]
fn failed_into_result_err() {
let resolution = Resolution::<DV, SI, TP>::Unresolved;
let resolver = test_source_resolver();
assert!(resolution.into_result(&resolver).is_err());
}
#[test]
fn resolved_description() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let resolution = Resolution::<DV, SI, TP>::Resolved(entry);
let resolver = test_source_resolver();
let desc = resolution.description(&resolver);
assert!(desc.contains("Resolved"));
}
#[test]
fn unresolved_description() {
let resolution = Resolution::<DV, SI, TP>::Unresolved;
let resolver = test_source_resolver();
let desc = resolution.description(&resolver);
assert!(desc.contains("Unresolved"));
}
#[test]
fn deferred_description() {
let resolution = Resolution::<DV, SI, TP>::Deferred;
let resolver = test_source_resolver();
let desc = resolution.description(&resolver);
assert!(desc.contains("Deferred"));
}
#[test]
fn phantom_new() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let phantom = PhantomSymbol::<DV, SI, TP>::new(
"function".to_string(),
SI::new("foo"),
"not found".to_string(),
entry,
);
assert_eq!(phantom.expected_kind, "function");
assert_eq!(phantom.reason, "not found");
assert!(phantom.hints.is_empty());
}
#[test]
fn phantom_with_hint() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let phantom = PhantomSymbol::<DV, SI, TP>::new(
"function".to_string(),
SI::new("foo"),
"not found".to_string(),
entry,
)
.with_hint("did you mean bar?".to_string());
assert_eq!(phantom.hints.len(), 1);
}
#[test]
fn phantom_with_hints() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let phantom = PhantomSymbol::<DV, SI, TP>::new(
"function".to_string(),
SI::new("foo"),
"not found".to_string(),
entry,
)
.with_hints(vec!["hint1".to_string(), "hint2".to_string()]);
assert_eq!(phantom.hints.len(), 2);
}
#[test]
fn phantom_has_hints() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let phantom_no_hints = PhantomSymbol::<DV, SI, TP>::new(
"function".to_string(),
SI::new("foo"),
"not found".to_string(),
entry,
);
assert!(!phantom_no_hints.has_hints());
let phantom_with = phantom_no_hints.with_hint("a hint".to_string());
assert!(phantom_with.has_hints());
}
#[test]
fn phantom_display_name() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let phantom = PhantomSymbol::<DV, SI, TP>::new(
"function".to_string(),
SI::new("my_func"),
"not found".to_string(),
entry,
);
let resolver = test_source_resolver();
assert_eq!(phantom.display_name(&resolver), "<unresolved>");
}
#[test]
fn step_new() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let span = test_span(&mut cache, 1);
let segment = SI::new("foo");
let step = ResolutionStep::<DV, SI, TP>::new(
segment.clone(),
span,
Resolution::Resolved(entry.clone()),
vec![segment],
);
assert_eq!(step.span, span);
assert!(step.resolution.is_resolved());
}
#[test]
fn step_is_successful_when_resolved() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let span = test_span(&mut cache, 1);
let step = ResolutionStep::<DV, SI, TP>::new(
SI::new("foo"),
span,
Resolution::Resolved(entry.clone()),
vec![SI::new("foo")],
);
assert!(step.is_successful());
}
#[test]
fn step_is_successful_when_failed() {
let mut cache = test_span_cache();
let span = test_span(&mut cache, 1);
let step = ResolutionStep::<DV, SI, TP>::new(
SI::new("foo"),
span,
Resolution::Unresolved,
vec![SI::new("foo")],
);
assert!(!step.is_successful());
}
#[test]
fn step_description() {
let mut cache = test_span_cache();
let span = test_span(&mut cache, 1);
let step = ResolutionStep::<DV, SI, TP>::new(
SI::new("my_segment"),
span,
Resolution::Unresolved,
vec![SI::new("my_segment")],
);
let resolver = test_source_resolver();
let desc = step.description(&resolver);
assert_eq!(desc, "Resolving '<unresolved>': Unresolved: no such definition");
}
#[test]
fn result_simple_resolved() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let result = ResolutionResult::<DV, SI, TP>::simple(Resolution::Resolved(
entry.clone(),
));
assert!(result.is_successful());
}
#[test]
fn result_simple_failed() {
let result = ResolutionResult::<DV, SI, TP>::simple(Resolution::Unresolved);
assert!(!result.is_successful());
}
#[test]
fn result_step_count_zero_for_simple() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let result = ResolutionResult::<DV, SI, TP>::simple(Resolution::Resolved(
entry.clone(),
));
assert_eq!(result.step_count(), 0);
}
#[test]
fn result_is_qualified_path_false_for_simple() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let result = ResolutionResult::<DV, SI, TP>::simple(Resolution::Resolved(
entry.clone(),
));
assert!(!result.is_qualified_path());
}
#[test]
fn result_symbol_id() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let result = ResolutionResult::<DV, SI, TP>::simple(Resolution::Resolved(
entry.clone(),
));
assert!(result.symbol_id().is_some());
}
#[test]
fn result_report_simple() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let result = ResolutionResult::<DV, SI, TP>::simple(Resolution::Resolved(
entry.clone(),
));
let resolver = test_source_resolver();
let report = result.report(&resolver);
assert!(report.contains("Simple resolution"));
}
#[test]
fn result_new_with_steps() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let span = test_span(&mut cache, 1);
let step = ResolutionStep::<DV, SI, TP>::new(
SI::new("a"),
span,
Resolution::Resolved(entry.clone()),
vec![SI::new("a")],
);
let result = ResolutionResult::<DV, SI, TP>::new(
vec![step],
Resolution::Resolved(entry.clone()),
);
assert_eq!(result.step_count(), 1);
}
#[test]
fn result_is_qualified_path_with_multiple_steps() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let span = test_span(&mut cache, 1);
let step_a = ResolutionStep::<DV, SI, TP>::new(
SI::new("a"),
span,
Resolution::Resolved(entry.clone()),
vec![SI::new("a")],
);
let step_b = ResolutionStep::<DV, SI, TP>::new(
SI::new("b"),
span,
Resolution::Resolved(entry.clone()),
vec![SI::new("a"), SI::new("b")],
);
let result = ResolutionResult::<DV, SI, TP>::new(
vec![step_a, step_b],
Resolution::Resolved(entry.clone()),
);
assert!(result.is_qualified_path());
}
#[test]
fn result_report_multi_step() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let span = test_span(&mut cache, 1);
let step_a = ResolutionStep::<DV, SI, TP>::new(
SI::new("a"),
span,
Resolution::Resolved(entry.clone()),
vec![SI::new("a")],
);
let step_b = ResolutionStep::<DV, SI, TP>::new(
SI::new("b"),
span,
Resolution::Resolved(entry.clone()),
vec![SI::new("a"), SI::new("b")],
);
let result = ResolutionResult::<DV, SI, TP>::new(
vec![step_a, step_b],
Resolution::Resolved(entry.clone()),
);
let resolver = test_source_resolver();
let report = result.report(&resolver);
assert!(report.contains("Multi-step resolution"));
}
struct TestResolver {
known: std::collections::HashMap<String, SymbolEntry<DV, SI, TP>>,
}
impl SymboliqueResolver<DV, SI, TP> for TestResolver {
fn resolve(
&self,
_ctx: &dyn ResolveCtx<DV, SI, TP>,
path: &TP,
_from_path: &TP,
_from_scope: SymbolEntry<DV, SI, TP>,
) -> Resolution<DV, SI, TP> {
match self.known.get(path) {
| Some(entry) => Resolution::Resolved(entry.clone()),
| None => Resolution::Unresolved,
}
}
}
#[test]
fn default_resolver_returns_failed() {
let mut cache = test_span_cache();
let scope = dummy_symbol_entry(&mut cache);
let resolver = DefaultResolver;
let path = "some.path".to_string();
let from = "caller".to_string();
let result: Resolution<DV, SI, TP> =
resolver.resolve(&EmptyResolveCtx, &path, &from, scope);
assert!(result.is_failed());
}
#[test]
fn test_resolver_resolves_known_path() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let mut known = std::collections::HashMap::new();
known.insert("foo.bar".to_string(), entry);
let resolver = TestResolver { known };
let scope = dummy_symbol_entry(&mut cache);
let path = "foo.bar".to_string();
let from = "caller".to_string();
let result = resolver.resolve(&EmptyResolveCtx, &path, &from, scope);
assert!(result.is_resolved());
}
#[test]
fn test_resolver_fails_unknown_path() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let mut known = std::collections::HashMap::new();
known.insert("foo.bar".to_string(), entry);
let resolver = TestResolver { known };
let scope = dummy_symbol_entry(&mut cache);
let path = "baz.qux".to_string();
let from = "caller".to_string();
let result = resolver.resolve(&EmptyResolveCtx, &path, &from, scope);
assert!(result.is_failed());
}
struct VisibilityResolver {
known: std::collections::HashMap<
String,
(SymbolEntry<DV, SI, TP>, SymbolVisibility),
>,
}
impl SymboliqueResolver<DV, SI, TP> for VisibilityResolver {
fn resolve(
&self,
_ctx: &dyn ResolveCtx<DV, SI, TP>,
path: &TP,
from_path: &TP,
_from_scope: SymbolEntry<DV, SI, TP>,
) -> Resolution<DV, SI, TP> {
match self.known.get(path) {
| Some((entry, visibility)) => {
if visibility.is_visible_from(path, from_path) {
Resolution::Resolved(entry.clone())
} else {
Resolution::Inaccessible(entry.clone())
}
},
| None => Resolution::Unresolved,
}
}
}
#[test]
fn visibility_resolver_public_visible_anywhere() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let scope = dummy_symbol_entry(&mut cache);
let mut known = std::collections::HashMap::new();
known.insert(
"mod_a.pub_fn".to_string(),
(entry, SymbolVisibility::Public),
);
let resolver = VisibilityResolver { known };
let result = resolver.resolve(
&EmptyResolveCtx,
&"mod_a.pub_fn".to_string(),
&"mod_b.caller".to_string(),
scope,
);
assert!(result.is_resolved());
}
#[test]
fn visibility_resolver_private_visible_within_scope() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let scope = dummy_symbol_entry(&mut cache);
let mut known = std::collections::HashMap::new();
known.insert(
"mod_a.priv_fn".to_string(),
(entry, SymbolVisibility::Private),
);
let resolver = VisibilityResolver { known };
let result = resolver.resolve(
&EmptyResolveCtx,
&"mod_a.priv_fn".to_string(),
&"mod_a.priv_fn.body".to_string(),
scope,
);
assert!(result.is_resolved());
}
#[test]
fn visibility_resolver_private_hidden_outside_scope() {
let mut cache = test_span_cache();
let entry = dummy_symbol_entry(&mut cache);
let scope = dummy_symbol_entry(&mut cache);
let mut known = std::collections::HashMap::new();
known.insert(
"mod_a.priv_fn".to_string(),
(entry, SymbolVisibility::Private),
);
let resolver = VisibilityResolver { known };
let result = resolver.resolve(
&EmptyResolveCtx,
&"mod_a.priv_fn".to_string(),
&"mod_b.caller".to_string(),
scope,
);
assert!(result.is_failed());
}
}