Proper extern resolution

This properly checks identifier types when resolving externs. It also
includes a bit of refactoring. Note that some of that refactoring was
already merged into master.

The old linker was missing some things, so there are template changes in
here as well.

An example of an error currently:

  error: extern `__retry` of type `cgen[boolean; 1]` is incompatible with
  type `cgen[boolean; 0]`
master
Mike Gerwitz 2020-03-26 09:24:02 -04:00
commit d39ec84399
11 changed files with 564 additions and 205 deletions

View File

@ -46,7 +46,7 @@
<text>1</text>
</param>
<extern name=":class:{@as@}" type="class" dim="0"
<extern name=":class:{@as@}" type="class" dim="@dim@"
yields="@yields@" />
<if name="@yields@">

View File

@ -9,7 +9,7 @@
<import package="extern" />
<import package="vector/cmatch" export="true" />
<t:classify-extern yields="__retry" />
<t:classify-extern yields="__retry" dim="0" />
<template name="_suggest-retry-when_" desc="Retry Rating">
<param name="@values@" desc="Rule matches" />

View File

@ -137,8 +137,14 @@
<param-value name="@on@" rmunderscore="true" lower="true" />
</param>
<!-- TODO: this default is transitionary, since there's no
meaningful default -->
<param name="@vis-dim@" desc="Classification dimensions (default 2)">
<text>2</text>
</param>
<extern name="ui_q_{@on@}" type="param" dtype="integer" dim="1" />
<extern name="@__yields@" type="cgen" dtype="boolean" dim="1" />
<extern name="@__yields@" type="cgen" dtype="boolean" dim="@vis-dim@" />
<all>
<match on="@__yields@" />

View File

