use std::collections::HashSet;
use super::resolve::{Scope, is_foreign};
use super::v1;
use crate::v2;
pub(crate) fn scalar_class(td: &v2::TypeDef) -> v1::ScalarClass {
match td
.backing
.as_ref()
.and_then(|backing| backing.kind.as_ref())
{
Some(v2::backing::Kind::Unit(_)) => v1::ScalarClass::Float,
Some(v2::backing::Kind::Primitive(primitive)) => {
match v2::PrimitiveType::try_from(*primitive).unwrap_or(v2::PrimitiveType::Unspecified)
{
v2::PrimitiveType::Boolean => v1::ScalarClass::Boolean,
v2::PrimitiveType::Integer => v1::ScalarClass::Integer,
v2::PrimitiveType::Float => v1::ScalarClass::Float,
v2::PrimitiveType::String => v1::ScalarClass::String,
v2::PrimitiveType::Bytes | v2::PrimitiveType::Unspecified => v1::ScalarClass::Bytes,
}
}
None => v1::ScalarClass::Float,
}
}
#[derive(Clone, Copy, Default)]
pub(crate) struct Position<'a> {
pub init_value: Option<&'a str>,
pub declared_init: Option<&'a str>,
pub flag: Option<bool>,
}
pub(crate) struct Inits<'a> {
scope: Scope<'a>,
visiting: HashSet<(String, String)>,
}
impl<'a> Inits<'a> {
pub fn new(scope: Scope<'a>) -> Self {
Self {
scope,
visiting: HashSet::new(),
}
}
pub fn declaration(&mut self, home: &'a v2::Package, decl: &v2::Decl) -> v1::Init {
let (derivable, value) = match &decl.kind {
Some(v2::decl::Kind::TypeDef(td)) => (
self.type_def(td),
td.init.as_ref().and_then(|i| i.value.clone()),
),
_ => (self.decl_derivable(home, decl), None),
};
let declared = match &decl.kind {
Some(v2::decl::Kind::TypeDef(td)) => td.declared_init.clone(),
_ => None,
};
let one_level = match &decl.kind {
Some(v2::decl::Kind::TypeDef(td)) => {
td.init.as_ref().is_some_and(|value| value.derivable)
}
_ => false,
};
init_with(derivable, value, declared.is_some(), one_level)
}
pub fn field(&mut self, home: &'a v2::Package, field: &v2::Field) -> v1::Init {
let resolved = field.init.as_ref();
let position = Position {
init_value: resolved.and_then(|i| i.value.as_deref()),
declared_init: field.declared_init.as_deref(),
flag: Some(resolved.is_some_and(|i| i.derivable)),
};
let derivable = match field.r#type.as_ref() {
Some(ty) => self.position(home, ty, position),
None => false,
};
init_with(
derivable,
resolved.and_then(|i| i.value.clone()),
field.declared_init.is_some(),
resolved.is_some_and(|i| i.derivable),
)
}
pub fn tuple_field(&mut self, home: &'a v2::Package, field: &v2::TupleField) -> v1::Init {
let derivable = match field.r#type.as_ref() {
Some(ty) => self.position(home, ty, Position::default()),
None => false,
};
init(derivable, None, false)
}
pub fn tuple(&mut self, home: &'a v2::Package, tuple: &v2::TupleType) -> v1::Init {
let derivable = self.tuple_derivable(home, tuple);
init(derivable, None, false)
}
pub fn signal(&mut self, value: Option<&v2::InitValue>, declared: bool) -> v1::Init {
init_with(
value.is_some_and(|i| i.derivable),
value.and_then(|i| i.value.clone()),
declared,
value.is_some_and(|i| i.derivable),
)
}
fn type_def(&mut self, td: &v2::TypeDef) -> bool {
let Some(resolved) = td.init.as_ref() else {
return false;
};
resolved.derivable && scalar_value(scalar_class(td), resolved.value.as_deref())
}
fn decl_derivable(&mut self, home: &'a v2::Package, decl: &v2::Decl) -> bool {
let key = (home.name.clone(), decl.name.clone());
if !self.visiting.insert(key.clone()) {
return false;
}
let derivable = match &decl.kind {
Some(v2::decl::Kind::TypeDef(td)) => self.type_def(td),
Some(v2::decl::Kind::StructDef(def)) => def.members.iter().all(|member| {
match &member.member {
Some(v2::struct_member::Member::Field(field)) => {
self.field_derivable(home, field)
}
_ => true,
}
}),
Some(v2::decl::Kind::EnumDef(def)) => !def.values.is_empty(),
Some(v2::decl::Kind::EnumSetDef(_)) => true,
Some(v2::decl::Kind::UnionDef(def)) => match def.arms.first() {
Some(arm) => self.reference_derivable(home, &arm.type_ref, Position::default()),
None => false,
},
_ => false,
};
self.visiting.remove(&key);
derivable
}
fn field_derivable(&mut self, home: &'a v2::Package, field: &v2::Field) -> bool {
let resolved = field.init.as_ref();
let position = Position {
init_value: resolved.and_then(|i| i.value.as_deref()),
declared_init: field.declared_init.as_deref(),
flag: Some(resolved.is_some_and(|i| i.derivable)),
};
match field.r#type.as_ref() {
Some(ty) => self.position(home, ty, position),
None => false,
}
}
fn tuple_derivable(&mut self, home: &'a v2::Package, tuple: &v2::TupleType) -> bool {
tuple
.fields
.iter()
.all(|field| match field.r#type.as_ref() {
Some(ty) => self.position(home, ty, Position::default()),
None => false,
})
}
fn position(&mut self, home: &'a v2::Package, ty: &v2::FieldType, at: Position<'_>) -> bool {
if ty.optional {
return true;
}
match ty.kind.as_ref() {
Some(v2::field_type::Kind::Named(reference)) => {
self.reference_derivable(home, reference, at)
}
Some(v2::field_type::Kind::Primitive(primitive)) => {
match v2::PrimitiveType::try_from(*primitive)
.unwrap_or(v2::PrimitiveType::Unspecified)
{
v2::PrimitiveType::Integer
| v2::PrimitiveType::Float
| v2::PrimitiveType::Boolean => true,
v2::PrimitiveType::String | v2::PrimitiveType::Bytes => at.flag != Some(false),
v2::PrimitiveType::Unspecified => false,
}
}
Some(v2::field_type::Kind::InlineScalar(td)) => {
at.flag != Some(false) && scalar_value(scalar_class(td), at.init_value)
}
Some(v2::field_type::Kind::Tuple(tuple)) => self.tuple_derivable(home, tuple),
Some(v2::field_type::Kind::Array(array)) => {
let Some(element) = array.element.as_deref() else {
return false;
};
let inner = Position {
flag: at.flag,
..Position::default()
};
if array.min == array.max {
array.max == 0 || self.position(home, element, inner)
} else if array.min == 0 {
true
} else {
self.position(home, element, inner)
}
}
Some(v2::field_type::Kind::Map(map)) => {
if map.min == 0 {
return true;
}
let inner = Position {
flag: at.flag,
..Position::default()
};
let (Some(key), Some(value)) = (map.key.as_deref(), map.value.as_deref()) else {
return false;
};
self.position(home, key, inner) && self.position(home, value, inner)
}
_ => false,
}
}
fn reference_derivable(
&mut self,
home: &'a v2::Package,
reference: &str,
at: Position<'_>,
) -> bool {
match self.scope.resolve(home, reference) {
Some((decl, declaring)) => match &decl.kind {
Some(v2::decl::Kind::TypeDef(td)) => {
if !self.type_def(td) {
return false;
}
match at.declared_init {
Some(declared) => scalar_value(scalar_class(td), Some(declared)),
None => true,
}
}
_ => self.decl_derivable(declaring, decl),
},
None if is_foreign(reference) => {
at.declared_init.is_none() && at.flag == Some(true)
}
None => false,
}
}
}
fn scalar_value(class: v1::ScalarClass, value: Option<&str>) -> bool {
match class {
v1::ScalarClass::Float | v1::ScalarClass::Integer => value.is_some(),
v1::ScalarClass::Boolean | v1::ScalarClass::String => true,
v1::ScalarClass::Bytes => value.is_none_or(str::is_empty),
v1::ScalarClass::Unspecified => false,
}
}
fn init(derivable: bool, value: Option<String>, declared: bool) -> v1::Init {
init_with(derivable, value, declared, false)
}
fn init_with(derivable: bool, value: Option<String>, declared: bool, one_level: bool) -> v1::Init {
let source = if declared {
v1::InitSource::Declared
} else if derivable {
v1::InitSource::Derived
} else {
v1::InitSource::None
};
v1::Init {
derivable,
value,
source: source as i32,
one_level,
}
}
pub(crate) struct Closures<'a> {
scope: Scope<'a>,
visiting: HashSet<(String, String)>,
}
impl<'a> Closures<'a> {
pub fn new(scope: Scope<'a>) -> Self {
Self {
scope,
visiting: HashSet::new(),
}
}
pub fn declaration(&mut self, home: &'a v2::Package, decl: &v2::Decl) -> v1::Closure {
let mut found = v1::Closure::default();
self.walk_decl(home, decl, &mut found);
found
}
pub fn tuple(&mut self, home: &'a v2::Package, tuple: &v2::TupleType) -> v1::Closure {
let mut found = v1::Closure::default();
self.walk_tuple(home, tuple, &mut found);
found
}
fn walk_decl(&mut self, home: &'a v2::Package, decl: &v2::Decl, found: &mut v1::Closure) {
let key = (home.name.clone(), decl.name.clone());
if !self.visiting.insert(key.clone()) {
found.reaches_cycle = true;
return;
}
match &decl.kind {
Some(v2::decl::Kind::TypeDef(td)) => class_fact(scalar_class(td), found),
Some(v2::decl::Kind::EnumDef(_)) | Some(v2::decl::Kind::EnumSetDef(_)) => {}
Some(v2::decl::Kind::StructDef(def)) => {
for member in &def.members {
if let Some(v2::struct_member::Member::Field(field)) = &member.member {
match field.r#type.as_ref() {
Some(ty) => self.walk_type(home, ty, found),
None => found.reaches_stream_or_unspecified = true,
}
}
}
}
Some(v2::decl::Kind::UnionDef(def)) => {
for arm in &def.arms {
self.walk_reference(home, &arm.type_ref, found);
}
}
_ => found.reaches_unresolved = true,
}
self.visiting.remove(&key);
}
fn walk_tuple(
&mut self,
home: &'a v2::Package,
tuple: &v2::TupleType,
found: &mut v1::Closure,
) {
for field in &tuple.fields {
match field.r#type.as_ref() {
Some(ty) => self.walk_type(home, ty, found),
None => found.reaches_stream_or_unspecified = true,
}
}
}
fn walk_type(&mut self, home: &'a v2::Package, ty: &v2::FieldType, found: &mut v1::Closure) {
match ty.kind.as_ref() {
Some(v2::field_type::Kind::Named(reference)) => {
self.walk_reference(home, reference, found);
}
Some(v2::field_type::Kind::Primitive(primitive)) => {
match v2::PrimitiveType::try_from(*primitive)
.unwrap_or(v2::PrimitiveType::Unspecified)
{
v2::PrimitiveType::Float => found.reaches_float = true,
v2::PrimitiveType::Integer | v2::PrimitiveType::Boolean => {}
v2::PrimitiveType::String | v2::PrimitiveType::Bytes => {
found.reaches_text_or_bytes = true;
}
v2::PrimitiveType::Unspecified => found.reaches_stream_or_unspecified = true,
}
}
Some(v2::field_type::Kind::InlineScalar(td)) => class_fact(scalar_class(td), found),
Some(v2::field_type::Kind::Tuple(tuple)) => self.walk_tuple(home, tuple, found),
Some(v2::field_type::Kind::Array(array)) => {
found.reaches_collection = true;
match array.element.as_deref() {
Some(element) => self.walk_type(home, element, found),
None => found.reaches_stream_or_unspecified = true,
}
}
Some(v2::field_type::Kind::Map(map)) => {
found.reaches_collection = true;
match map.key.as_deref() {
Some(key) => self.walk_type(home, key, found),
None => found.reaches_stream_or_unspecified = true,
}
match map.value.as_deref() {
Some(value) => self.walk_type(home, value, found),
None => found.reaches_stream_or_unspecified = true,
}
}
_ => found.reaches_stream_or_unspecified = true,
}
}
fn walk_reference(&mut self, home: &'a v2::Package, reference: &str, found: &mut v1::Closure) {
if is_foreign(reference) {
found.reaches_foreign = true;
}
match self.scope.resolve(home, reference) {
Some((decl, _)) if matches!(decl.kind, Some(v2::decl::Kind::ConstDef(_))) => {
found.reaches_unresolved = true;
}
Some((decl, declaring)) => self.walk_decl(declaring, decl, found),
None => found.reaches_unresolved = true,
}
}
}
fn class_fact(class: v1::ScalarClass, found: &mut v1::Closure) {
match class {
v1::ScalarClass::Float => found.reaches_float = true,
v1::ScalarClass::String | v1::ScalarClass::Bytes => found.reaches_text_or_bytes = true,
v1::ScalarClass::Integer | v1::ScalarClass::Boolean => {}
v1::ScalarClass::Unspecified => found.reaches_stream_or_unspecified = true,
}
}