1709 lines
57 KiB
Rust
1709 lines
57 KiB
Rust
// Objects represented on ASG
|
|
//
|
|
// Copyright (C) 2014-2021 Ryan Specialty Group, 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 <http://www.gnu.org/licenses/>.
|
|
|
|
//! Objects represented by the ASG.
|
|
//!
|
|
//! _This is a private module.
|
|
//! See [`super`] for available exports._
|
|
|
|
use super::ident::IdentKind;
|
|
use crate::ir::legacyir::SymAttrs;
|
|
use crate::sym::{GlobalSymbolResolve, SymbolId};
|
|
use std::result::Result;
|
|
|
|
pub type TransitionResult<T> = Result<T, (T, TransitionError)>;
|
|
|
|
/// Type of object.
|
|
///
|
|
/// These types represent object states:
|
|
///
|
|
/// ```text
|
|
/// (Missing) -> (Extern) -> ((Ident)) -> ((IdentFragment)).
|
|
/// \ ^ /
|
|
/// \ / \ /
|
|
/// `--------------------` `-----------'
|
|
/// ```
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub enum IdentObject {
|
|
/// An identifier is expected to be defined but is not yet available.
|
|
///
|
|
/// This variant contains the symbol representing the name of the
|
|
/// expected identifier.
|
|
/// By defining an object as missing,
|
|
/// this allows the graph to be built incrementally as objects are
|
|
/// discovered.
|
|
Missing(SymbolId),
|
|
|
|
/// A resolved identifier.
|
|
///
|
|
/// This represents an identifier that has been declared with certain
|
|
/// type information.
|
|
Ident(SymbolId, IdentKind, Source),
|
|
|
|
/// An identifier that has not yet been resolved.
|
|
///
|
|
/// Externs are upgraded to [`IdentObject::Ident`] once an identifier of
|
|
/// the same name is loaded.
|
|
/// It is an error if the loaded identifier does not have a compatible
|
|
/// [`IdentKind`].
|
|
///
|
|
/// The source location of an extern represents the location of the
|
|
/// extern declaration.
|
|
/// Once resolved, however,
|
|
/// the source will instead represent the location of the concrete
|
|
/// identifier.
|
|
Extern(SymbolId, IdentKind, Source),
|
|
|
|
/// 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(SymbolId, IdentKind, Source, FragmentText),
|
|
}
|
|
|
|
impl IdentObject {
|
|
pub fn name(&self) -> SymbolId {
|
|
match self {
|
|
Self::Missing(name)
|
|
| Self::Ident(name, _, _)
|
|
| Self::Extern(name, _, _)
|
|
| Self::IdentFragment(name, _, _, _) => *name,
|
|
}
|
|
}
|
|
|
|
pub fn kind(&self) -> Option<&IdentKind> {
|
|
match self {
|
|
Self::Missing(_) => None,
|
|
Self::Ident(_, kind, _)
|
|
| Self::Extern(_, kind, _)
|
|
| Self::IdentFragment(_, kind, _, _) => Some(kind),
|
|
}
|
|
}
|
|
|
|
pub fn src(&self) -> Option<&Source> {
|
|
match self {
|
|
Self::Missing(_) | Self::Extern(_, _, _) => None,
|
|
Self::Ident(_, _, src) | Self::IdentFragment(_, _, src, _) => {
|
|
Some(src)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn fragment(&self) -> Option<FragmentText> {
|
|
match self {
|
|
Self::Missing(_) | Self::Ident(_, _, _) | Self::Extern(_, _, _) => {
|
|
None
|
|
}
|
|
Self::IdentFragment(_, _, _, text) => Some(*text),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Retrieve information about an [`IdentObject`].
|
|
///
|
|
/// APIs should adhere to this trait rather than a concrete object type such
|
|
/// as [`IdentObject`];
|
|
/// this allows other representations to be used,
|
|
/// while still permitting the use of matching on [`IdentObject`]
|
|
/// through the use of [`ident`](IdentObjectData::as_ident).
|
|
///
|
|
/// Since an object implementing this trait may not be an identifier
|
|
/// (e.g. an expression),
|
|
/// even [`name`](IdentObjectData::name)---which
|
|
/// is used by all [`IdentObject`] variants---returns
|
|
/// an [`Option`].
|
|
/// These methods also provide a convenient alternative to `match`ing on
|
|
/// data that may not be present in all variants.
|
|
pub trait IdentObjectData {
|
|
/// Identifier name.
|
|
///
|
|
/// If the object is not an identifier,
|
|
/// [`None`] is returned.
|
|
fn name(&self) -> Option<SymbolId>;
|
|
|
|
/// Identifier [`IdentKind`].
|
|
///
|
|
/// If the object does not have a kind
|
|
/// (as is the case with [`IdentObject::Missing`]),
|
|
/// [`None`] is returned.
|
|
fn kind(&self) -> Option<&IdentKind>;
|
|
|
|
/// Identifier [`Source`].
|
|
///
|
|
/// If the object does not have source information
|
|
/// (as is the case with [`IdentObject::Extern`]),
|
|
/// [`None`] is returned.
|
|
fn src(&self) -> Option<&Source>;
|
|
|
|
/// Identifier [`FragmentText`].
|
|
///
|
|
/// If the object does not have an associated code fragment,
|
|
/// [`None`] is returned.
|
|
fn fragment(&self) -> Option<FragmentText>;
|
|
|
|
/// IdentObject as an identifier ([`IdentObject`]).
|
|
///
|
|
/// If the object is not or cannot be faithfully converted into an
|
|
/// [`IdentObject`],
|
|
/// [`None`] is returned.
|
|
/// For example,
|
|
/// expressions will always yield [`None`].
|
|
///
|
|
/// This allows pattern matching on [`IdentObject`] variants regardless
|
|
/// of the underlying object type.
|
|
fn as_ident(&self) -> Option<&IdentObject>;
|
|
}
|
|
|
|
impl IdentObjectData for IdentObject {
|
|
fn name(&self) -> Option<SymbolId> {
|
|
Some(Self::name(self))
|
|
}
|
|
|
|
fn kind(&self) -> Option<&IdentKind> {
|
|
Self::kind(self)
|
|
}
|
|
|
|
fn src(&self) -> Option<&Source> {
|
|
Self::src(self)
|
|
}
|
|
|
|
fn fragment(&self) -> Option<FragmentText> {
|
|
// TODO: Get rid of the reference; the type is `Copy` now.
|
|
Self::fragment(self)
|
|
}
|
|
|
|
/// Expose underlying [`IdentObject`].
|
|
///
|
|
/// This will never be [`None`] for this implementation.
|
|
/// However,
|
|
/// other [`IdentObjectData`] implementations may still result in
|
|
/// [`None`],
|
|
/// so it's important _not_ to rely on this as an excuse to be lazy
|
|
/// with unwrapping.
|
|
#[inline]
|
|
fn as_ident(&self) -> Option<&IdentObject> {
|
|
Some(&self)
|
|
}
|
|
}
|
|
|
|
/// Objects as a state machine.
|
|
pub trait IdentObjectState<T>
|
|
where
|
|
T: IdentObjectState<T>,
|
|
{
|
|
/// Produce an object representing a missing identifier.
|
|
///
|
|
/// This is the base state for all identifiers.
|
|
fn declare(ident: SymbolId) -> T;
|
|
|
|
/// Attempt to transition to a concrete identifier.
|
|
///
|
|
/// For specific information on compatibility rules,
|
|
/// see implementers of this trait,
|
|
/// since rules may vary between implementations.
|
|
fn resolve(self, kind: IdentKind, src: Source) -> TransitionResult<T>;
|
|
|
|
/// 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 [`IdentObject::Missing`] and [`IdentObject::Extern`] are
|
|
/// considered to be unresolved.
|
|
fn resolved(&self) -> Result<&T, UnresolvedError>;
|
|
|
|
/// 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 [`IdentObject::Missing`]),
|
|
/// then a new extern is produced.
|
|
/// See for example [`IdentObject::Extern`].
|
|
fn extern_(self, kind: IdentKind, src: Source) -> TransitionResult<T>;
|
|
|
|
/// 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.
|
|
fn set_fragment(self, text: FragmentText) -> TransitionResult<T>;
|
|
}
|
|
|
|
impl IdentObjectState<IdentObject> for IdentObject {
|
|
fn declare(ident: SymbolId) -> Self {
|
|
IdentObject::Missing(ident)
|
|
}
|
|
|
|
/// Attempt to redeclare an identifier with additional information.
|
|
///
|
|
/// If an existing identifier is an [`IdentObject::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 [`IdentObject::IdentFragment`] is
|
|
/// overridden,
|
|
/// then its fragment is cleared
|
|
/// (it returns to a [`IdentObject::Ident`])
|
|
/// 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.
|
|
fn resolve(
|
|
self,
|
|
kind: IdentKind,
|
|
mut src: Source,
|
|
) -> TransitionResult<IdentObject> {
|
|
match self {
|
|
IdentObject::Ident(name, ref orig_kind, ref orig_src)
|
|
| IdentObject::IdentFragment(
|
|
name,
|
|
ref orig_kind,
|
|
ref orig_src,
|
|
_,
|
|
) if src.override_ => {
|
|
if !orig_src.virtual_ {
|
|
let err =
|
|
TransitionError::NonVirtualOverride { name: name };
|
|
|
|
return Err((self, err));
|
|
}
|
|
|
|
if orig_kind != &kind {
|
|
let err = TransitionError::VirtualOverrideKind {
|
|
// TODO: defer lookup to error display
|
|
name: name,
|
|
existing: orig_kind.clone(),
|
|
given: kind.clone(),
|
|
};
|
|
|
|
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 `IdentObject::IdentFragment`.
|
|
Ok(IdentObject::Ident(name, kind, src))
|
|
}
|
|
|
|
// If we encountered the override _first_, flip the context by
|
|
// declaring a new identifier and trying to override that.
|
|
IdentObject::Ident(name, orig_kind, orig_src)
|
|
if orig_src.override_ =>
|
|
{
|
|
Self::declare(name)
|
|
.resolve(kind, src)?
|
|
.resolve(orig_kind, orig_src)
|
|
}
|
|
|
|
// Same as above, but for fragments, we want to keep the
|
|
// _original override_ fragment.
|
|
IdentObject::IdentFragment(
|
|
name,
|
|
orig_kind,
|
|
orig_src,
|
|
orig_text,
|
|
) if orig_src.override_ => Self::declare(name)
|
|
.resolve(kind, src)?
|
|
.resolve(orig_kind, orig_src)?
|
|
.set_fragment(orig_text),
|
|
|
|
IdentObject::Extern(name, ref orig_kind, _) => {
|
|
if orig_kind != &kind {
|
|
let err = TransitionError::ExternResolution {
|
|
name: name,
|
|
expected: orig_kind.clone(),
|
|
given: kind.clone(),
|
|
};
|
|
|
|
return Err((self, err));
|
|
}
|
|
|
|
Ok(IdentObject::Ident(name, kind, src))
|
|
}
|
|
|
|
// These represent the prolog and epilogue of maps. This
|
|
// situation will be resolved in the future.
|
|
IdentObject::IdentFragment(_, IdentKind::MapHead, _, _)
|
|
| IdentObject::IdentFragment(_, IdentKind::MapTail, _, _)
|
|
| IdentObject::IdentFragment(_, IdentKind::RetMapHead, _, _)
|
|
| IdentObject::IdentFragment(_, IdentKind::RetMapTail, _, _) => {
|
|
Ok(self)
|
|
}
|
|
|
|
IdentObject::Missing(name) => {
|
|
Ok(IdentObject::Ident(name, kind, src))
|
|
}
|
|
|
|
_ => {
|
|
let err = TransitionError::Redeclare { name: self.name() };
|
|
|
|
Err((self, err))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn resolved(&self) -> Result<&IdentObject, UnresolvedError> {
|
|
match self {
|
|
IdentObject::Missing(name) => {
|
|
Err(UnresolvedError::Missing { name: *name })
|
|
}
|
|
|
|
IdentObject::Extern(name, ref kind, ref src) => {
|
|
Err(UnresolvedError::Extern {
|
|
name: *name,
|
|
kind: kind.clone(),
|
|
pkg_name: src.pkg_name,
|
|
})
|
|
}
|
|
|
|
IdentObject::Ident(_, _, _)
|
|
| IdentObject::IdentFragment(_, _, _, _) => Ok(self),
|
|
}
|
|
}
|
|
|
|
fn extern_(
|
|
self,
|
|
kind: IdentKind,
|
|
src: Source,
|
|
) -> TransitionResult<IdentObject> {
|
|
match self.kind() {
|
|
None => Ok(IdentObject::Extern(self.name(), kind, src)),
|
|
Some(cur_kind) => {
|
|
if cur_kind != &kind {
|
|
let err = TransitionError::ExternResolution {
|
|
name: self.name(),
|
|
expected: kind.clone(),
|
|
given: cur_kind.clone(),
|
|
};
|
|
|
|
return Err((self, err));
|
|
}
|
|
|
|
// Resolved successfully, so keep what we already have.
|
|
Ok(self)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn set_fragment(self, text: FragmentText) -> TransitionResult<IdentObject> {
|
|
match self {
|
|
IdentObject::Ident(sym, kind, src) => {
|
|
Ok(IdentObject::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.
|
|
IdentObject::IdentFragment(_, _, ref src, _) if src.override_ => {
|
|
Ok(self)
|
|
}
|
|
|
|
// These represent the prolog and epilogue of maps. This
|
|
// situation will be resolved in the future.
|
|
IdentObject::IdentFragment(_, IdentKind::MapHead, _, _)
|
|
| IdentObject::IdentFragment(_, IdentKind::MapTail, _, _)
|
|
| IdentObject::IdentFragment(_, IdentKind::RetMapHead, _, _)
|
|
| IdentObject::IdentFragment(_, IdentKind::RetMapTail, _, _) => {
|
|
Ok(self)
|
|
}
|
|
|
|
_ => {
|
|
let msg = format!(
|
|
"identifier is not a IdentObject::Ident): {:?}",
|
|
self,
|
|
);
|
|
|
|
Err((self, TransitionError::BadFragmentDest { name: msg }))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An error attempting to transition from one [`IdentObject`] state to
|
|
/// another.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum TransitionError {
|
|
/// Attempted to redeclare a concrete, non-virtual identifier without an
|
|
/// override.
|
|
Redeclare { name: SymbolId },
|
|
|
|
/// Extern resolution failure.
|
|
///
|
|
/// An extern could not be resolved because the provided identifier had
|
|
/// a type that is incompatible with the extern definition.
|
|
ExternResolution {
|
|
name: SymbolId,
|
|
expected: IdentKind,
|
|
given: IdentKind,
|
|
},
|
|
|
|
/// Attempt to override a non-virtual identifier.
|
|
NonVirtualOverride { name: SymbolId },
|
|
|
|
/// Overriding a virtual identifier failed due to an incompatible
|
|
/// [`IdentKind`].
|
|
VirtualOverrideKind {
|
|
name: SymbolId,
|
|
existing: IdentKind,
|
|
given: IdentKind,
|
|
},
|
|
|
|
/// The provided identifier is not in a state that is permitted to
|
|
/// receive a fragment.
|
|
///
|
|
/// See [`IdentObjectState::set_fragment`].
|
|
BadFragmentDest { name: String },
|
|
}
|
|
|
|
impl std::fmt::Display for TransitionError {
|
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
match self {
|
|
Self::Redeclare { name } => write!(
|
|
fmt,
|
|
"cannot redeclare identifier `{}`",
|
|
name,
|
|
),
|
|
|
|
Self::ExternResolution {
|
|
name,
|
|
expected,
|
|
given,
|
|
} => write!(
|
|
fmt,
|
|
"extern `{}` of type `{}` is incompatible with type `{}`",
|
|
name, expected, given,
|
|
),
|
|
|
|
Self::NonVirtualOverride { name } => write!(
|
|
fmt,
|
|
"non-virtual identifier `{}` cannot be overridden",
|
|
name,
|
|
),
|
|
|
|
Self::VirtualOverrideKind {
|
|
name,
|
|
existing,
|
|
given,
|
|
} => write!(
|
|
fmt,
|
|
"virtual identifier `{}` of type `{}` cannot be overridden with type `{}`",
|
|
name, existing, given,
|
|
),
|
|
|
|
Self::BadFragmentDest{name: msg} => {
|
|
write!(fmt, "bad fragment destination: {}", msg)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for TransitionError {
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Resolved identifier was expected.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum UnresolvedError {
|
|
/// Expected identifier is missing and nothing about it is known.
|
|
Missing { name: SymbolId },
|
|
|
|
/// Expected identifier has not yet been resolved with a concrete
|
|
/// definition.
|
|
Extern {
|
|
/// Identifier name.
|
|
name: SymbolId,
|
|
/// Expected identifier type.
|
|
kind: IdentKind,
|
|
/// Name of package where the extern was defined.
|
|
pkg_name: Option<SymbolId>,
|
|
},
|
|
}
|
|
|
|
impl std::fmt::Display for UnresolvedError {
|
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
match self {
|
|
UnresolvedError::Missing { name } => {
|
|
write!(fmt, "missing expected identifier `{}`", name,)
|
|
}
|
|
|
|
UnresolvedError::Extern {
|
|
name,
|
|
kind,
|
|
pkg_name,
|
|
} => write!(
|
|
fmt,
|
|
"unresolved extern `{}` of type `{}`, declared in `{}`",
|
|
name,
|
|
kind,
|
|
pkg_name.map(|s| s.lookup_str()).unwrap_or(&"<unknown>"),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for UnresolvedError {
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Compiled fragment for identifier.
|
|
///
|
|
/// This represents the text associated with an identifier.
|
|
pub type FragmentText = SymbolId;
|
|
|
|
/// 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<SymbolId>,
|
|
|
|
/// Relative path to the source of this object,
|
|
/// if not present in the current package.
|
|
pub src: Option<SymbolId>,
|
|
|
|
/// 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<SymbolId>,
|
|
|
|
/// Child identifier associated with this identifier.
|
|
///
|
|
/// For [`IdentKind::Class`],
|
|
/// this represents an associated [`IdentKind::Cgen`].
|
|
pub yields: Option<SymbolId>,
|
|
|
|
/// User-friendly identifier description.
|
|
///
|
|
/// This is used primarily by [`IdentKind::Class`] and
|
|
/// [`IdentKind::Gen`].
|
|
pub desc: Option<SymbolId>,
|
|
|
|
/// 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<SymbolId>,
|
|
|
|
/// 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,
|
|
}
|
|
|
|
impl From<SymAttrs> for Source {
|
|
/// Raise Legacy IR [`SymAttrs`].
|
|
///
|
|
/// This simply extracts a subset of fields from the source attributes.
|
|
fn from(attrs: SymAttrs) -> Self {
|
|
Source {
|
|
pkg_name: attrs.pkg_name,
|
|
src: attrs.src,
|
|
generated: attrs.generated,
|
|
parent: attrs.parent,
|
|
yields: attrs.yields,
|
|
desc: attrs.desc,
|
|
from: attrs.from,
|
|
virtual_: attrs.virtual_,
|
|
override_: attrs.override_,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::super::ident::Dim;
|
|
use super::*;
|
|
use crate::sym::{GlobalSymbolIntern, SymbolId};
|
|
|
|
mod ident_object_data {
|
|
use super::*;
|
|
|
|
// Note that IdentObject has no variants capable of None
|
|
#[test]
|
|
fn ident_object_name() {
|
|
let sym: SymbolId = "sym".intern();
|
|
|
|
assert_eq!(
|
|
Some(sym),
|
|
IdentObjectData::name(&IdentObject::Missing(sym))
|
|
);
|
|
|
|
assert_eq!(
|
|
sym,
|
|
IdentObject::Ident(sym, IdentKind::Meta, Source::default())
|
|
.name()
|
|
);
|
|
|
|
assert_eq!(
|
|
sym,
|
|
IdentObject::Extern(sym, IdentKind::Meta, Source::default())
|
|
.name()
|
|
);
|
|
|
|
assert_eq!(
|
|
sym,
|
|
IdentObject::IdentFragment(
|
|
sym,
|
|
IdentKind::Meta,
|
|
Source::default(),
|
|
"".intern()
|
|
)
|
|
.name()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ident_object_kind() {
|
|
let sym: SymbolId = "sym".intern();
|
|
let kind = IdentKind::Class(Dim::from_u8(5));
|
|
|
|
assert_eq!(None, IdentObject::Missing(sym).kind());
|
|
|
|
assert_eq!(
|
|
Some(&kind),
|
|
IdentObject::Ident(sym, kind.clone(), Source::default()).kind()
|
|
);
|
|
|
|
assert_eq!(
|
|
Some(&kind),
|
|
IdentObject::Extern(sym, kind.clone(), Source::default())
|
|
.kind()
|
|
);
|
|
|
|
assert_eq!(
|
|
Some(&kind),
|
|
IdentObject::IdentFragment(
|
|
sym,
|
|
kind.clone(),
|
|
Source::default(),
|
|
"".intern()
|
|
)
|
|
.kind()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ident_object_src() {
|
|
let sym: SymbolId = "sym".intern();
|
|
let src = Source {
|
|
desc: Some("test source".into()),
|
|
..Default::default()
|
|
};
|
|
|
|
assert_eq!(None, IdentObject::Missing(sym).src());
|
|
|
|
assert_eq!(
|
|
Some(&src),
|
|
IdentObject::Ident(sym, IdentKind::Meta, src.clone()).src()
|
|
);
|
|
|
|
assert_eq!(
|
|
None,
|
|
IdentObject::Extern(sym, IdentKind::Meta, src.clone()).src()
|
|
);
|
|
|
|
assert_eq!(
|
|
Some(&src),
|
|
IdentObject::IdentFragment(
|
|
sym,
|
|
IdentKind::Meta,
|
|
src.clone(),
|
|
"".intern()
|
|
)
|
|
.src()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ident_object_fragment() {
|
|
let sym: SymbolId = "sym".intern();
|
|
let text = "foo".into();
|
|
|
|
assert_eq!(None, IdentObject::Missing(sym).fragment());
|
|
|
|
assert_eq!(
|
|
None,
|
|
IdentObject::Ident(sym, IdentKind::Meta, Source::default())
|
|
.fragment()
|
|
);
|
|
|
|
assert_eq!(
|
|
None,
|
|
IdentObject::Extern(sym, IdentKind::Meta, Source::default())
|
|
.fragment()
|
|
);
|
|
|
|
assert_eq!(
|
|
Some(text),
|
|
IdentObject::IdentFragment(
|
|
sym,
|
|
IdentKind::Meta,
|
|
Source::default(),
|
|
text,
|
|
)
|
|
.fragment()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ident_object_as_ident() {
|
|
let sym: SymbolId = "sym".intern();
|
|
let ident = IdentObject::Missing(sym);
|
|
|
|
// Since we _are_ an IdentObject, we should return a reference
|
|
// to ourselves. We want this, not a clone.
|
|
assert!(std::ptr::eq(
|
|
&ident as *const _,
|
|
ident.as_ident().unwrap() as *const _,
|
|
));
|
|
}
|
|
}
|
|
|
|
mod ident_object_state {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn ident_object_missing() {
|
|
let sym: SymbolId = "missing".intern();
|
|
assert_eq!(IdentObject::Missing(sym), IdentObject::declare(sym));
|
|
}
|
|
|
|
#[test]
|
|
fn resolved_on_missing() {
|
|
let sym: SymbolId = "missing".intern();
|
|
|
|
let result = IdentObject::declare(sym)
|
|
.resolved()
|
|
.expect_err("expected error asserting resolved() on missing");
|
|
|
|
match result {
|
|
UnresolvedError::Missing { name: e_name } => {
|
|
assert_eq!(sym, e_name);
|
|
}
|
|
_ => panic!("expected UnresolvedError {:?}", result),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ident_object_ident() {
|
|
let sym: SymbolId = "ident".intern();
|
|
let kind = IdentKind::Meta;
|
|
let src = Source {
|
|
desc: Some("ident ctor".into()),
|
|
..Default::default()
|
|
};
|
|
|
|
assert_eq!(
|
|
IdentObject::Ident(sym, kind.clone(), src.clone()),
|
|
IdentObject::declare(sym)
|
|
.resolve(kind.clone(), src.clone())
|
|
.unwrap(),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn resolved_on_ident() {
|
|
let sym: SymbolId = "ident resolve".intern();
|
|
let kind = IdentKind::Meta;
|
|
let src = Source {
|
|
desc: Some("ident ctor".into()),
|
|
..Default::default()
|
|
};
|
|
|
|
assert_eq!(
|
|
&IdentObject::Ident(sym, kind.clone(), src.clone()),
|
|
IdentObject::declare(sym)
|
|
.resolve(kind.clone(), src.clone())
|
|
.unwrap()
|
|
.resolved()
|
|
.unwrap(),
|
|
);
|
|
}
|
|
|
|
// Note that we don't care about similar sources. It's expected
|
|
// that the system populating the ASG will only resolve local
|
|
// symbols, and so redeclarations should represent that multiple
|
|
// packages have the same local symbol.
|
|
#[test]
|
|
fn ident_object_redeclare_same_src() {
|
|
let sym: SymbolId = "redecl".intern();
|
|
let kind = IdentKind::Meta;
|
|
let src = Source::default();
|
|
|
|
let first = IdentObject::declare(sym)
|
|
.resolve(kind.clone(), src.clone())
|
|
.unwrap();
|
|
|
|
// Resolve twice, as if we encountered two local symbols.
|
|
let result = first
|
|
.clone()
|
|
.resolve(kind.clone(), src.clone())
|
|
.expect_err("expected error redeclaring identifier");
|
|
|
|
match result {
|
|
(orig, TransitionError::Redeclare { name }) => {
|
|
assert_eq!(first, orig);
|
|
assert_eq!(sym, name);
|
|
}
|
|
_ => {
|
|
panic!("expected TransitionError::Redeclare: {:?}", result)
|
|
}
|
|
}
|
|
}
|
|
|
|
mod extern_ {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn ident_object() {
|
|
let sym: SymbolId = "extern".intern();
|
|
let kind = IdentKind::Class(Dim::from_u8(1));
|
|
let src = Source {
|
|
desc: Some("extern".into()),
|
|
..Default::default()
|
|
};
|
|
|
|
assert_eq!(
|
|
Ok(IdentObject::Extern(sym, kind.clone(), src.clone())),
|
|
IdentObject::declare(sym).extern_(kind, src),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn resolved_on_extern() {
|
|
let sym: SymbolId = "extern resolved".intern();
|
|
let kind = IdentKind::Class(Dim::from_u8(1));
|
|
let pkg_name: SymbolId = "pkg/name".intern();
|
|
let src = Source {
|
|
pkg_name: Some(pkg_name),
|
|
desc: Some("extern".into()),
|
|
..Default::default()
|
|
};
|
|
|
|
let result =
|
|
IdentObject::Extern(sym, kind.clone(), src.clone())
|
|
.resolved()
|
|
.expect_err(
|
|
"expected error asserting resolved() on extern",
|
|
);
|
|
|
|
match result {
|
|
UnresolvedError::Extern {
|
|
name: e_name,
|
|
kind: e_kind,
|
|
pkg_name: e_pkg_name,
|
|
} => {
|
|
assert_eq!(sym, e_name);
|
|
assert_eq!(kind, e_kind);
|
|
assert_eq!(Some(pkg_name), e_pkg_name);
|
|
}
|
|
_ => panic!("expected UnresolvedError: {:?}", result),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn resolved_on_extern_error_fmt_without_pkg() {
|
|
let meta = IdentKind::Meta;
|
|
let err = UnresolvedError::Extern {
|
|
name: "foo".into(),
|
|
kind: IdentKind::Meta,
|
|
pkg_name: None,
|
|
};
|
|
|
|
let msg = format!("{}", err);
|
|
|
|
assert!(msg.contains("`foo`"));
|
|
assert!(msg.contains("in `<unknown>`"));
|
|
assert!(msg.contains(&format!("`{}`", meta)));
|
|
}
|
|
|
|
#[test]
|
|
fn resolved_on_extern_error_fmt_with_pkg() {
|
|
let meta = IdentKind::Meta;
|
|
let pkg = "pkg".into();
|
|
|
|
let err = UnresolvedError::Extern {
|
|
name: "foo".into(),
|
|
kind: IdentKind::Meta,
|
|
pkg_name: Some(pkg),
|
|
};
|
|
|
|
let msg = format!("{}", err);
|
|
|
|
assert!(msg.contains("`foo`"));
|
|
assert!(msg.contains(&format!("in `{}`", pkg)));
|
|
assert!(msg.contains(&format!("`{}`", meta)));
|
|
}
|
|
|
|
// Extern first, then identifier
|
|
#[test]
|
|
fn redeclare_compatible_resolves() {
|
|
let sym: SymbolId = "extern_re_pre".intern();
|
|
let kind = IdentKind::Class(Dim::from_u8(2));
|
|
let src = Source {
|
|
desc: Some("okay".into()),
|
|
..Default::default()
|
|
};
|
|
|
|
// Compatible kind, should resolve.
|
|
let result = IdentObject::declare(sym)
|
|
.extern_(kind.clone(), Source::default())
|
|
.and_then(|o| o.resolve(kind.clone(), src.clone()));
|
|
|
|
assert_eq!(Ok(IdentObject::Ident(sym, kind, src)), result,);
|
|
}
|
|
|
|
// Identifier first, then extern
|
|
#[test]
|
|
fn redeclare_compatible_resolves_post() {
|
|
let sym: SymbolId = "extern_re_post".intern();
|
|
let kind = IdentKind::Class(Dim::from_u8(1));
|
|
let src = Source {
|
|
desc: Some("okay".into()),
|
|
..Default::default()
|
|
};
|
|
|
|
// Compatible kind, should resolve.
|
|
let result = IdentObject::declare(sym)
|
|
.resolve(kind.clone(), src.clone())
|
|
.and_then(|o| o.extern_(kind.clone(), Source::default()));
|
|
|
|
assert_eq!(Ok(IdentObject::Ident(sym, kind, src)), result,);
|
|
}
|
|
|
|
#[test]
|
|
fn redeclare_another_extern() {
|
|
let sym: SymbolId = "extern_extern".intern();
|
|
let kind = IdentKind::Class(Dim::from_u8(0));
|
|
let src_first = Source {
|
|
desc: Some("first src".into()),
|
|
..Default::default()
|
|
};
|
|
let src_second = Source {
|
|
desc: Some("second src".into()),
|
|
..Default::default()
|
|
};
|
|
|
|
let result = IdentObject::declare(sym)
|
|
.extern_(kind.clone(), src_first.clone())
|
|
.and_then(|o| o.extern_(kind.clone(), src_second));
|
|
|
|
// Note that, if it resolves, it should keep what is
|
|
// _existing_, meaning that it must keep the first src.
|
|
assert_eq!(
|
|
Ok(IdentObject::Extern(sym, kind, src_first)),
|
|
result
|
|
);
|
|
}
|
|
|
|
// Extern first, then identifier
|
|
#[test]
|
|
fn redeclare_post_incompatible_kind() {
|
|
let sym: SymbolId = "extern_re_bad_post".intern();
|
|
let kind = IdentKind::Class(Dim::from_u8(2));
|
|
let src = Source {
|
|
desc: Some("bad kind".into()),
|
|
..Default::default()
|
|
};
|
|
|
|
let orig = IdentObject::declare(sym)
|
|
.extern_(kind.clone(), Source::default())
|
|
.unwrap();
|
|
|
|
// Incompatible kind
|
|
let kind_bad = IdentKind::Meta;
|
|
let result = orig.clone().resolve(kind_bad.clone(), src);
|
|
|
|
match result {
|
|
Err((given_orig, err @ _)) => {
|
|
assert_eq!(orig, given_orig);
|
|
|
|
if let TransitionError::ExternResolution {
|
|
name: e_name,
|
|
expected: e_expected,
|
|
given: e_given,
|
|
} = err.clone()
|
|
{
|
|
assert_eq!(sym, e_name);
|
|
assert_eq!(kind, e_expected);
|
|
assert_eq!(kind_bad, e_given);
|
|
}
|
|
|
|
// Formatted error
|
|
let msg = format!("{}", err);
|
|
|
|
assert!(msg.contains(&format!("{}", sym)));
|
|
assert!(msg.contains(&format!("{}", kind)));
|
|
assert!(msg.contains(&format!("{}", kind_bad)));
|
|
}
|
|
_ => panic!("expected failure: {:?}", result),
|
|
}
|
|
}
|
|
|
|
// Identifier first, then extern
|
|
#[test]
|
|
fn redeclare_pre_incompatible_kind() {
|
|
let sym: SymbolId = "extern_re_bad_pre".intern();
|
|
let kind_given = IdentKind::Class(Dim::from_u8(1));
|
|
let src = Source {
|
|
desc: Some("bad kind".into()),
|
|
..Default::default()
|
|
};
|
|
|
|
let orig = IdentObject::declare(sym)
|
|
.resolve(kind_given.clone(), src.clone())
|
|
.unwrap();
|
|
|
|
// Extern with incompatible kind.
|
|
let kind_extern = IdentKind::Meta;
|
|
let result = orig
|
|
.clone()
|
|
.extern_(kind_extern.clone(), Source::default());
|
|
|
|
match result {
|
|
Err((given_orig, err @ _)) => {
|
|
assert_eq!(orig, given_orig);
|
|
|
|
if let TransitionError::ExternResolution {
|
|
name: e_name,
|
|
expected: e_expected,
|
|
given: e_given,
|
|
} = err.clone()
|
|
{
|
|
assert_eq!(sym, e_name);
|
|
assert_eq!(kind_extern, e_expected);
|
|
assert_eq!(kind_given, e_given);
|
|
}
|
|
|
|
// Formatted error
|
|
let msg = format!("{}", err);
|
|
|
|
assert!(msg.contains(&format!("{}", sym)));
|
|
assert!(msg.contains(&format!("{}", kind_extern)));
|
|
assert!(msg.contains(&format!("{}", kind_given)));
|
|
}
|
|
_ => panic!("expected failure: {:?}", result),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn add_fragment_to_ident() {
|
|
let sym: SymbolId = "tofrag".intern();
|
|
let src = Source {
|
|
generated: true,
|
|
..Default::default()
|
|
};
|
|
|
|
let kind = IdentKind::Meta;
|
|
let ident = IdentObject::declare(sym)
|
|
.resolve(kind.clone(), src.clone())
|
|
.unwrap();
|
|
let text = FragmentText::from("a fragment");
|
|
let ident_with_frag = ident.set_fragment(text.clone());
|
|
|
|
assert_eq!(
|
|
Ok(IdentObject::IdentFragment(sym, kind, src, text)),
|
|
ident_with_frag,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn resolved_on_fragment() {
|
|
let sym: SymbolId = "tofrag resolved".intern();
|
|
let src = Source {
|
|
generated: true,
|
|
..Default::default()
|
|
};
|
|
|
|
let kind = IdentKind::Meta;
|
|
let ident = IdentObject::declare(sym)
|
|
.resolve(kind.clone(), src.clone())
|
|
.unwrap();
|
|
let text = FragmentText::from("a fragment for resolved()");
|
|
let ident_with_frag = ident.set_fragment(text.clone());
|
|
|
|
assert_eq!(
|
|
Ok(&IdentObject::IdentFragment(sym, kind, src, text)),
|
|
ident_with_frag.unwrap().resolved(),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn add_fragment_to_fragment_fails() {
|
|
let sym: SymbolId = "badsym".intern();
|
|
let ident = IdentObject::declare(sym)
|
|
.resolve(IdentKind::Meta, Source::default())
|
|
.unwrap();
|
|
|
|
let ident_with_frag = ident
|
|
.set_fragment("orig fragment".into())
|
|
.expect("set_fragment failed");
|
|
|
|
// Since it's already a fragment, this should fail.
|
|
let err = ident_with_frag
|
|
.clone()
|
|
.set_fragment("replacement".intern())
|
|
.expect_err("Expected failure");
|
|
|
|
match err {
|
|
(orig, TransitionError::BadFragmentDest { .. }) => {
|
|
assert_eq!(ident_with_frag, orig);
|
|
}
|
|
_ => panic!(
|
|
"expected TransitionError::BadFragmentDest: {:?}",
|
|
err
|
|
),
|
|
}
|
|
}
|
|
|
|
mod override_ {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn declare_virtual_ident_first() {
|
|
let sym: SymbolId = "virtual".intern();
|
|
let over_src = "src".intern();
|
|
let kind = IdentKind::Meta;
|
|
|
|
let virt = IdentObject::declare(sym)
|
|
.resolve(
|
|
kind.clone(),
|
|
Source {
|
|
virtual_: true,
|
|
..Default::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
|
|
let over_src = Source {
|
|
virtual_: true, // this needn't be set, but see below
|
|
override_: true,
|
|
src: Some(over_src),
|
|
..Default::default()
|
|
};
|
|
|
|
let result = virt.resolve(kind.clone(), over_src.clone());
|
|
|
|
// Overriding should clear any virtual flag that may have
|
|
// been set to prevent override-overrides.
|
|
let expected_src = Source {
|
|
virtual_: false,
|
|
..over_src
|
|
};
|
|
|
|
assert_eq!(
|
|
Ok(IdentObject::Ident(sym, kind, expected_src)),
|
|
result
|
|
);
|
|
}
|
|
|
|
// Override is encountered before the virtual
|
|
#[test]
|
|
fn declare_virtual_ident_after_override() {
|
|
let sym: SymbolId = "virtual_second".intern();
|
|
let virt_src = "virt_src".intern();
|
|
let kind = IdentKind::Meta;
|
|
|
|
let over_src = Source {
|
|
virtual_: true, // this needn't be set, but see below
|
|
override_: true,
|
|
..Default::default()
|
|
};
|
|
|
|
let over = IdentObject::declare(sym)
|
|
.resolve(kind.clone(), over_src.clone())
|
|
.unwrap();
|
|
|
|
let virt_src = Source {
|
|
virtual_: true,
|
|
src: Some(virt_src),
|
|
..Default::default()
|
|
};
|
|
|
|
let result = over.resolve(kind.clone(), virt_src.clone());
|
|
|
|
// Overriding should clear any virtual flag that may have
|
|
// been set to prevent override-overrides. We should also
|
|
// take the override source even though virtual was second.
|
|
let expected_src = Source {
|
|
virtual_: false,
|
|
..over_src
|
|
};
|
|
|
|
assert_eq!(
|
|
Ok(IdentObject::Ident(sym, kind, expected_src)),
|
|
result
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn declare_override_non_virtual() {
|
|
let sym: SymbolId = "non_virtual".intern();
|
|
let kind = IdentKind::Meta;
|
|
|
|
let non_virt = IdentObject::declare(sym)
|
|
.resolve(
|
|
kind.clone(),
|
|
Source {
|
|
virtual_: false,
|
|
..Default::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
|
|
let over_src = Source {
|
|
override_: true,
|
|
..Default::default()
|
|
};
|
|
|
|
// This isn't the purpose of the test, but we want to make
|
|
// sure that the non-virtual override error occurs before
|
|
// the kind error.
|
|
let bad_kind = IdentKind::Cgen(Dim::from_u8(1));
|
|
|
|
let result = non_virt
|
|
.clone()
|
|
.resolve(bad_kind, over_src.clone())
|
|
.expect_err("expected error");
|
|
|
|
match result {
|
|
(
|
|
ref orig,
|
|
TransitionError::NonVirtualOverride { ref name },
|
|
) => {
|
|
assert_eq!(orig, &non_virt);
|
|
assert_eq!(sym, *name);
|
|
|
|
// Formatted error
|
|
let (_, err) = result;
|
|
let msg = format!("{}", err);
|
|
|
|
assert!(msg.contains(&format!("{}", sym)));
|
|
}
|
|
(_, TransitionError::VirtualOverrideKind { .. }) => {
|
|
panic!("kind check must happen _after_ virtual check")
|
|
}
|
|
_ => panic!(
|
|
"expected TransitionError::VirtualOverrideKind {:?}",
|
|
result
|
|
),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn declare_virtual_ident_incompatible_kind() {
|
|
let sym: SymbolId = "virtual".intern();
|
|
let src_sym: SymbolId = "src".intern();
|
|
let kind = IdentKind::Meta;
|
|
|
|
let virt = IdentObject::declare(sym)
|
|
.resolve(
|
|
kind.clone(),
|
|
Source {
|
|
virtual_: true,
|
|
..Default::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
|
|
let over_src = Source {
|
|
override_: true,
|
|
src: Some(src_sym),
|
|
..Default::default()
|
|
};
|
|
|
|
let bad_kind = IdentKind::Cgen(Dim::from_u8(1));
|
|
let result = virt
|
|
.clone()
|
|
.resolve(bad_kind.clone(), over_src.clone())
|
|
.expect_err("expected error");
|
|
|
|
match result {
|
|
(
|
|
ref orig,
|
|
TransitionError::VirtualOverrideKind {
|
|
ref name,
|
|
ref existing,
|
|
ref given,
|
|
},
|
|
) => {
|
|
assert_eq!(orig, &virt);
|
|
|
|
assert_eq!(sym, *name);
|
|
assert_eq!(&kind, existing);
|
|
assert_eq!(&bad_kind, given);
|
|
|
|
// Formatted error
|
|
let (_, err) = result;
|
|
let msg = format!("{}", err);
|
|
|
|
assert!(msg.contains(&format!("{}", sym)));
|
|
assert!(msg.contains(&format!("{}", kind)));
|
|
assert!(msg.contains(&format!("{}", bad_kind)));
|
|
}
|
|
_ => panic!(
|
|
"expected TransitionError::VirtualOverrideKind {:?}",
|
|
result
|
|
),
|
|
}
|
|
}
|
|
|
|
// Encounter virtual first and override second should cause the
|
|
// fragment to be cleared to make way for the new fragment.
|
|
#[test]
|
|
fn declare_override_virtual_ident_fragment_virtual_first() {
|
|
let sym: SymbolId = "virtual".intern();
|
|
let over_src = "src".intern();
|
|
let kind = IdentKind::Meta;
|
|
|
|
// Remember: override is going to come first...
|
|
let over_src = Source {
|
|
override_: true,
|
|
src: Some(over_src),
|
|
..Default::default()
|
|
};
|
|
|
|
// ...and virt second.
|
|
let virt_src = Source {
|
|
virtual_: true,
|
|
..Default::default()
|
|
};
|
|
|
|
let over = IdentObject::declare(sym)
|
|
.resolve(kind.clone(), over_src.clone())
|
|
.unwrap();
|
|
|
|
// So we should _keep_ this fragment, since it represent the
|
|
// override, even though it's appearing first.
|
|
let text = FragmentText::from("keep me");
|
|
let over_frag = over.set_fragment(text.clone());
|
|
|
|
assert_eq!(
|
|
Ok(IdentObject::IdentFragment(
|
|
sym,
|
|
kind.clone(),
|
|
over_src.clone(),
|
|
text.clone(),
|
|
)),
|
|
over_frag,
|
|
);
|
|
|
|
let result =
|
|
over_frag.unwrap().resolve(kind.clone(), virt_src.clone());
|
|
|
|
// Overriding should _not_ have cleared the fragment since
|
|
// the override was encountered _first_, so we want to keep
|
|
// its fragment.
|
|
assert_eq!(
|
|
Ok(IdentObject::IdentFragment(
|
|
sym,
|
|
kind.clone(),
|
|
over_src.clone(),
|
|
text.clone()
|
|
)),
|
|
result
|
|
);
|
|
|
|
// Finally, after performing this transition, we will
|
|
// inevitably encounter the fragment for the virtual
|
|
// identifier, which we must ignore. So we must make sure
|
|
// that encountering it will not cause an error, because we
|
|
// still have an IdentFragment at this point.
|
|
assert_eq!(
|
|
Ok(IdentObject::IdentFragment(
|
|
sym,
|
|
kind,
|
|
over_src.clone(),
|
|
text.clone()
|
|
)),
|
|
result.unwrap().set_fragment("virt fragment".into()),
|
|
);
|
|
}
|
|
|
|
// Encountering _override_ first and virtual second should _not_
|
|
// clear the fragment, otherwise the virtual fragment will take
|
|
// precedence over the override.
|
|
#[test]
|
|
fn declare_override_virtual_ident_fragment_override_first() {
|
|
let sym: SymbolId = "virtual".intern();
|
|
let over_src = "src".intern();
|
|
let kind = IdentKind::Meta;
|
|
|
|
let virt_src = Source {
|
|
virtual_: true,
|
|
..Default::default()
|
|
};
|
|
|
|
let virt = IdentObject::declare(sym)
|
|
.resolve(kind.clone(), virt_src.clone())
|
|
.unwrap();
|
|
let text = FragmentText::from("remove me");
|
|
let virt_frag = virt.set_fragment(text.clone());
|
|
|
|
assert_eq!(
|
|
Ok(IdentObject::IdentFragment(
|
|
sym,
|
|
kind.clone(),
|
|
virt_src,
|
|
text
|
|
)),
|
|
virt_frag,
|
|
);
|
|
|
|
let over_src = Source {
|
|
override_: true,
|
|
src: Some(over_src),
|
|
..Default::default()
|
|
};
|
|
|
|
let result =
|
|
virt_frag.unwrap().resolve(kind.clone(), over_src.clone());
|
|
|
|
// The act of overriding the object should have cleared any
|
|
// existing fragment, making way for a new fragment to take its
|
|
// place as soon as it is discovered. (So, back to an
|
|
// IdentObject::Ident.)
|
|
assert_eq!(Ok(IdentObject::Ident(sym, kind, over_src)), result);
|
|
}
|
|
|
|
#[test]
|
|
fn declare_override_virtual_ident_fragment_incompatible_type() {
|
|
let sym: SymbolId = "virtual".intern();
|
|
let over_src = "src".intern();
|
|
let kind = IdentKind::Meta;
|
|
|
|
let virt_src = Source {
|
|
virtual_: true,
|
|
..Default::default()
|
|
};
|
|
|
|
let virt = IdentObject::declare(sym)
|
|
.resolve(kind.clone(), virt_src.clone())
|
|
.unwrap();
|
|
let virt_frag = virt.set_fragment("".into()).unwrap();
|
|
|
|
let over_src = Source {
|
|
override_: true,
|
|
src: Some(over_src),
|
|
..Default::default()
|
|
};
|
|
|
|
let bad_kind = IdentKind::Cgen(Dim::from_u8(1));
|
|
let result = virt_frag
|
|
.clone()
|
|
.resolve(bad_kind.clone(), over_src.clone())
|
|
.expect_err("expected error");
|
|
|
|
match result {
|
|
(
|
|
ref orig,
|
|
TransitionError::VirtualOverrideKind {
|
|
ref name,
|
|
ref existing,
|
|
ref given,
|
|
},
|
|
) => {
|
|
assert_eq!(orig, &virt_frag);
|
|
|
|
assert_eq!(sym, *name);
|
|
assert_eq!(&kind, existing);
|
|
assert_eq!(&bad_kind, given);
|
|
|
|
// Formatted error
|
|
let (_, err) = result;
|
|
let msg = format!("{}", err);
|
|
|
|
assert!(msg.contains(&format!("{}", sym)));
|
|
assert!(msg.contains(&format!("{}", kind)));
|
|
assert!(msg.contains(&format!("{}", bad_kind)));
|
|
}
|
|
_ => panic!(
|
|
"expected TransitionError::VirtualOverrideKind {:?}",
|
|
result
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn add_ident_kind_ignores(given: IdentKind, expected: IdentKind) {
|
|
let sym: SymbolId = "tofrag".intern();
|
|
let src = Source {
|
|
generated: true,
|
|
..Default::default()
|
|
};
|
|
|
|
let obj = IdentObject::declare(sym)
|
|
.resolve(given, src.clone())
|
|
.unwrap();
|
|
|
|
let fragment = "a fragment".intern();
|
|
let obj_with_frag = obj.set_fragment(fragment);
|
|
|
|
assert_eq!(
|
|
Ok(IdentObject::IdentFragment(sym, expected, src, fragment)),
|
|
obj_with_frag,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn add_fragment_to_ident_map_head() {
|
|
add_ident_kind_ignores(IdentKind::MapHead, IdentKind::MapHead)
|
|
}
|
|
|
|
#[test]
|
|
fn add_fragment_to_ident_map_tail() {
|
|
add_ident_kind_ignores(IdentKind::MapTail, IdentKind::MapTail)
|
|
}
|
|
|
|
#[test]
|
|
fn add_fragment_to_ident_retmap_head() {
|
|
add_ident_kind_ignores(IdentKind::RetMapHead, IdentKind::RetMapHead)
|
|
}
|
|
|
|
#[test]
|
|
fn add_fragment_to_ident_retmap_tail() {
|
|
add_ident_kind_ignores(IdentKind::RetMapTail, IdentKind::RetMapTail)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn source_from_sym_attrs() {
|
|
let nsym: SymbolId = "name".intern();
|
|
let ssym: SymbolId = "src".intern();
|
|
let psym: SymbolId = "parent".intern();
|
|
let ysym: SymbolId = "yields".intern();
|
|
let fsym: SymbolId = "from".intern();
|
|
|
|
let attrs = SymAttrs {
|
|
pkg_name: Some(nsym),
|
|
src: Some(ssym),
|
|
generated: true,
|
|
parent: Some(psym),
|
|
yields: Some(ysym),
|
|
desc: Some("sym desc".into()),
|
|
from: Some(fsym),
|
|
virtual_: true,
|
|
override_: true,
|
|
..Default::default()
|
|
};
|
|
|
|
assert_eq!(
|
|
Source {
|
|
pkg_name: Some(nsym),
|
|
src: Some(ssym),
|
|
generated: attrs.generated,
|
|
parent: attrs.parent,
|
|
yields: attrs.yields,
|
|
desc: Some("sym desc".into()),
|
|
from: Some(fsym),
|
|
virtual_: true,
|
|
override_: true,
|
|
},
|
|
attrs.into(),
|
|
);
|
|
}
|
|
}
|