// ASG identifiers
//
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//! Identifiers (a type of [object](super)).
use super::{prelude::*, Expr, Meta, NameableMissingObject, Pkg, Tpl};
use crate::{
diagnose::{Annotate, Diagnostic},
diagnostic_todo,
f::Functor,
fmt::{DisplayWrapper, TtQuote},
num::{Dim, Dtype},
parse::{util::SPair, Token},
span::Span,
sym::{st, GlobalSymbolResolve, SymbolId},
};
use std::fmt::Display;
use Ident::*;
pub type TransitionResult = Result;
/// An identifier for some object on the [`Asg`].
///
/// An identifier can be either _opaque_ declaration,
/// meaning that it stands in _place_ of a definition,
/// or _transparent_,
/// meaning that references to the identifier should "see through" it
/// and directly reference the object to which it is bound.
///
/// Invariants
/// ==========
/// The [`Ident`] variants represent object states:
///
/// ```text
/// ,--> ((Transparent))
/// /
/// (Missing) -> (Extern) -> ((Opaque)) -> ((IdentFragment)).
/// \ ^ /
/// \ / \ /
/// `--------------------` `------------'
/// ```
///
/// The following invariants must hold with respect to [`Asg`]:
/// 1. A [`Transparent`] identifier must have _one_ edge to the object
/// that it describes
/// (upheld by [`ObjectIndex::::bind_definition`]).
/// 2. All other identifiers must have _zero_ outgoing edges,
/// since they describe nothing available on the graph.
/// Since edges can only be added by
/// [`ObjectIndex::::bind_definition`],
/// and since an [`Ident`] cannot transition away from
/// [`Transparent`],
/// this invariant is upheld.
#[derive(Debug, PartialEq, Clone)]
pub enum Ident {
/// An identifier is expected to be declared or defined but is not yet
/// available.
///
/// This variant contains the symbol representing the name of the
/// expected identifier.
/// This is important,
/// since identifiers in TAME may be referenced before they are
/// defined.
///
/// The [`Span`] associated with this missing identifier represents one
/// of the references to the identifier,
/// ensuring that there is always some useful information to help
/// debug missing identifiers.
/// A reference to this missing identifier ought to have its own span so
/// that diagnostic messages can direct the user to other instances
/// where unknown identifiers were referenced.
Missing(SPair),
/// A resolved opaque identifier without a corresponding definition.
///
/// This represents an identifier that has been declared with certain
/// type information.
/// An identifier has a single canonical location represented by the
/// [`SPair`]'s [`Span`].
///
/// Opaque identifiers are used when a definition is either not known,
/// or when the definition is not important.
/// These identifiers take the _place_ of an object definition on the
/// graph,
/// much like a declaration in a header file.
Opaque(SPair, IdentKind, Source),
/// An identifier that has not yet been declared or defined.
///
/// Externs are upgraded to [`Ident::Opaque`] once an identifier of
/// the same name is loaded.
/// It is an error if the loaded identifier does not have a compatible
/// [`IdentKind`].
///
/// The [`Span`] and [`Source`] of an extern represents the location of
/// the extern declaration.
/// Once resolved, however,
/// both will instead represent the location of the concrete
/// identifier.
Extern(SPair, IdentKind, Source),
/// Opaque identifier with associated text.
///
/// Code fragments are portions of the target language associated with
/// an identifier.
/// They are produced by the compiler and it is the job of the
/// [linker][crate::ld] to put them into the correct order for the
/// final executable.
IdentFragment(SPair, IdentKind, Source, FragmentText),
/// A resolved transparent identifier that has a corresponding
/// definition on the graph.
///
/// An identifier is transparent when the system is expected to use the
/// identifier only as a key for locating its associated object,
/// "seeing through" the identifier to reference directly the
/// underlying [`Object`](super::Object).
/// This is in contrast to [`Ident::Opaque`],
/// which is only _declared_,
/// and serves _in place of_ its corresponding definition.
///
/// Consequently,
/// this representation of an identifier is very light,
/// since dependents are expected to create edges to the
/// [`Object`](super::Object) it references rather than the identifier
/// itself;
/// this is safe since identifiers in TAME are immutable.
Transparent(SPair),
}
impl Display for Ident {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Missing(id) => {
write!(f, "missing identifier {}", TtQuote::wrap(id))
}
Opaque(id, kind, _) => {
write!(f, "{kind} identifier {}", TtQuote::wrap(id))
}
Extern(id, kind, _) => {
write!(f, "{kind} extern identifier {}", TtQuote::wrap(id))
}
IdentFragment(id, kind, _, _) => write!(
f,
"{kind} identifier {} with compiled fragment",
TtQuote::wrap(id)
),
Transparent(id) => {
write!(f, "transparent identifier {}", TtQuote::wrap(id))
}
}
}
}
impl NameableMissingObject for Ident {
fn missing(name: SPair) -> Self {
Self::Missing(name)
}
}
impl Ident {
/// Identifier name.
pub fn name(&self) -> SPair {
match self {
Missing(name)
| Opaque(name, ..)
| Extern(name, ..)
| IdentFragment(name, ..)
| Transparent(name) => *name,
}
}
pub fn span(&self) -> Span {
match self {
Missing(name)
| Opaque(name, ..)
| Extern(name, ..)
| IdentFragment(name, ..)
| Transparent(name) => name.span(),
}
}
/// Identifier [`IdentKind`].
///
/// If the object does not have a kind
/// (as is the case with [`Ident::Missing`]),
/// [`None`] is returned.
pub fn kind(&self) -> Option<&IdentKind> {
match self {
Missing(_) | Transparent(_) => None,
Opaque(_, kind, _)
| Extern(_, kind, _)
| IdentFragment(_, kind, _, _) => Some(kind),
}
}
/// Identifier [`Source`].
///
/// If the object does not have source information
/// (as is the case with [`Ident::Extern`]),
/// [`None`] is returned.
pub fn src(&self) -> Option<&Source> {
match self {
Missing(_) | Extern(_, _, _) | Transparent(_) => None,
Opaque(_, _, src) | IdentFragment(_, _, src, _) => Some(src),
}
}
/// Identifier [`FragmentText`].
///
/// If the object does not have an associated code fragment,
/// [`None`] is returned.
pub fn fragment(&self) -> Option {
match self {
Missing(_) | Opaque(_, _, _) | Extern(_, _, _) | Transparent(_) => {
None
}
IdentFragment(_, _, _, text) => Some(*text),
}
}
/// Produce an object representing a missing identifier.
///
/// This is the base state for all identifiers.
/// The [`Span`] associated with the pair should be the span of whatever
/// reference triggered this declaration;
/// it will be later replaced with the span of the identifier once
/// its definition is found.
pub fn declare(ident: SPair) -> Self {
Missing(ident)
}
/// Attempt to redeclare an identifier with additional information.
///
/// If an existing identifier is an [`Ident::Extern`],
/// then the declaration will be compared just the same,
/// but the identifier will be converted from an extern into an
/// identifier.
/// When this happens,
/// the extern is said to be _resolved_.
///
/// If a virtual identifier of type [`Ident::IdentFragment`] is
/// overridden,
/// then its fragment is cleared
/// (it returns to a [`Ident::Opaque`])
/// to make way for the fragment of the override.
///
/// Overrides will always have their virtual flag cleared,
/// even if set.
/// The compiler will hopefully have done this for us,
/// since the user may be confused with subsequent
/// [`TransitionError::NonVirtualOverride`] errors if they try to
/// override an override.
///
/// The kind of identifier cannot change,
/// but the argument is provided here for convenience so that the
/// caller does not need to perform such a check itself.
///
/// If no extern or virtual override is possible,
/// an identifier cannot be redeclared and this operation will fail.
pub fn resolve(
self,
span: Span,
kind: IdentKind,
mut src: Source,
) -> TransitionResult {
match self {
Opaque(name, ref orig_kind, ref orig_src)
| IdentFragment(name, ref orig_kind, ref orig_src, _)
if src.override_ =>
{
if !orig_src.virtual_ {
let err = TransitionError::NonVirtualOverride(name, span);
return Err((self, err));
}
if orig_kind != &kind {
let err = TransitionError::VirtualOverrideKind(
name,
orig_kind.clone(),
(kind, span),
);
return Err((self, err));
}
// Ensure that virtual flags are cleared to prohibit
// override-overrides.
// The compiler should do this;
// this is just an extra layer of defense.
src.virtual_ = false;
// Note that this has the effect of clearing fragments if we
// originally were in state `Ident::IdentFragment`.
Ok(Opaque(name.overwrite(span), kind, src))
}
// If we encountered the override _first_,
// flip the context by declaring a new identifier and trying
// to override that.
Opaque(name, orig_kind, orig_src) if orig_src.override_ => {
Self::declare(name)
.resolve(name.span(), kind, src)?
.resolve(span, orig_kind, orig_src)
}
// Same as above,
// but for fragments,
// we want to keep the _original override_ fragment.
IdentFragment(name, orig_kind, orig_src, orig_text)
if orig_src.override_ =>
{
Self::declare(name)
.resolve(name.span(), kind, src)?
.resolve(span, orig_kind, orig_src)?
.set_fragment(orig_text)
}
Extern(name, ref orig_kind, _) => {
if orig_kind != &kind {
let err = TransitionError::ExternResolution(
name,
orig_kind.clone(),
(kind, span),
);
return Err((self, err));
}
Ok(Opaque(name.overwrite(span), kind, src))
}
// These represent the prologue and epilogue of maps.
IdentFragment(
_,
IdentKind::MapHead
| IdentKind::MapTail
| IdentKind::RetMapHead
| IdentKind::RetMapTail,
..,
) => Ok(self),
Missing(name) => Ok(Opaque(name.overwrite(span), kind, src)),
// TODO: Remove guards and catch-all for exhaustiveness check.
_ => {
let err = TransitionError::Redeclare(self.name(), span);
Err((self, err))
}
}
}
/// Assertion to return self if identifier is resolved,
/// otherwise failing with [`UnresolvedError`].
///
/// This simplifies working with identifiers without having to match on
/// specific variants,
/// and will continue to work if new variants are added in the
/// future that are considered to be unresolved.
///
/// Since this does not cause a state transition and is useful in
/// contexts where ownership over the identifier is not possible,
/// this accepts and returns a reference to the identifier.
///
/// At present,
/// both [`Ident::Missing`] and [`Ident::Extern`] are
/// considered to be unresolved.
pub fn resolved(&self) -> Result<&Ident, UnresolvedError> {
match self {
Missing(name) => Err(UnresolvedError::Missing(*name)),
Extern(name, ref kind, _) => {
Err(UnresolvedError::Extern(*name, kind.clone()))
}
Opaque(..) | IdentFragment(..) | Transparent(..) => Ok(self),
}
}
/// Resolve identifier against an extern declaration or produce an
/// extern.
///
/// If the existing identifier has an assigned [`IdentKind`],
/// then it will be compared for equality against the given `kind`.
/// If it matches,
/// then the current identifier will be returned as-is.
/// This represents an extern resolution that occurs when a concrete
/// identifier is located before an extern that requires it,
/// or my represent a duplicate (but compatible) extern
/// declaration.
///
/// If no kind is assigned (such as [`Ident::Missing`]),
/// then a new extern is produced.
/// See for example [`Ident::Extern`].
pub fn extern_(
self,
span: Span,
kind: IdentKind,
src: Source,
) -> TransitionResult {
match self.kind() {
None => Ok(Extern(self.name().overwrite(span), kind, src)),
Some(cur_kind) => {
if cur_kind != &kind {
let err = TransitionError::ExternResolution(
self.name(),
cur_kind.clone(),
(kind, span),
);
return Err((self, err));
}
// Resolved successfully, so keep what we already have.
Ok(self)
}
}
}
/// Attach a code fragment (compiled text) to an identifier.
///
/// This will fail if an identifier already has a fragment,
/// since only the owner of the identifier should be producing
/// compiled code.
/// Note, however, that an identifier's fragment may be cleared under
/// certain circumstances (such as symbol overrides),
/// making way for a new fragment to be set.
pub fn set_fragment(self, text: FragmentText) -> TransitionResult {
match self {
Opaque(sym, kind, src) => Ok(IdentFragment(sym, kind, src, text)),
// If we get to this point in a properly functioning program (at
// least as of the time of writing), then we have encountered a
// fragment for a virtual identifier _after_ we have already
// encountered the fragment for its _override_. We therefore
// want to keep the override.
//
// If this is not permissable, then we should have already
// prevented the `resolve` transition before this fragment was
// encountered.
IdentFragment(_, _, ref src, ..) if src.override_ => Ok(self),
// These represent the prologue and epilogue of maps.
IdentFragment(
_,
IdentKind::MapHead
| IdentKind::MapTail
| IdentKind::RetMapHead
| IdentKind::RetMapTail,
..,
) => Ok(self),
_ => {
let name = self.name();
Err((self, TransitionError::BadFragmentDest(name)))
}
}
}
}
/// An error attempting to transition from one [`Ident`] state to
/// another.
#[derive(Clone, Debug, PartialEq)]
pub enum TransitionError {
/// Attempted to redeclare a concrete, non-virtual identifier without an
/// override.
Redeclare(SPair, Span),
/// Extern resolution failure.
///
/// An extern could not be resolved because the provided identifier had
/// a type that is incompatible with the extern definition.
///
// TODO: Need more granular spans for `IdentKind`.
ExternResolution(SPair, IdentKind, (IdentKind, Span)),
/// Attempt to override a non-virtual identifier.
NonVirtualOverride(SPair, Span),
/// Overriding a virtual identifier failed due to an incompatible
/// [`IdentKind`].
// TODO: More granular spans for kind.
VirtualOverrideKind(SPair, IdentKind, (IdentKind, Span)),
/// The provided identifier is not in a state that is permitted to
/// receive a fragment.
///
/// See [`Ident::set_fragment`].
BadFragmentDest(SPair),
}
impl std::fmt::Display for TransitionError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
use TransitionError::*;
match self {
Redeclare(name, _) => write!(
fmt,
"cannot redeclare identifier {}",
TtQuote::wrap(name),
),
ExternResolution(name, expected, (given, _)) => write!(
fmt,
"extern {} of type {} is incompatible with type {}",
TtQuote::wrap(name),
TtQuote::wrap(expected),
TtQuote::wrap(given),
),
NonVirtualOverride(name, _) => write!(
fmt,
"non-virtual identifier {} cannot be overridden",
TtQuote::wrap(name),
),
VirtualOverrideKind(name, existing, (given, _)) => write!(
fmt,
"virtual identifier {} of type {} cannot be overridden with type {}",
TtQuote::wrap(name),
TtQuote::wrap(existing),
TtQuote::wrap(given),
),
BadFragmentDest(name) => {
write!(fmt, "bad fragment destination: {}", TtQuote::wrap(name))
}
}
}
}
impl std::error::Error for TransitionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl Diagnostic for TransitionError {
fn describe(&self) -> Vec {
use TransitionError::*;
match self {
Redeclare(name, sdup) => vec![
name.note("first declaration was found here"),
sdup.error(format!("cannot redeclare {}", TtQuote::wrap(name))),
sdup.help("identifiers in TAME are immutable and can"),
sdup.help(" only be associated with one definition."),
],
ExternResolution(name, expected, (given, sresolve)) => vec![
name.note(format!(
"extern {} declared here with type {}",
TtQuote::wrap(name),
TtQuote::wrap(expected),
)),
sresolve.error(format!(
"attempted to resolve extern {} with incompatible type {}",
TtQuote::wrap(name),
TtQuote::wrap(given),
)),
],
// TODO: Does this make a lie out of `Redeclare`'s help?
NonVirtualOverride(name, soverride) => vec![
name.note("attempting to override this non-virtual identifier"),
soverride.error(format!(
"cannot override {} because the first declaration \
was not virtual",
TtQuote::wrap(name),
)),
// TODO: let's see what type of help text will be useful
// once we've decided what is to be done with virtual
// identifiers generally
],
VirtualOverrideKind(name, orig, (given, soverride)) => vec![
name.note(format!(
"attempting to override this identifier of type {}",
TtQuote::wrap(orig),
)),
soverride.error(format!(
"type of this override is {}, but {} was expected",
TtQuote::wrap(given),
TtQuote::wrap(orig),
)),
],
BadFragmentDest(name) => vec![
name.internal_error(
"identifier {} cannot be assigned a text fragment",
),
name.help(
"the term 'text fragment' refers to compiled code from an",
),
name.help(" object file; this error should never occur."),
],
}
}
}
/// Resolved identifier was expected.
#[derive(Clone, Debug, PartialEq)]
pub enum UnresolvedError {
/// Expected identifier is missing and nothing about it is known.
///
/// The span represents the first reference to this identifier that
/// caused it to be added to the graph;
/// it is not necessarily the _only_ reference.
/// When reporting errors based on references to unknown identifiers,
/// keep this in mind to avoid duplicates.
Missing(SPair),
/// Expected identifier has not yet been resolved with a concrete
/// definition.
Extern(SPair, IdentKind),
}
impl std::fmt::Display for UnresolvedError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
use UnresolvedError::*;
match self {
Missing(name) => {
write!(fmt, "unknown identifier {}", TtQuote::wrap(name))
}
Extern(name, kind) => write!(
fmt,
"unresolved extern {} of type {}",
TtQuote::wrap(name),
TtQuote::wrap(kind),
),
}
}
}
impl std::error::Error for UnresolvedError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl Diagnostic for UnresolvedError {
fn describe(&self) -> Vec {
use UnresolvedError::*;
match self {
// TODO: Do we want a single span, or should errors also be
// thrown for every reference to missing?
Missing(name) => vec![name.error(format!(
"identifier {} has not been defined",
TtQuote::wrap(name),
))],
Extern(name, _kind) => vec![
name.error(format!(
"no imported package provided a \
compatible definition for {}",
TtQuote::wrap(name),
)),
name.help(
"an extern declares an identifier so that it may be used,"
),
name.help(
" but with the expectation that some imported package will"
),
name.help(
" later provide a concrete definition for it."
)
],
}
}
}
/// Compiled fragment for identifier.
///
/// This represents the text associated with an identifier.
pub type FragmentText = SymbolId;
/// Types of identifiers.
///
/// Here, the term _calculation_ refers to a composable expression that
/// produces a numeric result.
///
/// These are derived from [`SymType`][crate::obj::xmlo::SymType]
/// and will be generalized in the future.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum IdentKind {
/// Classification generator.
///
/// This has the same number of dimensions as its highest-dimension
/// predicate.
/// Every [`Class`][IdentKind::Class] has an associated generator.
Cgen(Dim),
/// Boolean classification.
///
/// This is an artifact of an ancient system.
/// The dimensions here refers to the dimensions of the associated
/// [`Cgen`][IdentKind::Cgen].
Class(Dim),
/// Constant value.
Const(Dim, Dtype),
/// Re-usable encapsulated expression.
///
/// Functions are nothing more than expressions that can be re-used with
/// dynamic values at runtime.
/// See also [`Lparam`][IdentKind::Lparam].
Func(Dim, Dtype),
/// Generating calculation.
///
/// Generators are associated with iterative expressions,
/// such as sums and products.
/// They always have a parent [`Rate`][IdentKind::Rate].
Gen(Dim, Dtype),
/// Local (non-global) parameter.
///
/// Local parameters are lexically scoped to their parent expression:
/// - [`Func`][IdentKind::Func], where there exists one per defined
/// function parameter; and
/// - `let` expression bindings.
///
/// This is not to be confused with the global
/// [`Param`][IdentKind::Param].
Lparam(Dim, Dtype),
/// Global parameter.
///
/// These parameters serve as inputs to the system.
/// Input values are bound using [`Map`][IdentKind::Map].
Param(Dim, Dtype),
/// Scalar result of a named calculation.
///
/// The verb "rate" is historical,
/// since TAME was developed for insurance rating systems.
/// This represents a named expression that yields a scalar value.
///
/// This serves as a parent to [`Gen`][IdentKind::Gen].
Rate(Dtype),
/// Template definition.
///
/// A template is used only at expansion-time and,
/// unlike most other things in the system,
/// have no runtime value.
Tpl,
/// User-defined data type.
///
/// The only types typically defined are enums and unions of enums.
/// The type itself has no runtime value,
/// but each of the enum variants have an associated value of type
/// [`Dtype`].
Type(Dtype),
/// Input map head (meta identifier generated by compiler for each input
/// map).
MapHead,
/// Input field→param mapping.
///
/// These may only map to [`Param`][IdentKind::Param].
/// The source data is arbitrary and provided at runtime.
Map,
/// Input map tail (meta symbol generated by compiler for each input
/// map).
MapTail,
/// Return map head (meta symbol generated by compiler for each return
/// map).
RetMapHead,
/// Return param→field mapping.
///
/// Return mappings export data to calling systems.
/// They can map back any globally defined numeric expression.
RetMap,
/// Return map tail (meta symbol generated by compiler for each return
/// map).
RetMapTail,
/// Arbitrary metadata.
///
/// This permits the definition of static key/value data that is
/// compiled into the final executable.
Meta,
/// Rating worksheet (generated by compiler for worksheet packages).
///
/// The worksheet exposes intermediate calculation values in a much more
/// concise form than that of the Summary Page.
Worksheet,
}
impl IdentKind {
pub fn as_sym(&self) -> SymbolId {
match self {
Self::Cgen(..) => st::L_CGEN.as_sym(),
Self::Class(..) => st::L_CLASS.as_sym(),
Self::Const(..) => st::L_CONST.as_sym(),
Self::Func(..) => st::L_FUNC.as_sym(),
Self::Gen(..) => st::L_GEN.as_sym(),
Self::Lparam(..) => st::L_LPARAM.as_sym(),
Self::Param(..) => st::L_PARAM.as_sym(),
Self::Rate(..) => st::L_RATE.as_sym(),
Self::Tpl => st::L_TPL.as_sym(),
Self::Type(..) => st::L_TYPE.as_sym(),
Self::MapHead => st::L_MAP_HEAD.as_sym(),
Self::Map => st::L_MAP.as_sym(),
Self::MapTail => st::L_MAP_TAIL.as_sym(),
Self::RetMapHead => st::L_RETMAP_HEAD.as_sym(),
Self::RetMap => st::L_RETMAP.as_sym(),
Self::RetMapTail => st::L_RETMAP_TAIL.as_sym(),
Self::Meta => st::L_META.as_sym(),
Self::Worksheet => st::L_WORKSHEET.as_sym(),
}
}
/// Whether this identifier should be automatically added as a root when
/// declared.
pub fn is_auto_root(&self) -> bool {
matches!(
self,
Self::Meta | Self::Map | Self::RetMap | Self::Worksheet
)
}
}
impl std::fmt::Display for IdentKind {
/// Format identifier type for display to the user.
///
/// TODO: We have not yet finalized how we will represent types in the
/// new type system,
/// so for now this just uses a syntax similar to Rust.
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
let name = self.as_sym().lookup_str();
match self {
Self::Cgen(dim) => {
write!(fmt, "{}[{}; {}]", name, Dtype::Boolean, dim)
}
Self::Class(dim) => {
write!(fmt, "{}[{}; {}]", name, Dtype::Boolean, dim)
}
Self::Const(dim, dtype) => {
write!(fmt, "{}[{}; {}]", name, dtype, dim)
}
Self::Func(dim, dtype) => {
write!(fmt, "{}[{}; {}]", name, dtype, dim)
}
Self::Gen(dim, dtype) => {
write!(fmt, "{}[{}; {}]", name, dtype, dim)
}
Self::Lparam(dim, dtype) => {
write!(fmt, "{}[{}; {}]", name, dtype, dim)
}
Self::Param(dim, dtype) => {
write!(fmt, "{}[{}; {}]", name, dtype, dim)
}
Self::Rate(dtype) => write!(fmt, "{}[{}; 0]", name, dtype),
Self::Type(dtype) => write!(fmt, "{}[{}]", name, dtype),
_ => write!(fmt, "{}", name),
}
}
}
/// Metadata about the source of an object.
///
/// This contains information from the symbol table that does not belong on
/// [`IdentKind`],
/// since that stores _type_ information.
///
/// TODO: This does not currently store byte offsets within the source file
/// since the original XSLT-based compiler did not have that capability;
/// this will provide that information in the future.
#[derive(Debug, Default, PartialEq, Clone)]
pub struct Source {
/// Name of package containing reference to this object.
pub pkg_name: Option,
/// Relative path to the source of this object,
/// if not present in the current package.
pub src: Option,
/// The identifier from which this one is derived.
///
/// See [`IdentKind`] for more information on parents.
/// For example,
/// a [`IdentKind::Cgen`] always has a parent [`IdentKind::Class`].
pub parent: Option,
/// Child identifier associated with this identifier.
///
/// For [`IdentKind::Class`],
/// this represents an associated [`IdentKind::Cgen`].
pub yields: Option,
/// User-friendly identifier description.
///
/// This is used primarily by [`IdentKind::Class`] and
/// [`IdentKind::Gen`].
pub desc: Option,
/// Whether this identifier was generated by the compiler.
///
/// A generated identifier is representative of an internal
/// implementation detail that should remain encapsulated from the
/// user and is subject to change over time.
///
/// Identifiers created by templates are not considered to be generated.
pub generated: bool,
/// Related identifiers.
///
/// These data represent a kluge created to add additional symbol
/// information in two different contexts:
///
/// - [`IdentKind::Map`] includes the name of the source field; and
/// - [`IdentKind::Func`] lists params in order (so that the compiler
/// knows application order).
///
/// TODO: We have `parent`, `yields`, and `from`.
/// We should begin to consolodate.
pub from: Option,
/// Whether identifier is virtual (can be overridden).
///
/// This feature adds complexity and will ideally be removed in the
/// future.
///
/// See also [`override`][Source::override_].
pub virtual_: bool,
/// Whether identifier overrides a virtual identifier.
///
/// This feature adds complexity and will ideally be removed in the
/// future.
///
/// See also [`virtual_`][Source::virtual_].
pub override_: bool,
}
object_rel! {
/// Identifiers are either transparent
/// (bound to a definition)
/// or opaque.
/// If transparent,
/// then the identifier represents a definition,
/// and is therefore a root to that definition.
///
/// Opaque identifiers at the time of writing are used by the linker
/// which does not reason about cross edges
/// (again at the time of writing).
Ident -> {
tree Ident,
tree Expr,
tree Tpl,
tree Meta,
}
}
impl ObjectIndex {
/// Declare a concrete identifier.
///
/// An identifier declaration is similar to a declaration in a header
/// file in a language like C,
/// describing the structure of the identifier.
/// Once declared,
/// this information cannot be changed.
///
/// Identifiers are uniquely identified by a [`SPair`] `name`.
/// If an identifier of the same `name` already exists,
/// then the provided declaration is compared against the existing
/// declaration---should
/// they be incompatible,
/// then the operation will fail;
/// otherwise,
/// the existing identifier will be returned.
///
/// If a concrete identifier has already been declared,
/// then extern declarations will be compared and,
/// if compatible,
/// the identifier will be immediately _resolved_ and the object
/// on the graph will not be altered.
/// Resolution will otherwise fail in error.
///
/// For more information on state transitions that can occur when
/// redeclaring an identifier that already exists,
/// see [`Ident::resolve`].
///
/// A successful declaration will add an identifier to the graph
/// and return an [`ObjectIndex`] reference.
pub fn declare(
self,
asg: &mut Asg,
name: SPair,
kind: IdentKind,
src: Source,
) -> Result, AsgError> {
let is_auto_root = kind.is_auto_root();
self.try_map_obj(asg, |obj| obj.resolve(name.span(), kind, src))
.map_err(Into::into)
.map(|ident| {
is_auto_root.then(|| self.root(asg));
ident
})
}
/// Declare an abstract identifier.
///
/// See [`Ident::extern_`] and [`Ident::resolve`] for more information.
pub fn declare_extern(
self,
asg: &mut Asg,
name: SPair,
kind: IdentKind,
src: Source,
) -> Result {
self.try_map_obj(asg, |obj| obj.extern_(name.span(), kind, src))
.map_err(Into::into)
}
/// Bind an identifier to a `definition`,
/// making it [`Transparent`].
///
/// If an identifier is successfully bound,
/// then an edge will be added to `definition`.
/// An edge will _not_ be added if there is an error in this operation.
///
/// If an identifier is already [`Transparent`],
/// then it is already defined and this operation will result in an
/// [`AsgError::IdentRedefine`] error.
pub fn bind_definition(
self,
asg: &mut Asg,
id: SPair,
definition: ObjectIndex,
) -> Result, AsgError>
where
Ident: ObjectRelTo,
{
let my_span = self.into();
// TODO: Should we move all incoming edges to `definition`?
// This will complicate re-outputting source XML,
// but may simplify other aspects of the system.
// Perhaps wait until this is needed.
self.try_map_obj(asg, |ident| match ident {
Transparent(id) => {
Err((ident, AsgError::IdentRedefine(id, my_span)))
}
Opaque(id, ..) | IdentFragment(id, ..) | Extern(id, ..) => {
diagnostic_todo!(
vec![
id.note("must resolve definition against declaration"),
my_span.error(
"attempting to provide a definition for prior \
declaration"
),
my_span.help(
"this identifier was previously declared and so \
its definition needs to be checked for \
compatibility with that declaration"
)
],
"resolve opaque declaration {} to definition",
TtQuote::wrap(id),
)
}
// We are okay to proceed to add an edge to the `definition`.
// Discard the original span
// (which is the location of the first reference _to_ this
// identifier before it was defined),
// and use the newly provided `id` and its span.
Missing(_) => Ok(Transparent(id)),
})
.map(|ident_oi| ident_oi.add_edge_to(asg, definition, None))
}
/// Set the fragment associated with a concrete identifier.
///
/// Fragments are intended for use by the [linker][crate::ld].
/// For more information,
/// see [`Ident::set_fragment`].
pub fn set_fragment(
self,
asg: &mut Asg,
text: SymbolId,
) -> Result {
self.try_map_obj(asg, |obj| obj.set_fragment(text))
.map_err(Into::into)
}
/// Look up the definition that this identifier binds to,
/// if any.
///
/// See [`Self::bind_definition`].
pub fn definition + ObjectRelatable>(
&self,
asg: &Asg,
) -> Option> {
self.edges(asg).find_map(ObjectRel::narrow)
}
/// Whether this identifier is bound to the object represented by `oi`.
///
/// To bind an identifier,
/// see [`Self::bind_definition`].
pub fn is_bound_to + ObjectRelatable>(
&self,
asg: &Asg,
oi: ObjectIndex,
) -> bool {
self.edges(asg).find_map(ObjectRel::narrow) == Some(oi)
}
/// Whether this identifier is bound to an object of kind `O`.
///
/// To bind an identifier,
/// see [`Self::bind_definition`].
pub fn is_bound_to_kind>(&self, asg: &Asg) -> bool {
self.edges_filtered::(asg).next().is_some()
}
/// The source package defining this identifier,
/// if known.
pub fn src_pkg(&self, asg: &Asg) -> Option> {
self.incoming_edges_filtered(asg).next()
}
/// Declare that `oi_dep` is an opaque dependency of `self`.
pub fn add_opaque_dep(
&self,
asg: &mut Asg,
oi_dep: ObjectIndex,
) -> Self {
self.add_edge_to(asg, oi_dep, None)
}
}
#[cfg(test)]
mod test;