TAMER: Finalize object state transitions
In particular, this finalizes overrides and redeclarations. The linker should now be feature-complete.master
commit
587241bf9b
|
@ -31,3 +31,8 @@ configure
|
|||
# generated by TAME build
|
||||
suppliers.mk
|
||||
run-[0-9].log
|
||||
|
||||
# binary data and profiling
|
||||
a.out
|
||||
perf.data
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue