use crate::annotation::{Applied, UseSite};
type TemplateArgument = (
alloc::vec::Vec<alloc::sync::Arc<SyntaxNode>>,
alloc::sync::Arc<SyntaxNode>,
);
use crate::scope::ScopeStack;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use zerodds_ast::{
AnyId, Ast, BinaryOp, Bound, CharWidth, Completeness, ConstExpr, ConstExprNode, Facets,
InterfaceKind, Literal, MemberSpec, ParamDirection, PrimitiveKind, SourceFacet, SourceOrigin,
TypeKind, TypeNode, UnaryOp, ValuetypeKind,
};
use zerodds_ast_core::{Id, PendingRef, Ref, ScopedName};
use zerodds_idl_cst::{CstKind, Span, SyntaxNode, SyntaxSpans, TokenKind};
use zerodds_idl_parse::{Diagnostic, DiagnosticCode};
pub struct Lowered {
pub ast: Ast,
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Debug, Clone, Copy)]
pub struct Options {
pub default_extensibility: Option<zerodds_ast::Extensibility>,
pub default_nested: Option<bool>,
pub hashed_id_advances_the_counter: HashedIdCounter,
pub hashed_name_spelling: HashedNameSpelling,
pub key_on_union_discriminator: KeyOnUnionDiscriminator,
pub name_of_the_enclosing_scope: NameOfTheEnclosingScope,
pub dialect: zerodds_idl_parse::Dialect,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ValueForm {
Boolean,
Integer,
Float,
Fixed,
Char,
WChar,
String,
WString,
}
impl ValueForm {
const fn is_numeric(self) -> bool {
matches!(self, Self::Integer | Self::Float | Self::Fixed)
}
const fn describe(self) -> &'static str {
match self {
Self::Boolean => "a boolean",
Self::Integer => "an integer literal",
Self::Float => "a floating-point literal",
Self::Fixed => "a fixed-point literal",
Self::Char => "a character literal",
Self::WChar => "a wide character literal",
Self::String => "a string literal",
Self::WString => "a wide string literal",
}
}
}
impl Default for Options {
fn default() -> Self {
Self {
default_extensibility: None,
default_nested: None,
hashed_id_advances_the_counter: HashedIdCounter::default(),
hashed_name_spelling: HashedNameSpelling::default(),
key_on_union_discriminator: KeyOnUnionDiscriminator::default(),
name_of_the_enclosing_scope: NameOfTheEnclosingScope::default(),
dialect: zerodds_idl_parse::Dialect::ALL_VENDORS,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NameOfTheEnclosingScope {
#[default]
Refused,
Admitted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HashedNameSpelling {
#[default]
Stripped,
AsWritten,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HashedIdCounter {
#[default]
Advances,
Skipped,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum KeyOnUnionDiscriminator {
Refused,
#[default]
Carried,
}
impl KeyOnUnionDiscriminator {
pub const ALL: [Self; 2] = [Self::Refused, Self::Carried];
#[must_use]
pub const fn is_default(self) -> bool {
matches!(self, Self::Carried)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Refused => "refused",
Self::Carried => "carried",
}
}
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
match value {
"refused" => Some(Self::Refused),
"carried" => Some(Self::Carried),
_ => None,
}
}
}
#[must_use]
pub fn lower(root: &Arc<SyntaxNode>, path: &str) -> Lowered {
lower_with(root, path, Options::default())
}
#[must_use]
pub fn lower_with(root: &Arc<SyntaxNode>, path: &str, options: Options) -> Lowered {
let mut ast = Ast::new();
let diagnostics = lower_into_with(&mut ast, root, path, options);
Lowered { ast, diagnostics }
}
pub fn lower_into(ast: &mut Ast, root: &Arc<SyntaxNode>, path: &str) -> Vec<Diagnostic> {
lower_into_with(ast, root, path, Options::default())
}
pub fn lower_into_with(
ast: &mut Ast,
root: &Arc<SyntaxNode>,
path: &str,
options: Options,
) -> Vec<Diagnostic> {
let mut ctx = Lowering {
ast,
scopes: ScopeStack::new_placeholder(),
diagnostics: Vec::new(),
path: path.to_string(),
syntax_spans: SyntaxSpans::of(root),
options,
pending_annotations: Vec::new(),
pragma_extensibility: None,
repository_directives: Vec::new(),
module_ranges: Vec::new(),
keylists: Vec::new(),
prefixes: Vec::new(),
pragma_prefixes: Vec::new(),
pragma_versions: Vec::new(),
open_dds_types: Vec::new(),
open_dds_keys: Vec::new(),
unrecognised_pragmas: Vec::new(),
xtopics_regions: Vec::new(),
templates: Vec::new(),
};
ctx.scopes = ScopeStack::new(ctx.ast.root_module());
ctx.file(root);
ctx.complete();
ctx.validate();
let mut diagnostics = core::mem::take(&mut ctx.diagnostics);
for diagnostic in &mut diagnostics {
diagnostic.primary = ctx.syntax_spans.trim(diagnostic.primary);
for (span, _) in &mut diagnostic.labels {
*span = ctx.syntax_spans.trim(*span);
}
}
dedup(diagnostics)
}
pub(crate) struct Lowering<'a> {
pub(crate) ast: &'a mut Ast,
pub(crate) scopes: ScopeStack,
pub(crate) diagnostics: Vec<Diagnostic>,
pub(crate) path: String,
pub(crate) syntax_spans: SyntaxSpans,
pub(crate) options: Options,
pub(crate) pragma_extensibility: Option<zerodds_ast::Extensibility>,
pub(crate) pending_annotations: Vec<Vec<crate::annotation::Applied>>,
pub(crate) repository_directives: Vec<(Vec<ScopedName>, String, bool)>,
pub(crate) module_ranges: Vec<(Span, ScopedName)>,
pub(crate) keylists: Vec<(Vec<ScopedName>, Vec<String>, Span)>,
pub(crate) prefixes: Vec<(ScopedName, String)>,
pub(crate) pragma_prefixes: Vec<(ScopedName, String)>,
pub(crate) pragma_versions: Vec<(ScopedName, String)>,
pub(crate) open_dds_types: Vec<String>,
pub(crate) open_dds_keys: Vec<(String, Vec<String>, Span)>,
pub(crate) unrecognised_pragmas: Vec<(String, String)>,
pub(crate) xtopics_regions: Vec<(Span, String)>,
pub(crate) templates: Vec<(ScopedName, Arc<SyntaxNode>)>,
}
impl Lowering<'_> {
fn file(&mut self, node: &Arc<SyntaxNode>) {
self.identifiers_do_not_collide_with_keywords(node);
for child in node.children() {
self.definition(&child);
}
self.collect_pragmas(node);
}
fn identifiers_do_not_collide_with_keywords(&mut self, root: &Arc<SyntaxNode>) {
let mut found: Vec<(Span, String, &'static str)> = Vec::new();
for node in root.descendants() {
for (span, token) in node.tokens() {
if token.kind() != TokenKind::Ident {
continue;
}
let text = token.text();
if text.starts_with('_') {
continue;
}
if let Some(keyword) = zerodds_idl_cst::keyword_collision(text) {
found.push((span, text.to_string(), keyword));
}
}
}
for (span, name, keyword) in found {
let message = alloc::format!(
"`{name}` differs from the keyword `{keyword}` only in case, and IDL compares \
identifiers without regard to case. Write the keyword's own spelling, or escape \
the identifier as `_{name}`"
);
self.diagnose_at(
DiagnosticCode::KeywordCollision,
span,
&message,
"OMG IDL 4.2 ยง7.2.3, ยง7.2.4",
);
}
}
fn collect_pragmas(&mut self, root: &Arc<SyntaxNode>) {
let mut found: Vec<(Span, String)> = Vec::new();
for node in root.descendants() {
for (span, token) in node.tokens() {
if token.kind() != TokenKind::Directive {
continue;
}
let line = token.text().trim();
let Some(rest) = line.strip_prefix('#') else {
continue;
};
let rest = rest.trim_start();
let Some(body) = rest.strip_prefix("pragma") else {
continue;
};
found.push((span, body.trim().to_string()));
}
}
found.sort_by_key(|(span, _)| span.start);
for (span, body) in found {
self.pragma(span, &body);
}
}
fn pragma(&mut self, span: Span, body: &str) {
let mut words = body.split_whitespace();
let Some(directive) = words.next() else {
return;
};
let rest: Vec<&str> = words.collect();
match directive {
"prefix" => {
if let Some(text) = unquote(rest.first().copied().unwrap_or_default()) {
let scope = self.module_containing(span);
self.record_repository_directive("typeprefix", &text);
self.pragma_prefixes.push((scope, text));
}
}
"version" => {
if let (Some(name), Some(version)) = (rest.first(), rest.get(1)) {
self.pragma_versions
.push((pragma_name(name), (*version).to_string()));
}
}
"ID" => {
if let (Some(name), Some(id)) = (rest.first(), rest.get(1).copied()) {
if let Some(text) = unquote(id) {
let candidates = self.pragma_candidates(span, name);
self.record_repository_directive("typeid", &text);
self.repository_directives.push((candidates, text, false));
}
}
}
"keylist" => {
self.diagnose_vendor(
zerodds_idl_parse::VendorFlag::Cyclone,
span,
"`#pragma keylist` is a Cyclone DDS directive and is in no building block \
of OMG IDL 4.2; it makes members keys, so a front end that ignores it \
builds a different type from the same file",
);
if let Some((name, fields)) = rest.split_first() {
let fields = fields
.iter()
.flat_map(|f| f.split(','))
.filter(|f| !f.is_empty())
.map(ToString::to_string)
.collect();
let candidates = self.pragma_candidates(span, name);
self.keylists.push((candidates, fields, span));
}
}
"DCPS_DATA_TYPE" => {
self.diagnose_vendor(
zerodds_idl_parse::VendorFlag::OpenDds,
span,
"`#pragma DCPS_DATA_TYPE` is an OpenDDS directive and is in no building \
block of OMG IDL 4.2; it marks the type as a topic type, so a front end \
that ignores it builds a different type from the same file. The same \
spelling is also OMG DDS 1.2 ยง8's DLRL directive that marks a DLRL object \
type โ this front end reads it as OpenDDS's, and `zerodds-idlc dlrl` \
reads it as DLRL's",
);
match rest.first().copied() {
Some(word) => {
self.open_dds_types
.push(unquote(word).unwrap_or_else(|| word.to_string()));
}
None => self.diagnose_at(
DiagnosticCode::Unsupported,
span,
"`#pragma DCPS_DATA_TYPE` names no type",
"OMG DDS 1.4 ยง2.2.1.2.2",
),
}
}
"DCPS_DATA_KEY" => {
self.diagnose_vendor(
zerodds_idl_parse::VendorFlag::OpenDds,
span,
"`#pragma DCPS_DATA_KEY` is an OpenDDS directive and is in no building \
block of OMG IDL 4.2; it makes a member a key, so a front end that \
ignores it builds a different type from the same file. The same spelling \
is also OMG DDS 1.2 ยง8's DLRL directive that marks a DLRL object key โ \
this front end reads it as OpenDDS's, and `zerodds-idlc dlrl` reads it as \
DLRL's",
);
let joined = rest.join(" ");
let words: Vec<String> = unquote(&joined)
.unwrap_or(joined)
.split_whitespace()
.map(String::from)
.collect();
if let Some((name, fields)) = words.split_first() {
self.open_dds_keys
.push((name.clone(), fields.to_vec(), span));
} else {
self.diagnose_at(
DiagnosticCode::Unsupported,
span,
"`#pragma DCPS_DATA_KEY` names neither a type nor a member; \
the directive keys nothing",
"OMG DDS 1.4 ยง2.2.1.2.2",
);
}
}
"DCPS_DLRL_RELATION" => {
self.warn_at(
DiagnosticCode::Unsupported,
span,
"`#pragma DCPS_DLRL_RELATION` is OMG DDS 1.2 ยง8's DLRL relation \
directive; this front end does not turn it into a type-model facet โ \
`zerodds-idlc dlrl` reads it, together with `DCPS_DATA_TYPE` and \
`DCPS_DATA_KEY`, into DLRL object and home classes",
"OMG DDS 1.2 ยง8.2.2",
);
self.unrecognised_pragmas
.push((directive.to_string(), rest.join(" ")));
}
"dds_xtopics" => {
self.dds_xtopics(span, &rest);
self.unrecognised_pragmas
.push((directive.to_string(), rest.join(" ")));
}
other => {
self.warn_at(
DiagnosticCode::Unsupported,
span,
&alloc::format!(
"`#pragma {other}` is not a directive this front end interprets; it is \
kept verbatim on the model and changes nothing"
),
"DDS-XTypes 1.3 ยง7.3.1.1.1",
);
self.unrecognised_pragmas
.push((other.to_string(), rest.join(" ")));
}
}
}
fn dds_xtopics(&mut self, span: Span, rest: &[&str]) {
let spec = "DDS-XTypes 1.3 ยง7.3.1.1.2";
let Some((keyword, version)) = rest.split_first() else {
self.warn_at(
DiagnosticCode::Unsupported,
span,
"`#pragma dds_xtopics` was written with neither `begin` nor `end`; \
ยง7.3.1.1.2 defines the directive in those two forms only",
spec,
);
return;
};
let version = xtopics_version(version);
match *keyword {
"begin" => self.xtopics_regions.push((span, version)),
"end" => {
let Some((opened_at, opened)) = self.xtopics_regions.pop() else {
self.warn_at(
DiagnosticCode::Unsupported,
span,
"`#pragma dds_xtopics end` closes a region no `begin` opened",
spec,
);
return;
};
if opened.is_empty() || version.is_empty() || opened == version {
return;
}
let message = alloc::format!(
"`#pragma dds_xtopics end {version}` closes a region opened as \
`begin {opened}`; ยง7.3.1.1.2 has the two version numbers the same"
);
self.diagnostics.push(
Diagnostic::warning(DiagnosticCode::Unsupported, span, message, spec)
.with_label(opened_at, alloc::format!("opened as `begin {opened}`")),
);
}
other => self.warn_at(
DiagnosticCode::Unsupported,
span,
&alloc::format!(
"`#pragma dds_xtopics {other}` is neither `begin` nor `end`; \
ยง7.3.1.1.2 defines the directive in those two forms only"
),
spec,
),
}
}
fn pragma_candidates(&self, span: Span, written: &str) -> Vec<ScopedName> {
let name = pragma_name(written);
if written.starts_with("::") {
return alloc::vec![name];
}
let module = self.module_containing(span);
let enclosing = module.segments();
(0..=enclosing.len())
.rev()
.map(|depth| {
let mut path = enclosing[..depth].to_vec();
path.extend_from_slice(name.segments());
ScopedName::absolute(path)
})
.collect()
}
fn module_containing(&self, span: Span) -> ScopedName {
self.module_ranges
.iter()
.filter(|(range, _)| range.start <= span.start && range.end >= span.end)
.min_by_key(|(range, _)| range.end.saturating_sub(range.start))
.map_or_else(
|| ScopedName::absolute(Vec::new()),
|(_, scoped)| scoped.clone(),
)
}
fn definition(&mut self, node: &Arc<SyntaxNode>) {
let suffix = self.suffix_annotations_of(node);
if node.kind() == CstKind::StructDecl
&& suffix
.iter()
.any(|applied| applied.name == "annotation" && applied.arguments.is_empty())
{
self.annotation_from_struct(node);
return;
}
if suffix.is_empty() {
self.definition_inner(node);
return;
}
let outer = self.pending_annotations.pop();
let mut frame = outer.clone().unwrap_or_default();
frame.extend(suffix);
self.pending_annotations.push(frame);
self.definition_inner(node);
let _ = self.pending_annotations.pop();
if let Some(outer) = outer {
self.pending_annotations.push(outer);
}
}
fn definition_inner(&mut self, node: &Arc<SyntaxNode>) {
match node.kind() {
CstKind::Module => self.module(node),
CstKind::TemplateModuleInst => self.template_module_inst(node),
CstKind::TemplateModuleRef => self.template_module_ref(node),
CstKind::StructDecl => self.structure(node),
CstKind::EnumDecl => self.enumeration(node),
CstKind::BitmaskDecl => self.bitmask(node),
CstKind::UnionDecl => self.union(node),
CstKind::TypedefDecl => self.typedef(node),
CstKind::ExceptionDecl => self.exception(node),
CstKind::ForwardDecl => self.forward(node),
CstKind::NativeDecl => self.native(node),
CstKind::Member => {
let annotations = self.annotations_of(node);
self.pending_annotations.push(annotations);
for child in node.children() {
self.definition(&child);
}
let _ = self.pending_annotations.pop();
}
CstKind::InterfaceDecl => self.interface(node),
CstKind::ValuetypeDecl => self.valuetype(node),
CstKind::ValueBoxDecl => self.value_box(node),
CstKind::ConstDecl => self.constant(node),
CstKind::BitsetDecl => self.bitset(node),
CstKind::AnnotationDecl => self.annotation_decl(node),
CstKind::RepositoryDirective => self.repository_directive(node),
CstKind::ImportDecl => self.import(node),
CstKind::KeylistDecl => self.keylist(node),
CstKind::Error => {}
_ => {
for child in node.children() {
self.definition(&child);
}
}
}
}
fn module(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let Some(parent) = self.scopes.current_module() else {
return;
};
let Ok(module) = self.ast.ensure_module(parent, &name) else {
return;
};
if let Some(parameters) = node
.children()
.into_iter()
.find(|child| child.kind() == CstKind::TemplateParameters)
{
self.template_parameters(¶meters, module);
let qualified = self.scopes.qualify(&name);
self.templates.push((qualified, node.clone()));
return;
}
if let Ok(node_id) = self.ast.module(module).map(|m| m.node_id) {
self.apply_waiting_annotations(AnyId::Module(module, node_id));
}
let range = self.syntax_spans.trim(node.span());
self.module_ranges.push((range, self.scopes.qualify(&name)));
self.scopes.push(&name, Some(module));
for child in node.children() {
self.definition(&child);
}
self.scopes.pop();
}
fn template_module_inst(&mut self, node: &Arc<SyntaxNode>) {
let idents: Vec<String> = node
.syntax_tokens()
.into_iter()
.filter(|(_, token)| token.kind() == TokenKind::Ident)
.map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
.collect();
let Some(instance) = idents.last().cloned() else {
return;
};
let Some(template_name) = self.scoped_name_of(node) else {
return;
};
let Some(template) = self.template_body(node, &template_name) else {
let message = alloc::format!(
"`{}` is not a template module declared in this file",
template_name.segments().join("::")
);
self.diagnose_at(
DiagnosticCode::UnresolvedName,
node.span(),
&message,
"OMG IDL 4.2 ยง7.4.12.3 rule (190)",
);
return;
};
let formals = template_formals(&template);
let actuals = self.actual_parameters(node);
if formals.len() != actuals.len() {
let message = alloc::format!(
"`{}` takes {} actual parameter(s) and was given {}",
template_name.segments().join("::"),
formals.len(),
actuals.len()
);
self.diagnose_at(
DiagnosticCode::UnexpectedToken,
node.span(),
&message,
"OMG IDL 4.2 ยง7.4.12.3 rule (191)",
);
return;
}
let Some(parent) = self.scopes.current_module() else {
return;
};
self.check_collision(&instance, node);
let Ok(module) = self.ast.ensure_module(parent, &instance) else {
return;
};
self.instantiate(&template, &formals, &actuals, module, &instance, node);
}
fn template_module_ref(&mut self, node: &Arc<SyntaxNode>) {
let Some(instance) = node
.syntax_tokens()
.into_iter()
.filter(|(_, token)| token.kind() == TokenKind::Ident)
.map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
.next_back()
else {
return;
};
let Some(template_name) = self.scoped_name_of(node) else {
return;
};
let Some(template) = self.template_body(node, &template_name) else {
let message = alloc::format!(
"`alias` names `{}`, which is not a template module declared in this file",
template_name.segments().join("::")
);
self.diagnose_at(
DiagnosticCode::UnresolvedName,
node.span(),
&message,
"OMG IDL 4.2 ยง7.4.12.3 rule (193)",
);
return;
};
let mut actuals = Vec::new();
for written in self.formal_parameter_names(node) {
let Some(any) = self.scopes.lookup(&written) else {
let message = alloc::format!(
"`{written}` is not a formal parameter of the template this `alias` is \
written in; rule (193) instantiates with the enclosing template's own \
parameters"
);
self.diagnose_at(
DiagnosticCode::UnresolvedName,
node.span(),
&message,
"OMG IDL 4.2 ยง7.4.12.3 rule (194)",
);
return;
};
actuals.push(Actual::Bound(any));
}
let formals = template_formals(&template);
if formals.len() != actuals.len() {
let message = alloc::format!(
"`{}` takes {} formal parameter(s) and the `alias` names {}",
template_name.segments().join("::"),
formals.len(),
actuals.len()
);
self.diagnose_at(
DiagnosticCode::UnexpectedToken,
node.span(),
&message,
"OMG IDL 4.2 ยง7.4.12.3 rule (194)",
);
return;
}
let Some(parent) = self.scopes.current_module() else {
return;
};
self.check_collision(&instance, node);
let Ok(module) = self.ast.ensure_module(parent, &instance) else {
return;
};
self.instantiate(&template, &formals, &actuals, module, &instance, node);
}
fn formal_parameter_names(&self, node: &Arc<SyntaxNode>) -> Vec<String> {
node.children()
.into_iter()
.find(|child| child.kind() == CstKind::ActualParameters)
.map_or_else(Vec::new, |list| {
list.children()
.into_iter()
.filter(|child| child.kind() == CstKind::ScopedName)
.filter_map(|scoped| first_ident(&scoped))
.collect()
})
}
fn instantiate(
&mut self,
template: &Arc<SyntaxNode>,
formals: &[(String, bool)],
actuals: &[Actual],
module: Id<zerodds_ast::ModuleNode>,
instance: &str,
at: &Arc<SyntaxNode>,
) {
if let Ok(node_id) = self.ast.module(module).map(|m| m.node_id) {
self.record_origin(AnyId::Module(module, node_id), at.span());
}
self.scopes.push(instance, Some(module));
for (formal, actual) in formals.iter().zip(actuals) {
self.bind_actual(formal, actual, module, at);
}
for child in template.children() {
self.definition(&child);
}
self.scopes.pop();
}
fn template_body(
&self,
node: &Arc<SyntaxNode>,
written: &ScopedName,
) -> Option<Arc<SyntaxNode>> {
for candidate in self.directive_candidates(node, written) {
if let Some((_, body)) = self
.templates
.iter()
.find(|(name, _)| name.segments() == candidate.segments())
{
return Some(body.clone());
}
}
None
}
fn actual_parameters(&mut self, node: &Arc<SyntaxNode>) -> Vec<Actual> {
node.children()
.into_iter()
.find(|child| child.kind() == CstKind::ActualParameters)
.map_or_else(Vec::new, |list| {
list.children()
.into_iter()
.filter(|child| {
matches!(
child.kind(),
CstKind::PrimitiveType
| CstKind::NamedType
| CstKind::SequenceType
| CstKind::StringType
| CstKind::WStringType
| CstKind::MapType
| CstKind::FixedType
) || crate::expr::is_expr(child.kind())
})
.map(Actual::Written)
.collect()
})
}
fn bind_actual(
&mut self,
formal: &(String, bool),
actual: &Actual,
module: Id<zerodds_ast::ModuleNode>,
at: &Arc<SyntaxNode>,
) {
let (name, is_const) = (formal.0.clone(), formal.1);
let actual = match actual {
Actual::Bound(any) => {
self.scopes.bind(&name, *any);
return;
}
Actual::Written(node) => node.clone(),
};
let actual = &actual;
if is_const {
let Some(expr) = self.expr(actual) else {
self.diagnose_at(
DiagnosticCode::UnexpectedToken,
at.span(),
"a `const` formal parameter needs a constant expression as its actual",
"OMG IDL 4.2 ยง7.4.12.3 rule (192)",
);
return;
};
let type_ = Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(Vec::new())));
if let Ok(id) = self.ast.add_const(module, &name, type_, expr) {
if let Ok(node_id) = self.ast.constant(id).map(|c| c.node_id) {
self.record_origin(AnyId::Const(id, node_id), at.span());
self.record_declaration_scope(AnyId::Const(id, node_id), at.span());
self.scopes.bind(&name, AnyId::Const(id, node_id));
}
}
return;
}
let type_ref = self.type_ref_of(actual);
match type_ref.resolved() {
Some(id) => self.bind(&name, id),
None => self.diagnose_at(
DiagnosticCode::UnresolvedName,
at.span(),
"an actual parameter names a type that is not declared in this file",
"OMG IDL 4.2 ยง7.4.12.3 rule (192)",
),
}
}
fn structure(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let base = node
.children()
.iter()
.find(|child| child.kind() == CstKind::InheritanceClause)
.and_then(|clause| self.type_ref_from_scoped_name(clause));
let Some(id) = self.declare_type(
&name,
TypeKind::Structure {
base,
members: Vec::new(),
},
node,
) else {
return;
};
self.one_namespace_per_scope(node);
for child in node.children() {
if child.kind() == CstKind::Member {
self.member(&child, id);
}
}
}
fn exception(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
self.check_collision(&name, node);
let Some(module) = self.scopes.current_module() else {
return;
};
let Ok(id) = self.ast.add_exception(module, &name) else {
return;
};
if let Ok(node_id) = self.ast.exception(id).map(|e| e.node_id) {
self.record_origin(AnyId::Exception(id, node_id), node.span());
self.record_declaration_scope(AnyId::Exception(id, node_id), node.span());
self.scopes.bind(&name, AnyId::Exception(id, node_id));
self.apply_waiting_annotations(AnyId::Exception(id, node_id));
}
self.one_namespace_per_scope(node);
for child in node.children() {
if child.kind() == CstKind::Member {
let annotations = self.annotations_of(&child);
for (member_name, type_ref, at) in self.declared(&child) {
let spec = self.member_spec(&member_name, type_ref, &at);
let result = self.ast.add_exception_member(id, spec);
let Some(member) = self.report_model(result, at.span()) else {
continue;
};
let Ok(node_id) = self.ast.member(member).map(|m| m.node_id) else {
continue;
};
self.apply_annotations(&annotations, AnyId::Member(member, node_id));
}
}
}
}
fn member(&mut self, node: &Arc<SyntaxNode>, aggregate: Id<TypeNode>) {
let annotations = self.annotations_of(node);
let visibility = state_visibility(node);
for (name, type_ref, at) in self.declared(node) {
let spec = self.member_spec(&name, type_ref, &at);
let result = self.ast.add_member(aggregate, spec);
let Some(id) = self.report_model(result, at.span()) else {
continue;
};
let Ok(node_id) = self.ast.member(id).map(|m| m.node_id) else {
continue;
};
if let Some(visibility) = visibility {
if let Some(facets) = self.ast.facets_mut(AnyId::Member(id, node_id)) {
facets.corba_mut().state_visibility = Some(visibility);
}
}
if declarator_is_escaped(&at) {
if let Some(facets) = self.ast.facets_mut(AnyId::Member(id, node_id)) {
facets.xtypes_mut().escaped = true;
}
}
self.apply_annotations(&annotations, AnyId::Member(id, node_id));
}
}
fn declared(
&mut self,
node: &Arc<SyntaxNode>,
) -> Vec<(String, Ref<TypeNode>, Arc<SyntaxNode>)> {
let Some(element) = self.type_ref(node) else {
return Vec::new();
};
let declarators: Vec<Arc<SyntaxNode>> = node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::Declarator)
.collect();
let mut declared = Vec::new();
for declarator in declarators {
let Some(name) = first_ident(&declarator) else {
continue;
};
let type_ref = self.declarator_type(&element, &declarator);
declared.push((name, type_ref, declarator));
}
declared
}
fn declarator_type(
&mut self,
element: &Ref<TypeNode>,
declarator: &Arc<SyntaxNode>,
) -> Ref<TypeNode> {
let dims = self.array_dimensions(declarator);
let written = self.annotations_of(declarator);
if dims.is_empty() {
if let Some(first) = written.first() {
self.annotation_error(
first,
&alloc::format!(
"`@{}` stands between a declarator's name and its dimensions, and applies \
to the elements of an array; this declarator is not an array",
first.name
),
"DDS-XTypes 1.3 ยง7.3.1.2.1.4",
);
}
return element.clone();
}
let array = self.anonymous(
TypeKind::Array {
element: element.clone(),
dims,
},
declarator.span(),
);
if !written.is_empty() {
if let Some(id) = array.resolved() {
let any = AnyId::Type(id, self.node_id_of(id));
self.apply_use_site(&written, any, UseSite::Element);
}
}
array
}
fn array_dimensions(&mut self, declarator: &Arc<SyntaxNode>) -> Vec<Id<ConstExprNode>> {
declarator
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::ArrayDimension)
.filter_map(|dim| self.first_expr_child(&dim))
.collect()
}
fn member_spec(&self, name: &str, type_: Ref<TypeNode>, at: &Arc<SyntaxNode>) -> MemberSpec {
MemberSpec {
name: name.to_string(),
type_,
facets: self.facets_at(at.span()),
}
}
fn enumeration(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let Some(id) = self.declare_type(
&name,
TypeKind::Enumeration {
enumerators: Vec::new(),
},
node,
) else {
return;
};
for child in node.children() {
if child.kind() == CstKind::Enumerator {
if let Some(name) = first_ident(&child) {
let result = self.ast.add_enumerator(id, &name, None);
let Some(enumerator) = self.report_model(result, child.span()) else {
continue;
};
let annotations = self.annotations_of(&child);
let Ok(node_id) = self.ast.enumerator(enumerator).map(|e| e.node_id) else {
continue;
};
self.check_collision(&name, &child);
self.scopes
.bind(&name, AnyId::Enumerator(enumerator, node_id));
self.apply_annotations(&annotations, AnyId::Enumerator(enumerator, node_id));
}
}
}
}
fn bitmask(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let Some(id) = self.declare_type(&name, TypeKind::Bitmask { bits: Vec::new() }, node)
else {
return;
};
for child in node.children() {
if child.kind() == CstKind::BitValue {
if let Some(name) = first_ident(&child) {
let result = self.ast.add_bit_value(id, &name, None);
let Some(bit) = self.report_model(result, child.span()) else {
continue;
};
let annotations = self.annotations_of(&child);
let Ok(node_id) = self.ast.bit_value(bit).map(|b| b.node_id) else {
continue;
};
self.apply_annotations(&annotations, AnyId::BitValue(bit, node_id));
}
}
}
}
fn union(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let discriminator = self.type_ref(node).unwrap_or_else(|| {
Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(Vec::new())))
});
let Some(id) = self.declare_type(
&name,
TypeKind::Union {
discriminator,
cases: Vec::new(),
default_case: None,
},
node,
) else {
return;
};
let discriminator_annotations: Vec<Applied> = node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::Annotation)
.filter_map(|child| self.applied_annotation(&child))
.collect();
if !discriminator_annotations.is_empty() {
let any = AnyId::Type(id, self.node_id_of(id));
self.apply_use_site(&discriminator_annotations, any, UseSite::Discriminator);
}
self.one_namespace_per_scope(node);
for child in node.children() {
if child.kind() != CstKind::UnionCase {
continue;
}
let label_nodes: Vec<Arc<SyntaxNode>> = child
.children()
.into_iter()
.filter(|c| c.kind() == CstKind::CaseLabel)
.collect();
let mut labels: Vec<zerodds_ast::CaseLabel> = Vec::new();
for label in label_nodes {
let is_default = label
.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::Default);
if is_default {
labels.push(zerodds_ast::CaseLabel::Default);
continue;
}
match self.first_expr_child(&label) {
Some(expr) => labels.push(zerodds_ast::CaseLabel::Value(expr)),
None => self.diagnose_at(
DiagnosticCode::MissingToken,
label.span(),
"a `case` label needs a constant expression",
"OMG IDL 4.2 ยง7.4.1.4.4.3",
),
}
}
let declared = self.declared(&child);
for (_, _, extra) in declared.iter().skip(1) {
self.diagnose_at(
DiagnosticCode::UnexpectedToken,
extra.span(),
"a union case declares one member; `element_spec` takes a single declarator",
"OMG IDL 4.2 ยง7.4.1.4.4.3",
);
}
let Some((member_name, type_ref, declarator)) = declared.into_iter().next() else {
continue;
};
let spec = self.member_spec(&member_name, type_ref, &declarator);
let annotations = self.annotations_of(&child);
let result = self.ast.add_case(id, labels, spec);
let Some(case) = self.report_model(result, child.span()) else {
continue;
};
let Ok(node_id) = self.ast.case(case).map(|c| c.node_id) else {
continue;
};
self.record_origin(AnyId::Case(case, node_id), child.span());
if declarator_is_escaped(&declarator) {
if let Some(facets) = self.ast.facets_mut(AnyId::Case(case, node_id)) {
facets.xtypes_mut().escaped = true;
}
}
self.apply_annotations(&annotations, AnyId::Case(case, node_id));
let mirrored = self
.ast
.facets(AnyId::Case(case, node_id))
.and_then(|facets| facets.xtypes.clone());
if let Some(xtypes) = mirrored {
let member_any = self
.ast
.case(case)
.ok()
.map(|c| c.member)
.and_then(|member| self.ast.any_of_member(member).ok());
if let Some(facets) = member_any.and_then(|any| self.ast.facets_mut(any)) {
*facets.xtypes_mut() = xtypes;
}
}
}
}
fn typedef(&mut self, node: &Arc<SyntaxNode>) {
let defined = node.children().into_iter().find(|child| {
matches!(
child.kind(),
CstKind::StructDecl
| CstKind::UnionDecl
| CstKind::EnumDecl
| CstKind::BitsetDecl
| CstKind::BitmaskDecl
)
});
let element = match defined {
Some(definition) => {
self.definition_inner(&definition);
let Some(name) = first_ident(&definition) else {
return;
};
match self.scopes.lookup(&name) {
Some(AnyId::Type(id, _)) => Ref::Resolved(id),
_ => return,
}
}
None => {
let Some(element) = self.type_ref(node) else {
return;
};
element
}
};
for declarator in node
.children()
.iter()
.filter(|child| child.kind() == CstKind::Declarator)
{
let Some(name) = first_ident(declarator) else {
continue;
};
let aliased = self.declarator_type(&element, declarator);
let _ = self.declare_type(&name, TypeKind::Alias { aliased }, declarator);
}
}
fn native(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let _ = self.declare_type(&name, TypeKind::Native, node);
}
fn forward(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let Some(module) = self.scopes.current_module() else {
return;
};
if self.scopes.lookup(&name).is_some() {
return;
}
if let Some(kind) = forward_interface_kind(node) {
if let Ok(id) = self.ast.add_forward_interface(module, &name, kind) {
if let Ok(node_id) = self.ast.interface(id).map(|node| node.node_id) {
self.record_origin(AnyId::Interface(id, node_id), node.span());
self.scopes.bind(&name, AnyId::Interface(id, node_id));
}
}
return;
}
if let Ok(id) = self.ast.add_forward_type(
module,
&name,
TypeKind::Structure {
base: None,
members: Vec::new(),
},
) {
self.record_origin(AnyId::Type(id, self.node_id_of(id)), node.span());
self.record_declaration_scope(AnyId::Type(id, self.node_id_of(id)), node.span());
self.bind(&name, id);
}
}
fn constant(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let Some(expr) = self.first_expr_child(node) else {
self.diagnose_at(
DiagnosticCode::MissingToken,
node.span(),
"a constant needs a value expression",
"OMG IDL 4.2 ยง7.4.1.4.2",
);
return;
};
let type_ = if bare_fixed(node) {
self.fixed_const_type(expr, node)
} else {
self.type_ref(node)
.unwrap_or_else(|| Ref::Broken(broken_cause()))
};
self.check_const_value_form(&type_, expr, node);
self.check_collision(&name, node);
let Some(module) = self.scopes.current_module() else {
return;
};
if let Ok(id) = self.ast.add_const(module, &name, type_, expr) {
let node_id = self.ast.constant(id).map(|c| c.node_id).ok();
if let Some(node_id) = node_id {
self.record_origin(AnyId::Const(id, node_id), node.span());
self.record_declaration_scope(AnyId::Const(id, node_id), node.span());
self.scopes.bind(&name, AnyId::Const(id, node_id));
self.apply_waiting_annotations(AnyId::Const(id, node_id));
}
}
}
fn bitset(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let base = node
.children()
.iter()
.find(|child| child.kind() == CstKind::InheritanceClause)
.and_then(|clause| self.type_ref_from_scoped_name(clause));
let Some(id) = self.declare_type(
&name,
TypeKind::Bitset {
base,
fields: Vec::new(),
},
node,
) else {
return;
};
for field in node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::BitField)
{
self.bitfield(&field, id);
}
}
fn bitfield(&mut self, node: &Arc<SyntaxNode>, bitset: Id<TypeNode>) {
let Some(expr) = self.first_expr_child(node) else {
return;
};
let Ok(width) = self.ast.eval_integral(expr) else {
self.diagnose_at(
DiagnosticCode::UnexpectedToken,
node.span(),
"a bitfield width must be an integer constant",
"DDS-XTypes 1.3 ยง7.3.1.2.1.11",
);
return;
};
let Ok(bits) = u16::try_from(width) else {
self.diagnose_at(
DiagnosticCode::UnexpectedToken,
node.span(),
"a bitfield width does not fit in 16 bits",
"DDS-XTypes 1.3 ยง7.3.1.2.1.11",
);
return;
};
let holder = self
.type_ref(node)
.filter(|type_| !matches!(type_, Ref::Broken(_)));
let name = first_ident(node);
let result = self.ast.add_bitfield(bitset, name.as_deref(), bits, holder);
let Some(id) = self.report_model(result, node.span()) else {
return;
};
let annotations = self.annotations_of(node);
if let Ok(node_id) = self.ast.bitfield(id).map(|f| f.node_id) {
self.apply_annotations(&annotations, AnyId::Bitfield(id, node_id));
}
}
fn interface(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let kind = interface_kind(node);
let (bases, supports) = self.interface_bases(node);
let Some(id) = self.declare_interface(&name, kind, bases, node) else {
return;
};
let node_id = self.ast.interface(id).map(|i| i.node_id).ok();
if let Some(node_id) = node_id {
self.record_origin(AnyId::Interface(id, node_id), node.span());
self.scopes.bind(&name, AnyId::Interface(id, node_id));
self.apply_waiting_annotations(AnyId::Interface(id, node_id));
if !supports.is_empty() {
if let Some(facets) = self.ast.facets_mut(AnyId::Interface(id, node_id)) {
facets.corba_mut().supports = supports;
}
self.ast.reindex_pending(AnyId::Interface(id, node_id));
}
}
if let Some(clause) = node
.children()
.into_iter()
.find(|child| child.kind() == CstKind::ManagesClause)
{
self.manages_clause(&clause, id);
}
self.one_namespace_per_scope(node);
self.scopes.push_container(&name);
for child in node.children() {
match child.kind() {
CstKind::Operation => self.operation(&child, id),
CstKind::AttributeDecl => self.attribute(&child, id),
CstKind::PortDecl => self.port(&child, id),
CstKind::InheritanceClause | CstKind::ManagesClause => {}
_ => self.definition(&child),
}
}
self.scopes.pop();
}
fn interface_bases(
&mut self,
node: &Arc<SyntaxNode>,
) -> (
Vec<Ref<zerodds_ast::InterfaceNode>>,
Vec<Ref<zerodds_ast::InterfaceNode>>,
) {
let mut bases = Vec::new();
let mut supports = Vec::new();
for clause in node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::InheritanceClause)
{
let is_supports = clause
.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::Supports);
let names: Vec<Ref<zerodds_ast::InterfaceNode>> = clause
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::ScopedName)
.filter_map(|scoped| self.scoped_name_of(&scoped))
.map(|name| self.interface_ref(&name))
.collect();
if is_supports {
supports.extend(names);
} else {
bases.extend(names);
}
}
(bases, supports)
}
fn port(&mut self, node: &Arc<SyntaxNode>, interface: Id<zerodds_ast::InterfaceNode>) {
let keyword = node.syntax_tokens().first().map(|(_, token)| token.kind());
let idents: Vec<String> = node
.syntax_tokens()
.into_iter()
.filter(|(_, token)| token.kind() == TokenKind::Ident)
.map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
.collect();
let Some(port_name) = idents.last().cloned() else {
return;
};
let target = self
.scoped_names_of(node)
.into_iter()
.next()
.or_else(|| self.scoped_name_of(node));
let type_ = target.as_ref().map_or_else(
|| Ref::Broken(broken_cause()),
|name| self.named_type_ref(name),
);
let Ok(id) = self.ast.add_attribute(interface, &port_name, type_, false) else {
return;
};
let Ok(node_id) = self.ast.attribute(id).map(|a| a.node_id) else {
return;
};
self.record_origin(AnyId::Attribute(id, node_id), node.span());
let multiple = node
.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::Multiple);
let interface_ref = target.as_ref().map_or_else(
|| Ref::Broken(broken_cause()),
|name| self.interface_ref(name),
);
let event_ref = type_ref_of_port(self.ast, id);
let role = match keyword {
Some(TokenKind::Provides) => Some(zerodds_ast::ComponentRole::Facet {
name: port_name.clone(),
interface: interface_ref,
}),
Some(TokenKind::Uses) => Some(zerodds_ast::ComponentRole::Receptacle {
name: port_name.clone(),
interface: interface_ref,
multiple,
}),
Some(TokenKind::Publishes | TokenKind::Emits) => {
Some(zerodds_ast::ComponentRole::EventSource {
name: port_name.clone(),
event: event_ref,
publisher: keyword == Some(TokenKind::Publishes),
})
}
Some(TokenKind::Consumes) => Some(zerodds_ast::ComponentRole::EventSink {
name: port_name.clone(),
event: event_ref,
}),
Some(TokenKind::Port) => Some(zerodds_ast::ComponentRole::PortTypeRole),
Some(TokenKind::Mirrorport) => Some(zerodds_ast::ComponentRole::ConnectorRole),
_ => None,
};
if let Some(role) = role {
if let Some(facets) = self.ast.facets_mut(AnyId::Attribute(id, node_id)) {
facets.corba_mut().component_role = Some(role);
}
}
self.ast.reindex_pending(AnyId::Attribute(id, node_id));
}
fn manages_clause(
&mut self,
node: &Arc<SyntaxNode>,
interface: Id<zerodds_ast::InterfaceNode>,
) {
let names = self.scoped_names_of(node);
let Some(component) = names.first() else {
return;
};
let component_ref = self.interface_ref(component);
let primary_key = names.get(1).map(|name| self.named_type_ref(name));
let Ok(node_id) = self.ast.interface(interface).map(|i| i.node_id) else {
return;
};
if let Some(facets) = self.ast.facets_mut(AnyId::Interface(interface, node_id)) {
facets.corba_mut().manages = Some(zerodds_ast::HomeManages {
component: component_ref,
primary_key,
});
}
self.ast
.reindex_pending(AnyId::Interface(interface, node_id));
}
fn repository_directive(&mut self, node: &Arc<SyntaxNode>) {
let is_prefix = node
.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::Typeprefix);
let Some(name) = self.scoped_name_of(node) else {
return;
};
let Some(text) = joined_string_literals(node.syntax_tokens(), TokenKind::StringLiteral)
.into_iter()
.next()
else {
return;
};
self.record_repository_directive(if is_prefix { "typeprefix" } else { "typeid" }, &text);
self.repository_directives
.push((self.directive_candidates(node, &name), text, is_prefix));
}
fn record_repository_directive(&mut self, keyword: &str, text: &str) {
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
.corba_mut()
.repository_directives
.push(alloc::format!("{keyword} {text}"));
}
}
fn import(&mut self, node: &Arc<SyntaxNode>) {
let written = joined_string_literals(node.syntax_tokens(), TokenKind::StringLiteral)
.into_iter()
.next()
.or_else(|| {
self.scoped_name_of(node)
.map(|name| name.segments().join("::"))
});
let Some(written) = written else {
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.corba_mut().imports.push(written);
}
}
fn keylist(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = self.scoped_name_of(node) else {
return;
};
let fields: Vec<String> = node
.syntax_tokens()
.into_iter()
.filter(|(_, token)| token.kind() == TokenKind::Ident)
.map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
.collect();
self.keylists
.push((self.directive_candidates(node, &name), fields, node.span()));
}
fn directive_candidates(&self, node: &Arc<SyntaxNode>, name: &ScopedName) -> Vec<ScopedName> {
if is_root_anchored(node) {
return alloc::vec![name.clone()];
}
self.scopes.lookup_candidates(name.segments())
}
fn template_parameters(&mut self, node: &Arc<SyntaxNode>, module: Id<zerodds_ast::ModuleNode>) {
let mut params = Vec::new();
for parameter in node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::TemplateParameter)
{
let Some(name) = first_ident(¶meter) else {
continue;
};
let kind = template_param_kind(¶meter);
let constraint = if matches!(
kind,
zerodds_ast::TemplateParamKind::Const | zerodds_ast::TemplateParamKind::Sequence
) {
self.type_ref(¶meter)
} else {
None
};
params.push(zerodds_ast::TemplateParam {
name,
kind,
constraint,
});
}
if params.is_empty() {
return;
}
let Ok(node_id) = self.ast.module(module).map(|m| m.node_id) else {
return;
};
if let Some(facets) = self.ast.facets_mut(AnyId::Module(module, node_id)) {
facets.corba_mut().template_params = params;
}
self.ast.reindex_pending(AnyId::Module(module, node_id));
}
pub(crate) fn scoped_names_of(&self, node: &Arc<SyntaxNode>) -> Vec<ScopedName> {
node.children()
.into_iter()
.filter(|child| child.kind() == CstKind::ScopedName)
.filter_map(|scoped| self.scoped_name_of(&scoped))
.collect()
}
fn context_strings(&self, node: &Arc<SyntaxNode>) -> Vec<String> {
joined_string_literals(node.syntax_tokens(), TokenKind::StringLiteral)
}
fn exception_ref(&self, name: &ScopedName) -> Ref<zerodds_ast::ExceptionNode> {
if let Some(segment) = name.segments().last() {
if let Some(AnyId::Exception(id, _)) = self.scopes.lookup(segment) {
return Ref::Resolved(id);
}
}
Ref::Pending(PendingRef::ScopedName(name.clone()))
}
fn named_type_ref(&self, name: &ScopedName) -> Ref<TypeNode> {
if let Some(segment) = name.segments().last() {
if let Some(found) = self.scopes.lookup(segment).and_then(AnyId::as_type) {
return Ref::Resolved(found);
}
}
Ref::Pending(PendingRef::ScopedName(name.clone()))
}
fn interface_ref(&self, name: &ScopedName) -> Ref<zerodds_ast::InterfaceNode> {
if let Some(segment) = name.segments().last() {
if let Some(AnyId::Interface(id, _)) = self.scopes.lookup(segment) {
return Ref::Resolved(id);
}
}
Ref::Pending(PendingRef::ScopedName(name.clone()))
}
fn operation(&mut self, node: &Arc<SyntaxNode>, interface: Id<zerodds_ast::InterfaceNode>) {
let Some(name) = first_ident(node) else {
return;
};
let return_type = self.type_ref(node);
let oneway = node
.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::Oneway);
let Ok(id) = self
.ast
.add_operation(interface, &name, return_type, oneway)
else {
return;
};
let annotations = self.annotations_of(node);
let node_id = self.ast.operation(id).map(|o| o.node_id).ok();
if let Some(node_id) = node_id {
self.record_origin(AnyId::Operation(id, node_id), node.span());
self.apply_annotations(&annotations, AnyId::Operation(id, node_id));
}
self.one_namespace_per_scope(node);
for parameter in node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::Parameter)
{
self.parameter(¶meter, id);
}
for raises in node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::RaisesClause)
{
for name in self.scoped_names_of(&raises) {
let reference = self.exception_ref(&name);
let _ = self.ast.add_raises(id, reference);
}
}
for context in node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::ContextClause)
{
for context in self.context_strings(&context) {
let _ = self.ast.add_context(id, &context);
}
}
}
fn parameter(&mut self, node: &Arc<SyntaxNode>, operation: Id<zerodds_ast::OperationNode>) {
let Some(name) = first_ident(node) else {
return;
};
let direction = node
.syntax_tokens()
.iter()
.find_map(|(_, token)| match token.kind() {
TokenKind::In => Some(ParamDirection::In),
TokenKind::Out => Some(ParamDirection::Out),
TokenKind::Inout => Some(ParamDirection::InOut),
_ => None,
})
.unwrap_or(ParamDirection::In);
let type_ = self
.type_ref(node)
.unwrap_or_else(|| Ref::Broken(broken_cause()));
if let Ok(id) = self.ast.add_param(operation, &name, direction, type_) {
let annotations = self.annotations_of(node);
let node_id = self.ast.param(id).map(|p| p.node_id).ok();
if let Some(node_id) = node_id {
self.record_origin(AnyId::Param(id, node_id), node.span());
self.apply_annotations(&annotations, AnyId::Param(id, node_id));
}
}
}
fn attribute(&mut self, node: &Arc<SyntaxNode>, interface: Id<zerodds_ast::InterfaceNode>) {
let readonly = node
.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::Readonly);
for (name, type_, at) in self.declared(node) {
let Ok(id) = self.ast.add_attribute(interface, &name, type_, readonly) else {
continue;
};
let annotations = self.annotations_of(node);
let node_id = self.ast.attribute(id).map(|a| a.node_id).ok();
if let Some(node_id) = node_id {
self.record_origin(AnyId::Attribute(id, node_id), at.span());
self.apply_annotations(&annotations, AnyId::Attribute(id, node_id));
}
for raises in node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::RaisesClause)
{
let setter = raises
.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::Setraises);
for name in self.scoped_names_of(&raises) {
let reference = self.exception_ref(&name);
let _ = self.ast.add_attribute_raises(id, reference, setter);
}
}
}
}
fn valuetype(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let kind = valuetype_kind(node);
let clauses: Vec<Arc<SyntaxNode>> = node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::InheritanceClause)
.collect();
let mut bases = Vec::new();
let mut supported = Vec::new();
for clause in clauses {
let supports = clause
.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::Supports);
for scoped in self.scoped_names_of(&clause) {
if supports {
supported.push(self.interface_ref(&scoped));
} else {
bases.push(self.named_type_ref(&scoped));
}
}
}
let declared = TypeKind::Valuetype {
kind,
bases,
state: Vec::new(),
initializers: Vec::new(),
supported,
};
if !has_body(node) {
if self.scopes.lookup(&name).is_some() {
return;
}
let Some(module) = self.scopes.current_module() else {
return;
};
if let Ok(id) = self.ast.add_forward_type(module, &name, declared) {
self.record_origin(AnyId::Type(id, self.node_id_of(id)), node.span());
self.record_declaration_scope(AnyId::Type(id, self.node_id_of(id)), node.span());
if is_event_type(node) {
if let Some(facets) = self.ast.facets_mut(AnyId::Type(id, self.node_id_of(id)))
{
facets.corba_mut().is_event_type = true;
}
}
self.bind(&name, id);
}
return;
}
let Some(id) = self.declare_type(&name, declared, node) else {
return;
};
if is_event_type(node) {
if let Some(facets) = self.ast.facets_mut(AnyId::Type(id, self.node_id_of(id))) {
facets.corba_mut().is_event_type = true;
}
}
self.one_namespace_per_scope(node);
self.scopes.push_container(&name);
for child in node.children() {
match child.kind() {
CstKind::Member => self.member(&child, id),
CstKind::Operation => self.valuetype_operation(&child, id),
CstKind::AttributeDecl => self.diagnose_at(
DiagnosticCode::Unsupported,
child.span(),
"an attribute declared in a valuetype body has no model form yet; \
the model attaches an attribute to an interface",
"OMG IDL 4.2 ยง7.4.5",
),
CstKind::InheritanceClause => {}
_ => self.definition(&child),
}
}
self.scopes.pop();
}
fn valuetype_operation(&mut self, node: &Arc<SyntaxNode>, valuetype: Id<TypeNode>) {
let Some(name) = first_ident(node) else {
return;
};
let initializer = node
.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::Factory);
let return_type = if initializer {
None
} else {
self.type_ref(node)
};
let Ok(id) = self
.ast
.add_valuetype_operation(valuetype, &name, return_type, initializer)
else {
return;
};
let annotations = self.annotations_of(node);
let node_id = self.ast.operation(id).map(|o| o.node_id).ok();
if let Some(node_id) = node_id {
self.record_origin(AnyId::Operation(id, node_id), node.span());
self.apply_annotations(&annotations, AnyId::Operation(id, node_id));
}
for parameter in node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::Parameter)
{
self.parameter(¶meter, id);
}
for raises in node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::RaisesClause)
{
for scoped in self.scoped_names_of(&raises) {
let reference = self.exception_ref(&scoped);
let _ = self.ast.add_raises(id, reference);
}
}
}
fn value_box(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
let boxed = self
.type_ref(node)
.unwrap_or_else(|| Ref::Broken(broken_cause()));
let _ = self.declare_type(&name, TypeKind::ValueBox { boxed }, node);
}
fn annotation_from_struct(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
self.check_collision(&name, node);
let Some(module) = self.scopes.current_module() else {
return;
};
let Ok(id) = self.ast.add_annotation_def(module, &name) else {
return;
};
if let Ok(node_id) = self.ast.annotation_def(id).map(|a| a.node_id) {
self.record_origin(AnyId::AnnotationDef(id, node_id), node.span());
self.scopes.bind(&name, AnyId::AnnotationDef(id, node_id));
}
self.scopes.push(&name, None);
for child in node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::Member)
{
let default = self
.annotations_of(&child)
.into_iter()
.chain(self.suffix_annotations_of(&child))
.find(|applied| applied.name == "default")
.and_then(|applied| applied.arguments.first().and_then(|a| a.value));
for (member_name, type_ref, at) in self.declared(&child) {
let result = self
.ast
.add_annotation_member(id, &member_name, type_ref, default);
self.report_model(result, at.span());
}
}
self.scopes.pop();
}
fn annotation_decl(&mut self, node: &Arc<SyntaxNode>) {
let Some(name) = first_ident(node) else {
return;
};
self.check_collision(&name, node);
let Some(module) = self.scopes.current_module() else {
return;
};
let Ok(id) = self.ast.add_annotation_def(module, &name) else {
return;
};
let node_id = self.ast.annotation_def(id).map(|a| a.node_id).ok();
if let Some(node_id) = node_id {
self.record_origin(AnyId::AnnotationDef(id, node_id), node.span());
self.scopes.bind(&name, AnyId::AnnotationDef(id, node_id));
}
self.scopes.push(&name, None);
for child in node.children() {
if matches!(
child.kind(),
CstKind::EnumDecl | CstKind::ConstDecl | CstKind::TypedefDecl
) {
self.definition(&child);
}
}
for child in node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::Member)
{
let Some(member_name) = first_ident(&child) else {
self.diagnose_at(
DiagnosticCode::MissingToken,
child.span(),
"an annotation member needs a name",
"OMG IDL 4.2 ยง7.4.15.3 rule (222)",
);
continue;
};
let type_ = self
.type_ref(&child)
.unwrap_or_else(|| Ref::Broken(broken_cause()));
let default = self.first_expr_child(&child);
let result = self
.ast
.add_annotation_member(id, &member_name, type_, default);
self.report_model(result, child.span());
}
self.scopes.pop();
}
fn type_ref(&mut self, node: &Arc<SyntaxNode>) -> Option<Ref<TypeNode>> {
let type_node = node.children().into_iter().find(|child| {
matches!(
child.kind(),
CstKind::PrimitiveType
| CstKind::NamedType
| CstKind::SequenceType
| CstKind::StringType
| CstKind::WStringType
| CstKind::MapType
| CstKind::FixedType
)
})?;
Some(self.type_ref_of(&type_node))
}
fn type_ref_of(&mut self, node: &Arc<SyntaxNode>) -> Ref<TypeNode> {
match node.kind() {
CstKind::PrimitiveType => self.primitive_ref(node),
CstKind::StringType | CstKind::WStringType => {
let width = if node.kind() == CstKind::StringType {
CharWidth::Char
} else {
CharWidth::WChar
};
let bound = self.bound_argument(node, 0);
self.anonymous(TypeKind::String { width, bound }, node.span())
}
CstKind::SequenceType => {
let element = self.type_argument(node, 0);
let bound = self.bound_argument(node, 1);
let sequence = self.anonymous(TypeKind::Sequence { element, bound }, node.span());
self.use_site_annotations(node, 0, &sequence, UseSite::Element);
sequence
}
CstKind::MapType => {
let key = self.type_argument(node, 0);
let value = self.type_argument(node, 1);
let bound = self.bound_argument(node, 2);
let map = self.anonymous(TypeKind::Map { key, value, bound }, node.span());
self.use_site_annotations(node, 0, &map, UseSite::MapKey);
self.use_site_annotations(node, 1, &map, UseSite::Element);
map
}
CstKind::FixedType => self.fixed_ref(node),
CstKind::NamedType => self.type_ref_from_scoped_name(node).unwrap_or_else(|| {
Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(Vec::new())))
}),
_ => Ref::Broken(broken_cause()),
}
}
fn type_argument(&mut self, node: &Arc<SyntaxNode>, position: usize) -> Ref<TypeNode> {
let Some(argument) = self.argument_at(node, position) else {
return Ref::Broken(broken_cause());
};
self.type_ref_of(&argument)
}
fn bound_argument(&mut self, node: &Arc<SyntaxNode>, position: usize) -> Bound {
let Some(argument) = self.argument_at(node, position) else {
return Bound::Unbounded;
};
self.expr(&argument).map_or(Bound::Unbounded, Bound::Const)
}
fn argument_at(&self, node: &Arc<SyntaxNode>, position: usize) -> Option<Arc<SyntaxNode>> {
self.arguments_of(node)?
.into_iter()
.nth(position)
.map(|(_, argument)| argument)
}
fn arguments_of(&self, node: &Arc<SyntaxNode>) -> Option<Vec<TemplateArgument>> {
let list = node
.children()
.into_iter()
.find(|child| child.kind() == CstKind::TypeArguments)?;
let mut arguments: Vec<TemplateArgument> = Vec::new();
let mut pending: Vec<Arc<SyntaxNode>> = Vec::new();
for child in list.children() {
if child.kind() == CstKind::Annotation {
pending.push(child);
} else {
arguments.push((core::mem::take(&mut pending), child));
}
}
Some(arguments)
}
fn use_site_annotations(
&mut self,
node: &Arc<SyntaxNode>,
position: usize,
collection: &Ref<TypeNode>,
site: UseSite,
) {
let written = self.argument_annotations(node, position);
if written.is_empty() {
return;
}
let Some(id) = collection.resolved() else {
return;
};
let any = AnyId::Type(id, self.node_id_of(id));
self.apply_use_site(&written, any, site);
}
fn argument_annotations(
&mut self,
node: &Arc<SyntaxNode>,
position: usize,
) -> Vec<crate::annotation::Applied> {
let Some(arguments) = self.arguments_of(node) else {
return Vec::new();
};
let Some((written, _)) = arguments.into_iter().nth(position) else {
return Vec::new();
};
written
.iter()
.filter_map(|annotation| self.applied_annotation(annotation))
.collect()
}
fn fixed_ref(&mut self, node: &Arc<SyntaxNode>) -> Ref<TypeNode> {
let digits = self.fixed_parameter(node, 0);
let scale = self.fixed_parameter(node, 1);
match (digits, scale) {
(Some(digits), Some(scale)) => {
self.anonymous(TypeKind::Fixed { digits, scale }, node.span())
}
_ => {
let message = if has_type_arguments(node) {
"the digits and scale of `fixed` must both be integer constants"
} else {
"a `fixed` used as a type needs its digits and scale โ `fixed<d,s>`. Only a \
constant's `fixed` is written without them, and takes them from its value"
};
self.diagnose_at(
DiagnosticCode::UnexpectedToken,
node.span(),
message,
"OMG IDL 4.2 ยง7.4.1.4.4 rule (42)",
);
Ref::Broken(broken_cause())
}
}
}
fn fixed_const_type(
&mut self,
expr: Id<ConstExprNode>,
node: &Arc<SyntaxNode>,
) -> Ref<TypeNode> {
match self.fixed_type_of(expr, 0) {
Ok((digits, scale)) => self.anonymous(TypeKind::Fixed { digits, scale }, node.span()),
Err(reason) => {
self.diagnose_at(
DiagnosticCode::UnexpectedToken,
node.span(),
reason,
"OMG IDL 4.2 ยง7.4.1.4.3, Table 7-11",
);
Ref::Broken(broken_cause())
}
}
}
fn fixed_type_of(
&self,
expr: Id<ConstExprNode>,
depth: usize,
) -> Result<(u8, u8), &'static str> {
if depth > MAX_FIXED_EXPR_DEPTH {
return Err("this fixed-point constant expression nests too deeply to evaluate");
}
let Ok(node) = self.ast.expr(expr) else {
return Err("this fixed-point constant expression has no value");
};
match &node.expr {
ConstExpr::Literal(zerodds_ast::Literal::Fixed { digits, scale }) => {
let total = u8::try_from(digits.len()).map_err(
|_| "this fixed-point literal has more digits than `fixed` can hold",
)?;
Ok(cap_to_31_digits(total, *scale))
}
ConstExpr::Literal(zerodds_ast::Literal::Integer(_)) => Err(
"an integer literal is not a fixed-point value; write it with a `d` suffix, as \
`1.0d`",
),
ConstExpr::Literal(_) => {
Err("a `const fixed` takes a fixed-point value, written with a `d` suffix")
}
ConstExpr::NameRef(reference) => self.fixed_type_of_name(reference),
ConstExpr::Unary { op, operand } => match op {
UnaryOp::Plus | UnaryOp::Minus => self.fixed_type_of(*operand, depth + 1),
UnaryOp::Not => Err(
"`~` is not applicable to a fixed-point expression: ยง7.4.1.4.3 admits unary \
`+` and `-` and binary `*`, `/`, `+` and `-`",
),
},
ConstExpr::Binary { op, lhs, rhs } => {
let (d1, s1) = self.fixed_type_of(*lhs, depth + 1)?;
let (d2, s2) = self.fixed_type_of(*rhs, depth + 1)?;
fixed_binary(*op, d1, s1, d2, s2)
}
}
}
fn fixed_type_of_name(
&self,
reference: &Ref<zerodds_ast::ConstNode>,
) -> Result<(u8, u8), &'static str> {
let named = match reference {
Ref::Resolved(id) => Some(*id),
Ref::Pending(PendingRef::ScopedName(name)) => name
.segments()
.last()
.and_then(|segment| self.scopes.lookup(segment))
.and_then(|any| match any {
AnyId::Const(id, _) => Some(id),
_ => None,
}),
_ => None,
};
let Some(named) = named else {
return Err("this name does not stand for a constant declared before it");
};
let kind = self
.ast
.constant(named)
.ok()
.and_then(|constant| constant.type_.resolved())
.and_then(|type_| self.ast.type_(type_).ok())
.map(|node| node.kind.clone());
match kind {
Some(TypeKind::Fixed { digits, scale }) => Ok((digits, scale)),
_ => Err("a fixed-point constant expression may only name another `const fixed`"),
}
}
fn fixed_parameter(&mut self, node: &Arc<SyntaxNode>, position: usize) -> Option<u8> {
let argument = self.argument_at(node, position)?;
let expr = self.expr(&argument)?;
let value = self.ast.eval_integral(expr).ok()?;
u8::try_from(value).ok()
}
fn primitive_ref(&mut self, node: &Arc<SyntaxNode>) -> Ref<TypeNode> {
let kinds: Vec<TokenKind> = node
.syntax_tokens()
.iter()
.map(|(_, token)| token.kind())
.filter(|kind| *kind != TokenKind::Error)
.collect();
if let Some((name, kind)) = builtin_of(&kinds) {
return self.builtin_ref(name, kind);
}
let Some(primitive) = primitive_of(&kinds) else {
return Ref::Broken(broken_cause());
};
self.builtin_ref(primitive.as_str(), TypeKind::Primitive(primitive))
}
fn builtin_ref(&mut self, name: &str, kind: TypeKind) -> Ref<TypeNode> {
if let Some(existing) = self.scopes.lookup(name).and_then(AnyId::as_type) {
return Ref::Resolved(existing);
}
let root = self.ast.root_module();
match self.ast.add_type(root, name, kind) {
Ok(id) => {
self.scopes.bind(name, AnyId::Type(id, self.node_id_of(id)));
Ref::Resolved(id)
}
Err(_) => Ref::Broken(broken_cause()),
}
}
fn anonymous(&mut self, kind: TypeKind, at: Span) -> Ref<TypeNode> {
let root = self.ast.root_module();
let result = self.ast.add_type(root, "", kind);
self.report_model(result, at)
.map_or_else(|| Ref::Broken(broken_cause()), Ref::Resolved)
}
fn type_ref_from_scoped_name(&mut self, node: &Arc<SyntaxNode>) -> Option<Ref<TypeNode>> {
let scoped = node
.descendants()
.into_iter()
.find(|child| child.kind() == CstKind::ScopedName)?;
let segments: Vec<String> = scoped
.syntax_tokens()
.iter()
.filter(|(_, token)| token.kind() == TokenKind::Ident)
.map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
.collect();
if segments.is_empty() {
return None;
}
if segments.len() == 1 {
if let Some(found) = self.scopes.lookup(&segments[0]) {
if let Some(as_type) = found.as_type() {
return Some(Ref::Resolved(as_type));
}
if let Some(scoped) = self.ast.scoped_name_of(found) {
return Some(Ref::Pending(PendingRef::ScopedName(scoped)));
}
}
}
Some(Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(
segments,
))))
}
pub(crate) fn scoped_name_of(&self, node: &Arc<SyntaxNode>) -> Option<ScopedName> {
let scoped = if node.kind() == CstKind::ScopedName {
node.clone()
} else {
node.descendants()
.into_iter()
.find(|child| child.kind() == CstKind::ScopedName)?
};
let segments: Vec<String> = scoped
.syntax_tokens()
.iter()
.filter(|(_, token)| token.kind() == TokenKind::Ident)
.map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
.collect();
if segments.is_empty() {
return None;
}
Some(ScopedName::absolute(segments))
}
pub(crate) fn diagnose_at(
&mut self,
code: DiagnosticCode,
span: Span,
message: &str,
spec: &'static str,
) {
self.diagnostics
.push(Diagnostic::error(code, span, message.to_string(), spec));
}
pub(crate) fn warn_at(
&mut self,
code: DiagnosticCode,
span: Span,
message: &str,
spec: &'static str,
) {
self.diagnostics
.push(Diagnostic::warning(code, span, message.to_string(), spec));
}
pub(crate) fn diagnose_vendor(
&mut self,
flag: zerodds_idl_parse::VendorFlag,
span: Span,
message: &str,
) {
let spec = "zerodds-idl-frontend-1.0 ยง3.5";
let diagnostic = if self.options.dialect.admits(flag) {
Diagnostic::warning(
DiagnosticCode::VendorExtension,
span,
message.to_string(),
spec,
)
} else {
Diagnostic::error(
DiagnosticCode::VendorExtension,
span,
message.to_string(),
spec,
)
};
self.diagnostics.push(diagnostic);
}
fn check_const_value_form(
&mut self,
declared: &Ref<TypeNode>,
expr: Id<ConstExprNode>,
node: &Arc<SyntaxNode>,
) {
let Some(complaint) = self.value_disagrees_with(declared, expr) else {
return;
};
let spec = if complaint.contains("string literal") {
"OMG IDL 4.2 ยง7.2.6.3"
} else {
"OMG IDL 4.2 ยง7.4.1.4.3"
};
self.diagnose_at(
DiagnosticCode::TypeMismatch,
node.span(),
&alloc::format!("the constant {complaint}"),
spec,
);
}
pub(crate) fn value_disagrees_with(
&self,
declared: &Ref<TypeNode>,
expr: Id<ConstExprNode>,
) -> Option<alloc::string::String> {
let Ref::Resolved(id) = declared else {
return None;
};
let kind = self.ast.type_(*id).ok()?.kind.clone();
let form = self.value_form(expr, 0)?;
let (admitted, wanted) = match &kind {
TypeKind::Primitive(PrimitiveKind::Boolean) => {
(form == ValueForm::Boolean, "a boolean")
}
TypeKind::Primitive(
PrimitiveKind::Octet
| PrimitiveKind::Int8
| PrimitiveKind::UInt8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32
| PrimitiveKind::Int64
| PrimitiveKind::UInt64,
) => (form == ValueForm::Integer, "an integer"),
TypeKind::Primitive(
PrimitiveKind::Float32 | PrimitiveKind::Float64 | PrimitiveKind::Float128,
) => (
matches!(form, ValueForm::Integer | ValueForm::Float),
"a number",
),
TypeKind::Fixed { .. } => (
matches!(
form,
ValueForm::Integer | ValueForm::Float | ValueForm::Fixed
),
"a number",
),
TypeKind::Primitive(PrimitiveKind::Char) => (form == ValueForm::Char, "a character"),
TypeKind::Primitive(PrimitiveKind::WChar) => (
matches!(form, ValueForm::Char | ValueForm::WChar),
"a character",
),
TypeKind::String {
width: CharWidth::Char,
..
} => (form == ValueForm::String, "a string literal"),
TypeKind::String {
width: CharWidth::WChar,
..
} => (form == ValueForm::WString, "a wide string literal"),
_ => return None,
};
if admitted {
return None;
}
Some(alloc::format!(
"is declared as a type that takes {wanted}, and the value is {}; the declared \
type is what it has, so the two have to agree",
form.describe()
))
}
fn value_form(&self, expr: Id<ConstExprNode>, depth: usize) -> Option<ValueForm> {
if depth > zerodds_ast::node::constant::MAX_EXPR_DEPTH {
return None;
}
let node = self.ast.expr(expr).ok()?;
match &node.expr {
ConstExpr::Literal(literal) => match literal {
Literal::Boolean(_) => Some(ValueForm::Boolean),
Literal::Integer(_) => Some(ValueForm::Integer),
Literal::Float { .. } => Some(ValueForm::Float),
Literal::Fixed { .. } => Some(ValueForm::Fixed),
Literal::Char(_) => Some(ValueForm::Char),
Literal::WChar(_) => Some(ValueForm::WChar),
Literal::String(_) => Some(ValueForm::String),
Literal::WString(_) => Some(ValueForm::WString),
Literal::Enumerator(_) => None,
},
ConstExpr::Unary { operand, .. } => self.value_form(*operand, depth + 1),
ConstExpr::Binary { lhs, rhs, .. } => {
let left = self.value_form(*lhs, depth + 1)?;
let right = self.value_form(*rhs, depth + 1)?;
if left == right {
Some(left)
} else if left.is_numeric() && right.is_numeric() {
Some(if left == ValueForm::Fixed || right == ValueForm::Fixed {
ValueForm::Fixed
} else {
ValueForm::Float
})
} else {
None
}
}
ConstExpr::NameRef(_) => None,
}
}
pub(crate) fn record_expr_origin(&mut self, id: Id<zerodds_ast::ConstExprNode>, span: Span) {
let node_id = self.ast.expr(id).map(|node| node.node_id).ok();
if let Some(node_id) = node_id {
self.record_origin(AnyId::ConstExpr(id, node_id), span);
}
}
fn bind(&mut self, name: &str, id: Id<TypeNode>) {
let node_id = self.node_id_of(id);
self.scopes.bind(name, AnyId::Type(id, node_id));
}
fn node_id_of(&self, id: Id<TypeNode>) -> zerodds_ast_core::NodeId {
self.ast.type_(id).map_or(
zerodds_ast_core::NodeId::from_parts(zerodds_ast_core::IdSpace::SYNTHETIC, 0),
|node| node.node_id,
)
}
fn facets_at(&self, span: Span) -> Facets {
Facets {
source: self.source_at(span),
..Facets::default()
}
}
fn source_at(&self, span: Span) -> SourceFacet {
let span = self.syntax_spans.trim(span);
SourceFacet {
origin: SourceOrigin::File {
path: self.path.clone(),
},
span: Some(zerodds_ast::ByteSpan {
start: span.start,
end: span.end,
}),
macro_chain: Vec::new(),
doc_comments: Vec::new(),
}
}
fn record_origin(&mut self, any: AnyId, span: Span) {
let source = self.source_at(span);
if let Some(facets) = self.ast.facets_mut(any) {
facets.source = source;
}
}
fn record_declaration_scope(&mut self, any: AnyId, at: zerodds_idl_cst::Span) {
let Some(scope) = self.scopes.enclosing_container().map(String::from) else {
return;
};
let result = self.ast.set_declaration_scope(any, &scope);
self.report_model(result, at);
}
fn declare_type(
&mut self,
name: &str,
kind: TypeKind,
at: &Arc<SyntaxNode>,
) -> Option<Id<TypeNode>> {
if let Some(existing) = self.scopes.lookup_here(name).and_then(AnyId::as_type) {
let forward = self
.ast
.type_(existing)
.is_ok_and(|node| node.completeness == Completeness::Forward);
if forward {
return match self.ast.complete_type(existing, kind) {
Ok(()) => {
let any = AnyId::Type(existing, self.node_id_of(existing));
self.record_origin(any, at.span());
self.apply_waiting_annotations(any);
Some(existing)
}
Err(_) => None,
};
}
}
self.check_collision(name, at);
let module = self.scopes.current_module()?;
let result = self.ast.add_type(module, name, kind);
let id = self.report_model(result, at.span())?;
self.record_origin(AnyId::Type(id, self.node_id_of(id)), at.span());
self.record_declaration_scope(AnyId::Type(id, self.node_id_of(id)), at.span());
self.bind(name, id);
self.apply_waiting_annotations(AnyId::Type(id, self.node_id_of(id)));
Some(id)
}
fn declare_interface(
&mut self,
name: &str,
kind: InterfaceKind,
bases: Vec<Ref<zerodds_ast::InterfaceNode>>,
at: &Arc<SyntaxNode>,
) -> Option<Id<zerodds_ast::InterfaceNode>> {
if let Some(AnyId::Interface(existing, _)) = self.scopes.lookup_here(name) {
let forward = self
.ast
.interface(existing)
.is_ok_and(|node| node.completeness == Completeness::Forward);
if forward {
return match self.ast.complete_interface(existing, kind, bases) {
Ok(()) => {
if let Ok(node) = self.ast.interface(existing) {
let node_id = node.node_id;
self.apply_waiting_annotations(AnyId::Interface(existing, node_id));
}
Some(existing)
}
Err(_) => None,
};
}
}
self.check_collision(name, at);
let module = self.scopes.current_module()?;
let result = self.ast.add_interface(module, name, kind, bases);
self.report_model(result, at.span())
}
pub(crate) fn apply_waiting_annotations(&mut self, target: AnyId) {
let Some(annotations) = self.pending_annotations.pop() else {
return;
};
self.apply_annotations(&annotations, target);
self.pending_annotations.push(annotations);
}
fn one_namespace_per_scope(&mut self, node: &Arc<SyntaxNode>) {
let mut used: Vec<UsedName> = Vec::new();
let mut declared: Vec<DeclaredName> = Vec::new();
let (bodies, stop_at_inner_scope) = match node.kind() {
CstKind::InterfaceDecl => (
[CstKind::AttributeDecl, CstKind::Operation].as_slice(),
true,
),
CstKind::Operation => ([CstKind::Parameter].as_slice(), false),
_ => ([CstKind::Member, CstKind::UnionCase].as_slice(), false),
};
let mut child_index = 0;
for child in node.children() {
let is_body = bodies.contains(&child.kind());
if !is_body && child.kind() != CstKind::NamedType {
continue;
}
let inner_scope = stop_at_inner_scope && child.kind() == CstKind::Operation;
if inner_scope {
if let Some(name) = first_ident(&child) {
declared.push(DeclaredName {
name,
span: child.span(),
child_index,
});
}
for grandchild in child.children() {
if grandchild.kind() == CstKind::NamedType {
collect_simple_type_names_with_index(&grandchild, &mut used, child_index);
}
}
child_index += 1;
continue;
}
collect_simple_type_names_with_index(&child, &mut used, child_index);
if !is_body {
child_index += 1;
continue;
}
for declarator in child
.descendants()
.into_iter()
.filter(|inner| inner.kind() == CstKind::Declarator)
{
if let Some(name) = first_ident(&declarator) {
declared.push(DeclaredName {
name,
span: declarator.span(),
child_index,
});
}
}
if child.kind() == CstKind::Parameter
&& !child
.children()
.iter()
.any(|inner| inner.kind() == CstKind::Declarator)
{
if let Some(name) = first_ident(&child) {
declared.push(DeclaredName {
name,
span: child.span(),
child_index,
});
}
}
child_index += 1;
}
for index in 0..declared.len() {
let current = &declared[index];
let name = ¤t.name;
let span = current.span;
let child_idx = current.child_index;
let clash_used = used
.iter()
.filter(|u| u.child_index != child_idx)
.map(|u| u.name.as_str())
.find(|other| *other != name && other.eq_ignore_ascii_case(name));
let clash_declared = declared[..index]
.iter()
.map(|d| d.name.as_str())
.find(|other| *other != name && other.eq_ignore_ascii_case(name));
let clash_same_decl = used
.iter()
.filter(|u| u.child_index == child_idx)
.map(|u| u.name.as_str())
.find(|other| *other != name && other.eq_ignore_ascii_case(name));
if let Some(clash) = clash_declared {
let message = alloc::format!(
"`{name}` and `{clash}` are in one scope and differ only in case; IDL has a \
single namespace per scope and compares identifiers without regard to case"
);
self.diagnose_at(
DiagnosticCode::DuplicateName,
span,
&message,
"OMG IDL 4.2 ยง7.2.3.1",
);
} else if let Some(clash) = clash_used {
let message = alloc::format!(
"`{name}` and `{clash}` are in one scope and differ only in case; IDL has a \
single namespace per scope and compares identifiers without regard to case. \
Rename the declaration, or write the type's scoped name"
);
self.diagnose_at(
DiagnosticCode::DuplicateName,
span,
&message,
"OMG IDL 4.2 ยง7.2.3.1",
);
} else if let Some(clash) = clash_same_decl {
if self
.options
.dialect
.admits(zerodds_idl_parse::VendorFlag::Ros2)
{
} else {
let message = alloc::format!(
"`{name}` and `{clash}` are in one scope and differ only in case; IDL has a \
single namespace per scope and compares identifiers without regard to case. \
Rename the declaration, or write the type's scoped name"
);
self.diagnose_at(
DiagnosticCode::DuplicateName,
span,
&message,
"OMG IDL 4.2 ยง7.2.3.1",
);
}
}
}
}
fn duplicate(&mut self, name: &str, at: &Arc<SyntaxNode>) {
let message = alloc::format!(
"`{name}` is already declared in this scope; IDL compares identifiers case-insensitively"
);
self.diagnose_at(
DiagnosticCode::DuplicateName,
at.span(),
&message,
"OMG IDL 4.2 ยง7.2.3",
);
}
pub(crate) fn report_model<T>(
&mut self,
result: Result<T, zerodds_ast::AstError>,
at: Span,
) -> Option<T> {
match result {
Ok(value) => Some(value),
Err(error) => {
let (code, spec) = classify(&error);
let message = self.explain(&error);
self.diagnose_at(code, at, &message, spec);
None
}
}
}
fn explain(&self, error: &zerodds_ast::AstError) -> String {
use zerodds_ast::{AstError, InvariantViolation as V};
let AstError::Invariant(violations) = error else {
return alloc::format!("{error}");
};
match violations.first() {
Some(V::DuplicateMemberId {
id, first, second, ..
}) => {
let (first_name, first_origin) = self.member_id_source(*first);
let (second_name, second_origin) = self.member_id_source(*second);
member_id_collision(&second_name, second_origin, &first_name, first_origin, *id)
}
_ => alloc::format!("{error}"),
}
}
fn member_id_source(&self, node: zerodds_ast_core::NodeId) -> (String, &'static str) {
let Some(any) = self.ast.resolve_node_id(node) else {
return (String::from("a member"), "an unknown rule");
};
let name = match any {
AnyId::Member(id, _) => self
.ast
.member(id)
.map_or_else(|_| String::from("a member"), |m| m.name.clone()),
_ => String::from("a member"),
};
let origin = self
.ast
.facets(any)
.and_then(|facets| facets.xtypes.as_ref())
.and_then(|x| x.member_id.as_ref())
.map_or("an unknown rule", |assignment| assignment.origin.as_str());
(name, origin)
}
fn check_collision(&mut self, name: &str, at: &Arc<SyntaxNode>) {
if self.scopes.is_bound_here(name) {
self.duplicate(name, at);
return;
}
self.check_redefines_enclosing_scope(name, at);
}
fn check_redefines_enclosing_scope(&mut self, name: &str, at: &Arc<SyntaxNode>) {
let Some(scope) = self.scopes.innermost_name() else {
return;
};
if !scope.eq_ignore_ascii_case(name) {
return;
}
if self.options.name_of_the_enclosing_scope == NameOfTheEnclosingScope::Admitted {
return;
}
let message = alloc::format!(
"`{name}` is the name of the scope it is declared in, and OMG IDL 4.2 ยง7.5.2 does \
not admit redefining that name within its own immediate scope"
);
self.diagnose_at(
DiagnosticCode::DuplicateName,
at.span(),
&message,
"OMG IDL 4.2 ยง7.5.2",
);
}
}
fn broken_cause() -> zerodds_ast_core::BreakCause {
zerodds_ast_core::BreakCause::NotFound {
what: PendingRef::ScopedName(ScopedName::absolute(Vec::new())),
}
}
struct UsedName {
name: String,
child_index: usize,
}
struct DeclaredName {
name: String,
span: Span,
child_index: usize,
}
fn collect_simple_type_names_with_index(
node: &Arc<SyntaxNode>,
into: &mut Vec<UsedName>,
child_index: usize,
) {
for named in node
.descendants()
.into_iter()
.filter(|child| child.kind() == CstKind::NamedType)
{
let Some(scoped) = named
.descendants()
.into_iter()
.find(|child| child.kind() == CstKind::ScopedName)
else {
continue;
};
let tokens = scoped.syntax_tokens();
if tokens
.iter()
.any(|(_, token)| token.kind() == TokenKind::ColonColon)
{
continue;
}
let segments: Vec<&str> = tokens
.iter()
.filter(|(_, token)| token.kind() == TokenKind::Ident)
.map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()))
.collect();
if let [only] = segments.as_slice() {
into.push(UsedName {
name: (*only).to_string(),
child_index,
});
}
}
}
pub(crate) fn joined_string_literals(
tokens: impl IntoIterator<Item = (zerodds_idl_cst::Span, zerodds_idl_cst::GreenToken)>,
kind: TokenKind,
) -> Vec<String> {
let mut joined = Vec::new();
let mut run: Option<Option<String>> = None;
for (_, token) in tokens {
if token.kind() == kind {
let piece = match zerodds_idl_cst::literal_value(kind, token.text()) {
zerodds_idl_cst::TokenValue::Text { value } => Some(value),
_ => None,
};
run = Some(match (run.take(), piece) {
(None, piece) => piece,
(Some(Some(mut value)), Some(piece)) => {
value.push_str(&piece);
Some(value)
}
(Some(_), _) => None,
});
} else if let Some(finished) = run.take() {
joined.extend(finished);
}
}
if let Some(finished) = run {
joined.extend(finished);
}
joined
}
fn first_ident(node: &Arc<SyntaxNode>) -> Option<String> {
node.syntax_tokens()
.iter()
.find(|(_, token)| token.kind() == TokenKind::Ident)
.map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
}
fn declarator_is_escaped(node: &Arc<SyntaxNode>) -> bool {
node.syntax_tokens()
.iter()
.find(|(_, token)| token.kind() == TokenKind::Ident)
.is_some_and(|(_, token)| token.text().starts_with('_'))
}
const MAX_FIXED_EXPR_DEPTH: usize = 256;
const MAX_FIXED_DIGITS: u8 = 31;
fn bare_fixed(node: &Arc<SyntaxNode>) -> bool {
node.children()
.into_iter()
.find(|child| child.kind() == CstKind::FixedType)
.is_some_and(|fixed| !has_type_arguments(&fixed))
}
fn has_body(node: &Arc<SyntaxNode>) -> bool {
node.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::LBrace)
}
fn has_type_arguments(node: &Arc<SyntaxNode>) -> bool {
node.children()
.iter()
.any(|child| child.kind() == CstKind::TypeArguments)
}
fn fixed_binary(op: BinaryOp, d1: u8, s1: u8, d2: u8, s2: u8) -> Result<(u8, u8), &'static str> {
if s1 > d1 || s2 > d2 {
return Err("a `fixed` scale cannot exceed its digits");
}
let (d1, s1, d2, s2) = (u16::from(d1), u16::from(s1), u16::from(d2), u16::from(s2));
let (digits, scale) = match op {
BinaryOp::Add | BinaryOp::Sub => {
let scale = s1.max(s2);
((d1 - s1).max(d2 - s2) + scale + 1, scale)
}
BinaryOp::Mul => (d1 + d2, s1 + s2),
BinaryOp::Div => {
let whole = d1 - s1 + s2;
let sinf = u16::from(MAX_FIXED_DIGITS).saturating_sub(whole);
(whole + sinf, sinf)
}
BinaryOp::Or
| BinaryOp::Xor
| BinaryOp::And
| BinaryOp::Shl
| BinaryOp::Shr
| BinaryOp::Mod => {
return Err(
"ยง7.4.1.4.3 admits unary `+` and `-` and binary `*`, `/`, `+` and `-` in a \
fixed-point expression, and nothing else",
);
}
};
let too_wide = "this fixed-point expression reaches a precision `fixed` cannot state";
Ok(cap_to_31_digits(
u8::try_from(digits).map_err(|_| too_wide)?,
u8::try_from(scale).map_err(|_| too_wide)?,
))
}
fn cap_to_31_digits(digits: u8, scale: u8) -> (u8, u8) {
if digits <= MAX_FIXED_DIGITS {
return (digits, scale);
}
let capped = i32::from(MAX_FIXED_DIGITS) - i32::from(digits) + i32::from(scale);
(MAX_FIXED_DIGITS, u8::try_from(capped.max(0)).unwrap_or(0))
}
fn is_event_type(node: &Arc<SyntaxNode>) -> bool {
node.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::Eventtype)
}
fn state_visibility(node: &Arc<SyntaxNode>) -> Option<zerodds_ast::StateVisibility> {
node.syntax_tokens()
.iter()
.find_map(|(_, token)| match token.kind() {
TokenKind::Public => Some(zerodds_ast::StateVisibility::Public),
TokenKind::Private => Some(zerodds_ast::StateVisibility::Private),
_ => None,
})
}
fn builtin_of(kinds: &[TokenKind]) -> Option<(&'static str, TypeKind)> {
match kinds {
[TokenKind::Any] => Some(("any", TypeKind::Any)),
[TokenKind::Object] => Some(("Object", TypeKind::ObjectRef { interface: None })),
[TokenKind::Valuebase] => Some(("ValueBase", TypeKind::ValueBase)),
_ => None,
}
}
fn primitive_of(kinds: &[TokenKind]) -> Option<PrimitiveKind> {
Some(match kinds {
[TokenKind::Boolean] => PrimitiveKind::Boolean,
[TokenKind::Octet] => PrimitiveKind::Octet,
[TokenKind::Char] => PrimitiveKind::Char,
[TokenKind::Wchar] => PrimitiveKind::WChar,
[TokenKind::Short] => PrimitiveKind::Int16,
[TokenKind::Long] => PrimitiveKind::Int32,
[TokenKind::Long, TokenKind::Long] => PrimitiveKind::Int64,
[TokenKind::Long, TokenKind::Double] => PrimitiveKind::Float128,
[TokenKind::Unsigned, TokenKind::Short] => PrimitiveKind::UInt16,
[TokenKind::Unsigned, TokenKind::Long] => PrimitiveKind::UInt32,
[TokenKind::Unsigned, TokenKind::Long, TokenKind::Long] => PrimitiveKind::UInt64,
[TokenKind::Float] => PrimitiveKind::Float32,
[TokenKind::Double] => PrimitiveKind::Float64,
[TokenKind::Int8] => PrimitiveKind::Int8,
[TokenKind::Uint8] => PrimitiveKind::UInt8,
[TokenKind::Int16] => PrimitiveKind::Int16,
[TokenKind::Uint16] => PrimitiveKind::UInt16,
[TokenKind::Int32] => PrimitiveKind::Int32,
[TokenKind::Uint32] => PrimitiveKind::UInt32,
[TokenKind::Int64] => PrimitiveKind::Int64,
[TokenKind::Uint64] => PrimitiveKind::UInt64,
_ => return None,
})
}
fn is_root_anchored(node: &Arc<SyntaxNode>) -> bool {
let scoped = if node.kind() == CstKind::ScopedName {
Some(node.clone())
} else {
node.descendants()
.into_iter()
.find(|child| child.kind() == CstKind::ScopedName)
};
scoped.is_some_and(|scoped| {
scoped
.syntax_tokens()
.first()
.is_some_and(|(_, token)| token.kind() == TokenKind::ColonColon)
})
}
fn forward_interface_kind(node: &Arc<SyntaxNode>) -> Option<InterfaceKind> {
let mut modifier = None;
for (_, token) in node.syntax_tokens() {
match token.kind() {
TokenKind::Local => modifier = Some(InterfaceKind::Local),
TokenKind::Abstract => modifier = Some(InterfaceKind::Abstract),
TokenKind::Interface => {
return Some(modifier.unwrap_or(InterfaceKind::Interface));
}
TokenKind::Component => return Some(InterfaceKind::Component),
TokenKind::Home => return Some(InterfaceKind::Home),
TokenKind::Connector => return Some(InterfaceKind::Connector),
TokenKind::Porttype => return Some(InterfaceKind::PortType),
TokenKind::Ident => return None,
_ => {}
}
}
None
}
fn interface_kind(node: &Arc<SyntaxNode>) -> InterfaceKind {
for (_, token) in node.syntax_tokens() {
match token.kind() {
TokenKind::Local => return InterfaceKind::Local,
TokenKind::Abstract => return InterfaceKind::Abstract,
TokenKind::Component => return InterfaceKind::Component,
TokenKind::Home => return InterfaceKind::Home,
TokenKind::Connector => return InterfaceKind::Connector,
TokenKind::Porttype => return InterfaceKind::PortType,
TokenKind::Interface => break,
_ => {}
}
}
InterfaceKind::Interface
}
fn valuetype_kind(node: &Arc<SyntaxNode>) -> ValuetypeKind {
let mut kind = ValuetypeKind::Regular;
for (_, token) in node.syntax_tokens() {
match token.kind() {
TokenKind::Custom => kind = ValuetypeKind::Custom,
TokenKind::Abstract => kind = ValuetypeKind::Abstract,
TokenKind::LBrace => break,
_ => {}
}
}
if kind != ValuetypeKind::Regular {
return kind;
}
let truncatable = node
.children()
.into_iter()
.filter(|child| child.kind() == CstKind::InheritanceClause)
.any(|clause| {
clause
.syntax_tokens()
.iter()
.any(|(_, token)| token.kind() == TokenKind::Truncatable)
});
if truncatable {
ValuetypeKind::Truncatable
} else {
ValuetypeKind::Regular
}
}
fn classify(error: &zerodds_ast::AstError) -> (DiagnosticCode, &'static str) {
use zerodds_ast::{AstError, InvariantViolation as V};
let AstError::Invariant(violations) = error else {
return (DiagnosticCode::UnexpectedToken, "zerodds-ast-1.0 ยง8.1");
};
match violations.first() {
Some(V::NonPositiveArrayDimension { .. }) => {
(DiagnosticCode::InvalidBound, "OMG IDL 4.2 ยง7.4.1.4.4")
}
Some(V::IncompatibleCaseLabel { .. }) => {
(DiagnosticCode::InvalidCaseLabel, "OMG IDL 4.2 ยง7.4.1.4.4.3")
}
Some(
V::DuplicateMemberName { .. }
| V::DuplicateEnumeratorName { .. }
| V::NameCollidesOnlyInCase { .. },
) => (DiagnosticCode::DuplicateName, "OMG IDL 4.2 ยง7.2.3"),
Some(V::DuplicateMemberId { .. }) => (
DiagnosticCode::DuplicateMemberId,
"DDS-XTypes 1.3 ยง7.3.1.2.1.1",
),
Some(V::MultipleDefaultCases { .. }) => {
(DiagnosticCode::InvalidCaseLabel, "OMG IDL 4.2 ยง7.4.1.4.4.3")
}
Some(V::DanglingReference { .. }) | None => {
(DiagnosticCode::UnexpectedToken, "zerodds-ast-1.0 ยง8.1")
}
}
}
fn dedup(diagnostics: Vec<Diagnostic>) -> Vec<Diagnostic> {
let mut seen: Vec<(DiagnosticCode, String)> = Vec::new();
let mut out = Vec::new();
for diagnostic in diagnostics {
let key = (diagnostic.code, diagnostic.message.clone());
if seen.contains(&key) {
continue;
}
seen.push(key);
out.push(diagnostic);
}
out
}
pub(crate) fn member_id_collision(
name: &str,
origin: &str,
other: &str,
other_origin: &str,
id: u32,
) -> String {
alloc::format!(
"`{name}` and `{other}` both have member id {id}: {name} from {origin}, {other} from {other_origin}"
)
}
enum Actual {
Written(Arc<SyntaxNode>),
Bound(AnyId),
}
fn template_formals(body: &Arc<SyntaxNode>) -> Vec<(String, bool)> {
body.children()
.into_iter()
.find(|child| child.kind() == CstKind::TemplateParameters)
.map_or_else(Vec::new, |list| {
list.children()
.into_iter()
.filter(|child| child.kind() == CstKind::TemplateParameter)
.filter_map(|parameter| {
let name = first_ident(¶meter)?;
let is_const =
template_param_kind(¶meter) == zerodds_ast::TemplateParamKind::Const;
Some((name, is_const))
})
.collect()
})
}
fn template_param_kind(node: &Arc<SyntaxNode>) -> zerodds_ast::TemplateParamKind {
use zerodds_ast::TemplateParamKind as K;
if node
.children()
.iter()
.any(|child| child.kind() == CstKind::SequenceType)
{
return K::Sequence;
}
for (_, token) in node.syntax_tokens() {
match token.kind() {
TokenKind::Typename => return K::TypeName,
TokenKind::Const => return K::Const,
TokenKind::Sequence => return K::Sequence,
TokenKind::Interface => return K::Interface,
TokenKind::Valuetype => return K::ValueType,
TokenKind::Eventtype => return K::EventType,
TokenKind::Struct => return K::Struct,
TokenKind::Union => return K::Union,
TokenKind::Exception => return K::Exception,
TokenKind::Enum => return K::Enum,
_ => {}
}
}
K::TypeName
}
fn type_ref_of_port(ast: &Ast, attribute: Id<zerodds_ast::AttributeNode>) -> Ref<TypeNode> {
ast.attribute(attribute)
.map_or_else(|_| Ref::Broken(broken_cause()), |node| node.type_.clone())
}
fn unquote(text: &str) -> Option<String> {
let trimmed = text.trim();
if trimmed.is_empty() {
return None;
}
Some(
trimmed
.strip_prefix('"')
.and_then(|rest| rest.strip_suffix('"'))
.unwrap_or(trimmed)
.to_string(),
)
}
fn xtopics_version(words: &[&str]) -> String {
words.join("_").to_ascii_lowercase()
}
fn pragma_name(written: &str) -> ScopedName {
ScopedName::absolute(
written
.split("::")
.filter(|segment| !segment.is_empty())
.map(ToString::to_string)
.collect(),
)
}