use crate::lower::{HashedIdCounter, HashedNameSpelling, Lowering};
use alloc::string::String;
use alloc::vec::Vec;
use zerodds_ast::{
AnyId, AutoId, MemberIdAssignment, MemberIdOrigin, RepositoryIdOrigin, TypeKind, TypeNode,
};
use zerodds_ast_core::{Id, ScopedName};
use zerodds_idl_parse::DiagnosticCode;
type MemberSet = alloc::collections::BTreeSet<Id<zerodds_ast::MemberNode>>;
struct KeyDirective {
text: String,
field: String,
span: zerodds_idl_cst::Span,
}
struct KeyPath {
owner: Id<TypeNode>,
member: Id<zerodds_ast::MemberNode>,
rest: Vec<String>,
directive: KeyDirective,
}
impl Lowering<'_> {
pub(crate) fn complete(&mut self) {
self.apply_default_nested();
let types: Vec<Id<TypeNode>> = self.ast.types().map(|(id, _)| id).collect();
for id in self.base_first(&types) {
self.complete_type_defaults(id);
}
self.apply_repository_directives();
let mut paths = self.apply_keylists();
paths.extend(self.apply_open_dds_pragmas());
self.apply_key_paths(paths);
self.keep_unrecognised_pragmas();
self.derive_repository_ids(&types);
self.check_bit_bounds(&types);
}
fn apply_default_nested(&mut self) {
let Some(value) = self.options.default_nested else {
return;
};
let root = self.ast.root_module();
let Ok(node) = self.ast.module(root) else {
return;
};
let any = AnyId::Module(root, node.node_id);
let already = self
.ast
.facets(any)
.and_then(|facets| facets.xtypes.as_ref())
.and_then(|x| x.default_nested);
if already.is_none() {
if let Some(facets) = self.ast.facets_mut(any) {
facets.xtypes_mut().default_nested = Some(value);
}
}
}
fn check_bit_bounds(&mut self, types: &[Id<TypeNode>]) {
for id in types {
let Ok(node) = self.ast.type_(*id) else {
continue;
};
match node.kind {
TypeKind::Bitset { .. } => self.check_bitset_bounds(*id),
TypeKind::Bitmask { .. } => self.check_bitmask_bounds(*id),
TypeKind::Enumeration { .. } => self.check_enumeration_bound(*id),
_ => {}
}
}
}
fn check_enumeration_bound(&mut self, id: Id<TypeNode>) {
let Some(node) = self.ast.type_(id).ok() else {
return;
};
let Some(written) = node.facets.xtypes.as_ref().and_then(|x| x.bit_bound) else {
return;
};
let span = span_of(node.facets.source.span);
if written == 0 || written > MAX_ENUM_BIT_BOUND {
self.diagnose_at(
DiagnosticCode::InvalidBound,
span,
&alloc::format!(
"`@bit_bound({written})` on an enumeration, and §7.3.1.2.1.5 says the \
value is from 1 to {MAX_ENUM_BIT_BOUND}"
),
"DDS-XTypes 1.3 §7.3.1.2.1.5",
);
}
}
fn check_bitset_bounds(&mut self, id: Id<TypeNode>) {
let fields: Vec<_> = self.ast.bitfields_of(id).to_vec();
for field in fields {
let Ok(node) = self.ast.bitfield(field) else {
continue;
};
let bits = node.bits;
let span = span_of(node.facets.source.span);
let holder = node.holder_type.clone();
if bits > MAX_BITFIELD_BITS {
self.diagnose_at(
DiagnosticCode::InvalidBound,
span,
&alloc::format!(
"a bitfield stores {bits} bits, and §7.4.13.4.3.2 puts the maximum at \
{MAX_BITFIELD_BITS}"
),
"OMG IDL 4.2 §7.4.13.4.3.2",
);
continue;
}
let Some(width) = holder.and_then(|holder| self.holder_width(&holder)) else {
continue;
};
if bits > width {
self.diagnose_at(
DiagnosticCode::InvalidBound,
span,
&alloc::format!(
"a bitfield stores {bits} bits in a destination type that holds \
{width}; §7.4.13.4.3.2 says the number of stored bits cannot exceed \
the destination's size"
),
"OMG IDL 4.2 §7.4.13.4.3.2",
);
}
}
}
fn holder_width(&self, holder: &zerodds_ast_core::Ref<TypeNode>) -> Option<u16> {
let zerodds_ast_core::Ref::Resolved(id) = holder else {
return None;
};
let TypeKind::Primitive(primitive) = self.ast.type_(*id).ok()?.kind else {
return None;
};
Some(match primitive {
zerodds_ast::PrimitiveKind::Boolean => 1,
zerodds_ast::PrimitiveKind::Octet
| zerodds_ast::PrimitiveKind::Int8
| zerodds_ast::PrimitiveKind::UInt8 => 8,
zerodds_ast::PrimitiveKind::Int16 | zerodds_ast::PrimitiveKind::UInt16 => 16,
zerodds_ast::PrimitiveKind::Int32 | zerodds_ast::PrimitiveKind::UInt32 => 32,
zerodds_ast::PrimitiveKind::Int64 | zerodds_ast::PrimitiveKind::UInt64 => 64,
_ => return None,
})
}
fn check_bitmask_bounds(&mut self, id: Id<TypeNode>) {
let bound = self.ast.effective_bit_bound(id);
let declared = self
.ast
.type_(id)
.ok()
.and_then(|node| node.facets.xtypes.as_ref())
.and_then(|x| x.bit_bound);
let span = self
.ast
.type_(id)
.ok()
.map_or_else(|| span_of(None), |node| span_of(node.facets.source.span));
if let Some(written) = declared {
if written == 0 || written > MAX_BIT_BOUND {
self.diagnose_at(
DiagnosticCode::InvalidBound,
span,
&alloc::format!(
"`@bit_bound({written})` on a bitmask, and §7.4.13.4.3.3 says it \
must be lower than or equal to {MAX_BIT_BOUND}"
),
"OMG IDL 4.2 §7.4.13.4.3.3",
);
return;
}
}
let bits: Vec<_> = self.ast.bits_of(id).to_vec();
let count = bits.len();
if count > usize::from(bound) {
let how = if declared.is_some() {
"the `@bit_bound` it was given"
} else {
"the default size of 32"
};
self.diagnose_at(
DiagnosticCode::InvalidBound,
span,
&alloc::format!(
"the bitmask has {count} values and a size of {bound} bits; \
§7.4.13.4.3.3 says a bit mask holds no more values than {how}"
),
"OMG IDL 4.2 §7.4.13.4.3.3",
);
}
for bit in bits {
let Ok(node) = self.ast.bit_value(bit) else {
continue;
};
let Some(position) = node.position else {
continue;
};
if position >= bound {
let name = node.name.clone();
let bit_span = span_of(node.facets.source.span);
self.diagnose_at(
DiagnosticCode::InvalidBound,
bit_span,
&alloc::format!(
"`@position({position})` on `{name}` in a bitmask of {bound} bits; \
§7.4.13.4.3.3 puts the possible positions at 0 up to {}",
bound - 1
),
"OMG IDL 4.2 §7.4.13.4.3.3",
);
}
}
}
fn apply_open_dds_pragmas(&mut self) -> Vec<KeyPath> {
let mut paths = Vec::new();
for name in core::mem::take(&mut self.open_dds_types) {
let scoped = scoped_from_pragma(&name);
let Ok(any) = self.ast.find_by_scoped_name(&scoped) else {
self.unresolved_directive(Some(&scoped), "#pragma DCPS_DATA_TYPE");
continue;
};
if let Some(facets) = self.ast.facets_mut(any) {
facets
.vendor
.open_dds
.get_or_insert_with(Default::default)
.dcps_data_type = true;
}
}
for (name, fields, span) in core::mem::take(&mut self.open_dds_keys) {
let scoped = scoped_from_pragma(&name);
let Ok(any) = self.ast.find_by_scoped_name(&scoped) else {
self.unresolved_directive(Some(&scoped), "#pragma DCPS_DATA_KEY");
continue;
};
if let Some(facets) = self.ast.facets_mut(any) {
facets
.vendor
.open_dds
.get_or_insert_with(Default::default)
.dcps_data_keys
.extend(fields.iter().cloned());
}
let Some(type_) = any.as_type() else {
let message = alloc::format!(
"`#pragma DCPS_DATA_KEY {name}` names something that is not a type; \
the directive keys nothing"
);
self.diagnose_at(
DiagnosticCode::Unsupported,
span,
&message,
"OMG DDS 1.4 §2.2.1.2.2",
);
continue;
};
if fields.is_empty() {
let message = alloc::format!(
"`#pragma DCPS_DATA_KEY {name}` names no member; the directive keys nothing"
);
self.diagnose_at(
DiagnosticCode::Unsupported,
span,
&message,
"OMG DDS 1.4 §2.2.1.2.2",
);
continue;
}
for field in &fields {
let directive = KeyDirective {
text: alloc::format!("#pragma DCPS_DATA_KEY {name}"),
field: field.clone(),
span,
};
self.key_by_directive(type_, directive, &mut paths);
}
}
paths
}
fn keep_unrecognised_pragmas(&mut self) {
let pragmas = core::mem::take(&mut self.unrecognised_pragmas);
if pragmas.is_empty() {
return;
}
let root = self.ast.root_module();
let Ok(node_id) = self.ast.module(root).map(|m| m.node_id) else {
return;
};
if let Some(facets) = self.ast.facets_mut(AnyId::Module(root, node_id)) {
facets
.vendor
.rti
.get_or_insert_with(Default::default)
.unrecognised
.extend(pragmas);
}
}
fn apply_repository_directives(&mut self) {
for (candidates, text, is_prefix) in core::mem::take(&mut self.repository_directives) {
let Some((name, any)) = self.first_that_resolves(&candidates) else {
self.unresolved_directive(
candidates.first(),
if is_prefix { "typeprefix" } else { "typeid" },
);
continue;
};
let Some(facets) = self.ast.facets_mut(any) else {
continue;
};
let corba = facets.corba_mut();
if is_prefix {
self.prefixes.push((name, text));
} else {
corba.repository_id = Some(zerodds_ast_core::RepositoryId(text));
corba.repository_id_origin = Some(RepositoryIdOrigin::PragmaId);
}
}
}
fn first_that_resolves(&self, candidates: &[ScopedName]) -> Option<(ScopedName, AnyId)> {
candidates.iter().find_map(|name| {
self.ast
.find_by_scoped_name(name)
.ok()
.map(|any| (name.clone(), any))
})
}
fn apply_keylists(&mut self) -> Vec<KeyPath> {
let mut paths = Vec::new();
for (candidates, fields, span) in core::mem::take(&mut self.keylists) {
let Some((name, any)) = self.first_that_resolves(&candidates) else {
self.unresolved_directive(candidates.first(), "keylist");
continue;
};
if let Some(facets) = self.ast.facets_mut(any) {
facets
.vendor
.rti
.get_or_insert_with(Default::default)
.keylist = fields.clone();
}
let Some(type_) = any.as_type() else {
continue;
};
let written = name.segments().join("::");
for field in &fields {
let directive = KeyDirective {
text: alloc::format!("keylist {written}"),
field: field.clone(),
span,
};
self.key_by_directive(type_, directive, &mut paths);
}
}
paths
}
fn key_by_directive(
&mut self,
type_: Id<TypeNode>,
directive: KeyDirective,
paths: &mut Vec<KeyPath>,
) {
const SPEC: &str = "OMG DDS 1.4 §2.2.1.2.2";
let field = directive.field.as_str();
let text = directive.text.as_str();
if field.contains('[') {
let message = alloc::format!(
"`{text} {field}` names an element of a collection; a key is a member, and \
no member here says which element — the directive keys nothing"
);
self.diagnose_at(DiagnosticCode::Unsupported, directive.span, &message, SPEC);
return;
}
let segments: Vec<String> = field.split('.').map(String::from).collect();
if segments.iter().any(String::is_empty) {
let message =
alloc::format!("`{text} {field}` is not a member name or a path of member names");
self.diagnose_at(
DiagnosticCode::UnresolvedName,
directive.span,
&message,
SPEC,
);
return;
}
let Some(member) = self.member_named(type_, &segments[0]) else {
let message = alloc::format!(
"`{text}` names `{}`, which the type neither declares nor inherits; the \
directive keys nothing",
segments[0]
);
self.diagnose_at(
DiagnosticCode::UnresolvedName,
directive.span,
&message,
SPEC,
);
return;
};
if !self.ast.members_of(type_).contains(&member) {
if segments.len() == 1 && self.member_is_key(member) {
return;
}
let owner = self
.declaring_base(type_, member)
.and_then(|b| self.ast.type_(b).ok().map(|n| n.name.clone()))
.unwrap_or_default();
let message = alloc::format!(
"`{text} {field}` makes `{}`, which the type inherits from `{owner}`, a key; a \
derived structure defines no key fields, its keys are its root base's — the \
key belongs on `{owner}`",
segments[0]
);
self.diagnose_at(
DiagnosticCode::InvalidInheritance,
directive.span,
&message,
"DDS-XTypes 1.3 §7.2.2.4.5",
);
return;
}
if segments.len() == 1 {
self.mark_key(member);
return;
}
paths.push(KeyPath {
owner: type_,
member,
rest: segments[1..].to_vec(),
directive,
});
}
fn member_named(&self, type_: Id<TypeNode>, name: &str) -> Option<Id<zerodds_ast::MemberNode>> {
self.ast
.all_members_of(type_)
.iter()
.copied()
.find(|id| self.ast.member(*id).is_ok_and(|node| node.name == name))
}
fn declaring_base(
&self,
type_: Id<TypeNode>,
member: Id<zerodds_ast::MemberNode>,
) -> Option<Id<TypeNode>> {
let mut current = self.ast.base_of(type_);
while let Some(base) = current {
if self.ast.members_of(base).contains(&member) {
return Some(base);
}
current = self.ast.base_of(base);
}
None
}
fn member_is_key(&self, member: Id<zerodds_ast::MemberNode>) -> bool {
self.ast.member(member).is_ok_and(|node| {
self.ast
.facets(AnyId::Member(member, node.node_id))
.and_then(|f| f.xtypes.as_ref())
.is_some_and(|x| x.is_key)
})
}
fn mark_key(&mut self, member: Id<zerodds_ast::MemberNode>) {
let Ok(node_id) = self.ast.member(member).map(|node| node.node_id) else {
return;
};
if let Some(facets) = self.ast.facets_mut(AnyId::Member(member, node_id)) {
facets.xtypes.get_or_insert_with(Default::default).is_key = true;
}
}
fn apply_key_paths(&mut self, paths: Vec<KeyPath>) {
const SPEC: &str = "DDS-XTypes 1.3 §7.6.8";
struct Resolved {
directive: KeyDirective,
owner_member: Id<zerodds_ast::MemberNode>,
levels: Vec<(Id<TypeNode>, Id<zerodds_ast::MemberNode>)>,
}
let mut resolved: Vec<Resolved> = Vec::new();
'paths: for path in paths {
let text = path.directive.text.clone();
let field = path.directive.field.clone();
let mut levels = Vec::new();
let mut through = path.member;
for segment in &path.rest {
let nested = self
.ast
.member(through)
.ok()
.and_then(|node| node.type_.resolved())
.and_then(|t| self.through_aliases(t))
.filter(|t| {
matches!(
self.ast.type_(*t).map(|n| &n.kind),
Ok(TypeKind::Structure { .. })
)
});
let Some(nested) = nested else {
let name = self
.ast
.member(through)
.map(|n| n.name.clone())
.unwrap_or_default();
let message = alloc::format!(
"`{text} {field}` goes through `{name}`, which is not a structure; a \
path can only name a member of a nested structure"
);
self.diagnose_at(
DiagnosticCode::Unsupported,
path.directive.span,
&message,
SPEC,
);
continue 'paths;
};
let Some(member) = self.member_named(nested, segment) else {
let type_name = self
.ast
.type_(nested)
.map(|n| n.name.clone())
.unwrap_or_default();
let message = alloc::format!(
"`{text} {field}` names `{segment}`, which `{type_name}` neither \
declares nor inherits; the directive keys nothing"
);
self.diagnose_at(
DiagnosticCode::UnresolvedName,
path.directive.span,
&message,
SPEC,
);
continue 'paths;
};
levels.push((nested, member));
through = member;
}
let _ = path.owner;
resolved.push(Resolved {
directive: path.directive,
owner_member: path.member,
levels,
});
}
type Request = (MemberSet, usize);
let mut asked: alloc::collections::BTreeMap<Id<TypeNode>, Vec<Request>> =
alloc::collections::BTreeMap::new();
type Lead = (usize, Vec<Id<zerodds_ast::MemberNode>>);
let mut groups: alloc::collections::BTreeMap<Lead, (Id<TypeNode>, MemberSet, usize)> =
alloc::collections::BTreeMap::new();
let directive_index = |r: &Resolved| r.directive.span.start as usize;
for (index, r) in resolved.iter().enumerate() {
let mut lead = alloc::vec![r.owner_member];
for (nested, member) in &r.levels {
let entry = groups
.entry((directive_index(r), lead.clone()))
.or_insert_with(|| (*nested, Default::default(), index));
entry.1.insert(*member);
lead.push(*member);
}
}
for (nested, set, index) in groups.into_values() {
asked.entry(nested).or_default().push((set, index));
}
let mut refused: Vec<usize> = Vec::new();
let mut clashes: Vec<(zerodds_idl_cst::Span, String)> = Vec::new();
for (nested, requests) in &asked {
let own: alloc::collections::BTreeSet<_> = self
.ast
.all_members_of(*nested)
.iter()
.copied()
.filter(|m| self.member_is_key(*m))
.collect();
let type_name = self
.ast
.type_(*nested)
.map(|n| n.name.clone())
.unwrap_or_default();
let names = |set: &MemberSet| {
set.iter()
.filter_map(|m| self.ast.member(*m).ok().map(|n| n.name.clone()))
.collect::<Vec<_>>()
.join(", ")
};
let first = &requests[0].0;
for (set, index) in requests {
let clash = if !own.is_empty() && *set != own {
Some(alloc::format!(
"`{type_name}` keys itself by {}",
names(&own)
))
} else if set != first {
Some(alloc::format!(
"another path keys `{type_name}` by {}",
names(first)
))
} else {
None
};
if let Some(clash) = clash {
let directive = &resolved[*index].directive;
let message = alloc::format!(
"`{} {}` would key `{type_name}` by {}, but {clash}; one structure \
has one set of key members, which every key member of its type uses",
directive.text,
directive.field,
names(set)
);
clashes.push((directive.span, message));
refused.push(*index);
}
}
}
for (span, message) in clashes {
self.diagnose_at(DiagnosticCode::ConflictingAnnotations, span, &message, SPEC);
}
let path_members: alloc::collections::BTreeSet<_> = resolved
.iter()
.flat_map(|r| core::iter::once(r.owner_member).chain(r.levels.iter().map(|(_, m)| *m)))
.collect();
let keyless_targets: Vec<Id<TypeNode>> = asked
.keys()
.copied()
.filter(|t| {
!self
.ast
.all_members_of(*t)
.iter()
.any(|m| self.member_is_key(*m))
})
.collect();
let all_types: Vec<Id<TypeNode>> = self.ast.types().map(|(id, _)| id).collect();
for holder in all_types {
for member in self.ast.members_of(holder).to_vec() {
if path_members.contains(&member) || !self.member_is_key(member) {
continue;
}
let Some(target) = self
.ast
.member(member)
.ok()
.and_then(|node| node.type_.resolved())
.and_then(|t| self.through_aliases(t))
else {
continue;
};
if !keyless_targets.contains(&target) {
continue;
}
let holder_name = self
.ast
.type_(holder)
.map(|n| n.name.clone())
.unwrap_or_default();
let member_name = self
.ast
.member(member)
.map(|n| n.name.clone())
.unwrap_or_default();
let target_name = self
.ast
.type_(target)
.map(|n| n.name.clone())
.unwrap_or_default();
for (index, r) in resolved.iter().enumerate() {
if refused.contains(&index) || !r.levels.iter().any(|(t, _)| *t == target) {
continue;
}
let message = alloc::format!(
"`{} {}` would give `{target_name}` key members, and `{holder_name}::\
{member_name}` keys by the whole `{target_name}`; its key would shrink \
to the path's members",
r.directive.text,
r.directive.field
);
self.diagnose_at(
DiagnosticCode::ConflictingAnnotations,
r.directive.span,
&message,
SPEC,
);
refused.push(index);
}
}
}
for (index, r) in resolved.iter().enumerate() {
if refused.contains(&index) {
continue;
}
self.mark_key(r.owner_member);
for (_, member) in &r.levels {
self.mark_key(*member);
}
}
}
fn derive_repository_ids(&mut self, types: &[Id<TypeNode>]) {
let mut targets: Vec<AnyId> = Vec::new();
for id in types.iter().copied() {
if let Ok(node) = self.ast.type_(id)
&& !node.name.is_empty()
{
targets.push(AnyId::Type(id, node.node_id));
}
}
for (id, node) in self.ast.interfaces() {
if !node.name.is_empty() {
targets.push(AnyId::Interface(id, node.node_id));
}
}
for (id, node) in self.ast.exceptions() {
if !node.name.is_empty() {
targets.push(AnyId::Exception(id, node.node_id));
}
}
for any in targets {
let declared = self
.ast
.facets(any)
.and_then(|facets| facets.corba.as_ref())
.and_then(|c| c.repository_id.clone());
if declared.is_some() {
continue;
}
let Some(scoped) = self.ast.scoped_name_of(any) else {
continue;
};
let (id_text, origin) = self.derived_repository_id(&scoped);
if let Some(facets) = self.ast.facets_mut(any) {
let corba = facets.corba_mut();
corba.repository_id = Some(zerodds_ast_core::RepositoryId(id_text));
corba.repository_id_origin = Some(origin);
}
}
}
fn derived_repository_id(&self, scoped: &ScopedName) -> (String, RepositoryIdOrigin) {
let path = scoped.segments().join("/");
let version = self
.pragma_versions
.iter()
.find(|(name, _)| name.segments().last() == scoped.segments().last())
.map_or("1.0", |(_, version)| version.as_str());
let prefix = self
.prefixes
.iter()
.chain(self.pragma_prefixes.iter())
.filter(|(name, _)| starts_with(scoped, name))
.max_by_key(|(name, _)| name.segments().len());
match prefix {
Some((_, text)) => (
alloc::format!("IDL:{text}/{path}:{version}"),
RepositoryIdOrigin::PragmaPrefix,
),
None if version == "1.0" => (
alloc::format!("IDL:{path}:1.0"),
RepositoryIdOrigin::Derived,
),
None => (
alloc::format!("IDL:{path}:{version}"),
RepositoryIdOrigin::PragmaVersion,
),
}
}
fn unresolved_directive(&mut self, name: Option<&ScopedName>, what: &str) {
let written = name.map_or_else(
|| alloc::string::String::from("nothing"),
|name| name.segments().join("::"),
);
let message =
alloc::format!("`{what}` names `{written}`, which is not declared in this file");
self.diagnose_at(
DiagnosticCode::UnresolvedName,
zerodds_idl_cst::Span::new(0, 0),
&message,
"CORBA 3.3 §10.7",
);
}
fn complete_choice(&mut self, id: Id<TypeNode>) {
let Ok(node) = self.ast.type_(id) else {
return;
};
if !matches!(node.kind, TypeKind::Structure { .. })
|| !node
.facets
.annotations
.iter()
.any(|applied| applied.name == "Choice")
{
return;
}
let any = AnyId::Type(id, node.node_id);
let span = span_of(node.facets.source.span);
let declared = node.facets.xtypes.as_ref().and_then(|x| x.extensibility);
match declared {
Some(zerodds_ast::Extensibility::Mutable) => {}
Some(_) => self.diagnose_at(
DiagnosticCode::InvalidAnnotation,
span,
"a `@Choice` structure is `@mutable`; it cannot be given another extensibility",
"DDS-RPC 1.0 §7.5.1.2.1.1",
),
None => {
if let Some(facets) = self.ast.facets_mut(any) {
facets.xtypes_mut().extensibility = Some(zerodds_ast::Extensibility::Mutable);
}
}
}
for member in self.ast.members_of(id).to_vec() {
let Ok(node) = self.ast.member(member) else {
continue;
};
let member_any = AnyId::Member(member, node.node_id);
if let Some(facets) = self.ast.facets_mut(member_any) {
facets.xtypes_mut().is_optional = true;
}
}
}
fn complete_type_defaults(&mut self, id: Id<TypeNode>) {
self.complete_choice(id);
let Ok(node) = self.ast.type_(id) else {
return;
};
let node_id = node.node_id;
let any = AnyId::Type(id, node_id);
let is_aggregate = matches!(
node.kind,
TypeKind::Structure { .. } | TypeKind::Union { .. } | TypeKind::Valuetype { .. }
);
let derives = matches!(node.kind, TypeKind::Structure { base: Some(_), .. });
let filled = self.pragma_extensibility.or(if is_aggregate && !derives {
self.options.default_extensibility
} else {
None
});
if let Some(extensibility) = filled {
let declared = self
.ast
.facets(any)
.and_then(|facets| facets.xtypes.as_ref())
.and_then(|x| x.extensibility);
if declared.is_none() {
if let Some(facets) = self.ast.facets_mut(any) {
facets.xtypes_mut().extensibility = Some(extensibility);
}
}
}
if is_aggregate {
self.member_ids(id);
}
}
fn base_first(&self, types: &[Id<TypeNode>]) -> Vec<Id<TypeNode>> {
let mut out: Vec<Id<TypeNode>> = Vec::with_capacity(types.len());
for id in types.iter().copied() {
self.push_base_first(id, &mut out, 0);
}
out
}
fn push_base_first(&self, id: Id<TypeNode>, out: &mut Vec<Id<TypeNode>>, depth: usize) {
if depth > MAX_INHERITANCE_DEPTH || out.contains(&id) {
return;
}
if let Some(base) = self.ast.base_of(id) {
self.push_base_first(base, out, depth + 1);
}
out.push(id);
}
fn last_base_member_id(&self, id: Id<TypeNode>) -> Option<u32> {
let base = self.ast.base_of(id)?;
let last = *self.ast.all_members_of(base).last()?;
let node_id = self.ast.member(last).ok()?.node_id;
self.ast
.facets(AnyId::Member(last, node_id))
.and_then(|facets| facets.xtypes.as_ref())
.and_then(|x| x.member_id.as_ref())
.map(|assignment| assignment.effective)
}
fn member_ids(&mut self, id: Id<TypeNode>) {
let autoid = self.ast.effective_autoid(id);
let members: Vec<_> = self.ast.members_of(id).to_vec();
let mut previous: Option<u32> = self.last_base_member_id(id);
for member in members {
let Ok(node) = self.ast.member(member) else {
continue;
};
let member_any = AnyId::Member(member, node.node_id);
let name = node.name.clone();
let declared = self
.ast
.facets(member_any)
.and_then(|facets| facets.xtypes.as_ref())
.and_then(|x| x.member_id.clone());
let escaped = self.options.hashed_name_spelling == HashedNameSpelling::AsWritten
&& self
.ast
.facets(member_any)
.and_then(|facets| facets.xtypes.as_ref())
.is_some_and(|x| x.escaped);
let hash_of = |source: &str| {
if escaped && source == name {
hash_id(&alloc::format!("_{source}"))
} else {
hash_id(source)
}
};
let assignment = match declared {
Some(assignment) if assignment.origin == MemberIdOrigin::Declared => assignment,
Some(MemberIdAssignment {
origin: MemberIdOrigin::Hashed { source_name },
..
}) => MemberIdAssignment {
effective: hash_of(&source_name),
origin: MemberIdOrigin::Hashed { source_name },
},
_ => match autoid {
Some(AutoId::Hash) => MemberIdAssignment {
effective: hash_of(&name),
origin: MemberIdOrigin::AutoHash,
},
Some(AutoId::Sequential) => MemberIdAssignment {
effective: next_id(previous),
origin: MemberIdOrigin::AutoSequential,
},
None => MemberIdAssignment {
effective: next_id(previous),
origin: MemberIdOrigin::PreviousPlusOne,
},
},
};
let hashed = matches!(
assignment.origin,
MemberIdOrigin::Hashed { .. } | MemberIdOrigin::AutoHash
);
if !hashed || self.options.hashed_id_advances_the_counter == HashedIdCounter::Advances {
previous = Some(assignment.effective);
}
if let Some(facets) = self.ast.facets_mut(member_any) {
facets.xtypes_mut().member_id = Some(assignment);
}
}
}
}
const MAX_INHERITANCE_DEPTH: usize = 64;
fn next_id(previous: Option<u32>) -> u32 {
previous.map_or(0, |last| last.saturating_add(1))
}
fn hash_id(name: &str) -> u32 {
let digest = md5(name.as_bytes());
let leading = u32::from_le_bytes([digest[0], digest[1], digest[2], digest[3]]);
leading & 0x0FFF_FFFF
}
#[allow(clippy::many_single_char_names)]
fn md5(input: &[u8]) -> [u8; 16] {
const S: [u32; 64] = [
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5,
9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10,
15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
];
const K: [u32; 64] = [
0xd76a_a478,
0xe8c7_b756,
0x2420_70db,
0xc1bd_ceee,
0xf57c_0faf,
0x4787_c62a,
0xa830_4613,
0xfd46_9501,
0x6980_98d8,
0x8b44_f7af,
0xffff_5bb1,
0x895c_d7be,
0x6b90_1122,
0xfd98_7193,
0xa679_438e,
0x49b4_0821,
0xf61e_2562,
0xc040_b340,
0x265e_5a51,
0xe9b6_c7aa,
0xd62f_105d,
0x0244_1453,
0xd8a1_e681,
0xe7d3_fbc8,
0x21e1_cde6,
0xc337_07d6,
0xf4d5_0d87,
0x455a_14ed,
0xa9e3_e905,
0xfcef_a3f8,
0x676f_02d9,
0x8d2a_4c8a,
0xfffa_3942,
0x8771_f681,
0x6d9d_6122,
0xfde5_380c,
0xa4be_ea44,
0x4bde_cfa9,
0xf6bb_4b60,
0xbebf_bc70,
0x289b_7ec6,
0xeaa1_27fa,
0xd4ef_3085,
0x0488_1d05,
0xd9d4_d039,
0xe6db_99e5,
0x1fa2_7cf8,
0xc4ac_5665,
0xf429_2244,
0x432a_ff97,
0xab94_23a7,
0xfc93_a039,
0x655b_59c3,
0x8f0c_cc92,
0xffef_f47d,
0x8584_5dd1,
0x6fa8_7e4f,
0xfe2c_e6e0,
0xa301_4314,
0x4e08_11a1,
0xf753_7e82,
0xbd3a_f235,
0x2ad7_d2bb,
0xeb86_d391,
];
let mut message = input.to_vec();
let bit_length = (input.len() as u64).wrapping_mul(8);
message.push(0x80);
while message.len() % 64 != 56 {
message.push(0);
}
message.extend_from_slice(&bit_length.to_le_bytes());
let mut a0: u32 = 0x6745_2301;
let mut b0: u32 = 0xefcd_ab89;
let mut c0: u32 = 0x98ba_dcfe;
let mut d0: u32 = 0x1032_5476;
for chunk in message.chunks_exact(64) {
let mut words = [0u32; 16];
for (index, word) in words.iter_mut().enumerate() {
let at = index * 4;
*word = u32::from_le_bytes([chunk[at], chunk[at + 1], chunk[at + 2], chunk[at + 3]]);
}
let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
for i in 0..64 {
let (f, g) = match i / 16 {
0 => ((b & c) | (!b & d), i),
1 => ((d & b) | (!d & c), (5 * i + 1) % 16),
2 => (b ^ c ^ d, (3 * i + 5) % 16),
_ => (c ^ (b | !d), (7 * i) % 16),
};
let tmp = d;
d = c;
c = b;
let sum = a.wrapping_add(f).wrapping_add(K[i]).wrapping_add(words[g]);
b = b.wrapping_add(sum.rotate_left(S[i]));
a = tmp;
}
a0 = a0.wrapping_add(a);
b0 = b0.wrapping_add(b);
c0 = c0.wrapping_add(c);
d0 = d0.wrapping_add(d);
}
let mut out = [0u8; 16];
out[0..4].copy_from_slice(&a0.to_le_bytes());
out[4..8].copy_from_slice(&b0.to_le_bytes());
out[8..12].copy_from_slice(&c0.to_le_bytes());
out[12..16].copy_from_slice(&d0.to_le_bytes());
out
}
#[must_use]
pub fn hashed_member_id(name: &str) -> u32 {
hash_id(name)
}
#[must_use]
pub fn md5_hex(input: &[u8]) -> String {
let digest = md5(input);
let mut out = String::new();
for byte in digest {
out.push(hex_digit(byte >> 4));
out.push(hex_digit(byte & 0x0F));
}
out
}
fn hex_digit(value: u8) -> char {
match value {
0..=9 => char::from(b'0' + value),
_ => char::from(b'a' + value - 10),
}
}
fn scoped_from_pragma(name: &str) -> ScopedName {
ScopedName::absolute(
name.split("::")
.filter(|segment| !segment.is_empty())
.map(alloc::string::ToString::to_string)
.collect(),
)
}
fn starts_with(name: &ScopedName, prefix: &ScopedName) -> bool {
let segments = name.segments();
let head = prefix.segments();
head.len() < segments.len() && segments.starts_with(head)
}
const MAX_BITFIELD_BITS: u16 = 64;
const MAX_BIT_BOUND: u16 = 64;
const MAX_ENUM_BIT_BOUND: u16 = 32;
fn span_of(span: Option<zerodds_ast::ByteSpan>) -> zerodds_idl_cst::Span {
span.map_or_else(
|| zerodds_idl_cst::Span::new(0, 0),
|span| zerodds_idl_cst::Span::new(span.start, span.end),
)
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn the_digest_matches_the_published_vectors() {
for (input, expected) in [
("", "d41d8cd98f00b204e9800998ecf8427e"),
("a", "0cc175b9c0f1b6a831c399e269772661"),
("abc", "900150983cd24fb0d6963f7d28e17f72"),
("message digest", "f96b697d7cb7938d525a2f31aaf161d0"),
(
"abcdefghijklmnopqrstuvwxyz",
"c3fcd3d76192e4007dfb496cca67e13b",
),
(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
"d174ab98d277d9f5a5611c2c9f419d9f",
),
(
"12345678901234567890123456789012345678901234567890123456789012345678901234567890",
"57edf4a22be3c955ac49da2e2107b67a",
),
] {
assert_eq!(md5_hex(input.as_bytes()), expected, "{input:?}");
}
}
#[test]
fn a_hashed_id_stays_inside_the_28_bit_space() {
for name in ["a", "member", "a_rather_longer_member_name"] {
assert_eq!(hash_id(name) & 0xF000_0000, 0, "{name}");
}
}
#[test]
fn different_names_hash_differently() {
assert_ne!(hash_id("a"), hash_id("b"));
}
#[test]
fn the_clause_works_two_hashes_out_by_hand() {
assert_eq!(hash_id("color"), 0x0FA5_DD70);
assert_eq!(hash_id("shapesize"), 0x0477_90DA);
}
#[test]
fn two_names_can_share_an_id_after_the_mask() {
assert_eq!(hash_id("m9852"), 0x0BE1_7DB5);
assert_eq!(hash_id("m12254"), 0x0BE1_7DB5);
assert_ne!(md5_hex(b"m9852")[..8], md5_hex(b"m12254")[..8]);
}
}