use super::model::{Element, ElementId, ElementKind, Model};
#[derive(Clone, Copy)]
pub struct ElementView<'m> {
pub element: &'m Element,
pub model: &'m Model,
}
impl<'m> ElementView<'m> {
pub fn new(element: &'m Element, model: &'m Model) -> Self {
Self { element, model }
}
pub fn from_id(id: &ElementId, model: &'m Model) -> Option<Self> {
model.get(id).map(|element| Self { element, model })
}
pub fn id(&self) -> &'m ElementId {
&self.element.id
}
pub fn name(&self) -> Option<&'m str> {
self.element.name.as_deref()
}
pub fn short_name(&self) -> Option<&'m str> {
self.element.short_name.as_deref()
}
pub fn qualified_name(&self) -> Option<&'m str> {
self.element.qualified_name.as_deref()
}
pub fn kind(&self) -> ElementKind {
self.element.kind
}
pub fn owner(&self) -> Option<ElementView<'m>> {
let raw_owner_id = self.element.owner.as_ref()?;
let raw_owner = Self::from_id(raw_owner_id, self.model)?;
if raw_owner.element.kind.is_membership() {
return raw_owner.owner();
}
Some(raw_owner)
}
pub fn raw_owner(&self) -> Option<ElementView<'m>> {
self.element
.owner
.as_ref()
.and_then(|id| Self::from_id(id, self.model))
}
pub fn owned_members(&self) -> Vec<ElementView<'m>> {
self.model
.owned_members(&self.element.id)
.into_iter()
.filter_map(|e| Self::from_id(&e.id, self.model))
.collect()
}
pub fn owned_elements(&self) -> Vec<ElementView<'m>> {
self.element
.owned_elements
.iter()
.filter_map(|id| Self::from_id(id, self.model))
.collect()
}
pub fn documentation(&self) -> Option<&'m str> {
if let Some(doc) = self.element.documentation.as_deref() {
return Some(doc);
}
self.owned_elements().into_iter().find_map(|child| {
if child.kind() == ElementKind::Documentation {
child.element.documentation.as_deref()
} else {
None
}
})
}
pub fn is_abstract(&self) -> bool {
self.element.is_abstract
}
pub fn is_variation(&self) -> bool {
self.element.is_variation
}
pub fn is_derived(&self) -> bool {
self.element.is_derived
}
pub fn is_readonly(&self) -> bool {
self.element.is_readonly
}
pub fn is_end(&self) -> bool {
self.element.is_end
}
pub fn is_ordered(&self) -> bool {
self.element.is_ordered
}
pub fn is_nonunique(&self) -> bool {
self.element.is_nonunique
}
pub fn is_portion(&self) -> bool {
self.element.is_portion
}
pub fn is_individual(&self) -> bool {
self.element.is_individual
}
pub fn relationships_from(&self) -> Vec<&'m Element> {
self.model.rel_elements_from(&self.element.id).collect()
}
pub fn relationships_to(&self) -> Vec<&'m Element> {
self.model.rel_elements_to(&self.element.id).collect()
}
pub fn relationships_of_kind(&self, kind: ElementKind) -> Vec<&'m Element> {
self.model
.rel_elements_of_kind(&self.element.id, kind)
.collect()
}
pub fn rel_elements_from(&self) -> impl Iterator<Item = &'m Element> {
self.model.rel_elements_from(&self.element.id)
}
pub fn rel_elements_of_kind(&self, kind: ElementKind) -> impl Iterator<Item = &'m Element> {
self.model.rel_elements_of_kind(&self.element.id, kind)
}
fn resolve_targets_by_kind(&self, kind: ElementKind) -> Vec<ElementView<'m>> {
self.rel_elements_of_kind(kind)
.filter_map(|re| re.target().and_then(|tid| Self::from_id(tid, self.model)))
.collect()
}
pub fn typing(&self) -> Vec<ElementView<'m>> {
self.resolve_targets_by_kind(ElementKind::FeatureTyping)
}
pub fn typed_by(&self) -> Option<ElementView<'m>> {
self.typing().into_iter().next()
}
pub fn supertypes(&self) -> Vec<ElementView<'m>> {
self.resolve_targets_by_kind(ElementKind::Specialization)
}
pub fn redefined_features(&self) -> Vec<ElementView<'m>> {
self.resolve_targets_by_kind(ElementKind::Redefinition)
}
pub fn subsetted_features(&self) -> Vec<ElementView<'m>> {
self.resolve_targets_by_kind(ElementKind::Subsetting)
}
pub fn as_package(&self) -> Option<PackageView<'m>> {
match self.element.kind {
ElementKind::Package | ElementKind::LibraryPackage => {
Some(PackageView { inner: *self })
}
_ => None,
}
}
pub fn as_definition(&self) -> Option<DefinitionView<'m>> {
if self.element.kind.is_definition() {
Some(DefinitionView { inner: *self })
} else {
None
}
}
pub fn as_usage(&self) -> Option<UsageView<'m>> {
if self.element.kind.is_usage() {
Some(UsageView { inner: *self })
} else {
None
}
}
pub fn as_connection(&self) -> Option<ConnectionView<'m>> {
match self.element.kind {
ElementKind::ConnectionUsage
| ElementKind::InterfaceUsage
| ElementKind::FlowConnectionUsage => Some(ConnectionView {
inner: UsageView { inner: *self },
}),
_ => None,
}
}
pub fn as_requirement(&self) -> Option<RequirementView<'m>> {
match self.element.kind {
ElementKind::RequirementDefinition | ElementKind::RequirementUsage => {
Some(RequirementView { inner: *self })
}
_ => None,
}
}
pub fn as_port(&self) -> Option<PortView<'m>> {
match self.element.kind {
ElementKind::PortDefinition | ElementKind::PortUsage => Some(PortView { inner: *self }),
_ => None,
}
}
pub fn as_state(&self) -> Option<StateView<'m>> {
match self.element.kind {
ElementKind::StateDefinition | ElementKind::StateUsage => {
Some(StateView { inner: *self })
}
_ => None,
}
}
pub fn as_action(&self) -> Option<ActionView<'m>> {
match self.element.kind {
ElementKind::ActionDefinition | ElementKind::ActionUsage => {
Some(ActionView { inner: *self })
}
_ => None,
}
}
}
impl<'m> std::fmt::Debug for ElementView<'m> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ElementView")
.field("id", &self.element.id.as_str())
.field("name", &self.name())
.field("kind", &self.kind())
.finish()
}
}
#[derive(Clone, Copy, Debug)]
pub struct PackageView<'m> {
pub inner: ElementView<'m>,
}
impl<'m> PackageView<'m> {
pub fn name(&self) -> Option<&'m str> {
self.inner.name()
}
pub fn is_library(&self) -> bool {
self.inner.kind() == ElementKind::LibraryPackage
}
pub fn owned_members(&self) -> Vec<ElementView<'m>> {
self.inner.owned_members()
}
pub fn definitions(&self) -> Vec<ElementView<'m>> {
self.inner
.owned_members()
.into_iter()
.filter(|v| v.kind().is_definition())
.collect()
}
pub fn usages(&self) -> Vec<ElementView<'m>> {
self.inner
.owned_members()
.into_iter()
.filter(|v| v.kind().is_usage())
.collect()
}
pub fn packages(&self) -> Vec<PackageView<'m>> {
self.inner
.owned_members()
.into_iter()
.filter_map(|v| v.as_package())
.collect()
}
pub fn imports(&self) -> Vec<&'m Element> {
self.inner
.rel_elements_from()
.filter(|e| {
e.kind == ElementKind::NamespaceImport
|| e.kind == ElementKind::Import
|| e.kind == ElementKind::MembershipImport
})
.collect()
}
}
#[derive(Clone, Copy, Debug)]
pub struct DefinitionView<'m> {
pub inner: ElementView<'m>,
}
impl<'m> DefinitionView<'m> {
pub fn name(&self) -> Option<&'m str> {
self.inner.name()
}
pub fn kind(&self) -> ElementKind {
self.inner.kind()
}
pub fn is_abstract(&self) -> bool {
self.inner.is_abstract()
}
pub fn is_variation(&self) -> bool {
self.inner.is_variation()
}
pub fn owned_features(&self) -> Vec<ElementView<'m>> {
self.inner.owned_members()
}
pub fn supertypes(&self) -> Vec<ElementView<'m>> {
self.inner.supertypes()
}
pub fn documentation(&self) -> Option<&'m str> {
self.inner.documentation()
}
}
#[derive(Clone, Copy, Debug)]
pub struct UsageView<'m> {
pub inner: ElementView<'m>,
}
impl<'m> UsageView<'m> {
pub fn name(&self) -> Option<&'m str> {
self.inner.name()
}
pub fn kind(&self) -> ElementKind {
self.inner.kind()
}
pub fn typed_by(&self) -> Option<ElementView<'m>> {
self.inner.typed_by()
}
pub fn typing(&self) -> Vec<ElementView<'m>> {
self.inner.typing()
}
pub fn is_end(&self) -> bool {
self.inner.is_end()
}
pub fn is_derived(&self) -> bool {
self.inner.is_derived()
}
pub fn is_readonly(&self) -> bool {
self.inner.is_readonly()
}
pub fn is_ordered(&self) -> bool {
self.inner.is_ordered()
}
pub fn is_nonunique(&self) -> bool {
self.inner.is_nonunique()
}
pub fn is_portion(&self) -> bool {
self.inner.is_portion()
}
pub fn redefines(&self) -> Vec<ElementView<'m>> {
self.inner.redefined_features()
}
pub fn subsets(&self) -> Vec<ElementView<'m>> {
self.inner.subsetted_features()
}
pub fn owned_features(&self) -> Vec<ElementView<'m>> {
self.inner.owned_members()
}
pub fn documentation(&self) -> Option<&'m str> {
self.inner.documentation()
}
}
#[derive(Clone, Copy, Debug)]
pub struct ConnectionView<'m> {
pub inner: UsageView<'m>,
}
impl<'m> ConnectionView<'m> {
pub fn name(&self) -> Option<&'m str> {
self.inner.name()
}
pub fn ends(&self) -> Vec<UsageView<'m>> {
self.inner
.inner
.owned_members()
.into_iter()
.filter(|v| v.is_end())
.filter_map(|v| v.as_usage())
.collect()
}
pub fn typed_by(&self) -> Option<ElementView<'m>> {
self.inner.typed_by()
}
pub fn owned_features(&self) -> Vec<ElementView<'m>> {
self.inner.owned_features()
}
}
#[derive(Clone, Copy, Debug)]
pub struct RequirementView<'m> {
pub inner: ElementView<'m>,
}
impl<'m> RequirementView<'m> {
pub fn name(&self) -> Option<&'m str> {
self.inner.name()
}
pub fn short_name(&self) -> Option<&'m str> {
self.inner.short_name()
}
pub fn subject(&self) -> Option<ElementView<'m>> {
self.inner.owned_members().into_iter().find(|v| {
v.name()
.map(|n| n == "subject" || n.ends_with("::subject"))
.unwrap_or(false)
})
}
pub fn text(&self) -> Option<&'m str> {
self.inner.documentation()
}
pub fn owned_members(&self) -> Vec<ElementView<'m>> {
self.inner.owned_members()
}
pub fn is_definition(&self) -> bool {
self.inner.kind() == ElementKind::RequirementDefinition
}
pub fn supertypes(&self) -> Vec<ElementView<'m>> {
self.inner.supertypes()
}
}
#[derive(Clone, Copy, Debug)]
pub struct PortView<'m> {
pub inner: ElementView<'m>,
}
impl<'m> PortView<'m> {
pub fn name(&self) -> Option<&'m str> {
self.inner.name()
}
pub fn typed_by(&self) -> Option<ElementView<'m>> {
self.inner.typed_by()
}
pub fn is_definition(&self) -> bool {
self.inner.kind() == ElementKind::PortDefinition
}
pub fn owned_features(&self) -> Vec<ElementView<'m>> {
self.inner.owned_members()
}
}
#[derive(Clone, Copy, Debug)]
pub struct StateView<'m> {
pub inner: ElementView<'m>,
}
impl<'m> StateView<'m> {
pub fn name(&self) -> Option<&'m str> {
self.inner.name()
}
pub fn is_parallel(&self) -> bool {
self.inner.element.is_parallel
}
pub fn is_definition(&self) -> bool {
self.inner.kind() == ElementKind::StateDefinition
}
pub fn owned_members(&self) -> Vec<ElementView<'m>> {
self.inner.owned_members()
}
pub fn transitions(&self) -> Vec<&'m Element> {
self.inner
.rel_elements_of_kind(ElementKind::Succession)
.collect()
}
}
#[derive(Clone, Copy, Debug)]
pub struct ActionView<'m> {
pub inner: ElementView<'m>,
}
impl<'m> ActionView<'m> {
pub fn name(&self) -> Option<&'m str> {
self.inner.name()
}
pub fn is_definition(&self) -> bool {
self.inner.kind() == ElementKind::ActionDefinition
}
pub fn owned_members(&self) -> Vec<ElementView<'m>> {
self.inner.owned_members()
}
pub fn successions(&self) -> Vec<&'m Element> {
self.inner
.rel_elements_of_kind(ElementKind::Succession)
.collect()
}
}
impl Model {
pub fn root_views(&self) -> Vec<ElementView<'_>> {
self.roots
.iter()
.filter_map(|id| ElementView::from_id(id, self))
.collect()
}
pub fn view(&self, id: &ElementId) -> Option<ElementView<'_>> {
ElementView::from_id(id, self)
}
pub fn find_by_name(&self, name: &str) -> Vec<ElementView<'_>> {
self.elements
.values()
.filter(|e| e.name.as_deref() == Some(name))
.map(|e| ElementView::new(e, self))
.collect()
}
pub fn find_by_kind(&self, kind: ElementKind) -> Vec<ElementView<'_>> {
self.elements
.values()
.filter(|e| e.kind == kind)
.map(|e| ElementView::new(e, self))
.collect()
}
pub fn find_by_qualified_name(&self, qn: &str) -> Option<ElementView<'_>> {
self.elements
.values()
.find(|e| e.qualified_name.as_deref() == Some(qn))
.map(|e| ElementView::new(e, self))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn model_from_text(source: &str) -> Model {
use crate::base::FileId;
use crate::hir::{FileText, RootDatabase, file_symbols_from_text};
use crate::interchange::model_from_symbols;
let db = RootDatabase::new();
let file_text = FileText::new(&db, FileId::new(0), source.to_string());
let symbols = file_symbols_from_text(&db, file_text);
model_from_symbols(&symbols)
}
#[test]
fn view_package_owned_members() {
let model = model_from_text("package X { part def A; part b: A; }");
let roots = model.root_views();
assert_eq!(roots.len(), 1);
let pkg = roots[0].as_package().expect("root should be a package");
assert_eq!(pkg.name(), Some("X"));
let members = pkg.owned_members();
assert!(members.len() >= 2, "should have at least A and b");
let a = members.iter().find(|m| m.name() == Some("A"));
assert!(a.is_some(), "should find definition A");
assert_eq!(a.unwrap().kind(), ElementKind::PartDefinition);
let b = members.iter().find(|m| m.name() == Some("b"));
assert!(b.is_some(), "should find usage b");
assert_eq!(b.unwrap().kind(), ElementKind::PartUsage);
}
#[test]
fn view_usage_typing() {
let model = model_from_text("package P { part def Wheel; part w: Wheel; }");
let w_views = model.find_by_name("w");
assert_eq!(w_views.len(), 1);
let w = w_views[0].as_usage().expect("w should be a usage");
let typed_by = w.typed_by();
assert!(typed_by.is_some(), "w should be typed by Wheel");
assert_eq!(typed_by.unwrap().name(), Some("Wheel"));
}
#[test]
fn view_definition_supertypes() {
let model = model_from_text("package P { part def Vehicle; part def Car :> Vehicle; }");
let car_views = model.find_by_name("Car");
assert_eq!(car_views.len(), 1);
let car = car_views[0]
.as_definition()
.expect("Car should be a definition");
let supers = car.supertypes();
assert_eq!(supers.len(), 1);
assert_eq!(supers[0].name(), Some("Vehicle"));
}
#[test]
fn view_ownership_navigation() {
let model = model_from_text("package Outer { part def Inner; }");
let inner_views = model.find_by_name("Inner");
assert_eq!(inner_views.len(), 1);
let inner = &inner_views[0];
let owner = inner.owner();
assert!(owner.is_some(), "Inner should have an owner");
assert_eq!(owner.unwrap().name(), Some("Outer"));
let outer = owner.unwrap();
let members = outer.owned_members();
assert!(
members.iter().any(|m| m.name() == Some("Inner")),
"Outer should own Inner"
);
}
#[test]
fn view_package_definitions_and_usages() {
let model =
model_from_text("package P { part def A; part def B; part x: A; attribute y; }");
let pkg = model.root_views()[0]
.as_package()
.expect("root should be a package");
let defs = pkg.definitions();
assert_eq!(defs.len(), 2, "should have 2 definitions (A, B)");
let usages = pkg.usages();
assert!(usages.len() >= 2, "should have at least x and y");
}
#[test]
fn view_find_by_kind() {
let model = model_from_text("package P { part def A; part def B; part x: A; }");
let part_defs = model.find_by_kind(ElementKind::PartDefinition);
assert_eq!(part_defs.len(), 2);
let part_usages = model.find_by_kind(ElementKind::PartUsage);
assert_eq!(part_usages.len(), 1);
assert_eq!(part_usages[0].name(), Some("x"));
}
#[test]
fn view_find_by_qualified_name() {
let model = model_from_text("package Outer { part def Inner; }");
let found = model.find_by_qualified_name("Outer::Inner");
assert!(found.is_some(), "should find Outer::Inner");
assert_eq!(found.unwrap().kind(), ElementKind::PartDefinition);
let not_found = model.find_by_qualified_name("DoesNotExist");
assert!(not_found.is_none());
}
#[test]
fn view_connection_ends() {
let model = model_from_text(
r#"package P {
part def A;
part def B;
part a: A;
part b: B;
connection c: A connect a to b;
}"#,
);
let conn_views = model.find_by_name("c");
if !conn_views.is_empty() {
if let Some(conn) = conn_views[0].as_connection() {
let ends = conn.ends();
for end in &ends {
assert!(end.is_end(), "end features should have is_end set");
}
}
}
}
#[test]
fn view_requirement_subject() {
let model = model_from_text(
r#"package P {
part def BrakeSubsystem;
requirement def Safety {
subject brakes: BrakeSubsystem;
doc /* Braking shall be safe. */
}
}"#,
);
let req_views = model.find_by_name("Safety");
if !req_views.is_empty() {
if let Some(req) = req_views[0].as_requirement() {
assert_eq!(req.name(), Some("Safety"));
assert!(req.is_definition());
}
}
}
#[test]
fn view_downcast_returns_none_for_wrong_kind() {
let model = model_from_text("package P { part def A; }");
let a = model.find_by_name("A");
assert_eq!(a.len(), 1);
assert!(a[0].as_package().is_none());
assert!(a[0].as_usage().is_none());
assert!(a[0].as_definition().is_some());
}
#[test]
fn view_root_views_match_roots() {
let model = model_from_text("package A; package B;");
let roots = model.root_views();
assert_eq!(roots.len(), model.roots.len());
}
}