TAMER: Finalize object state transitions

In particular, this finalizes overrides and redeclarations.  The linker
should now be feature-complete.
master
Mike Gerwitz 2020-04-06 10:30:33 -04:00
commit 587241bf9b
7 changed files with 1158 additions and 182 deletions

5
.gitignore vendored
View File

@ -31,3 +31,8 @@ configure
# generated by TAME build
suppliers.mk
run-[0-9].log
# binary data and profiling
a.out
perf.data

View File

@ -132,7 +132,6 @@ standalones: $(dest_standalone)
strip: $(dest_standalone_strip) ui/package.strip.js
%.xmle: %.xmlo $(path_tame)/.rev-xmle
$(TAME_TS)
@echo "WARNING: using WIP proof-of-concept linker!"
$(path_tame)/tamer/target/release/tameld -o $@ $<
%.js: %.xmle
$(TAME_TS)

View File

@ -0,0 +1,550 @@
// Abstract semantic graph benchmarks
//
// Copyright (C) 2014-2020 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/>.
//
// Note that the baseline tests have a _suffix_ rather than a prefix so that
// they are still grouped with the associated test in the output, since it's
// sorted lexically by function name.
#![feature(test)]
extern crate tamer;
extern crate test;
use test::Bencher;
mod base {
use super::*;
use tamer::global;
use tamer::ir::asg::{
Asg, DataType, DefaultAsg, IdentKind, IdentObject, SortableAsg, Source,
};
use tamer::sym::{DefaultInterner, Interner, Symbol};
type Sut<'i> = DefaultAsg<'i, IdentObject<'i>, global::PkgIdentSize>;
type SutProg<'i> = DefaultAsg<'i, IdentObject<'i>, global::ProgIdentSize>;
fn interned_n<'i>(
interner: &'i DefaultInterner<'i>,
n: u16,
) -> Vec<&'i Symbol<'i>> {
(0..n).map(|i| interner.intern(&i.to_string())).collect()
}
#[bench]
fn declare_1_000(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
bench.iter(|| {
xs.iter()
.map(|i| sut.declare(i, IdentKind::Meta, Source::default()))
.for_each(drop);
});
}
#[bench]
fn declare_1_000_full_inital_capacity(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(1024, 1024);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
bench.iter(|| {
xs.iter()
.map(|i| sut.declare(i, IdentKind::Meta, Source::default()))
.for_each(drop);
});
}
// The Ix size affects memory, but how about performance?
#[bench]
fn declare_1_000_prog_ident_size(bench: &mut Bencher) {
let mut sut = SutProg::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
bench.iter(|| {
xs.iter()
.map(|i| sut.declare(i, IdentKind::Meta, Source::default()))
.for_each(drop);
});
}
#[bench]
fn declare_extern_1_000(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
bench.iter(|| {
xs.iter()
.map(|i| {
sut.declare_extern(i, IdentKind::Meta, Source::default())
})
.for_each(drop);
});
}
#[bench]
fn resolve_extern_1_000(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
xs.iter().for_each(|sym| {
let _ = sut.declare_extern(sym, IdentKind::Meta, Source::default());
});
// Bench only the resolution, not initial declare.
bench.iter(|| {
xs.iter()
.map(|sym| sut.declare(sym, IdentKind::Meta, Source::default()))
.for_each(drop);
});
}
// N.B.: This benchmark isn't easily comparable to the others because
// `set_fragment` takes ownership over a string, and so we have to clone
// strings for each call.
#[bench]
fn set_fragment_1_000_with_new_str(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
let orefs = xs
.iter()
.map(|sym| {
sut.declare(sym, IdentKind::Meta, Source::default())
.unwrap()
})
.collect::<Vec<_>>();
// Bench only the resolution, not initial declare.
bench.iter(|| {
orefs
.iter()
.map(|oref| sut.set_fragment(*oref, "".into())) // see N.B.
.for_each(drop);
});
}
#[bench]
fn lookup_1_000(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
xs.iter().for_each(|sym| {
let _ = sut.declare(&sym, IdentKind::Meta, Source::default());
});
bench.iter(|| {
xs.iter().map(|sym| sut.lookup(sym).unwrap()).for_each(drop);
});
}
#[bench]
fn get_1_000(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
let orefs = xs
.iter()
.map(|sym| {
sut.declare(sym, IdentKind::Meta, Source::default())
.unwrap()
})
.collect::<Vec<_>>();
bench.iter(|| {
orefs
.iter()
.map(|oref| sut.get(*oref).unwrap())
.for_each(drop);
});
}
// All dependencies on a single node. Petgraph does poorly with
// supernodes at the time of writing, relatively speaking.
#[bench]
fn add_dep_1_000_to_single_node(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
let orefs = xs
.iter()
.map(|sym| {
sut.declare(sym, IdentKind::Meta, Source::default())
.unwrap()
})
.collect::<Vec<_>>();
let root = orefs[0];
// Note that this adds all edges to one node
bench.iter(|| {
orefs
.iter()
.map(|oref| sut.add_dep(root, *oref))
.for_each(drop);
});
}
// Same as above but only one edge per node.
#[bench]
fn add_dep_1_000_one_edge_per_node(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
let orefs = xs
.iter()
.map(|sym| {
sut.declare(sym, IdentKind::Meta, Source::default())
.unwrap()
})
.collect::<Vec<_>>();
bench.iter(|| {
orefs
.iter()
.zip(orefs.iter().cycle().skip(1))
.map(|(from, to)| sut.add_dep(*from, *to))
.for_each(drop);
});
}
#[bench]
fn has_dep_1_000_single_node(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
let orefs = xs
.iter()
.map(|sym| {
sut.declare(sym, IdentKind::Meta, Source::default())
.unwrap()
})
.collect::<Vec<_>>();
let root = orefs[0];
orefs.iter().for_each(|oref| {
sut.add_dep(root, *oref);
});
bench.iter(|| {
orefs
.iter()
.map(|oref| sut.has_dep(root, *oref))
.for_each(drop);
});
}
// Same as above but only one edge per node.
#[bench]
fn has_dep_1_000_one_edge_per_node(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
let orefs = xs
.iter()
.map(|sym| {
sut.declare(sym, IdentKind::Meta, Source::default())
.unwrap()
})
.collect::<Vec<_>>();
orefs.iter().zip(orefs.iter().cycle().skip(1)).for_each(
|(from, to)| {
sut.add_dep(*from, *to);
},
);
bench.iter(|| {
orefs
.iter()
.zip(orefs.iter().cycle().skip(1))
.map(|(from, to)| sut.has_dep(*from, *to))
.for_each(drop);
});
}
#[bench]
fn add_dep_lookup_1_000_missing_one_edge_per_node(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
bench.iter(|| {
xs.iter()
.zip(xs.iter().cycle().skip(1))
.map(|(from, to)| sut.add_dep_lookup(from, to))
.for_each(drop);
});
}
#[bench]
fn add_dep_lookup_1_000_existing_one_edge_per_node(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
xs.iter().for_each(|sym| {
let _ = sut.declare(sym, IdentKind::Meta, Source::default());
});
bench.iter(|| {
xs.iter()
.zip(xs.iter().cycle().skip(1))
.map(|(from, to)| sut.add_dep_lookup(from, to))
.for_each(drop);
});
}
#[bench]
fn sort_1_with_1_000_existing_supernode(bench: &mut Bencher) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
let orefs = xs
.iter()
.map(|sym| {
sut.declare(
sym,
IdentKind::Rate(DataType::Integer),
Source::default(),
)
.unwrap()
})
.collect::<Vec<_>>();
let root = orefs[0];
// All edges from a single node.
orefs.iter().skip(1).for_each(|to| {
sut.add_dep(root, *to);
});
bench.iter(|| {
drop(sut.sort(&[root]));
});
}
#[bench]
fn sort_1_with_1_000_existing_one_edge_per_node_one_path(
bench: &mut Bencher,
) {
let mut sut = Sut::with_capacity(0, 0);
let interner = DefaultInterner::new();
let xs = interned_n(&interner, 1_000);
let orefs = xs
.iter()
.map(|sym| {
sut.declare(
sym,
IdentKind::Rate(DataType::Integer),
Source::default(),
)
.unwrap()
})
.collect::<Vec<_>>();
// Note that there's no `cycle` call on the iterator, like the
// above tests, to make sure we don't create a cycle on the
// graph.
orefs
.iter()
.zip(orefs.iter().skip(1))
.for_each(|(from, to)| {
sut.add_dep(*from, *to);
});
let root = orefs[0];
bench.iter(|| {
drop(sut.sort(&[root]));
});
}
}
mod object {
use super::*;
mod ident {
use super::*;
use tamer::ir::asg::{
IdentKind, IdentObject, IdentObjectData, IdentObjectState, Source,
};
use tamer::sym::{DefaultInterner, Interner};
type Sut<'i> = IdentObject<'i>;
#[bench]
fn declare_1_000(bench: &mut Bencher) {
let interner = DefaultInterner::new();
let sym = interner.intern("sym");
bench.iter(|| {
(0..1000).map(|_| Sut::declare(&sym)).for_each(drop);
});
}
#[bench]
fn resolve_1_000_missing(bench: &mut Bencher) {
let interner = DefaultInterner::new();
let sym = interner.intern("sym");
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(&sym)
.resolve(IdentKind::Meta, Source::default())
})
.for_each(drop);
});
}
#[bench]
fn extern_1_000_missing(bench: &mut Bencher) {
let interner = DefaultInterner::new();
let sym = interner.intern("sym");
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(&sym)
.extern_(IdentKind::Meta, Source::default())
})
.for_each(drop);
});
}
#[bench]
fn resolve_1_000_extern(bench: &mut Bencher) {
let interner = DefaultInterner::new();
let sym = interner.intern("sym");
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(&sym)
.extern_(IdentKind::Meta, Source::default())
.unwrap()
.resolve(IdentKind::Meta, Source::default())
})
.for_each(drop);
});
}
#[bench]
fn resolve_1_000_override(bench: &mut Bencher) {
let interner = DefaultInterner::new();
let sym = interner.intern("sym");
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(&sym)
.resolve(
IdentKind::Meta,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.resolve(
IdentKind::Meta,
Source {
override_: true,
..Default::default()
},
)
})
.for_each(drop);
});
}
// Override encountered before virtual
#[bench]
fn resolve_1_000_override_virt_after_override(bench: &mut Bencher) {
let interner = DefaultInterner::new();
let sym = interner.intern("sym");
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(&sym)
.resolve(
IdentKind::Meta,
Source {
override_: true,
..Default::default()
},
)
.unwrap()
.resolve(
IdentKind::Meta,
Source {
virtual_: true,
..Default::default()
},
)
})
.for_each(drop);
});
}
#[bench]
fn set_fragment_1_000_resolved_with_new_str(bench: &mut Bencher) {
let interner = DefaultInterner::new();
let sym = interner.intern("sym");
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(&sym)
.resolve(IdentKind::Meta, Source::default())
.unwrap()
.set_fragment("".into())
})
.for_each(drop);
});
}
// No need to do all of the others, since they're all the same thing.
#[bench]
fn declared_name_1_000(bench: &mut Bencher) {
let interner = DefaultInterner::new();
let sym = interner.intern("sym");
bench.iter(|| {
(0..1000).map(|_| Sut::declare(&sym).name()).for_each(drop);
});
}
}
}

View File

@ -140,7 +140,7 @@ where
})
}
/// Perform a state transition on an identifier.
/// Perform a state transition on an identifier by name.
///
/// Look up `ident` or add a missing identifier if it does not yet exist
/// (see `lookup_or_missing`).
@ -149,7 +149,7 @@ where
///
/// This will safely restore graph state to the original identifier
/// value on transition failure.
fn with_ident<F>(
fn with_ident_lookup<F>(
&mut self,
name: &'i Symbol<'i>,
f: F,
@ -158,11 +158,29 @@ where
F: FnOnce(O) -> TransitionResult<O>,
{
let identi = self.lookup_or_missing(name);
self.with_ident(identi, f)
}
/// Perform a state transition on an identifier by [`ObjectRef`].
///
/// 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,
identi: ObjectRef<Ix>,
f: F,
) -> AsgResult<ObjectRef<Ix>, Ix>
where
F: FnOnce(O) -> TransitionResult<O>,
{
let node = self.graph.node_weight_mut(identi.0).unwrap();
let obj = node
.take()
.expect(&format!("internal error: missing object for {}", name));
.expect(&format!("internal error: missing object"));
f(obj)
.and_then(|obj| {
@ -237,7 +255,7 @@ where
kind: IdentKind,
src: Source<'i>,
) -> AsgResult<ObjectRef<Ix>, Ix> {
self.with_ident(name, |obj| obj.resolve(kind, src))
self.with_ident_lookup(name, |obj| obj.resolve(kind, src))
}
fn declare_extern(
@ -246,7 +264,7 @@ where
kind: IdentKind,
src: Source<'i>,
) -> AsgResult<ObjectRef<Ix>, Ix> {
self.with_ident(name, |obj| obj.extern_(kind, src))
self.with_ident_lookup(name, |obj| obj.extern_(kind, src))
}
fn set_fragment(
@ -254,30 +272,7 @@ where
identi: ObjectRef<Ix>,
text: FragmentText,
) -> AsgResult<ObjectRef<Ix>, Ix> {
// This should _never_ happen as long as you're only using ObjectRef
// values produced by these methods.
let node = self
.graph
.node_weight_mut(identi.0)
.expect("internal error: BaseAsg::set_fragment bogus identi");
// This should also never happen, since we immediately repopulate
// the node below.
let ty = node
.take()
.expect("internal error: BaseAsg::set_fragment missing Node data");
// Be sure to restore the previous node if the transition fails,
// otherwise we'll be left in an inconsistent internal state.
ty.set_fragment(text)
.and_then(|obj| {
node.replace(obj);
Ok(identi)
})
.or_else(|(orig, err)| {
node.replace(orig);
Err(err.into())
})
self.with_ident(identi, |obj| obj.set_fragment(text))
}
#[inline]
@ -424,6 +419,7 @@ mod test {
given_set_fragment: Option<FragmentText>,
fail_redeclare: RefCell<Option<TransitionError>>,
fail_extern: RefCell<Option<TransitionError>>,
fail_set_fragment: RefCell<Option<TransitionError>>,
}
impl<'i> IdentObjectData<'i> for StubIdentObject<'i> {
@ -488,6 +484,11 @@ mod test {
mut self,
text: FragmentText,
) -> TransitionResult<StubIdentObject<'i>> {
if self.fail_set_fragment.borrow().is_some() {
let err = self.fail_set_fragment.replace(None).unwrap();
return Err((self, err));
}
self.given_set_fragment.replace(text);
Ok(self)
}
@ -626,7 +627,11 @@ 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 terr = TransitionError::Incompatible(String::from("test fail"));
let terr = TransitionError::ExternResolution {
name: String::from("test fail"),
expected: IdentKind::Meta,
given: IdentKind::Meta,
};
obj.fail_redeclare.replace(Some(terr.clone()));
// Should invoke StubIdentObject::redeclare on the above `obj`.
@ -687,7 +692,11 @@ mod test {
// 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"));
let terr = TransitionError::ExternResolution {
name: String::from("test fail"),
expected: IdentKind::Meta,
given: IdentKind::Meta,
};
obj.fail_extern.replace(Some(terr.clone()));
// Should invoke StubIdentObject::extern_ on the above `obj`.
@ -737,7 +746,39 @@ mod test {
Ok(())
}
// TODO: fragment fail
#[test]
fn add_fragment_to_ident_fails_if_transition_fails() -> AsgResult<(), u8> {
let mut sut = Sut::with_capacity(0, 0);
let sym = symbol_dummy!(1, "failfrag");
let src = Source {
generated: true,
..Default::default()
};
// The failure will come from terr below, not this.
let node = sut.declare(&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::BadFragmentDest {
name: String::from("test fail"),
};
obj.fail_set_fragment.replace(Some(terr.clone()));
let result = sut
.set_fragment(node, "".into())
.expect_err("error expected");
// The node should have been restored.
let obj = sut.get(node).unwrap();
assert_eq!(&sym, *obj.given_declare.as_ref().unwrap());
assert_eq!(AsgError::ObjectTransition(terr), result);
Ok(())
}
#[test]
fn add_ident_dep_to_ident() -> AsgResult<(), u8> {

View File

@ -197,10 +197,10 @@ mod object;
mod section;
pub use graph::{Asg, AsgError, AsgResult, ObjectRef, SortableAsg};
pub use ident::{Dim, IdentKind};
pub use ident::{DataType, Dim, IdentKind};
pub use object::{
FragmentText, IdentObject, IdentObjectData, Source, TransitionError,
TransitionResult,
FragmentText, IdentObject, IdentObjectData, IdentObjectState, Source,
TransitionError, TransitionResult,
};
pub use section::{Section, SectionIterator, Sections};

View File

@ -251,22 +251,82 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
/// (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(
mut self,
self,
kind: IdentKind,
src: Source<'i>,
mut src: Source<'i>,
) -> TransitionResult<IdentObject<'i>> {
match self {
IdentObject::Ident(_, _, ref mut orig_src)
if orig_src.virtual_ && src.override_ =>
{
*orig_src = src;
Ok(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.to_string(),
};
return Err((self, err));
}
if orig_kind != &kind {
let err = TransitionError::VirtualOverrideKind {
name: name.to_string(),
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 {
@ -281,20 +341,26 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
Ok(IdentObject::Ident(name, kind, src))
}
// TODO: no override-override
IdentObject::IdentFragment(name, _, orig_src, _)
if orig_src.virtual_ && src.override_ =>
{
// clears fragment, which is no longer applicable
// 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))
}
IdentObject::Missing(name) | IdentObject::Ident(name, _, _) => {
Ok(IdentObject::Ident(name, kind, src))
}
_ => {
let err = TransitionError::Redeclare {
name: self.name().unwrap().to_string(),
};
// TODO
_ => Ok(self),
Err((self, err))
}
}
}
@ -331,6 +397,21 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
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, _, _)
@ -338,47 +419,13 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
Ok(self)
}
// TODO remove these ignores when fixed
IdentObject::IdentFragment(
sym,
IdentKind::Map,
Source {
virtual_: true,
override_: true,
..
},
_,
) => {
eprintln!(
"ignoring virtual and overridden map object: {}",
sym
);
Ok(self)
}
IdentObject::IdentFragment(
sym,
IdentKind::RetMap,
Source {
virtual_: true,
override_: true,
..
},
_,
) => {
eprintln!(
"ignoring virtual and overridden retmap object: {}",
sym
);
Ok(self)
}
_ => {
let msg = format!(
"identifier is not a IdentObject::Ident): {:?}",
self,
);
Err((self, TransitionError::BadFragmentDest(msg)))
Err((self, TransitionError::BadFragmentDest { name: msg }))
}
}
}
@ -386,16 +433,11 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
/// An error attempting to transition from one [`IdentObject`] state to
/// another.
///
/// TODO: Provide enough information to construct a useful message.
#[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::resolve`].
Incompatible(String),
/// Attempted to redeclare a concrete, non-virtual identifier without an
/// override.
Redeclare { name: String },
/// Extern resolution failure.
///
@ -407,19 +449,32 @@ pub enum TransitionError {
given: IdentKind,
},
/// Attempt to override a non-virtual identifier.
NonVirtualOverride { name: String },
/// Overriding a virtual identifier failed due to an incompatible
/// [`IdentKind`].
VirtualOverrideKind {
name: String,
existing: IdentKind,
given: IdentKind,
},
/// The provided identifier is not in a state that is permitted to
/// receive a fragment.
///
/// See [`IdentObjectState::set_fragment`].
BadFragmentDest(String),
BadFragmentDest { name: String },
}
impl std::fmt::Display for TransitionError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Incompatible(msg) => {
write!(fmt, "object incompatible: {}", msg)
}
Self::Redeclare { name } => write!(
fmt,
"cannot redeclare identifier `{}`",
name,
),
Self::ExternResolution {
name,
@ -431,7 +486,23 @@ impl std::fmt::Display for TransitionError {
name, expected, given,
),
Self::BadFragmentDest(msg) => {
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)
}
}
@ -718,6 +789,37 @@ mod test {
);
}
// 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 = symbol_dummy!(1, "redecl");
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::*;
@ -888,25 +990,6 @@ mod test {
}
}
// TODO: incompatible
#[test]
fn redeclare_returns_existing_compatible() {
let sym = symbol_dummy!(1, "symdup");
let first = IdentObject::declare(&sym)
.resolve(IdentKind::Meta, Source::default())
.unwrap();
// Same declaration a second time
assert_eq!(
Ok(first.clone()),
first.clone().resolve(
first.kind().unwrap().clone(),
first.src().unwrap().clone(),
)
);
}
#[test]
fn add_fragment_to_ident() {
let sym = symbol_dummy!(1, "tofrag");
@ -946,7 +1029,7 @@ mod test {
.expect_err("Expected failure");
match err {
(orig, TransitionError::BadFragmentDest(str))
(orig, TransitionError::BadFragmentDest { name: str })
if str.contains("badsym") =>
{
assert_eq!(ident_with_frag, orig);
@ -958,76 +1041,376 @@ mod test {
}
}
// TODO: incompatible
#[test]
fn declare_override_virtual_ident() {
let sym = symbol_dummy!(1, "virtual");
let over_src = symbol_dummy!(2, "src");
let kind = IdentKind::Meta;
mod override_ {
use super::*;
let virt = IdentObject::declare(&sym)
.resolve(
kind.clone(),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
#[test]
fn declare_virtual_ident_first() {
let sym = symbol_dummy!(1, "virtual");
let over_src = symbol_dummy!(2, "src");
let kind = IdentKind::Meta;
let over_src = Source {
override_: true,
src: Some(&over_src),
..Default::default()
};
let virt = IdentObject::declare(&sym)
.resolve(
kind.clone(),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let result = virt.resolve(kind.clone(), over_src.clone());
let over_src = Source {
virtual_: true, // this needn't be set, but see below
override_: true,
src: Some(&over_src),
..Default::default()
};
assert_eq!(Ok(IdentObject::Ident(&sym, kind, over_src)), result);
}
let result = virt.resolve(kind.clone(), over_src.clone());
// TODO: incompatible
#[test]
fn declare_override_virtual_ident_fragment() {
let sym = symbol_dummy!(1, "virtual");
let over_src = symbol_dummy!(2, "src");
let kind = IdentKind::Meta;
// Overriding should clear any virtual flag that may have
// been set to prevent override-overrides.
let expected_src = Source {
virtual_: false,
..over_src
};
let virt_src = Source {
virtual_: true,
..Default::default()
};
assert_eq!(
Ok(IdentObject::Ident(&sym, kind, expected_src)),
result
);
}
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());
// Override is encountered before the virtual
#[test]
fn declare_virtual_ident_after_override() {
let sym = symbol_dummy!(1, "virtual_second");
let virt_src = symbol_dummy!(2, "virt_src");
let kind = IdentKind::Meta;
assert_eq!(
Ok(IdentObject::IdentFragment(
&sym,
kind.clone(),
virt_src,
text
)),
virt_frag,
);
let over_src = Source {
virtual_: true, // this needn't be set, but see below
override_: true,
..Default::default()
};
let over_src = Source {
override_: true,
src: Some(&over_src),
..Default::default()
};
let over = IdentObject::declare(&sym)
.resolve(kind.clone(), over_src.clone())
.unwrap();
let result =
virt_frag.unwrap().resolve(kind.clone(), over_src.clone());
let virt_src = Source {
virtual_: true,
src: Some(&virt_src),
..Default::default()
};
// 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);
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 = symbol_dummy!(1, "non_virtual");
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 = symbol_dummy!(1, "virtual");
let src_sym = symbol_dummy!(2, "src");
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 = symbol_dummy!(1, "virtual");
let over_src = symbol_dummy!(2, "src");
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 = symbol_dummy!(1, "virtual");
let over_src = symbol_dummy!(2, "src");
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 = symbol_dummy!(1, "virtual");
let over_src = symbol_dummy!(2, "src");
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) {

View File

@ -49,8 +49,6 @@ pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
let abs_path = fs::canonicalize(package_path)?;
println!("WARNING: This is proof-of-concept; do not use!");
let (name, relroot) = load_xmlo(
&abs_path.to_str().unwrap().to_string(),
&mut pkgs_seen,