@ -45,7 +45,15 @@ pub fn main() -> Result<(), Box<dyn Error>> {
let usage = opts.usage(&format!("Usage: {} -o OUTPUT FILE", program));
match parse_options(opts, args) {
Ok(Command::Link(input, output)) => poc::main(&input, &output),
Ok(Command::Link(input, output)) => match poc::main(&input, &output) {
Err(e) => {
eprintln!("error: {}", e);
eprintln!("fatal: failed to link `{}`", output);
std::process::exit(1);
}
ok => ok,
},
Ok(Command::Usage) => {
println!("{}", usage);
std::process::exit(exitcode::OK);

View File

@ -23,7 +23,9 @@ use super::graph::{
Asg, AsgEdge, AsgError, AsgResult, Node, ObjectRef, SortableAsg,
};
use super::ident::IdentKind;
use super::object::{FragmentText, IdentObjectData, IdentObjectState, Source};
use super::object::{
FragmentText, IdentObjectData, IdentObjectState, Source, TransitionResult,
};
use super::Sections;
use crate::sym::Symbol;
use fixedbitset::FixedBitSet;
@ -128,17 +130,51 @@ where
/// Lookup `ident` or add a missing identifier to the graph and return a
/// reference to it.
///
/// See [`IdentObjectState::missing`] for more information.
#[inline]
/// See [`IdentObjectState::declare`] for more information.
fn lookup_or_missing(&mut self, ident: &'i Symbol<'i>) -> ObjectRef<Ix> {
self.lookup(ident).unwrap_or_else(|| {
let index = self.graph.add_node(Some(O::missing(ident)));
let index = self.graph.add_node(Some(O::declare(ident)));
self.index_identifier(ident, index);
ObjectRef(index)
})
}
/// Perform a state transition on an identifier.
///
/// Look up `ident` or add a missing identifier if it does not yet exist
/// (see `lookup_or_missing`).
/// Then invoke `f` with the located identifier and replace the
/// identifier on the graph with the result.
///
/// This will safely restore graph state to the original identifier
/// value on transition failure.
fn with_ident<F>(
&mut self,
name: &'i Symbol<'i>,
f: F,
) -> AsgResult<ObjectRef<Ix>, Ix>
where
F: FnOnce(O) -> TransitionResult<O>,
{
let identi = self.lookup_or_missing(name);
let node = self.graph.node_weight_mut(identi.0).unwrap();
let obj = node
.take()
.expect(&format!("internal error: missing object for {}", name));
f(obj)
.and_then(|obj| {
node.replace(obj);
Ok(identi)
})
.or_else(|(orig, err)| {
node.replace(orig);
Err(err.into())
})
}
/// Check graph for cycles
///
/// We want to catch any cycles before we start using the graph.
@ -201,45 +237,16 @@ where
kind: IdentKind,
src: Source<'i>,
) -> AsgResult<ObjectRef<Ix>, Ix> {
if let Some(existing) = self.lookup(name) {
let node = self.graph.node_weight_mut(existing.0).unwrap();
let obj = node.take().expect(&format!(
"internal error: missing object for {}",
name
));
// TODO: test inconsistent state (fixed)
return obj
.redeclare(kind, src)
.and_then(|obj| {
node.replace(obj);
Ok(existing)
})
.or_else(|(orig, err)| {
node.replace(orig);
Err(err.into())
});
}
let node = self.graph.add_node(Some(O::ident(name, kind, src)));
self.index_identifier(name, node);
Ok(ObjectRef(node))
self.with_ident(name, |obj| obj.resolve(kind, src))
}
fn declare_extern(
&mut self,
name: &'i Symbol<'i>,
expected_kind: IdentKind,
kind: IdentKind,
src: Source<'i>,
) -> AsgResult<ObjectRef<Ix>, Ix> {
// TODO: resolution!
let node = self.graph.add_node(Some(O::extern_(name, expected_kind)));
self.index_identifier(name, node);
Ok(ObjectRef(node))
self.with_ident(name, |obj| obj.extern_(kind, src))
}
fn set_fragment(
@ -411,27 +418,21 @@ mod test {
#[derive(Debug, Default, PartialEq)]
struct StubIdentObject<'i> {
given_missing: Option<&'i Symbol<'i>>,
given_ident: Option<(&'i Symbol<'i>, IdentKind, Source<'i>)>,
given_extern: Option<(&'i Symbol<'i>, IdentKind)>,
given_redeclare: Option<(IdentKind, Source<'i>)>,
given_declare: Option<&'i Symbol<'i>>,
given_extern: Option<(IdentKind, Source<'i>)>,
given_resolve: Option<(IdentKind, Source<'i>)>,
given_set_fragment: Option<FragmentText>,
fail_redeclare: RefCell<Option<TransitionError>>,
fail_extern: RefCell<Option<TransitionError>>,
}
impl<'i> IdentObjectData<'i> for StubIdentObject<'i> {
fn name(&self) -> Option<&'i Symbol<'i>> {
self.given_missing
.or(self.given_ident.as_ref().map(|args| args.0))
.or(self.given_extern.as_ref().map(|args| args.0))
self.given_declare
}
fn kind(&self) -> Option<&IdentKind> {
self.given_ident
.as_ref()
.map(|args| &args.1)
.or(self.given_extern.as_ref().map(|args| &args.1))
.or(self.given_redeclare.as_ref().map(|args| &args.0))
self.given_resolve.as_ref().map(|args| &args.0)
}
fn src(&self) -> Option<&Source<'i>> {
@ -448,32 +449,14 @@ mod test {
}
impl<'i> IdentObjectState<'i, StubIdentObject<'i>> for StubIdentObject<'i> {
fn missing(ident: &'i Symbol<'i>) -> Self {
fn declare(ident: &'i Symbol<'i>) -> Self {
Self {
given_missing: Some(ident),
given_declare: Some(ident),
..Default::default()
}
}
fn ident(
name: &'i Symbol<'i>,
kind: IdentKind,
src: Source<'i>,
) -> Self {
Self {
given_ident: Some((name, kind, src)),
..Default::default()
}
}
fn extern_(name: &'i Symbol<'i>, kind: IdentKind) -> Self {
Self {
given_extern: Some((name, kind)),
..Default::default()
}
}
fn redeclare(
fn resolve(
mut self,
kind: IdentKind,
src: Source<'i>,
@ -483,7 +466,21 @@ mod test {
return Err((self, err));
}
self.given_redeclare = Some((kind, src));
self.given_resolve = Some((kind, src));
Ok(self)
}
fn extern_(
mut self,
kind: IdentKind,
src: Source<'i>,
) -> TransitionResult<StubIdentObject<'i>> {
if self.fail_extern.borrow().is_some() {
let err = self.fail_extern.replace(None).unwrap();
return Err((self, err));
}
self.given_extern = Some((kind, src));
Ok(self)
}
@ -545,28 +542,28 @@ mod test {
assert_ne!(nodea, nodeb);
assert_eq!(Some(&syma), sut.get(nodea).unwrap().given_declare);
assert_eq!(
Some((
&syma,
IdentKind::Meta,
Source {
desc: Some("a".to_string()),
..Default::default()
},
)),
sut.get(nodea).unwrap().given_ident
sut.get(nodea).unwrap().given_resolve
);
assert_eq!(Some(&symb), sut.get(nodeb).unwrap().given_declare);
assert_eq!(
Some((
&symb,
IdentKind::Worksheet,
Source {
desc: Some("b".to_string()),
..Default::default()
},
)),
sut.get(nodeb).unwrap().given_ident
sut.get(nodeb).unwrap().given_resolve
);
Ok(())
@ -591,21 +588,6 @@ mod test {
Ok(())
}
#[test]
fn declare_extern() -> AsgResult<(), u8> {
let mut sut = Sut::with_capacity(0, 0);
let sym = symbol_dummy!(1, "extern");
let node = sut.declare_extern(&sym, IdentKind::Meta)?;
assert_eq!(
Some((&sym, IdentKind::Meta)),
sut.get(node).unwrap().given_extern,
);
Ok(())
}
#[test]
fn declare_returns_existing() -> AsgResult<(), u8> {
let mut sut = Sut::with_capacity(0, 0);
@ -626,10 +608,7 @@ mod test {
// same node is referenced.
assert_eq!(node, redeclare);
assert_eq!(
Some((rekind, resrc)),
sut.get(node).unwrap().given_redeclare,
);
assert_eq!(Some((rekind, resrc)), sut.get(node).unwrap().given_resolve,);
Ok(())
}
@ -648,9 +627,8 @@ mod test {
// Set up an object to fail redeclaration.
let node = sut.declare(&sym, IdentKind::Meta, src.clone())?;
let obj = sut.get(node).unwrap();
let msg = String::from("test fail");
obj.fail_redeclare
.replace(Some(TransitionError::Incompatible(msg.clone())));
let terr = TransitionError::Incompatible(String::from("test fail"));
obj.fail_redeclare.replace(Some(terr.clone()));
// Should invoke StubIdentObject::redeclare on the above `obj`.
let result = sut.declare(&sym, IdentKind::Meta, Source::default());
@ -658,9 +636,71 @@ mod test {
if let Err(err) = result {
// The node should have been restored.
let obj = sut.get(node).unwrap();
assert_eq!(src, obj.given_ident.as_ref().unwrap().2);
assert_eq!(src, obj.given_resolve.as_ref().unwrap().1);
assert_eq!(AsgError::IncompatibleIdent(msg), err);
assert_eq!(AsgError::ObjectTransition(terr), err);
Ok(())
} else {
panic!("failure expected: {:?}", result);
}
}
#[test]
fn declare_extern_returns_existing() -> AsgResult<(), u8> {
let mut sut = Sut::with_capacity(0, 0);
let sym = symbol_dummy!(1, "symext");
let src = Source::default();
let node = sut.declare_extern(&sym, IdentKind::Meta, src.clone())?;
// Remember that our stub does not care about compatibility.
let rekind = IdentKind::Class(Dim::from_u8(3));
let resrc = Source {
desc: Some("redeclare".into()),
..Default::default()
};
let redeclare =
sut.declare_extern(&sym, rekind.clone(), resrc.clone())?;
// We don't care what the objects are for this test, just that the
// same node is referenced.
assert_eq!(node, redeclare);
assert_eq!(Some((rekind, resrc)), sut.get(node).unwrap().given_extern);
Ok(())
}
// Builds upon declare_returns_existing.
#[test]
fn declare_extern_fails_if_transition_fails() -> AsgResult<(), u8> {
let mut sut = Sut::with_capacity(0, 0);
let sym = symbol_dummy!(1, "symdup");
let src = Source {
desc: Some("orig".into()),
..Default::default()
};
// Set up an object to fail redeclaration.
let node = sut.declare_extern(&sym, IdentKind::Meta, src.clone())?;
let obj = sut.get(node).unwrap();
// It doesn't matter that this isn't the error that'll actually be
// returned, as long as it's some sort of TransitionError.
let terr = TransitionError::Incompatible(String::from("test fail"));
obj.fail_extern.replace(Some(terr.clone()));
// Should invoke StubIdentObject::extern_ on the above `obj`.
let result =
sut.declare_extern(&sym, IdentKind::Meta, Source::default());
if let Err(err) = result {
// The node should have been restored.
let obj = sut.get(node).unwrap();
assert_eq!(src, obj.given_extern.as_ref().unwrap().1);
assert_eq!(AsgError::ObjectTransition(terr), err);
Ok(())
} else {
@ -691,7 +731,8 @@ mod test {
let obj = sut.get(node).unwrap();
assert_eq!(Some((&sym, IdentKind::Meta, src,)), obj.given_ident);
assert_eq!(Some(&sym), obj.given_declare);
assert_eq!(Some((IdentKind::Meta, src)), obj.given_resolve);
assert_eq!(Some(fragment), obj.given_set_fragment);
Ok(())
@ -747,8 +788,8 @@ mod test {
let (symnode, depnode) = sut.add_dep_lookup(&sym, &dep);
assert!(sut.has_dep(symnode, depnode));
assert_eq!(Some(&sym), sut.get(symnode).unwrap().given_missing);
assert_eq!(Some(&dep), sut.get(depnode).unwrap().given_missing);
assert_eq!(Some(&sym), sut.get(symnode).unwrap().given_declare);
assert_eq!(Some(&dep), sut.get(depnode).unwrap().given_declare);
Ok(())
}
@ -775,8 +816,8 @@ mod test {
let obj = sut.get(declared).unwrap();
assert_eq!(Some(&sym), obj.given_missing);
assert_eq!(Some((IdentKind::Meta, src)), obj.given_redeclare);
assert_eq!(Some(&sym), obj.given_declare);
assert_eq!(Some((IdentKind::Meta, src)), obj.given_resolve);
Ok(())
}

View File

@ -62,9 +62,18 @@ where
/// then the operation will fail;
/// otherwise,
/// the existing identifier will be returned.
///
/// If a concrete identifier has already been declared (see
/// [`Asg::declare`]),
/// 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 [`IdentObjectState::redeclare`].
/// see [`IdentObjectState::resolve`].
///
/// A successful declaration will add an identifier to the graph
/// and return an [`ObjectRef`] reference.
@ -93,12 +102,13 @@ where
/// Resolution will otherwise fail in error.
///
/// See [`IdentObjectState::extern_`] and
/// [`IdentObjectState::redeclare`] for more information on
/// [`IdentObjectState::resolve`] for more information on
/// compatibility related to extern resolution.
fn declare_extern(
&mut self,
name: &'i Symbol<'i>,
expected_kind: IdentKind,
kind: IdentKind,
src: Source<'i>,
) -> AsgResult<ObjectRef<Ix>, Ix>;
/// Set the fragment associated with a concrete identifier.
@ -153,7 +163,7 @@ where
/// a missing identifier will be added as a placeholder,
/// allowing the ASG to be built with partial information as
/// identifiers continue to be discovered.
/// See [`IdentObjectState::missing`] for more information.
/// See [`IdentObjectState::declare`] for more information.
///
/// References to both identifiers are returned in argument order.
fn add_dep_lookup(
@ -227,18 +237,12 @@ pub type Node<O> = Option<O>;
/// The caller will know the problem values.
#[derive(Debug, PartialEq)]
pub enum AsgError<Ix: Debug> {
/// The provided identifier is not in a state that is permitted to
/// receive a fragment.
/// An object could not change state in the manner requested.
///
/// See [`Asg::set_fragment`] for more information.
BadFragmentDest(String),
/// An attempt to redeclare an identifier with additional information
/// has failed because the provided information was not compatible
/// with the original declaration.
///
/// See [`Asg::declare`] for more information.
IncompatibleIdent(String),
/// See [`Asg::declare`] and [`Asg::set_fragment`] for more
/// information.
/// See also [`TransitionError`].
ObjectTransition(TransitionError),
/// The node was not expected in the current context
UnexpectedNode(String),
@ -250,12 +254,7 @@ pub enum AsgError<Ix: Debug> {
impl<Ix: Debug> std::fmt::Display for AsgError<Ix> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::BadFragmentDest(msg) => {
write!(fmt, "bad fragment destination: {}", msg)
}
Self::IncompatibleIdent(msg) => {
write!(fmt, "identifier redeclaration failed: {}", msg)
}
Self::ObjectTransition(err) => std::fmt::Display::fmt(&err, fmt),
Self::UnexpectedNode(msg) => {
write!(fmt, "unexpected node: {}", msg)
}
@ -268,16 +267,16 @@ impl<Ix: Debug> std::fmt::Display for AsgError<Ix> {
impl<Ix: Debug> std::error::Error for AsgError<Ix> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
match self {
Self::ObjectTransition(err) => err.source(),
_ => None,
}
}
}
impl<Ix: Debug> From<TransitionError> for AsgError<Ix> {
fn from(e: TransitionError) -> Self {
match e {
TransitionError::Incompatible(msg) => Self::IncompatibleIdent(msg),
TransitionError::BadFragmentDest(msg) => Self::BadFragmentDest(msg),
}
fn from(err: TransitionError) -> Self {
Self::ObjectTransition(err)
}
}

View File

@ -144,6 +144,42 @@ pub enum IdentKind {
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 {
match self {
Self::Cgen(dim) => {
write!(fmt, "cgen[{}; {}]", DataType::Boolean, dim)
}
Self::Class(dim) => {
write!(fmt, "class[{}; {}]", DataType::Boolean, dim)
}
Self::Const(dim, dtype) => write!(fmt, "const[{}; {}]", dtype, dim),
Self::Func(dim, dtype) => write!(fmt, "func[{}; {}]", dtype, dim),
Self::Gen(dim, dtype) => write!(fmt, "gen[{}; {}]", dtype, dim),
Self::Lparam(dim, dtype) => {
write!(fmt, "lparam[{}; {}]", dtype, dim)
}
Self::Param(dim, dtype) => write!(fmt, "param[{}; {}]", dtype, dim),
Self::Rate(dtype) => write!(fmt, "rate[{}; 0]", dtype),
Self::Tpl => write!(fmt, "tpl"),
Self::Type(dtype) => write!(fmt, "type[{}]", dtype),
Self::MapHead => write!(fmt, "map:head"),
Self::Map => write!(fmt, "map"),
Self::MapTail => write!(fmt, "map:tail"),
Self::RetMapHead => write!(fmt, "retmap:head"),
Self::RetMap => write!(fmt, "retmap"),
Self::RetMapTail => write!(fmt, "retmap:tail"),
Self::Meta => write!(fmt, "meta"),
Self::Worksheet => write!(fmt, "worksheet"),
}
}
}
impl<'i> TryFrom<SymAttrs<'i>> for IdentKind {
type Error = &'static str;
@ -247,6 +283,12 @@ impl AsRef<str> for Dim {
}
}
impl std::fmt::Display for Dim {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
(self.0).fmt(fmt)
}
}
/// Underlying datatype of identifier.
pub type DataType = SymDtype;

View File

@ -77,10 +77,10 @@
//! let identb_sym = interner.intern("identb");
//!
//! let identa = asg.declare(identa_sym, IdentKind::Meta, Source::default())?;
//! let identb = asg.declare_extern(identb_sym, IdentKind::Meta)?;
//! let identb = asg.declare_extern(identb_sym, IdentKind::Meta, Source::default())?;
//!
//! assert_eq!(
//! Some(&IdentObject::Extern(identb_sym, IdentKind::Meta)),
//! Some(&IdentObject::Extern(identb_sym, IdentKind::Meta, Source::default())),
//! asg.get(identb),
//! );
//!

View File

@ -62,7 +62,13 @@ pub enum IdentObject<'i> {
/// the same name is loaded.
/// It is an error if the loaded identifier does not have a compatible
/// [`IdentKind`].
Extern(&'i Symbol<'i>, 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(&'i Symbol<'i>, IdentKind, Source<'i>),
/// Identifier with associated text.
///
@ -80,7 +86,7 @@ pub enum IdentObject<'i> {
/// as [`IdentObject`];
/// this allows other representations to be used,
/// while still permitting the use of matching on [`IdentObject`]
/// through the use of [`ident`](IdentObjectState::ident).
/// through the use of [`ident`](IdentObjectData::as_ident).
///
/// Since an object implementing this trait may not be an identifier
/// (e.g. an expression),
@ -134,7 +140,7 @@ impl<'i> IdentObjectData<'i> for IdentObject<'i> {
match self {
Self::Missing(name)
| Self::Ident(name, _, _)
| Self::Extern(name, _)
| Self::Extern(name, _, _)
| Self::IdentFragment(name, _, _, _) => Some(name),
}
}
@ -143,14 +149,14 @@ impl<'i> IdentObjectData<'i> for IdentObject<'i> {
match self {
Self::Missing(_) => None,
Self::Ident(_, kind, _)
| Self::Extern(_, kind)
| Self::Extern(_, kind, _)
| Self::IdentFragment(_, kind, _, _) => Some(kind),
}
}
fn src(&self) -> Option<&Source<'i>> {
match self {
Self::Missing(_) | Self::Extern(_, _) => None,
Self::Missing(_) | Self::Extern(_, _, _) => None,
Self::Ident(_, _, src) | Self::IdentFragment(_, _, src, _) => {
Some(src)
}
@ -159,7 +165,7 @@ impl<'i> IdentObjectData<'i> for IdentObject<'i> {
fn fragment(&self) -> Option<&FragmentText> {
match self {
Self::Missing(_) | Self::Ident(_, _, _) | Self::Extern(_, _) => {
Self::Missing(_) | Self::Ident(_, _, _) | Self::Extern(_, _, _) => {
None
}
Self::IdentFragment(_, _, _, text) => Some(text),
@ -186,21 +192,33 @@ where
T: IdentObjectState<'i, T>,
{
/// Produce an object representing a missing identifier.
fn missing(ident: &'i Symbol<'i>) -> T;
///
/// This is the base state for all identifiers.
fn declare(ident: &'i Symbol<'i>) -> T;
/// Produce an object representing a concrete identifier.
fn ident(name: &'i Symbol<'i>, kind: IdentKind, src: Source<'i>) -> T;
/// Produce an object representing an extern.
fn extern_(name: &'i Symbol<'i>, kind: IdentKind) -> T;
/// Attempt to redeclare an identifier with additional information.
/// 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 redeclare(self, kind: IdentKind, src: Source<'i>)
-> TransitionResult<T>;
fn resolve(self, kind: IdentKind, src: Source<'i>) -> TransitionResult<T>;
/// 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<'i>) -> TransitionResult<T>;
/// Attach a code fragment (compiled text) to an identifier.
///
@ -214,18 +232,10 @@ where
}
impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
fn missing(ident: &'i Symbol<'i>) -> Self {
fn declare(ident: &'i Symbol<'i>) -> Self {
IdentObject::Missing(ident)
}
fn ident(name: &'i Symbol<'i>, kind: IdentKind, src: Source<'i>) -> Self {
IdentObject::Ident(name, kind, src)
}
fn extern_(name: &'i Symbol<'i>, kind: IdentKind) -> Self {
IdentObject::Extern(name, kind)
}
/// Attempt to redeclare an identifier with additional information.
///
/// If an existing identifier is an [`IdentObject::Extern`],
@ -244,7 +254,7 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
/// 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.
fn redeclare(
fn resolve(
mut self,
kind: IdentKind,
src: Source<'i>,
@ -257,6 +267,20 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
Ok(self)
}
IdentObject::Extern(name, ref orig_kind, _) => {
if orig_kind != &kind {
let err = TransitionError::ExternResolution {
name: name.to_string(),
expected: orig_kind.clone(),
given: kind.clone(),
};
return Err((self, err));
}
Ok(IdentObject::Ident(name, kind, src))
}
// TODO: no override-override
IdentObject::IdentFragment(name, _, orig_src, _)
if orig_src.virtual_ && src.override_ =>
@ -269,11 +293,35 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
Ok(IdentObject::Ident(name, kind, src))
}
// TODO: incompatible (check now-dangling commits)
// TODO
_ => Ok(self),
}
}
fn extern_(
self,
kind: IdentKind,
src: Source<'i>,
) -> TransitionResult<IdentObject<'i>> {
match self.kind() {
None => Ok(IdentObject::Extern(self.name().unwrap(), kind, src)),
Some(cur_kind) => {
if cur_kind != &kind {
let err = TransitionError::ExternResolution {
name: self.name().unwrap().to_string(),
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,
@ -340,15 +388,25 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
/// another.
///
/// TODO: Provide enough information to construct a useful message.
#[derive(Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub enum TransitionError {
/// An attempt to redeclare an identifier with additional information
/// has failed because the provided information was not compatible
/// with the original declaration.
///
/// See [`IdentObjectState::redeclare`].
/// See [`IdentObjectState::resolve`].
Incompatible(String),
/// 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: String,
expected: IdentKind,
given: IdentKind,
},
/// The provided identifier is not in a state that is permitted to
/// receive a fragment.
///
@ -363,6 +421,16 @@ impl std::fmt::Display for TransitionError {
write!(fmt, "object incompatible: {}", msg)
}
Self::ExternResolution {
name,
expected,
given,
} => write!(
fmt,
"extern `{}` of type `{}` is incompatible with type `{}`",
name, expected, given,
),
Self::BadFragmentDest(msg) => {
write!(fmt, "bad fragment destination: {}", msg)
}
@ -370,6 +438,12 @@ impl std::fmt::Display for TransitionError {
}
}
impl std::error::Error for TransitionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
/// Compiled fragment for identifier.
///
/// This represents the text associated with an identifier.
@ -494,7 +568,8 @@ mod test {
assert_eq!(
Some(&sym),
IdentObject::Extern(&sym, IdentKind::Meta).name()
IdentObject::Extern(&sym, IdentKind::Meta, Source::default())
.name()
);
assert_eq!(
@ -524,7 +599,8 @@ mod test {
assert_eq!(
Some(&kind),
IdentObject::Extern(&sym, kind.clone()).kind()
IdentObject::Extern(&sym, kind.clone(), Source::default())
.kind()
);
assert_eq!(
@ -554,7 +630,10 @@ mod test {
IdentObject::Ident(&sym, IdentKind::Meta, src.clone()).src()
);
assert_eq!(None, IdentObject::Extern(&sym, IdentKind::Meta).src());
assert_eq!(
None,
IdentObject::Extern(&sym, IdentKind::Meta, src.clone()).src()
);
assert_eq!(
Some(&src),
@ -583,7 +662,8 @@ mod test {
assert_eq!(
None,
IdentObject::Extern(&sym, IdentKind::Meta).fragment()
IdentObject::Extern(&sym, IdentKind::Meta, Source::default())
.fragment()
);
assert_eq!(
@ -618,7 +698,7 @@ mod test {
#[test]
fn ident_object_missing() {
let sym = symbol_dummy!(1, "missing");
assert_eq!(IdentObject::Missing(&sym), IdentObject::missing(&sym));
assert_eq!(IdentObject::Missing(&sym), IdentObject::declare(&sym));
}
#[test]
@ -632,19 +712,180 @@ mod test {
assert_eq!(
IdentObject::Ident(&sym, kind.clone(), src.clone()),
IdentObject::ident(&sym, kind.clone(), src.clone()),
IdentObject::declare(&sym)
.resolve(kind.clone(), src.clone())
.unwrap(),
);
}
#[test]
fn ident_object_extern() {
let sym = symbol_dummy!(1, "missing");
let kind = IdentKind::Class(Dim::from_u8(1));
mod extern_ {
use super::*;
assert_eq!(
IdentObject::Extern(&sym, kind.clone()),
IdentObject::extern_(&sym, kind.clone()),
);
#[test]
fn ident_object() {
let sym = symbol_dummy!(1, "missing");
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),
);
}
// Extern first, then identifier
#[test]
fn redeclare_compatible_resolves() {
let sym = symbol_dummy!(1, "extern_re_pre");
let kind = IdentKind::Class(Dim::from_u8(10));
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 = symbol_dummy!(1, "extern_re_post");
let kind = IdentKind::Class(Dim::from_u8(10));
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 = symbol_dummy!(1, "extern_extern");
let kind = IdentKind::Class(Dim::from_u8(20));
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 = symbol_dummy!(1, "extern_re_bad_post");
let kind = IdentKind::Class(Dim::from_u8(10));
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.to_string(), 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 = symbol_dummy!(1, "extern_re_bad_pre");
let kind_given = IdentKind::Class(Dim::from_u8(10));
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.to_string(), 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),
}
}
}
// TODO: incompatible
@ -652,13 +893,14 @@ mod test {
fn redeclare_returns_existing_compatible() {
let sym = symbol_dummy!(1, "symdup");
let first =
IdentObject::ident(&sym, IdentKind::Meta, Source::default());
let first = IdentObject::declare(&sym)
.resolve(IdentKind::Meta, Source::default())
.unwrap();
// Same declaration a second time
assert_eq!(
Ok(first.clone()),
first.clone().redeclare(
first.clone().resolve(
first.kind().unwrap().clone(),
first.src().unwrap().clone(),
)
@ -674,7 +916,9 @@ mod test {
};
let kind = IdentKind::Meta;
let ident = IdentObject::ident(&sym, kind.clone(), src.clone());
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());
@ -687,8 +931,9 @@ mod test {
#[test]
fn add_fragment_to_fragment_fails() {
let sym = symbol_dummy!(1, "badsym");
let ident =
IdentObject::ident(&sym, IdentKind::Meta, Source::default());
let ident = IdentObject::declare(&sym)
.resolve(IdentKind::Meta, Source::default())
.unwrap();
let ident_with_frag = ident
.set_fragment("orig fragment".into())
@ -720,14 +965,15 @@ mod test {
let over_src = symbol_dummy!(2, "src");
let kind = IdentKind::Meta;
let virt = IdentObject::ident(
&sym,
kind.clone(),
Source {
virtual_: true,
..Default::default()
},
);
let virt = IdentObject::declare(&sym)
.resolve(
kind.clone(),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let over_src = Source {
override_: true,
@ -735,7 +981,7 @@ mod test {
..Default::default()
};
let result = virt.redeclare(kind.clone(), over_src.clone());
let result = virt.resolve(kind.clone(), over_src.clone());
assert_eq!(Ok(IdentObject::Ident(&sym, kind, over_src)), result);
}
@ -752,7 +998,9 @@ mod test {
..Default::default()
};
let virt = IdentObject::ident(&sym, kind.clone(), virt_src.clone());
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());
@ -773,7 +1021,7 @@ mod test {
};
let result =
virt_frag.unwrap().redeclare(kind.clone(), over_src.clone());
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
@ -789,7 +1037,9 @@ mod test {
..Default::default()
};
let obj = IdentObject::ident(&sym, given, src.clone());
let obj = IdentObject::declare(&sym)
.resolve(given, src.clone())
.unwrap();
let fragment = "a fragment".to_string();
let obj_with_frag = obj.set_fragment(fragment.clone());

View File

@ -305,6 +305,17 @@ impl TryFrom<&[u8]> for SymDtype {
}
}
impl std::fmt::Display for SymDtype {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Boolean => write!(fmt, "boolean"),
Self::Integer => write!(fmt, "integer"),
Self::Float => write!(fmt, "float"),
Self::Empty => write!(fmt, "(unknown)"),
}
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@ -174,12 +174,9 @@ fn load_xmlo<'a, 'i, I: Interner<'i>>(
Ok(XmloEvent::SymDecl(sym, attrs)) => {
if let Some(sym_src) = attrs.src {
found.insert(sym_src);
} else if attrs.extern_ {
// TODO: externs (they're implicitly handled, without
// checks, by Missing)
// depgraph.declare_extern(sym, kind);
} else {
let owned = attrs.src.is_none();
let extern_ = attrs.extern_;
let kind = (&attrs).try_into().map_err(|err| {
format!("sym `{}` attrs error: {}", sym, err)
@ -201,10 +198,15 @@ fn load_xmlo<'a, 'i, I: Interner<'i>>(
|| kindval == IdentKind::Map
|| kindval == IdentKind::RetMap);
let node = depgraph.declare(sym, kindval, src)?;
if extern_ {
depgraph.declare_extern(sym, kindval, src)?;
} else {
let node =
depgraph.declare(sym, kindval, src)?;
if link_root {
roots.push(node);
if link_root {
roots.push(node);
}
}
}
Err(e) => return Err(e.into()),