knave-expand 0.14.21

The reading, scoping and expansion stages of knave
Documentation
use super::Expander;
use super::context::DefinitionContext;
use super::problems::ExpandProblems;
use super::recorded_inputs::BoundName;
use crate::core::{BindingId, BindingInfo, BindingKind, ModuleId, Namespace};
use crate::diagnostic::Diagnostic;
use crate::diagnostic::SimilarName;
use crate::reader::{Form, Symbol};
use crate::scope::{Phase, Resolution};
use crate::source::Span;

impl Expander<'_> {
    pub(super) fn resolve(
        &self,
        symbol: &Symbol,
        namespace: Namespace,
        span: Span,
    ) -> Result<BindingId, Diagnostic> {
        match self.tables.table.resolve(symbol, namespace, self.phase) {
            Resolution::Bound(binding) => Ok(binding),
            Resolution::Unbound => Err(self.unbound(symbol, namespace, span)),
            Resolution::Ambiguous(bindings) => {
                let definitions = bindings
                    .iter()
                    .filter_map(|binding| self.tables.bindings.get(*binding).span())
                    .collect();
                Err(ExpandProblems::ambiguous(symbol.name(), span, definitions))
            }
        }
    }

    pub(super) fn try_resolve(&self, symbol: &Symbol, namespace: Namespace) -> Option<BindingId> {
        match self.tables.table.resolve(symbol, namespace, self.phase) {
            Resolution::Bound(binding) => Some(binding),
            Resolution::Unbound | Resolution::Ambiguous(_) => None,
        }
    }

    pub(super) fn kind(&self, binding: BindingId) -> BindingKind {
        self.tables.bindings.get(binding).kind()
    }

    pub(super) fn define_local(
        &mut self,
        binder: &Form,
        namespace: Namespace,
        kind: BindingKind,
        context: Option<&DefinitionContext>,
    ) -> Result<BindingId, Diagnostic> {
        self.define(binder, namespace, kind, self.phase, context)
    }

    pub(super) fn define_global(
        &mut self,
        binder: &Form,
        namespace: Namespace,
        kind: BindingKind,
        context: &DefinitionContext,
    ) -> Result<BindingId, Diagnostic> {
        self.note_bound(binder, namespace);
        let binding = self.define(binder, namespace, kind, Phase::Runtime, Some(context))?;
        self.note_global(binding, binder);
        Ok(binding)
    }

    pub(super) fn define_unrecorded_global(
        &mut self,
        binder: &Form,
        namespace: Namespace,
        kind: BindingKind,
        context: &DefinitionContext,
    ) -> Result<BindingId, Diagnostic> {
        self.define(binder, namespace, kind, Phase::Runtime, Some(context))
    }

    pub(super) fn bind_import(
        &mut self,
        symbol: &Symbol,
        namespace: Namespace,
        binding: BindingId,
        span: Span,
    ) -> Result<(), Diagnostic> {
        self.recorded_inputs.note_bound(span, || {
            Some(BoundName {
                name: symbol.shared_name(),
                namespace,
                span,
            })
        });
        self.tables
            .table
            .bind(symbol, namespace, binding, Phase::Runtime)
            .map_err(|existing| {
                ExpandProblems::duplicate(
                    symbol.name(),
                    span,
                    self.tables.bindings.get(existing).span(),
                )
            })
    }

    fn note_bound(&mut self, binder: &Form, namespace: Namespace) {
        let tables = &self.tables;
        self.recorded_inputs.note_bound(binder.span(), || {
            let symbol = binder
                .as_symbol()
                .filter(|_| !tables.scopes.carries_introduction_scope(binder))?;
            Some(BoundName {
                name: symbol.shared_name(),
                namespace,
                span: binder.span(),
            })
        });
    }

    fn define(
        &mut self,
        binder: &Form,
        namespace: Namespace,
        kind: BindingKind,
        phase: Phase,
        context: Option<&DefinitionContext>,
    ) -> Result<BindingId, Diagnostic> {
        let mut binder = binder.clone();
        if let Some(context) = context {
            context.strip_use_sites(&mut binder);
        }
        let span = binder.span();
        let symbol = binder
            .as_symbol()
            .ok_or_else(|| ExpandProblems::wrong_namespace(&binder.to_string(), "a name", span))?;
        Self::reject_keyword_binder(symbol.name(), span)?;
        let info = BindingInfo::new(symbol.shared_name(), kind, Some(span));
        let binding = self.tables.bindings.add(info);
        self.bind_over_failed(symbol, namespace, binding, phase)
            .inspect_err(|_| self.remember_duplicate_definition(symbol, binding))
            .map_err(|existing| {
                ExpandProblems::duplicate(
                    symbol.name(),
                    span,
                    self.tables.bindings.get(existing).span(),
                )
            })?;
        Ok(binding)
    }

    fn remember_duplicate_definition(&mut self, symbol: &Symbol, binding: BindingId) {
        if let Some(module) = self.definition_module(binding) {
            self.duplicate_definitions
                .insert((module, symbol.shared_name()));
        }
    }

    pub(super) fn unbound(&self, symbol: &Symbol, namespace: Namespace, span: Span) -> Diagnostic {
        let candidates: Vec<_> = self
            .tables
            .table
            .visible_name_bindings(symbol, namespace, self.phase)
            .into_iter()
            .filter(|(name, binding)| self.is_public(name, namespace, *binding))
            .map(|(name, _)| name)
            .collect();
        let similar = SimilarName::closest(symbol.name(), &candidates);
        let definition = self.introduced_name(span, symbol.name());
        let primary = definition.unwrap_or(span);
        let diagnostic = ExpandProblems::unbound(symbol.name(), namespace, primary, similar);
        match (definition, self.tables.macro_origins.get(span)) {
            (Some(_), Some(origin)) => diagnostic
                .with_note(format!("in this expansion of `{}`", origin.name))
                .with_label(ExpandProblems::macro_call(span)),
            _ => diagnostic,
        }
    }

    fn is_public(&self, name: &str, namespace: Namespace, binding: BindingId) -> bool {
        match self.definition_module(binding) {
            Some(module) if Some(module) != self.current_module => {
                self.modules.get(module).exported(name, namespace) == Some(binding)
            }
            _ => true,
        }
    }

    pub(super) fn definition_module(&self, binding: BindingId) -> Option<ModuleId> {
        self.kind(binding).defining_module()
    }
}