zerodds_idl_lower/lower.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The CST-to-model walk. Spec §4.
4//!
5//! Builds through the model's own edit surface, so a file that fails
6//! validation leaves nothing behind. Module reopening across files
7//! converges on one node through `Ast::ensure_module` — the frontend
8//! needs no dedup pass, which is what building the middle first bought.
9
10use crate::annotation::{Applied, UseSite};
11
12/// One argument of a parameterised type, with the annotations written
13/// immediately in front of it.
14///
15/// `sequence<@try_construct(TRIM) String5, 10>` puts both in the same
16/// child list, and the pairing is what tells the element apart from the
17/// bound.
18type TemplateArgument = (
19 alloc::vec::Vec<alloc::sync::Arc<SyntaxNode>>,
20 alloc::sync::Arc<SyntaxNode>,
21);
22use crate::scope::ScopeStack;
23use alloc::string::{String, ToString};
24use alloc::sync::Arc;
25use alloc::vec::Vec;
26use zerodds_ast::{
27 AnyId, Ast, BinaryOp, Bound, CharWidth, Completeness, ConstExpr, ConstExprNode, Facets,
28 InterfaceKind, Literal, MemberSpec, ParamDirection, PrimitiveKind, SourceFacet, SourceOrigin,
29 TypeKind, TypeNode, UnaryOp, ValuetypeKind,
30};
31use zerodds_ast_core::{Id, PendingRef, Ref, ScopedName};
32use zerodds_idl_cst::{CstKind, Span, SyntaxNode, SyntaxSpans, TokenKind};
33use zerodds_idl_parse::{Diagnostic, DiagnosticCode};
34
35/// What lowering produced.
36pub struct Lowered {
37 /// The model, built as far as the file allowed.
38 pub ast: Ast,
39 /// Everything wrong that the model itself did not already reject.
40 pub diagnostics: Vec<Diagnostic>,
41}
42
43/// What the caller may decide that the source does not.
44///
45/// Empty by default: everything the IDL did not say stays unsaid in the
46/// model, and whoever asks a question the source left open applies the
47/// rule then. An option here is the caller overruling that, which the
48/// model records as though the source had said it.
49#[derive(Debug, Clone, Copy)]
50pub struct Options {
51 /// What an aggregate of unspecified extensibility is taken to be.
52 ///
53 /// `None` leaves the facet absent, which keeps "the source said
54 /// appendable" apart from "the source said nothing" and lets
55 /// `Ast::effective_extensibility` and the layout key apply their
56 /// own default. `Some` writes it into the model, so every consumer
57 /// — the wire plan, the TypeObject, the generated code — sees one
58 /// answer.
59 ///
60 /// XTypes 1.3 §7.3.1.2.1.8 provides for exactly this:
61 ///
62 /// > In the event that the representation of a given type does not
63 /// > indicate the type's extensibility kind, the type shall be
64 /// > considered appendable. Implementations may provide a mechanism
65 /// > to override this default behavior; for example, IDL compilers
66 /// > may provide configuration options to allow users to specify
67 /// > whether types of unspecified extensibility are to be considered
68 /// > final, appendable, or mutable.
69 ///
70 /// What the source *did* say still wins: an `@final` annotation, an
71 /// `@extensibility(...)`, or a `#pragma` are all the source
72 /// speaking, and this option only fills the silence.
73 pub default_extensibility: Option<zerodds_ast::Extensibility>,
74 /// The `@default_nested` of the top-level modules that carry no
75 /// annotation of their own, and of types declared outside any module.
76 ///
77 /// DDS-XTypes 1.3 §7.3.1.2.1.7: "If a top-level module is not
78 /// annotated, the default is FALSE. […] IDL compilers shall provide
79 /// the means to change the default value for non-annotated top-level
80 /// modules." `None` leaves the specification's FALSE; `Some` writes
81 /// the answer onto the root module, where `Ast::effective_nested`
82 /// finds it for every type below that no module annotated.
83 pub default_nested: Option<bool>,
84 /// Whether a `@hashid` member advances the sequential counter the
85 /// next unannotated member is numbered from.
86 ///
87 /// `true` by default, and the default is a measurement rather than a
88 /// preference. DDS-XTypes 1.3 §7.3.1.2.1.1 states the rule twice and
89 /// not the same way: one sentence numbers a member from "the
90 /// most-recently specified ID (using the @id annotation …)", which
91 /// leaves a hashed id out of the count, and the next from "the
92 /// member ID of the previous member of the same Aggregated Type plus
93 /// one", which does not. Cyclone and Fast DDS take the second, RTI
94 /// the first — `crates/ast-layout/tests/fixtures/vendor_bytes.md`
95 /// §7. Three of the four against one.
96 ///
97 /// It is here and not in the layout key because member ids are fixed
98 /// in [`crate::complete`], before any plan exists.
99 /// `zerodds_ast_layout::Dialect::hashed_id_advances_the_counter` is
100 /// where the vendor fact lives; this is the switch it sets.
101 pub hashed_id_advances_the_counter: HashedIdCounter,
102 /// Which spelling of an escaped name a member-id hash reads.
103 ///
104 /// IDL 4.2 §7.2.3.2 makes the escape underscore "a purely lexical
105 /// convention" that is not part of the identifier, so the
106 /// specification's hash input for `_long` is `long` — the default.
107 /// RTI Connext hashes the spelling as written, keyword or not:
108 /// measured with rtiddsgen 7.7.0, whose TypeCode carries member id
109 /// 79659193 = hash("_long") and 230782087 = hash("_foo") where the
110 /// clause gives 56906255 and 186170796. Like the counter above it
111 /// is settled in [`crate::complete`], before any layout key exists;
112 /// `zerodds_ast_layout::Dialect` carries the vendor fact.
113 pub hashed_name_spelling: HashedNameSpelling,
114 /// Whether `@key` on a union discriminator is carried into the
115 /// model.
116 ///
117 /// A frontend axis in the shape [`HashedIdCounter`] has, and here
118 /// for the reason that one is here: the answer is settled while the
119 /// file is read, long before any plan exists, and two load paths
120 /// that answer it differently produce two different
121 /// `TypeIdentifier`s for one declaration.
122 ///
123 /// [`KeyOnUnionDiscriminator::Refused`] is the default and is what
124 /// every measured front end does — see that type for the runs.
125 pub key_on_union_discriminator: KeyOnUnionDiscriminator,
126 /// Whether a declaration may carry the name of the scope it sits in.
127 ///
128 /// A dialect axis, measured and not chosen: the corpus case
129 /// `resolve-module-named-as-type` — `module common { struct common
130 /// { long x; }; ... }` — is refused by Cyclone DDS and OpenDDS and
131 /// taken by Fast DDS and RTI Connext, and the ledger records four
132 /// octet rows on each side of that split. A reader that refuses the
133 /// file can never reproduce the octets the other two wrote, so the
134 /// refusal cannot live in the front end alone.
135 pub name_of_the_enclosing_scope: NameOfTheEnclosingScope,
136 /// Which vendors' extensions this lowering admits. Spec §3.5.
137 ///
138 /// Two `#pragma` families change the model rather than decorate it:
139 /// Cyclone's `#pragma keylist` and OpenDDS's `#pragma DCPS_DATA_TYPE`
140 /// / `DCPS_DATA_KEY` both make members keys. They are in no building
141 /// block of OMG IDL 4.2, so a file that needs one is a file another
142 /// vendor's front end reads differently — and under
143 /// `Dialect::STRICT` that is an error rather than a warning.
144 ///
145 /// Not part of the derived `Default`: see the `impl Default` below.
146 pub dialect: zerodds_idl_parse::Dialect,
147}
148
149/// The literal form a constant expression settles to.
150///
151/// Only what the lowering can decide from the expression itself. It is
152/// deliberately coarser than a type: the question here is IDL 4.2
153/// §7.4.1.4.3's "consistent with the type declared for the constant",
154/// not what the value's exact type is.
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156enum ValueForm {
157 /// `TRUE` / `FALSE`.
158 Boolean,
159 /// An integer literal.
160 Integer,
161 /// A floating-point literal.
162 Float,
163 /// A fixed-point literal.
164 Fixed,
165 /// A narrow character literal.
166 Char,
167 /// A wide character literal.
168 WChar,
169 /// A narrow string literal.
170 String,
171 /// A wide string literal.
172 WString,
173}
174
175impl ValueForm {
176 /// Whether arithmetic applies to it.
177 const fn is_numeric(self) -> bool {
178 matches!(self, Self::Integer | Self::Float | Self::Fixed)
179 }
180
181 /// How the diagnostic names it.
182 const fn describe(self) -> &'static str {
183 match self {
184 Self::Boolean => "a boolean",
185 Self::Integer => "an integer literal",
186 Self::Float => "a floating-point literal",
187 Self::Fixed => "a fixed-point literal",
188 Self::Char => "a character literal",
189 Self::WChar => "a wide character literal",
190 Self::String => "a string literal",
191 Self::WString => "a wide string literal",
192 }
193 }
194}
195
196/// `Options::default()` admits every vendor extension.
197///
198/// Written out rather than derived, because `Dialect::default()` is
199/// `STRICT` and the two defaults are deliberately opposite. A parser
200/// pointed at a file somebody else wrote has to read it before it judges
201/// it, so the lenient dialect is what a caller who did not think about
202/// the question gets; `--strict` is the caller who did.
203impl Default for Options {
204 fn default() -> Self {
205 Self {
206 default_extensibility: None,
207 default_nested: None,
208 hashed_id_advances_the_counter: HashedIdCounter::default(),
209 hashed_name_spelling: HashedNameSpelling::default(),
210 key_on_union_discriminator: KeyOnUnionDiscriminator::default(),
211 name_of_the_enclosing_scope: NameOfTheEnclosingScope::default(),
212 dialect: zerodds_idl_parse::Dialect::ALL_VENDORS,
213 }
214 }
215}
216
217/// Whether a declaration may reuse the name of its immediate scope.
218///
219/// A named type for the reason [`HashedNameSpelling`] is one: the
220/// default has to be the specification's reading rather than whatever
221/// `false` happens to mean.
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
223pub enum NameOfTheEnclosingScope {
224 /// Refused. IDL 4.2 §7.5.2: "The name of a module, structure,
225 /// union, map, interface, value type, event type, exception or home
226 /// may not be redefined within the immediate scope of" that same
227 /// construct. Cyclone DDS and OpenDDS agree, and this is what a
228 /// caller who did not ask for a vendor gets.
229 #[default]
230 Refused,
231 /// Taken. Fast DDS and RTI Connext accept the declaration and write
232 /// octets for it; a probe that reproduces either of them has to
233 /// reach the same model they did.
234 Admitted,
235}
236
237/// Which spelling of an escaped name a member-id hash reads.
238///
239/// A named type for the reason [`HashedIdCounter`] is one: `Options` is
240/// `Default`, and the default has to be the specification's reading, not
241/// whatever `false` happens to mean.
242#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
243pub enum HashedNameSpelling {
244 /// The identifier without its escape underscore — `long` for
245 /// `_long`. IDL 4.2 §7.2.3.2's reading.
246 #[default]
247 Stripped,
248 /// The spelling in the file — `_long`, `_foo`. RTI Connext's
249 /// measured reading.
250 AsWritten,
251}
252
253/// Whether a `@hashid` member advances the sequential counter.
254///
255/// A named type and not a bare `bool`, because `Options` is `Default`
256/// and a boolean field defaulting to `false` would silently take RTI's
257/// reading for every caller that did not think about it. This defaults
258/// to the reading three of the four implementations take.
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
260pub enum HashedIdCounter {
261 /// The next unannotated member is the hashed id plus one. Cyclone,
262 /// Fast DDS and the second of §7.3.1.2.1.1's two sentences.
263 #[default]
264 Advances,
265 /// The hashed id is skipped: the next unannotated member is numbered
266 /// from the last *declared* id, or from zero when there is none.
267 /// RTI Connext, and the first of the two sentences.
268 Skipped,
269}
270
271/// Whether `@key` on a union discriminator is carried into the model.
272///
273/// # The clause permits it
274///
275/// DDS-XTypes 1.3 Table 21 lists `@key` against "Structure Members,
276/// Union discriminator", and §7.2.2.4.4.4.8's own example is
277///
278/// ```idl
279/// union MyCommand switch (@key CommandKind) { /* … */ };
280/// ```
281///
282/// so the default is to carry it: a front end that refuses the
283/// declaration, or tells the author the annotation "has no meaning"
284/// there, states the opposite of the specification.
285///
286/// # What the implementations do
287///
288/// Measured on the four toolchains the Linux test host carries:
289///
290/// | | `union U switch (@key uint32)` |
291/// |---|---|
292/// | Cyclone `idlc` | refuses the file — "Using union type as part of the key is currently unsupported" |
293/// | `rtiddsgen` | refuses it at the discriminator — "@key is not supported" |
294/// | `fastddsgen` | generates no `PubSubType` for a union at all, so it has no keyed topic to compare against |
295/// | `opendds_idl` | not recorded in that run |
296///
297/// So under [`Self::Carried`] this project computes a KeyHash over a
298/// union discriminator that no measured peer computes. [`Self::Refused`]
299/// is the setting for a tool that has to stay within what those
300/// toolchains accept; it names the clause that permits the declaration
301/// in its diagnostic.
302///
303/// # Why the OMG suite needs it
304///
305/// `union_uint32_one_key` pairs `unions.xml`'s `union_uint32` against
306/// `unions_key_discriminator.xml`'s `union_uint32_key`, whose
307/// discriminator carries `key="true"`, and expects `INCONSISTENT_TOPIC`
308/// on both sides: §7.2.4.4.4.5 requires that "either the discriminators
309/// of both T1 and T2 are keys or neither are keys". Under
310/// [`Self::Refused`] the subscriber never creates the topic and the case
311/// reports `TOPIC_NOT_CREATED`; under [`Self::Carried`] the declaration
312/// loads, the discriminator's `IS_KEY` reaches its `TypeObject`, and the
313/// two types are found not assignable — which is the verdict the suite
314/// asks for.
315///
316/// A named type and not a `bool` for the reason [`HashedIdCounter`] is
317/// one: `Options` is `Default`, and a boolean defaulting to `false`
318/// would carry no statement about which reading that is.
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
320pub enum KeyOnUnionDiscriminator {
321 /// The annotation is reported and the declaration does not carry
322 /// it. What the measured toolchains do.
323 Refused,
324 /// The annotation is carried: the model records it, the
325 /// discriminator's `UnionDiscriminatorFlag` gets `IS_KEY`, the
326 /// union is a keyed topic type and its KeyHash is computed over the
327 /// discriminator alone. **The default**, as Table 21 and
328 /// §7.2.2.4.4.4.8 have it.
329 #[default]
330 Carried,
331}
332
333impl KeyOnUnionDiscriminator {
334 /// Both.
335 pub const ALL: [Self; 2] = [Self::Refused, Self::Carried];
336
337 /// Whether this is the default.
338 #[must_use]
339 pub const fn is_default(self) -> bool {
340 matches!(self, Self::Carried)
341 }
342
343 /// Short name, for configuration and diagnostics.
344 #[must_use]
345 pub const fn as_str(self) -> &'static str {
346 match self {
347 Self::Refused => "refused",
348 Self::Carried => "carried",
349 }
350 }
351
352 /// Parse a configuration value.
353 ///
354 /// An unknown value returns `None` rather than falling back to the
355 /// default: a typo here changes a type's `TypeIdentifier`.
356 #[must_use]
357 pub fn parse(value: &str) -> Option<Self> {
358 match value {
359 "refused" => Some(Self::Refused),
360 "carried" => Some(Self::Carried),
361 _ => None,
362 }
363 }
364}
365
366/// Lower a parsed file into a fresh model.
367#[must_use]
368pub fn lower(root: &Arc<SyntaxNode>, path: &str) -> Lowered {
369 lower_with(root, path, Options::default())
370}
371
372/// Lower a parsed file into a fresh model, deciding what the source left
373/// open.
374#[must_use]
375pub fn lower_with(root: &Arc<SyntaxNode>, path: &str, options: Options) -> Lowered {
376 let mut ast = Ast::new();
377 let diagnostics = lower_into_with(&mut ast, root, path, options);
378 Lowered { ast, diagnostics }
379}
380
381/// Lower a parsed file into an existing model.
382///
383/// The second file of a two-file build goes here, which is where module
384/// reopening has to converge rather than duplicate.
385pub fn lower_into(ast: &mut Ast, root: &Arc<SyntaxNode>, path: &str) -> Vec<Diagnostic> {
386 lower_into_with(ast, root, path, Options::default())
387}
388
389/// Lower a parsed file into an existing model, deciding what the source
390/// left open.
391pub fn lower_into_with(
392 ast: &mut Ast,
393 root: &Arc<SyntaxNode>,
394 path: &str,
395 options: Options,
396) -> Vec<Diagnostic> {
397 let mut ctx = Lowering {
398 ast,
399 scopes: ScopeStack::new_placeholder(),
400 diagnostics: Vec::new(),
401 path: path.to_string(),
402 syntax_spans: SyntaxSpans::of(root),
403 options,
404 pending_annotations: Vec::new(),
405 pragma_extensibility: None,
406 repository_directives: Vec::new(),
407 module_ranges: Vec::new(),
408 keylists: Vec::new(),
409 prefixes: Vec::new(),
410 pragma_prefixes: Vec::new(),
411 pragma_versions: Vec::new(),
412 open_dds_types: Vec::new(),
413 open_dds_keys: Vec::new(),
414 unrecognised_pragmas: Vec::new(),
415 xtopics_regions: Vec::new(),
416 templates: Vec::new(),
417 };
418 ctx.scopes = ScopeStack::new(ctx.ast.root_module());
419 ctx.file(root);
420 // Completion runs over the finished model: a rule that reads the
421 // previous member's id needs every member to be there first.
422 ctx.complete();
423 // Validation last: a rule about member ids needs the derived ones,
424 // and a rule about forward declarations needs the whole file.
425 ctx.validate();
426 // A diagnostic points at text, not at the whitespace before it. The
427 // ranges that came from a node still carry that node's leading
428 // trivia, so each one — the primary and every label — is narrowed
429 // here, at the one place every diagnostic of the walk passes
430 // through. A range holding no syntax at all keeps what it had.
431 let mut diagnostics = core::mem::take(&mut ctx.diagnostics);
432 for diagnostic in &mut diagnostics {
433 diagnostic.primary = ctx.syntax_spans.trim(diagnostic.primary);
434 for (span, _) in &mut diagnostic.labels {
435 *span = ctx.syntax_spans.trim(*span);
436 }
437 }
438 // One fact, one diagnostic: a collision the model refused and the
439 // validation pass found again is the same collision.
440 dedup(diagnostics)
441}
442
443/// The walk's state.
444///
445/// Shared with the sibling modules — expressions, annotations,
446/// completion and validation are separate files over one walk, not
447/// separate passes over the tree.
448pub(crate) struct Lowering<'a> {
449 pub(crate) ast: &'a mut Ast,
450 pub(crate) scopes: ScopeStack,
451 pub(crate) diagnostics: Vec<Diagnostic>,
452 pub(crate) path: String,
453 /// Where the file's syntax is, with the trivia left out.
454 ///
455 /// A range taken from the tree begins at the node's leading trivia,
456 /// because that is where the lexer attaches it — right for the round
457 /// trip, wrong for anything a person reads. Every range this walk
458 /// records on a node and every range it puts in a diagnostic is
459 /// narrowed through this index first.
460 pub(crate) syntax_spans: SyntaxSpans,
461 /// What the caller decided that the source does not say.
462 pub(crate) options: Options,
463 /// The extensibility an enclosing `#pragma` asked for, which a type
464 /// without its own annotation inherits. Spec §5.
465 pub(crate) pragma_extensibility: Option<zerodds_ast::Extensibility>,
466 /// Annotations read from an enclosing wrapper node, waiting for the
467 /// declaration they modify to exist.
468 ///
469 /// A stack rather than one slot: an annotated declaration may
470 /// contain another one, and the inner annotations must not be
471 /// applied to the outer node.
472 pub(crate) pending_annotations: Vec<Vec<crate::annotation::Applied>>,
473 /// `typeid` and `typeprefix` directives, applied once the whole file
474 /// is walked: both may name a declaration that comes later.
475 ///
476 /// The name is kept as every scope it could denote, innermost first
477 /// — §7.5's lookup — because a directive inside a module may name a
478 /// sibling, its own module, or anything further out.
479 pub(crate) repository_directives: Vec<(Vec<ScopedName>, String, bool)>,
480 /// Every range a module's body was written in, with the module's
481 /// scoped name — one entry per opening, since a module may be reopened.
482 /// `#pragma prefix` and the type name of a `#pragma keylist` are read
483 /// in the module around them, which only these ranges know: a module
484 /// node carries no source range of its own.
485 pub(crate) module_ranges: Vec<(Span, ScopedName)>,
486 /// `keylist` directives, applied for the same reason: the scopes the
487 /// type name could denote (innermost first), the member names or
488 /// paths, and where the directive stands.
489 pub(crate) keylists: Vec<(Vec<ScopedName>, Vec<String>, Span)>,
490 /// `typeprefix` scopes, longest match first when a repository id is
491 /// derived.
492 pub(crate) prefixes: Vec<(ScopedName, String)>,
493 /// `#pragma prefix` scopes, which do the same thing in the older
494 /// spelling.
495 pub(crate) pragma_prefixes: Vec<(ScopedName, String)>,
496 /// `#pragma version X 1.2`, which replaces the `1.0` in a derived
497 /// repository id.
498 pub(crate) pragma_versions: Vec<(ScopedName, String)>,
499 /// `#pragma DCPS_DATA_TYPE "X"`.
500 pub(crate) open_dds_types: Vec<String>,
501 /// `#pragma DCPS_DATA_KEY X f`.
502 pub(crate) open_dds_keys: Vec<(String, Vec<String>, Span)>,
503 /// Pragmas with no typed meaning, kept verbatim on the root module.
504 pub(crate) unrecognised_pragmas: Vec<(String, String)>,
505 /// The `#pragma dds_xtopics begin` declarations still open, with the
506 /// version each named, innermost last. DDS-XTypes 1.3 §7.3.1.1.2.
507 ///
508 /// A stack because the clause nests them: "In the event that such
509 /// pragma declarations are nested within one another, the innermost
510 /// version number specified, if any, shall be in effect." Nothing
511 /// closes it at the end of the file, because the same clause says a
512 /// `begin` without a matching `end` "shall be considered to continue
513 /// until the end of the IDL input" — which is a complete region and
514 /// not a defect.
515 pub(crate) xtopics_regions: Vec<(Span, String)>,
516 /// The body of every template module declared so far, by the scoped
517 /// name it was declared under.
518 ///
519 /// A template module is not a scope with types in it — §7.4.12: its
520 /// contents are definitions *of a template*, and a definition whose
521 /// type is a formal parameter has no type until an actual one is
522 /// given. Lowering the body in place is what left `module
523 /// M<typename T> { struct S { T a; }; };` with `S.a` pending for
524 /// ever, and the block reporting success on a model that was not
525 /// one. The body is kept here and lowered once per instantiation.
526 pub(crate) templates: Vec<(ScopedName, Arc<SyntaxNode>)>,
527}
528
529impl Lowering<'_> {
530 fn file(&mut self, node: &Arc<SyntaxNode>) {
531 self.identifiers_do_not_collide_with_keywords(node);
532 for child in node.children() {
533 self.definition(&child);
534 }
535 // Pragmas are read after the walk: `#pragma ID X` names a
536 // declaration that may come after it, and the scope a `#pragma
537 // prefix` applies to is the module whose range contains it.
538 self.collect_pragmas(node);
539 }
540
541 /// An identifier that differs from a keyword only in case.
542 ///
543 /// OMG IDL 4.2 §7.2.4, p. 20: "Keywords must be written exactly as
544 /// shown in the above list. Identifiers that collide with keywords
545 /// (see 7.2.3, Identifiers) are illegal." §7.2.3 supplies the
546 /// comparison — "upper- and lower-case letters are treated as the
547 /// same letter" — and its worked example is `typedef boolean
548 /// BOOLEAN;`.
549 ///
550 /// Two rules, and the lexer used to apply the first as though it
551 /// were the second: it matched keywords without regard to case, so
552 /// `enum E { CUSTOM };` parsed as though the author had written the
553 /// keyword `custom` there. The lexer now matches exactly, which
554 /// leaves `CUSTOM` an ordinary identifier and leaves this the place
555 /// the clause is enforced.
556 ///
557 /// Over the token stream and not over the model, because the rule is
558 /// about the spelling in the file: a name that collides is illegal
559 /// wherever it is written, whether it declares something, names a
560 /// type or stands for a value. Five of the seven foreign front ends
561 /// measured on the Linux test host answer the same way, three of
562 /// them naming the keyword — OpenDDS and TAO with "identifier
563 /// spellings differ only in case", JacORB with "Identifier CUSTOM
564 /// collides with keyword custom".
565 ///
566 /// The escaped form is exempt. §7.2.3.2: prefixing an underscore "is
567 /// a purely lexical convention that ONLY turns off keyword
568 /// checking", which is exactly the check this is.
569 fn identifiers_do_not_collide_with_keywords(&mut self, root: &Arc<SyntaxNode>) {
570 let mut found: Vec<(Span, String, &'static str)> = Vec::new();
571 for node in root.descendants() {
572 for (span, token) in node.tokens() {
573 if token.kind() != TokenKind::Ident {
574 continue;
575 }
576 let text = token.text();
577 // `_AnIdentifier` is the name `AnIdentifier` with the
578 // keyword check turned off, so it is not asked at all.
579 if text.starts_with('_') {
580 continue;
581 }
582 if let Some(keyword) = zerodds_idl_cst::keyword_collision(text) {
583 found.push((span, text.to_string(), keyword));
584 }
585 }
586 }
587 for (span, name, keyword) in found {
588 let message = alloc::format!(
589 "`{name}` differs from the keyword `{keyword}` only in case, and IDL compares \
590 identifiers without regard to case. Write the keyword's own spelling, or escape \
591 the identifier as `_{name}`"
592 );
593 self.diagnose_at(
594 DiagnosticCode::KeywordCollision,
595 span,
596 &message,
597 "OMG IDL 4.2 §7.2.3, §7.2.4",
598 );
599 }
600 }
601
602 /// Every `#pragma` in the file, with where it was written.
603 ///
604 /// The preprocessor leaves them alone — a pragma carries declaration
605 /// metadata rather than affecting expansion — so they arrive here as
606 /// the directive trivia they were, still in the tree.
607 fn collect_pragmas(&mut self, root: &Arc<SyntaxNode>) {
608 let mut found: Vec<(Span, String)> = Vec::new();
609 for node in root.descendants() {
610 for (span, token) in node.tokens() {
611 if token.kind() != TokenKind::Directive {
612 continue;
613 }
614 let line = token.text().trim();
615 let Some(rest) = line.strip_prefix('#') else {
616 continue;
617 };
618 let rest = rest.trim_start();
619 let Some(body) = rest.strip_prefix("pragma") else {
620 continue;
621 };
622 found.push((span, body.trim().to_string()));
623 }
624 }
625 // Source order, and not the order the walk happened to reach
626 // the tokens in: `#pragma dds_xtopics begin` and its `end` are
627 // a pair whose nesting DDS-XTypes 1.3 §7.3.1.1.2 reads off the
628 // order they were written in, and a `keylist` naming a type
629 // declared twice reaches the same reading only that way.
630 found.sort_by_key(|(span, _)| span.start);
631 for (span, body) in found {
632 self.pragma(span, &body);
633 }
634 }
635
636 /// One `#pragma`, routed by its first word.
637 fn pragma(&mut self, span: Span, body: &str) {
638 let mut words = body.split_whitespace();
639 let Some(directive) = words.next() else {
640 return;
641 };
642 let rest: Vec<&str> = words.collect();
643 match directive {
644 // CORBA 3.3 §10.7: the three that shape repository ids.
645 "prefix" => {
646 if let Some(text) = unquote(rest.first().copied().unwrap_or_default()) {
647 let scope = self.module_containing(span);
648 self.record_repository_directive("typeprefix", &text);
649 self.pragma_prefixes.push((scope, text));
650 }
651 }
652 "version" => {
653 if let (Some(name), Some(version)) = (rest.first(), rest.get(1)) {
654 self.pragma_versions
655 .push((pragma_name(name), (*version).to_string()));
656 }
657 }
658 "ID" => {
659 if let (Some(name), Some(id)) = (rest.first(), rest.get(1).copied()) {
660 if let Some(text) = unquote(id) {
661 let candidates = self.pragma_candidates(span, name);
662 self.record_repository_directive("typeid", &text);
663 self.repository_directives.push((candidates, text, false));
664 }
665 }
666 }
667 // Cyclone DDS says keys the way RTI says `keylist`.
668 "keylist" => {
669 self.diagnose_vendor(
670 zerodds_idl_parse::VendorFlag::Cyclone,
671 span,
672 "`#pragma keylist` is a Cyclone DDS directive and is in no building block \
673 of OMG IDL 4.2; it makes members keys, so a front end that ignores it \
674 builds a different type from the same file",
675 );
676 // OpenSplice's `idlpp` and Cyclone's `idlc` both take
677 // `a, b` as well as `a b` (measured, 11.0.1 and 6.9).
678 if let Some((name, fields)) = rest.split_first() {
679 let fields = fields
680 .iter()
681 .flat_map(|f| f.split(','))
682 .filter(|f| !f.is_empty())
683 .map(ToString::to_string)
684 .collect();
685 let candidates = self.pragma_candidates(span, name);
686 self.keylists.push((candidates, fields, span));
687 }
688 }
689 "DCPS_DATA_TYPE" => {
690 self.diagnose_vendor(
691 zerodds_idl_parse::VendorFlag::OpenDds,
692 span,
693 "`#pragma DCPS_DATA_TYPE` is an OpenDDS directive and is in no building \
694 block of OMG IDL 4.2; it marks the type as a topic type, so a front end \
695 that ignores it builds a different type from the same file. The same \
696 spelling is also OMG DDS 1.2 §8's DLRL directive that marks a DLRL object \
697 type — this front end reads it as OpenDDS's, and `zerodds-idlc dlrl` \
698 reads it as DLRL's",
699 );
700 // Quoted as OpenDDS writes it, or bare; either names the type.
701 match rest.first().copied() {
702 Some(word) => {
703 self.open_dds_types
704 .push(unquote(word).unwrap_or_else(|| word.to_string()));
705 }
706 None => self.diagnose_at(
707 DiagnosticCode::Unsupported,
708 span,
709 "`#pragma DCPS_DATA_TYPE` names no type",
710 "OMG DDS 1.4 §2.2.1.2.2",
711 ),
712 }
713 }
714 "DCPS_DATA_KEY" => {
715 self.diagnose_vendor(
716 zerodds_idl_parse::VendorFlag::OpenDds,
717 span,
718 "`#pragma DCPS_DATA_KEY` is an OpenDDS directive and is in no building \
719 block of OMG IDL 4.2; it makes a member a key, so a front end that \
720 ignores it builds a different type from the same file. The same spelling \
721 is also OMG DDS 1.2 §8's DLRL directive that marks a DLRL object key — \
722 this front end reads it as OpenDDS's, and `zerodds-idlc dlrl` reads it as \
723 DLRL's",
724 );
725 // OpenDDS writes the type and the field in one string,
726 // `"Robot::Pose id"`; the unquoted form `Robot::Pose id` is
727 // read the same.
728 let joined = rest.join(" ");
729 let words: Vec<String> = unquote(&joined)
730 .unwrap_or(joined)
731 .split_whitespace()
732 .map(String::from)
733 .collect();
734 if let Some((name, fields)) = words.split_first() {
735 self.open_dds_keys
736 .push((name.clone(), fields.to_vec(), span));
737 } else {
738 self.diagnose_at(
739 DiagnosticCode::Unsupported,
740 span,
741 "`#pragma DCPS_DATA_KEY` names neither a type nor a member; \
742 the directive keys nothing",
743 "OMG DDS 1.4 §2.2.1.2.2",
744 );
745 }
746 }
747 // OMG DDS 1.2 §8.2.2 — the third DLRL pragma, and the one
748 // OpenDDS never repurposed: `DCPS_DATA_TYPE`/`DCPS_DATA_KEY`
749 // collide with OpenDDS's own two-pragma dialect above, this
750 // one names nothing OpenDDS defines. Not routed through
751 // `diagnose_vendor`: it is not a vendor extension admitted
752 // under `ALL_VENDORS` and rejected under `--strict` — it is
753 // an OMG-specification construct this front end simply does
754 // not turn into a type-model facet, the same standing
755 // `dds_xtopics` below has. Kept in the bag as well as
756 // reported, so a round trip writes it back; `zerodds-idlc
757 // dlrl` is what reads it, together with the other two, into
758 // DLRL object and home classes.
759 "DCPS_DLRL_RELATION" => {
760 self.warn_at(
761 DiagnosticCode::Unsupported,
762 span,
763 "`#pragma DCPS_DLRL_RELATION` is OMG DDS 1.2 §8's DLRL relation \
764 directive; this front end does not turn it into a type-model facet — \
765 `zerodds-idlc dlrl` reads it, together with `DCPS_DATA_TYPE` and \
766 `DCPS_DATA_KEY`, into DLRL object and home classes",
767 "OMG DDS 1.2 §8.2.2",
768 );
769 self.unrecognised_pragmas
770 .push((directive.to_string(), rest.join(" ")));
771 }
772 // DDS-XTypes 1.3 §7.3.1.1.2. Kept in the bag below as
773 // well as read: the model has no typed field for it, and a
774 // round trip has to write back what was there.
775 "dds_xtopics" => {
776 self.dds_xtopics(span, &rest);
777 self.unrecognised_pragmas
778 .push((directive.to_string(), rest.join(" ")));
779 }
780 // A pragma nobody recognises is kept rather than dropped: a
781 // round trip back to that vendor's dialect needs it, and the
782 // model has a place for exactly this.
783 //
784 // **And it is reported.** Keeping it was the whole of the
785 // handling, so `#pragma this_pragma_does_not_exist_anywhere`
786 // and `#pragma keylist Sample id` were indistinguishable
787 // from the outside: nought errors, nought warnings, exit
788 // zero, even under `--strict --deny-warnings`. A pragma is
789 // the one place in IDL where a typo has no syntax to fail
790 // against, so silence there is silence about a directive
791 // that was meant to change the type.
792 //
793 // A warning and not an error, and the level is the
794 // specification's rather than a choice. DDS-XTypes 1.3
795 // §7.3.1.1.1 on constructs outside the building blocks it
796 // uses: "Compilers that do not support them shall ignore
797 // them or issue a warning rather than halting with an
798 // error." Ignoring is what was measured and is what the
799 // finding is about, so the other permitted level is the one
800 // taken — and `--deny-warnings` is the caller who wants it
801 // to fail.
802 other => {
803 self.warn_at(
804 DiagnosticCode::Unsupported,
805 span,
806 &alloc::format!(
807 "`#pragma {other}` is not a directive this front end interprets; it is \
808 kept verbatim on the model and changes nothing"
809 ),
810 "DDS-XTypes 1.3 §7.3.1.1.1",
811 );
812 self.unrecognised_pragmas
813 .push((other.to_string(), rest.join(" ")));
814 }
815 }
816 }
817
818 /// `#pragma dds_xtopics begin [<version>]` and its `end`.
819 /// DDS-XTypes 1.3 §7.3.1.1.2.
820 ///
821 /// The clause defines the directive in exactly those two forms, as
822 /// a marker "to indicate to their tools and to human readers that
823 /// their IDL file (or a portion of it) makes use of constructs
824 /// defined by this specification". It carries no meaning for a
825 /// declaration: the same clause calls the declarations "informative
826 /// only", which is why nothing here reaches a type. What it does
827 /// carry is a version, and the clause names three things that can
828 /// be wrong with a pair of them.
829 ///
830 /// Every one of them is a warning. The clause admits "halting with
831 /// an error" as one behaviour a compiler may choose, and the
832 /// requirement it states in its own words is a "should" — "those
833 /// version numbers should be the same as those used with the
834 /// matching 'begin' declarations". A marker the specification calls
835 /// informative does not stop a build; `--deny-warnings` is the
836 /// caller who wants it to.
837 fn dds_xtopics(&mut self, span: Span, rest: &[&str]) {
838 let spec = "DDS-XTypes 1.3 §7.3.1.1.2";
839 let Some((keyword, version)) = rest.split_first() else {
840 self.warn_at(
841 DiagnosticCode::Unsupported,
842 span,
843 "`#pragma dds_xtopics` was written with neither `begin` nor `end`; \
844 §7.3.1.1.2 defines the directive in those two forms only",
845 spec,
846 );
847 return;
848 };
849 let version = xtopics_version(version);
850 match *keyword {
851 "begin" => self.xtopics_regions.push((span, version)),
852 "end" => {
853 let Some((opened_at, opened)) = self.xtopics_regions.pop() else {
854 self.warn_at(
855 DiagnosticCode::Unsupported,
856 span,
857 "`#pragma dds_xtopics end` closes a region no `begin` opened",
858 spec,
859 );
860 return;
861 };
862 if opened.is_empty() || version.is_empty() || opened == version {
863 return;
864 }
865 let message = alloc::format!(
866 "`#pragma dds_xtopics end {version}` closes a region opened as \
867 `begin {opened}`; §7.3.1.1.2 has the two version numbers the same"
868 );
869 self.diagnostics.push(
870 Diagnostic::warning(DiagnosticCode::Unsupported, span, message, spec)
871 .with_label(opened_at, alloc::format!("opened as `begin {opened}`")),
872 );
873 }
874 other => self.warn_at(
875 DiagnosticCode::Unsupported,
876 span,
877 &alloc::format!(
878 "`#pragma dds_xtopics {other}` is neither `begin` nor `end`; \
879 §7.3.1.1.2 defines the directive in those two forms only"
880 ),
881 spec,
882 ),
883 }
884 }
885
886 /// The innermost module whose source range contains `span`.
887 /// Every scope the type name a `#pragma` writes could denote, innermost
888 /// first — [`Self::directive_candidates`] for a directive the walk no
889 /// longer stands in, whose scope is the module around its position.
890 ///
891 /// `pragma_name` alone read every name from the root: `module M {
892 /// struct T {…}; #pragma keylist T k };` named `::T`, which does not
893 /// exist, and the directive keyed nothing — the placement OpenSplice's
894 /// `idlpp` requires for it. A name written `::T` still means the root
895 /// and nothing else.
896 fn pragma_candidates(&self, span: Span, written: &str) -> Vec<ScopedName> {
897 let name = pragma_name(written);
898 if written.starts_with("::") {
899 return alloc::vec![name];
900 }
901 let module = self.module_containing(span);
902 let enclosing = module.segments();
903 (0..=enclosing.len())
904 .rev()
905 .map(|depth| {
906 let mut path = enclosing[..depth].to_vec();
907 path.extend_from_slice(name.segments());
908 ScopedName::absolute(path)
909 })
910 .collect()
911 }
912
913 fn module_containing(&self, span: Span) -> ScopedName {
914 // The narrowest range around `span`: modules nest, and the
915 // innermost is the one a directive stands in.
916 self.module_ranges
917 .iter()
918 .filter(|(range, _)| range.start <= span.start && range.end >= span.end)
919 .min_by_key(|(range, _)| range.end.saturating_sub(range.start))
920 .map_or_else(
921 || ScopedName::absolute(Vec::new()),
922 |(_, scoped)| scoped.clone(),
923 )
924 }
925
926 /// One declaration, with whatever annotations reach it.
927 ///
928 /// A prefix annotation sits in the wrapper node the parser puts
929 /// around the declaration, and [`Self::apply_waiting_annotations`]
930 /// reads it from there. The suffix form of DDS-XTypes 1.3
931 /// §7.3.1.2.5, Table 24 — `}; //@my_type_annotation` — has no
932 /// wrapper to sit in: it belongs to the declaration it follows, so
933 /// the parser puts it inside the declaration's own node. Both reach
934 /// the same frame here, which is what makes Table 24's claim that
935 /// the two forms are equivalent true of a type as well as of a
936 /// member.
937 fn definition(&mut self, node: &Arc<SyntaxNode>) {
938 let suffix = self.suffix_annotations_of(node);
939 // DDS-XTypes 1.3 §7.3.1.2.4, Table 22: `struct <ann_identifier> {
940 // <ann_members> }; //@annotation` "is actually an annotation type
941 // containing the members <ann_members>". It is the declaration of
942 // an annotation for compilers that do not know the `@annotation`
943 // token, not a structure that the annotation `@annotation` is
944 // applied to.
945 if node.kind() == CstKind::StructDecl
946 && suffix
947 .iter()
948 .any(|applied| applied.name == "annotation" && applied.arguments.is_empty())
949 {
950 self.annotation_from_struct(node);
951 return;
952 }
953 if suffix.is_empty() {
954 self.definition_inner(node);
955 return;
956 }
957 let outer = self.pending_annotations.pop();
958 let mut frame = outer.clone().unwrap_or_default();
959 frame.extend(suffix);
960 self.pending_annotations.push(frame);
961 self.definition_inner(node);
962 let _ = self.pending_annotations.pop();
963 if let Some(outer) = outer {
964 self.pending_annotations.push(outer);
965 }
966 }
967
968 fn definition_inner(&mut self, node: &Arc<SyntaxNode>) {
969 match node.kind() {
970 CstKind::Module => self.module(node),
971 CstKind::TemplateModuleInst => self.template_module_inst(node),
972 CstKind::TemplateModuleRef => self.template_module_ref(node),
973 CstKind::StructDecl => self.structure(node),
974 CstKind::EnumDecl => self.enumeration(node),
975 CstKind::BitmaskDecl => self.bitmask(node),
976 CstKind::UnionDecl => self.union(node),
977 CstKind::TypedefDecl => self.typedef(node),
978 CstKind::ExceptionDecl => self.exception(node),
979 CstKind::ForwardDecl => self.forward(node),
980 CstKind::NativeDecl => self.native(node),
981 // An annotated declaration wraps its annotations and the
982 // declaration itself. The annotations are read here and
983 // applied to whatever the inner declaration produced, which
984 // is why `declare_type` reports the node it made.
985 CstKind::Member => {
986 let annotations = self.annotations_of(node);
987 self.pending_annotations.push(annotations);
988 for child in node.children() {
989 self.definition(&child);
990 }
991 let _ = self.pending_annotations.pop();
992 }
993 CstKind::InterfaceDecl => self.interface(node),
994 CstKind::ValuetypeDecl => self.valuetype(node),
995 CstKind::ValueBoxDecl => self.value_box(node),
996 CstKind::ConstDecl => self.constant(node),
997 CstKind::BitsetDecl => self.bitset(node),
998 CstKind::AnnotationDecl => self.annotation_decl(node),
999 CstKind::RepositoryDirective => self.repository_directive(node),
1000 CstKind::ImportDecl => self.import(node),
1001 CstKind::KeylistDecl => self.keylist(node),
1002 CstKind::Error => {}
1003 _ => {
1004 for child in node.children() {
1005 self.definition(&child);
1006 }
1007 }
1008 }
1009 }
1010
1011 fn module(&mut self, node: &Arc<SyntaxNode>) {
1012 let Some(name) = first_ident(node) else {
1013 return;
1014 };
1015 let Some(parent) = self.scopes.current_module() else {
1016 return;
1017 };
1018 // Reopening converges here rather than duplicating. Spec §4.
1019 let Ok(module) = self.ast.ensure_module(parent, &name) else {
1020 return;
1021 };
1022 if let Some(parameters) = node
1023 .children()
1024 .into_iter()
1025 .find(|child| child.kind() == CstKind::TemplateParameters)
1026 {
1027 self.template_parameters(¶meters, module);
1028 // The body is the template's, not the model's. It is kept
1029 // whole and lowered once per instantiation, with the formal
1030 // parameters bound to what the instantiation supplied.
1031 let qualified = self.scopes.qualify(&name);
1032 self.templates.push((qualified, node.clone()));
1033 return;
1034 }
1035 // A module is an annotation target of its own. DDS-XTypes 1.3
1036 // §7.3.1.2.1.7 puts `@default_nested` on one, and Table 21 lists
1037 // "Module" for it; the annotations in front of the declaration
1038 // reached nothing until this call, so `@default_nested(TRUE)
1039 // module M { … }` was parsed and dropped.
1040 //
1041 // Before the body, because a reopened module keeps the facets of
1042 // its first appearance and the annotation belongs to the
1043 // declaration that carries it.
1044 if let Ok(node_id) = self.ast.module(module).map(|m| m.node_id) {
1045 self.apply_waiting_annotations(AnyId::Module(module, node_id));
1046 }
1047 let range = self.syntax_spans.trim(node.span());
1048 self.module_ranges.push((range, self.scopes.qualify(&name)));
1049 self.scopes.push(&name, Some(module));
1050 for child in node.children() {
1051 self.definition(&child);
1052 }
1053 self.scopes.pop();
1054 }
1055
1056 /// `module M<long, 8> N;` — rules (190), (191) and (192).
1057 ///
1058 /// Instantiation is substitution: the template's body is lowered
1059 /// into a module of the instance's name, with each formal parameter
1060 /// bound to the actual the instantiation gave it. Binding rather
1061 /// than rewriting the tree, because every reference the body makes
1062 /// to a formal is a name, and a name is what a scope answers.
1063 fn template_module_inst(&mut self, node: &Arc<SyntaxNode>) {
1064 // The instance's own name is the last identifier, after the
1065 // template's scoped name and its actual parameters.
1066 let idents: Vec<String> = node
1067 .syntax_tokens()
1068 .into_iter()
1069 .filter(|(_, token)| token.kind() == TokenKind::Ident)
1070 .map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
1071 .collect();
1072 let Some(instance) = idents.last().cloned() else {
1073 return;
1074 };
1075 let Some(template_name) = self.scoped_name_of(node) else {
1076 return;
1077 };
1078 let Some(template) = self.template_body(node, &template_name) else {
1079 let message = alloc::format!(
1080 "`{}` is not a template module declared in this file",
1081 template_name.segments().join("::")
1082 );
1083 self.diagnose_at(
1084 DiagnosticCode::UnresolvedName,
1085 node.span(),
1086 &message,
1087 "OMG IDL 4.2 §7.4.12.3 rule (190)",
1088 );
1089 return;
1090 };
1091
1092 let formals = template_formals(&template);
1093 let actuals = self.actual_parameters(node);
1094 if formals.len() != actuals.len() {
1095 let message = alloc::format!(
1096 "`{}` takes {} actual parameter(s) and was given {}",
1097 template_name.segments().join("::"),
1098 formals.len(),
1099 actuals.len()
1100 );
1101 self.diagnose_at(
1102 DiagnosticCode::UnexpectedToken,
1103 node.span(),
1104 &message,
1105 "OMG IDL 4.2 §7.4.12.3 rule (191)",
1106 );
1107 return;
1108 }
1109
1110 let Some(parent) = self.scopes.current_module() else {
1111 return;
1112 };
1113 self.check_collision(&instance, node);
1114 let Ok(module) = self.ast.ensure_module(parent, &instance) else {
1115 return;
1116 };
1117 self.instantiate(&template, &formals, &actuals, module, &instance, node);
1118 }
1119
1120 /// `alias M<T> R;` — rules (193) and (194).
1121 ///
1122 /// A template module body may name another template module,
1123 /// instantiated with its **own** formal parameters. Inside an
1124 /// instantiation those formals are bound to actuals already, so the
1125 /// reference hands the bindings on unchanged; outside one there is
1126 /// nothing to hand on, and (189) admits the form only as a
1127 /// `<tpl_definition>`.
1128 fn template_module_ref(&mut self, node: &Arc<SyntaxNode>) {
1129 let Some(instance) = node
1130 .syntax_tokens()
1131 .into_iter()
1132 .filter(|(_, token)| token.kind() == TokenKind::Ident)
1133 .map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
1134 .next_back()
1135 else {
1136 return;
1137 };
1138 let Some(template_name) = self.scoped_name_of(node) else {
1139 return;
1140 };
1141 let Some(template) = self.template_body(node, &template_name) else {
1142 let message = alloc::format!(
1143 "`alias` names `{}`, which is not a template module declared in this file",
1144 template_name.segments().join("::")
1145 );
1146 self.diagnose_at(
1147 DiagnosticCode::UnresolvedName,
1148 node.span(),
1149 &message,
1150 "OMG IDL 4.2 §7.4.12.3 rule (193)",
1151 );
1152 return;
1153 };
1154
1155 let mut actuals = Vec::new();
1156 for written in self.formal_parameter_names(node) {
1157 let Some(any) = self.scopes.lookup(&written) else {
1158 let message = alloc::format!(
1159 "`{written}` is not a formal parameter of the template this `alias` is \
1160 written in; rule (193) instantiates with the enclosing template's own \
1161 parameters"
1162 );
1163 self.diagnose_at(
1164 DiagnosticCode::UnresolvedName,
1165 node.span(),
1166 &message,
1167 "OMG IDL 4.2 §7.4.12.3 rule (194)",
1168 );
1169 return;
1170 };
1171 actuals.push(Actual::Bound(any));
1172 }
1173
1174 let formals = template_formals(&template);
1175 if formals.len() != actuals.len() {
1176 let message = alloc::format!(
1177 "`{}` takes {} formal parameter(s) and the `alias` names {}",
1178 template_name.segments().join("::"),
1179 formals.len(),
1180 actuals.len()
1181 );
1182 self.diagnose_at(
1183 DiagnosticCode::UnexpectedToken,
1184 node.span(),
1185 &message,
1186 "OMG IDL 4.2 §7.4.12.3 rule (194)",
1187 );
1188 return;
1189 }
1190
1191 let Some(parent) = self.scopes.current_module() else {
1192 return;
1193 };
1194 self.check_collision(&instance, node);
1195 let Ok(module) = self.ast.ensure_module(parent, &instance) else {
1196 return;
1197 };
1198 self.instantiate(&template, &formals, &actuals, module, &instance, node);
1199 }
1200
1201 /// The names written between the angle brackets of an `alias`.
1202 fn formal_parameter_names(&self, node: &Arc<SyntaxNode>) -> Vec<String> {
1203 node.children()
1204 .into_iter()
1205 .find(|child| child.kind() == CstKind::ActualParameters)
1206 .map_or_else(Vec::new, |list| {
1207 list.children()
1208 .into_iter()
1209 .filter(|child| child.kind() == CstKind::ScopedName)
1210 .filter_map(|scoped| first_ident(&scoped))
1211 .collect()
1212 })
1213 }
1214
1215 /// Lower a template body into one instance module.
1216 fn instantiate(
1217 &mut self,
1218 template: &Arc<SyntaxNode>,
1219 formals: &[(String, bool)],
1220 actuals: &[Actual],
1221 module: Id<zerodds_ast::ModuleNode>,
1222 instance: &str,
1223 at: &Arc<SyntaxNode>,
1224 ) {
1225 if let Ok(node_id) = self.ast.module(module).map(|m| m.node_id) {
1226 self.record_origin(AnyId::Module(module, node_id), at.span());
1227 }
1228 self.scopes.push(instance, Some(module));
1229 for (formal, actual) in formals.iter().zip(actuals) {
1230 self.bind_actual(formal, actual, module, at);
1231 }
1232 for child in template.children() {
1233 self.definition(&child);
1234 }
1235 self.scopes.pop();
1236 }
1237
1238 /// The stored body of the template a name denotes.
1239 fn template_body(
1240 &self,
1241 node: &Arc<SyntaxNode>,
1242 written: &ScopedName,
1243 ) -> Option<Arc<SyntaxNode>> {
1244 for candidate in self.directive_candidates(node, written) {
1245 if let Some((_, body)) = self
1246 .templates
1247 .iter()
1248 .find(|(name, _)| name.segments() == candidate.segments())
1249 {
1250 return Some(body.clone());
1251 }
1252 }
1253 None
1254 }
1255
1256 /// The actual parameters an instantiation wrote, in order.
1257 fn actual_parameters(&mut self, node: &Arc<SyntaxNode>) -> Vec<Actual> {
1258 node.children()
1259 .into_iter()
1260 .find(|child| child.kind() == CstKind::ActualParameters)
1261 .map_or_else(Vec::new, |list| {
1262 list.children()
1263 .into_iter()
1264 .filter(|child| {
1265 matches!(
1266 child.kind(),
1267 CstKind::PrimitiveType
1268 | CstKind::NamedType
1269 | CstKind::SequenceType
1270 | CstKind::StringType
1271 | CstKind::WStringType
1272 | CstKind::MapType
1273 | CstKind::FixedType
1274 ) || crate::expr::is_expr(child.kind())
1275 })
1276 .map(Actual::Written)
1277 .collect()
1278 })
1279 }
1280
1281 /// Bind one formal parameter to the actual the instantiation gave.
1282 ///
1283 /// A `const` formal becomes a constant of the instance module, so a
1284 /// bound written as `sequence<T, Capacity>` in the template body
1285 /// resolves the way any other named constant does. Every other kind
1286 /// binds the actual type under the formal's name.
1287 fn bind_actual(
1288 &mut self,
1289 formal: &(String, bool),
1290 actual: &Actual,
1291 module: Id<zerodds_ast::ModuleNode>,
1292 at: &Arc<SyntaxNode>,
1293 ) {
1294 let (name, is_const) = (formal.0.clone(), formal.1);
1295 // (193) hands on what the enclosing instantiation already bound,
1296 // so there is nothing to evaluate: the actual is a node of the
1297 // model and the formal takes the same one under its own name.
1298 let actual = match actual {
1299 Actual::Bound(any) => {
1300 self.scopes.bind(&name, *any);
1301 return;
1302 }
1303 Actual::Written(node) => node.clone(),
1304 };
1305 let actual = &actual;
1306 if is_const {
1307 let Some(expr) = self.expr(actual) else {
1308 self.diagnose_at(
1309 DiagnosticCode::UnexpectedToken,
1310 at.span(),
1311 "a `const` formal parameter needs a constant expression as its actual",
1312 "OMG IDL 4.2 §7.4.12.3 rule (192)",
1313 );
1314 return;
1315 };
1316 let type_ = Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(Vec::new())));
1317 if let Ok(id) = self.ast.add_const(module, &name, type_, expr) {
1318 if let Ok(node_id) = self.ast.constant(id).map(|c| c.node_id) {
1319 self.record_origin(AnyId::Const(id, node_id), at.span());
1320 self.record_declaration_scope(AnyId::Const(id, node_id), at.span());
1321 self.scopes.bind(&name, AnyId::Const(id, node_id));
1322 }
1323 }
1324 return;
1325 }
1326 let type_ref = self.type_ref_of(actual);
1327 match type_ref.resolved() {
1328 Some(id) => self.bind(&name, id),
1329 None => self.diagnose_at(
1330 DiagnosticCode::UnresolvedName,
1331 at.span(),
1332 "an actual parameter names a type that is not declared in this file",
1333 "OMG IDL 4.2 §7.4.12.3 rule (192)",
1334 ),
1335 }
1336 }
1337
1338 fn structure(&mut self, node: &Arc<SyntaxNode>) {
1339 let Some(name) = first_ident(node) else {
1340 return;
1341 };
1342 let base = node
1343 .children()
1344 .iter()
1345 .find(|child| child.kind() == CstKind::InheritanceClause)
1346 .and_then(|clause| self.type_ref_from_scoped_name(clause));
1347
1348 let Some(id) = self.declare_type(
1349 &name,
1350 TypeKind::Structure {
1351 base,
1352 members: Vec::new(),
1353 },
1354 node,
1355 ) else {
1356 return;
1357 };
1358
1359 self.one_namespace_per_scope(node);
1360 for child in node.children() {
1361 if child.kind() == CstKind::Member {
1362 self.member(&child, id);
1363 }
1364 }
1365 }
1366
1367 fn exception(&mut self, node: &Arc<SyntaxNode>) {
1368 let Some(name) = first_ident(node) else {
1369 return;
1370 };
1371 self.check_collision(&name, node);
1372 let Some(module) = self.scopes.current_module() else {
1373 return;
1374 };
1375 let Ok(id) = self.ast.add_exception(module, &name) else {
1376 return;
1377 };
1378 if let Ok(node_id) = self.ast.exception(id).map(|e| e.node_id) {
1379 self.record_origin(AnyId::Exception(id, node_id), node.span());
1380 self.record_declaration_scope(AnyId::Exception(id, node_id), node.span());
1381 self.scopes.bind(&name, AnyId::Exception(id, node_id));
1382 self.apply_waiting_annotations(AnyId::Exception(id, node_id));
1383 }
1384 self.one_namespace_per_scope(node);
1385 for child in node.children() {
1386 if child.kind() == CstKind::Member {
1387 let annotations = self.annotations_of(&child);
1388 for (member_name, type_ref, at) in self.declared(&child) {
1389 let spec = self.member_spec(&member_name, type_ref, &at);
1390 let result = self.ast.add_exception_member(id, spec);
1391 let Some(member) = self.report_model(result, at.span()) else {
1392 continue;
1393 };
1394 let Ok(node_id) = self.ast.member(member).map(|m| m.node_id) else {
1395 continue;
1396 };
1397 self.apply_annotations(&annotations, AnyId::Member(member, node_id));
1398 }
1399 }
1400 }
1401 }
1402
1403 fn member(&mut self, node: &Arc<SyntaxNode>, aggregate: Id<TypeNode>) {
1404 let annotations = self.annotations_of(node);
1405 // `public` and `private` are the only two ways to open a
1406 // valuetype state member — rule (106) — and the keyword was
1407 // consumed and thrown away. CCM 4.0 §6.7.2 forbids a private
1408 // state member in a primary-key valuetype, a rule nothing could
1409 // check once the keyword was gone.
1410 let visibility = state_visibility(node);
1411 for (name, type_ref, at) in self.declared(node) {
1412 let spec = self.member_spec(&name, type_ref, &at);
1413 let result = self.ast.add_member(aggregate, spec);
1414 let Some(id) = self.report_model(result, at.span()) else {
1415 continue;
1416 };
1417 let Ok(node_id) = self.ast.member(id).map(|m| m.node_id) else {
1418 continue;
1419 };
1420 if let Some(visibility) = visibility {
1421 if let Some(facets) = self.ast.facets_mut(AnyId::Member(id, node_id)) {
1422 facets.corba_mut().state_visibility = Some(visibility);
1423 }
1424 }
1425 // The name is stored stripped (§7.2.3.2); whether the
1426 // declaration spelled it `_name` survives only here, for
1427 // the one dialect whose member-id hash reads the spelling.
1428 if declarator_is_escaped(&at) {
1429 if let Some(facets) = self.ast.facets_mut(AnyId::Member(id, node_id)) {
1430 facets.xtypes_mut().escaped = true;
1431 }
1432 }
1433 self.apply_annotations(&annotations, AnyId::Member(id, node_id));
1434 }
1435 }
1436
1437 /// Every name a declaration introduces, with the type each one has.
1438 ///
1439 /// One type specification, several declarators: `double x, y, z;`.
1440 /// An array declarator gives its own name an array type over the
1441 /// shared element type — `long a, b[4];` is one `long` and one
1442 /// `long[4]`, not two arrays. IDL 4.2 §7.4.1.4.4.
1443 fn declared(
1444 &mut self,
1445 node: &Arc<SyntaxNode>,
1446 ) -> Vec<(String, Ref<TypeNode>, Arc<SyntaxNode>)> {
1447 let Some(element) = self.type_ref(node) else {
1448 return Vec::new();
1449 };
1450 let declarators: Vec<Arc<SyntaxNode>> = node
1451 .children()
1452 .into_iter()
1453 .filter(|child| child.kind() == CstKind::Declarator)
1454 .collect();
1455
1456 let mut declared = Vec::new();
1457 for declarator in declarators {
1458 let Some(name) = first_ident(&declarator) else {
1459 continue;
1460 };
1461 let type_ref = self.declarator_type(&element, &declarator);
1462 declared.push((name, type_ref, declarator));
1463 }
1464 declared
1465 }
1466
1467 /// The type a declarator gives `element`: the element itself, or an
1468 /// array of it when the declarator writes dimensions.
1469 ///
1470 /// DDS-XTypes 1.3 §7.3.1.2.1.4 writes the annotations of an array's
1471 /// elements between the declarator's name and its dimensions — `Foo
1472 /// array_of_foo @external [42];` — so they are applied to the array
1473 /// as the annotations of its element, the way `sequence<@external
1474 /// Foo, 42>` applies them to a sequence's. Written on a declarator
1475 /// that has no dimensions they have no element to apply to, and are
1476 /// reported rather than dropped.
1477 fn declarator_type(
1478 &mut self,
1479 element: &Ref<TypeNode>,
1480 declarator: &Arc<SyntaxNode>,
1481 ) -> Ref<TypeNode> {
1482 let dims = self.array_dimensions(declarator);
1483 let written = self.annotations_of(declarator);
1484 if dims.is_empty() {
1485 if let Some(first) = written.first() {
1486 self.annotation_error(
1487 first,
1488 &alloc::format!(
1489 "`@{}` stands between a declarator's name and its dimensions, and applies \
1490 to the elements of an array; this declarator is not an array",
1491 first.name
1492 ),
1493 "DDS-XTypes 1.3 §7.3.1.2.1.4",
1494 );
1495 }
1496 return element.clone();
1497 }
1498 let array = self.anonymous(
1499 TypeKind::Array {
1500 element: element.clone(),
1501 dims,
1502 },
1503 declarator.span(),
1504 );
1505 if !written.is_empty() {
1506 if let Some(id) = array.resolved() {
1507 let any = AnyId::Type(id, self.node_id_of(id));
1508 self.apply_use_site(&written, any, UseSite::Element);
1509 }
1510 }
1511 array
1512 }
1513
1514 /// The `[expr]` dimensions of a declarator, outermost first.
1515 fn array_dimensions(&mut self, declarator: &Arc<SyntaxNode>) -> Vec<Id<ConstExprNode>> {
1516 declarator
1517 .children()
1518 .into_iter()
1519 .filter(|child| child.kind() == CstKind::ArrayDimension)
1520 .filter_map(|dim| self.first_expr_child(&dim))
1521 .collect()
1522 }
1523
1524 fn member_spec(&self, name: &str, type_: Ref<TypeNode>, at: &Arc<SyntaxNode>) -> MemberSpec {
1525 MemberSpec {
1526 name: name.to_string(),
1527 type_,
1528 facets: self.facets_at(at.span()),
1529 }
1530 }
1531
1532 fn enumeration(&mut self, node: &Arc<SyntaxNode>) {
1533 let Some(name) = first_ident(node) else {
1534 return;
1535 };
1536 let Some(id) = self.declare_type(
1537 &name,
1538 TypeKind::Enumeration {
1539 enumerators: Vec::new(),
1540 },
1541 node,
1542 ) else {
1543 return;
1544 };
1545 for child in node.children() {
1546 if child.kind() == CstKind::Enumerator {
1547 if let Some(name) = first_ident(&child) {
1548 // No explicit value here: `@value(n)` is applied
1549 // with the other annotations, and a positional one
1550 // is filled in by the completion pass. Spec §5.
1551 let result = self.ast.add_enumerator(id, &name, None);
1552 let Some(enumerator) = self.report_model(result, child.span()) else {
1553 continue;
1554 };
1555 let annotations = self.annotations_of(&child);
1556 let Ok(node_id) = self.ast.enumerator(enumerator).map(|e| e.node_id) else {
1557 continue;
1558 };
1559 // The enumerator goes into the scope enclosing the
1560 // enumeration, not into one of its own. OMG IDL 4.2
1561 // §7.4.1.4.2: `module M { enum Size { small, medium,
1562 // large }; }; const M::Size MYSIZE = M::medium;`.
1563 // This is what lets `case RED:` name an enumerator,
1564 // and what makes two enumerations sharing a literal
1565 // name the collision §7.2.3 says it is.
1566 self.check_collision(&name, &child);
1567 self.scopes
1568 .bind(&name, AnyId::Enumerator(enumerator, node_id));
1569 self.apply_annotations(&annotations, AnyId::Enumerator(enumerator, node_id));
1570 }
1571 }
1572 }
1573 }
1574
1575 fn bitmask(&mut self, node: &Arc<SyntaxNode>) {
1576 let Some(name) = first_ident(node) else {
1577 return;
1578 };
1579 let Some(id) = self.declare_type(&name, TypeKind::Bitmask { bits: Vec::new() }, node)
1580 else {
1581 return;
1582 };
1583 for child in node.children() {
1584 if child.kind() == CstKind::BitValue {
1585 if let Some(name) = first_ident(&child) {
1586 let result = self.ast.add_bit_value(id, &name, None);
1587 let Some(bit) = self.report_model(result, child.span()) else {
1588 continue;
1589 };
1590 let annotations = self.annotations_of(&child);
1591 let Ok(node_id) = self.ast.bit_value(bit).map(|b| b.node_id) else {
1592 continue;
1593 };
1594 self.apply_annotations(&annotations, AnyId::BitValue(bit, node_id));
1595 }
1596 }
1597 }
1598 }
1599
1600 fn union(&mut self, node: &Arc<SyntaxNode>) {
1601 let Some(name) = first_ident(node) else {
1602 return;
1603 };
1604 let discriminator = self.type_ref(node).unwrap_or_else(|| {
1605 Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(Vec::new())))
1606 });
1607 let Some(id) = self.declare_type(
1608 &name,
1609 TypeKind::Union {
1610 discriminator,
1611 cases: Vec::new(),
1612 default_case: None,
1613 },
1614 node,
1615 ) else {
1616 return;
1617 };
1618
1619 // `union U switch (@try_construct K)` — §7.3.1.2.1.12.5. An
1620 // `Annotation` child of the declaration itself can only be the
1621 // discriminator's: the union's own annotations sit on the
1622 // enclosing `Member` node and a case's on its `UnionCase`.
1623 let discriminator_annotations: Vec<Applied> = node
1624 .children()
1625 .into_iter()
1626 .filter(|child| child.kind() == CstKind::Annotation)
1627 .filter_map(|child| self.applied_annotation(&child))
1628 .collect();
1629 if !discriminator_annotations.is_empty() {
1630 let any = AnyId::Type(id, self.node_id_of(id));
1631 self.apply_use_site(&discriminator_annotations, any, UseSite::Discriminator);
1632 }
1633 self.one_namespace_per_scope(node);
1634
1635 for child in node.children() {
1636 if child.kind() != CstKind::UnionCase {
1637 continue;
1638 }
1639 let label_nodes: Vec<Arc<SyntaxNode>> = child
1640 .children()
1641 .into_iter()
1642 .filter(|c| c.kind() == CstKind::CaseLabel)
1643 .collect();
1644 let mut labels: Vec<zerodds_ast::CaseLabel> = Vec::new();
1645 for label in label_nodes {
1646 let is_default = label
1647 .syntax_tokens()
1648 .iter()
1649 .any(|(_, token)| token.kind() == TokenKind::Default);
1650 if is_default {
1651 labels.push(zerodds_ast::CaseLabel::Default);
1652 continue;
1653 }
1654 // `case <expr>:` keeps its expression: the label may name
1655 // a constant or an enumerator, and the value it stands
1656 // for is not known until those resolve.
1657 match self.first_expr_child(&label) {
1658 Some(expr) => labels.push(zerodds_ast::CaseLabel::Value(expr)),
1659 None => self.diagnose_at(
1660 DiagnosticCode::MissingToken,
1661 label.span(),
1662 "a `case` label needs a constant expression",
1663 "OMG IDL 4.2 §7.4.1.4.4.3",
1664 ),
1665 }
1666 }
1667 // `element_spec ::= type_spec declarator` — one declarator,
1668 // where a struct member's `member ::= type_spec
1669 // declarators ";"` takes a list. `parse_union_case` shares
1670 // `parse_declarator_list` with the struct member and so
1671 // accepts `case 1: long a, b;` without a word, and
1672 // `member_parts` answers with the first declarator only:
1673 // every branch after the comma was lowered into nothing,
1674 // named in no diagnostic, and absent from the model the
1675 // codec and every generator read.
1676 let declared = self.declared(&child);
1677 for (_, _, extra) in declared.iter().skip(1) {
1678 self.diagnose_at(
1679 DiagnosticCode::UnexpectedToken,
1680 extra.span(),
1681 "a union case declares one member; `element_spec` takes a single declarator",
1682 "OMG IDL 4.2 §7.4.1.4.4.3",
1683 );
1684 }
1685 let Some((member_name, type_ref, declarator)) = declared.into_iter().next() else {
1686 continue;
1687 };
1688 // The branch and the member behind it are two ranges, and a
1689 // reader needs both: the case is the whole `case 1: long a`,
1690 // the member is its declarator — the same range a struct
1691 // member of the same declaration gets. Giving the member the
1692 // branch's range made every message about it point at the
1693 // `case` keyword.
1694 let spec = self.member_spec(&member_name, type_ref, &declarator);
1695 let annotations = self.annotations_of(&child);
1696 let result = self.ast.add_case(id, labels, spec);
1697 let Some(case) = self.report_model(result, child.span()) else {
1698 continue;
1699 };
1700 let Ok(node_id) = self.ast.case(case).map(|c| c.node_id) else {
1701 continue;
1702 };
1703 // `Ast::add_case` marks a case's facets synthetic, and
1704 // nothing overwrote them: every rule that reports a branch —
1705 // a repeated label, `@key` on a case member — pointed at
1706 // byte zero of the file.
1707 self.record_origin(AnyId::Case(case, node_id), child.span());
1708 // The same trace of the escape spelling a struct member
1709 // keeps, for the same one consumer.
1710 if declarator_is_escaped(&declarator) {
1711 if let Some(facets) = self.ast.facets_mut(AnyId::Case(case, node_id)) {
1712 facets.xtypes_mut().escaped = true;
1713 }
1714 }
1715 self.apply_annotations(&annotations, AnyId::Case(case, node_id));
1716 // Mirror the case's XTypes facet onto the member behind it.
1717 // The source writes a branch's annotations on the case, and
1718 // that is where `apply_annotations` puts them — but the
1719 // layout planner (`effective_member_id`, `must_understand`),
1720 // the codec walks (`is_external`, `try_construct`) and every
1721 // language adapter read `MemberNode::facets`. Left unmirrored,
1722 // `case 1: @external Inner i;` lowered without error and no
1723 // consumer ever saw the annotation — a silent drop.
1724 // `validate.rs` already reads both sides for its union rules,
1725 // so the mirror changes no diagnostic; it is applied once,
1726 // after every annotation of the branch, so no diagnostic is
1727 // emitted twice either.
1728 let mirrored = self
1729 .ast
1730 .facets(AnyId::Case(case, node_id))
1731 .and_then(|facets| facets.xtypes.clone());
1732 if let Some(xtypes) = mirrored {
1733 let member_any = self
1734 .ast
1735 .case(case)
1736 .ok()
1737 .map(|c| c.member)
1738 .and_then(|member| self.ast.any_of_member(member).ok());
1739 if let Some(facets) = member_any.and_then(|any| self.ast.facets_mut(any)) {
1740 *facets.xtypes_mut() = xtypes;
1741 }
1742 }
1743 }
1744 }
1745
1746 /// `typedef <type> <declarator>, ...;`
1747 ///
1748 /// A declarator here carries dimensions exactly as one on a struct
1749 /// member does: `typedef long Matrix[2][3];` aliases an array of
1750 /// `long`, not a `long`. IDL 4.2 §7.4.1.4.4 gives both forms the same
1751 /// `<declarator>` production, so both give their own name the array
1752 /// type over the shared element type.
1753 fn typedef(&mut self, node: &Arc<SyntaxNode>) {
1754 // Rule (64)'s third alternative: the type is defined inside the
1755 // typedef. It is declared first, in the typedef's own scope,
1756 // and the declarators alias it.
1757 let defined = node.children().into_iter().find(|child| {
1758 matches!(
1759 child.kind(),
1760 CstKind::StructDecl
1761 | CstKind::UnionDecl
1762 | CstKind::EnumDecl
1763 | CstKind::BitsetDecl
1764 | CstKind::BitmaskDecl
1765 )
1766 });
1767 let element = match defined {
1768 Some(definition) => {
1769 self.definition_inner(&definition);
1770 let Some(name) = first_ident(&definition) else {
1771 return;
1772 };
1773 match self.scopes.lookup(&name) {
1774 Some(AnyId::Type(id, _)) => Ref::Resolved(id),
1775 _ => return,
1776 }
1777 }
1778 None => {
1779 let Some(element) = self.type_ref(node) else {
1780 return;
1781 };
1782 element
1783 }
1784 };
1785 for declarator in node
1786 .children()
1787 .iter()
1788 .filter(|child| child.kind() == CstKind::Declarator)
1789 {
1790 let Some(name) = first_ident(declarator) else {
1791 continue;
1792 };
1793 let aliased = self.declarator_type(&element, declarator);
1794 let _ = self.declare_type(&name, TypeKind::Alias { aliased }, declarator);
1795 }
1796 }
1797
1798 fn native(&mut self, node: &Arc<SyntaxNode>) {
1799 let Some(name) = first_ident(node) else {
1800 return;
1801 };
1802 let _ = self.declare_type(&name, TypeKind::Native, node);
1803 }
1804
1805 fn forward(&mut self, node: &Arc<SyntaxNode>) {
1806 let Some(name) = first_ident(node) else {
1807 return;
1808 };
1809 let Some(module) = self.scopes.current_module() else {
1810 return;
1811 };
1812 // A forward declaration is a completeness state, not a kind: the
1813 // definition completes this node rather than replacing it, so
1814 // references taken now stay valid. Spec §3.2 of the AST.
1815 if self.scopes.lookup(&name).is_some() {
1816 return;
1817 }
1818 // *Which* node, though, is the keyword's decision. Every forward
1819 // used to bind a `Structure` placeholder whatever it said, so
1820 // `interface I; interface I { … };` collided with itself:
1821 // `DuplicateName` for the definition, which could not complete a
1822 // type it was not, and `UndefinedForward` for the placeholder
1823 // nothing ever finished. Rules (75) and (135), the same bug
1824 // twice. `struct S;` and `union U;` were right all along, which
1825 // is what made it invisible.
1826 if let Some(kind) = forward_interface_kind(node) {
1827 if let Ok(id) = self.ast.add_forward_interface(module, &name, kind) {
1828 if let Ok(node_id) = self.ast.interface(id).map(|node| node.node_id) {
1829 self.record_origin(AnyId::Interface(id, node_id), node.span());
1830 self.scopes.bind(&name, AnyId::Interface(id, node_id));
1831 }
1832 }
1833 return;
1834 }
1835 if let Ok(id) = self.ast.add_forward_type(
1836 module,
1837 &name,
1838 TypeKind::Structure {
1839 base: None,
1840 members: Vec::new(),
1841 },
1842 ) {
1843 // The interface branch above records where the declaration
1844 // was and this one did not, so `UndefinedForward` for a
1845 // `struct S;` that nothing completes — the one message whose
1846 // whole content is *which* declaration was left open — named
1847 // byte zero instead of the declaration.
1848 self.record_origin(AnyId::Type(id, self.node_id_of(id)), node.span());
1849 self.record_declaration_scope(AnyId::Type(id, self.node_id_of(id)), node.span());
1850 self.bind(&name, id);
1851 }
1852 }
1853
1854 fn constant(&mut self, node: &Arc<SyntaxNode>) {
1855 let Some(name) = first_ident(node) else {
1856 return;
1857 };
1858 let Some(expr) = self.first_expr_child(node) else {
1859 self.diagnose_at(
1860 DiagnosticCode::MissingToken,
1861 node.span(),
1862 "a constant needs a value expression",
1863 "OMG IDL 4.2 §7.4.1.4.2",
1864 );
1865 return;
1866 };
1867 // The declared type is what the constant has; the expression is
1868 // checked against it rather than used to derive it. Spec §5.
1869 //
1870 // Rule (43) is the one exception the language itself makes:
1871 // `<fixed_pt_const_type> ::= "fixed"` has no parameters, so a
1872 // `const fixed` takes its digits and scale from its value.
1873 let type_ = if bare_fixed(node) {
1874 self.fixed_const_type(expr, node)
1875 } else {
1876 self.type_ref(node)
1877 .unwrap_or_else(|| Ref::Broken(broken_cause()))
1878 };
1879 self.check_const_value_form(&type_, expr, node);
1880 self.check_collision(&name, node);
1881 let Some(module) = self.scopes.current_module() else {
1882 return;
1883 };
1884 if let Ok(id) = self.ast.add_const(module, &name, type_, expr) {
1885 let node_id = self.ast.constant(id).map(|c| c.node_id).ok();
1886 if let Some(node_id) = node_id {
1887 self.record_origin(AnyId::Const(id, node_id), node.span());
1888 self.record_declaration_scope(AnyId::Const(id, node_id), node.span());
1889 self.scopes.bind(&name, AnyId::Const(id, node_id));
1890 self.apply_waiting_annotations(AnyId::Const(id, node_id));
1891 }
1892 }
1893 }
1894
1895 fn bitset(&mut self, node: &Arc<SyntaxNode>) {
1896 let Some(name) = first_ident(node) else {
1897 return;
1898 };
1899 let base = node
1900 .children()
1901 .iter()
1902 .find(|child| child.kind() == CstKind::InheritanceClause)
1903 .and_then(|clause| self.type_ref_from_scoped_name(clause));
1904 let Some(id) = self.declare_type(
1905 &name,
1906 TypeKind::Bitset {
1907 base,
1908 fields: Vec::new(),
1909 },
1910 node,
1911 ) else {
1912 return;
1913 };
1914
1915 for field in node
1916 .children()
1917 .into_iter()
1918 .filter(|child| child.kind() == CstKind::BitField)
1919 {
1920 self.bitfield(&field, id);
1921 }
1922 }
1923
1924 /// One `bitfield<n>` or `bitfield<n, T>` of a bitset.
1925 ///
1926 /// An unnamed bitfield is padding and keeps its width, so the layout
1927 /// of the ones that follow stays right. XTypes 1.3 §7.3.1.2.1.11.
1928 fn bitfield(&mut self, node: &Arc<SyntaxNode>, bitset: Id<TypeNode>) {
1929 let Some(expr) = self.first_expr_child(node) else {
1930 return;
1931 };
1932 let Ok(width) = self.ast.eval_integral(expr) else {
1933 self.diagnose_at(
1934 DiagnosticCode::UnexpectedToken,
1935 node.span(),
1936 "a bitfield width must be an integer constant",
1937 "DDS-XTypes 1.3 §7.3.1.2.1.11",
1938 );
1939 return;
1940 };
1941 let Ok(bits) = u16::try_from(width) else {
1942 self.diagnose_at(
1943 DiagnosticCode::UnexpectedToken,
1944 node.span(),
1945 "a bitfield width does not fit in 16 bits",
1946 "DDS-XTypes 1.3 §7.3.1.2.1.11",
1947 );
1948 return;
1949 };
1950 let holder = self
1951 .type_ref(node)
1952 .filter(|type_| !matches!(type_, Ref::Broken(_)));
1953 let name = first_ident(node);
1954 let result = self.ast.add_bitfield(bitset, name.as_deref(), bits, holder);
1955 let Some(id) = self.report_model(result, node.span()) else {
1956 return;
1957 };
1958 let annotations = self.annotations_of(node);
1959 if let Ok(node_id) = self.ast.bitfield(id).map(|f| f.node_id) {
1960 self.apply_annotations(&annotations, AnyId::Bitfield(id, node_id));
1961 }
1962 }
1963
1964 fn interface(&mut self, node: &Arc<SyntaxNode>) {
1965 let Some(name) = first_ident(node) else {
1966 return;
1967 };
1968 let kind = interface_kind(node);
1969 let (bases, supports) = self.interface_bases(node);
1970 let Some(id) = self.declare_interface(&name, kind, bases, node) else {
1971 return;
1972 };
1973 let node_id = self.ast.interface(id).map(|i| i.node_id).ok();
1974 if let Some(node_id) = node_id {
1975 self.record_origin(AnyId::Interface(id, node_id), node.span());
1976 self.scopes.bind(&name, AnyId::Interface(id, node_id));
1977 self.apply_waiting_annotations(AnyId::Interface(id, node_id));
1978 if !supports.is_empty() {
1979 if let Some(facets) = self.ast.facets_mut(AnyId::Interface(id, node_id)) {
1980 facets.corba_mut().supports = supports;
1981 }
1982 self.ast.reindex_pending(AnyId::Interface(id, node_id));
1983 }
1984 }
1985
1986 // The body is its own scope: a type declared inside an interface
1987 // is named through the interface. IDL 4.2 §7.5.
1988 if let Some(clause) = node
1989 .children()
1990 .into_iter()
1991 .find(|child| child.kind() == CstKind::ManagesClause)
1992 {
1993 self.manages_clause(&clause, id);
1994 }
1995
1996 self.one_namespace_per_scope(node);
1997 self.scopes.push_container(&name);
1998 for child in node.children() {
1999 match child.kind() {
2000 CstKind::Operation => self.operation(&child, id),
2001 CstKind::AttributeDecl => self.attribute(&child, id),
2002 CstKind::PortDecl => self.port(&child, id),
2003 CstKind::InheritanceClause | CstKind::ManagesClause => {}
2004 _ => self.definition(&child),
2005 }
2006 }
2007 self.scopes.pop();
2008 }
2009
2010 /// The base interfaces of an interface, and the ones it supports.
2011 ///
2012 /// `component C : B supports I1, I2` writes two inheritance clauses
2013 /// and they mean different things. Taking only the first dropped
2014 /// every `supports` clause a component, home or connector carried,
2015 /// with no diagnostic — CCM 4.0 §6.3.2 needs both lists and cannot
2016 /// tell them apart once they are merged. The valuetype path already
2017 /// walked every clause; this one stopped at the first.
2018 fn interface_bases(
2019 &mut self,
2020 node: &Arc<SyntaxNode>,
2021 ) -> (
2022 Vec<Ref<zerodds_ast::InterfaceNode>>,
2023 Vec<Ref<zerodds_ast::InterfaceNode>>,
2024 ) {
2025 let mut bases = Vec::new();
2026 let mut supports = Vec::new();
2027 for clause in node
2028 .children()
2029 .into_iter()
2030 .filter(|child| child.kind() == CstKind::InheritanceClause)
2031 {
2032 let is_supports = clause
2033 .syntax_tokens()
2034 .iter()
2035 .any(|(_, token)| token.kind() == TokenKind::Supports);
2036 let names: Vec<Ref<zerodds_ast::InterfaceNode>> = clause
2037 .children()
2038 .into_iter()
2039 .filter(|child| child.kind() == CstKind::ScopedName)
2040 .filter_map(|scoped| self.scoped_name_of(&scoped))
2041 .map(|name| self.interface_ref(&name))
2042 .collect();
2043 if is_supports {
2044 supports.extend(names);
2045 } else {
2046 bases.extend(names);
2047 }
2048 }
2049 (bases, supports)
2050 }
2051
2052 /// One port of a component, home, porttype or connector.
2053 ///
2054 /// A port is a named access point with an interface or event type
2055 /// behind it, which is an attribute with a component role on it.
2056 /// Making each port kind its own node kind would widen the type
2057 /// space every consumer matches on for a dialect most of them never
2058 /// see. Spec §4.2 of the AST.
2059 fn port(&mut self, node: &Arc<SyntaxNode>, interface: Id<zerodds_ast::InterfaceNode>) {
2060 let keyword = node.syntax_tokens().first().map(|(_, token)| token.kind());
2061 // The port's own name is the last identifier; the one before it
2062 // names the interface or event type.
2063 let idents: Vec<String> = node
2064 .syntax_tokens()
2065 .into_iter()
2066 .filter(|(_, token)| token.kind() == TokenKind::Ident)
2067 .map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
2068 .collect();
2069 let Some(port_name) = idents.last().cloned() else {
2070 return;
2071 };
2072 let target = self
2073 .scoped_names_of(node)
2074 .into_iter()
2075 .next()
2076 .or_else(|| self.scoped_name_of(node));
2077
2078 let type_ = target.as_ref().map_or_else(
2079 || Ref::Broken(broken_cause()),
2080 |name| self.named_type_ref(name),
2081 );
2082 let Ok(id) = self.ast.add_attribute(interface, &port_name, type_, false) else {
2083 return;
2084 };
2085 let Ok(node_id) = self.ast.attribute(id).map(|a| a.node_id) else {
2086 return;
2087 };
2088 self.record_origin(AnyId::Attribute(id, node_id), node.span());
2089
2090 let multiple = node
2091 .syntax_tokens()
2092 .iter()
2093 .any(|(_, token)| token.kind() == TokenKind::Multiple);
2094 let interface_ref = target.as_ref().map_or_else(
2095 || Ref::Broken(broken_cause()),
2096 |name| self.interface_ref(name),
2097 );
2098 let event_ref = type_ref_of_port(self.ast, id);
2099 let role = match keyword {
2100 Some(TokenKind::Provides) => Some(zerodds_ast::ComponentRole::Facet {
2101 name: port_name.clone(),
2102 interface: interface_ref,
2103 }),
2104 Some(TokenKind::Uses) => Some(zerodds_ast::ComponentRole::Receptacle {
2105 name: port_name.clone(),
2106 interface: interface_ref,
2107 multiple,
2108 }),
2109 Some(TokenKind::Publishes | TokenKind::Emits) => {
2110 Some(zerodds_ast::ComponentRole::EventSource {
2111 name: port_name.clone(),
2112 event: event_ref,
2113 publisher: keyword == Some(TokenKind::Publishes),
2114 })
2115 }
2116 Some(TokenKind::Consumes) => Some(zerodds_ast::ComponentRole::EventSink {
2117 name: port_name.clone(),
2118 event: event_ref,
2119 }),
2120 Some(TokenKind::Port) => Some(zerodds_ast::ComponentRole::PortTypeRole),
2121 Some(TokenKind::Mirrorport) => Some(zerodds_ast::ComponentRole::ConnectorRole),
2122 _ => None,
2123 };
2124 if let Some(role) = role {
2125 if let Some(facets) = self.ast.facets_mut(AnyId::Attribute(id, node_id)) {
2126 facets.corba_mut().component_role = Some(role);
2127 }
2128 }
2129 self.ast.reindex_pending(AnyId::Attribute(id, node_id));
2130 }
2131
2132 /// A home's `manages X primarykey K` clause.
2133 fn manages_clause(
2134 &mut self,
2135 node: &Arc<SyntaxNode>,
2136 interface: Id<zerodds_ast::InterfaceNode>,
2137 ) {
2138 let names = self.scoped_names_of(node);
2139 let Some(component) = names.first() else {
2140 return;
2141 };
2142 let component_ref = self.interface_ref(component);
2143 let primary_key = names.get(1).map(|name| self.named_type_ref(name));
2144 let Ok(node_id) = self.ast.interface(interface).map(|i| i.node_id) else {
2145 return;
2146 };
2147 if let Some(facets) = self.ast.facets_mut(AnyId::Interface(interface, node_id)) {
2148 facets.corba_mut().manages = Some(zerodds_ast::HomeManages {
2149 component: component_ref,
2150 primary_key,
2151 });
2152 }
2153 // Both references may name a declaration that has not arrived
2154 // yet; without this the waiter index never learns of them and
2155 // `manages C primarykey K` stays pending for the life of the
2156 // model.
2157 self.ast
2158 .reindex_pending(AnyId::Interface(interface, node_id));
2159 }
2160
2161 /// `typeid X "id";` and `typeprefix X "prefix";`
2162 ///
2163 /// Both name a declaration that may not exist yet, so they are kept
2164 /// until the whole file is walked and applied in the completion
2165 /// pass. Spec §5.
2166 fn repository_directive(&mut self, node: &Arc<SyntaxNode>) {
2167 let is_prefix = node
2168 .syntax_tokens()
2169 .iter()
2170 .any(|(_, token)| token.kind() == TokenKind::Typeprefix);
2171 let Some(name) = self.scoped_name_of(node) else {
2172 return;
2173 };
2174 let Some(text) = joined_string_literals(node.syntax_tokens(), TokenKind::StringLiteral)
2175 .into_iter()
2176 .next()
2177 else {
2178 return;
2179 };
2180 self.record_repository_directive(if is_prefix { "typeprefix" } else { "typeid" }, &text);
2181 self.repository_directives
2182 .push((self.directive_candidates(node, &name), text, is_prefix));
2183 }
2184
2185 /// Note on the root module that a `typeid`/`typeprefix` directive was
2186 /// used, so the RPC layer can see it.
2187 ///
2188 /// The directive itself writes onto its target and leaves no node
2189 /// behind; DDS-RPC 1.0 §7.3.1.1 removes the directive from the
2190 /// service-definition grammar, so a service definition that carries
2191 /// one is outside it and `lower_service` has to report it.
2192 fn record_repository_directive(&mut self, keyword: &str, text: &str) {
2193 let root = self.ast.root_module();
2194 let Ok(node_id) = self.ast.module(root).map(|m| m.node_id) else {
2195 return;
2196 };
2197 if let Some(facets) = self.ast.facets_mut(AnyId::Module(root, node_id)) {
2198 facets
2199 .corba_mut()
2200 .repository_directives
2201 .push(alloc::format!("{keyword} {text}"));
2202 }
2203 }
2204
2205 /// `import X;` — kept as written on the root module.
2206 fn import(&mut self, node: &Arc<SyntaxNode>) {
2207 let written = joined_string_literals(node.syntax_tokens(), TokenKind::StringLiteral)
2208 .into_iter()
2209 .next()
2210 .or_else(|| {
2211 self.scoped_name_of(node)
2212 .map(|name| name.segments().join("::"))
2213 });
2214 let Some(written) = written else {
2215 return;
2216 };
2217 let root = self.ast.root_module();
2218 let Ok(node_id) = self.ast.module(root).map(|m| m.node_id) else {
2219 return;
2220 };
2221 if let Some(facets) = self.ast.facets_mut(AnyId::Module(root, node_id)) {
2222 facets.corba_mut().imports.push(written);
2223 }
2224 }
2225
2226 /// `keylist T (a, b);` — RTI's way of declaring a type's keys.
2227 fn keylist(&mut self, node: &Arc<SyntaxNode>) {
2228 let Some(name) = self.scoped_name_of(node) else {
2229 return;
2230 };
2231 // The type's name is a `ScopedName` child; the identifiers
2232 // directly under the declaration are the key fields.
2233 let fields: Vec<String> = node
2234 .syntax_tokens()
2235 .into_iter()
2236 .filter(|(_, token)| token.kind() == TokenKind::Ident)
2237 .map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
2238 .collect();
2239 self.keylists
2240 .push((self.directive_candidates(node, &name), fields, node.span()));
2241 }
2242
2243 /// Every scope a directive's target name could be in, innermost
2244 /// first.
2245 ///
2246 /// §7.5 resolves a name from the scope that writes it outwards, and
2247 /// a directive's argument is a name being used. Qualifying it with
2248 /// the current scope and stopping there made `module m { typeprefix
2249 /// m "…"; };` name `m::m` — a scope that does not exist — and the
2250 /// directive was reported as naming nothing while `::m` sat one step
2251 /// out. `typeid`, `typeprefix` and `keylist` all read their target
2252 /// this way.
2253 ///
2254 /// A name written root-anchored means the root and nothing else, so
2255 /// it gets one candidate. Rule (4) `<scoped_name> ::+ "::"
2256 /// <identifier>`.
2257 fn directive_candidates(&self, node: &Arc<SyntaxNode>, name: &ScopedName) -> Vec<ScopedName> {
2258 if is_root_anchored(node) {
2259 return alloc::vec![name.clone()];
2260 }
2261 self.scopes.lookup_candidates(name.segments())
2262 }
2263
2264 /// A template module's formal parameters.
2265 fn template_parameters(&mut self, node: &Arc<SyntaxNode>, module: Id<zerodds_ast::ModuleNode>) {
2266 let mut params = Vec::new();
2267 for parameter in node
2268 .children()
2269 .into_iter()
2270 .filter(|child| child.kind() == CstKind::TemplateParameter)
2271 {
2272 let Some(name) = first_ident(¶meter) else {
2273 continue;
2274 };
2275 let kind = template_param_kind(¶meter);
2276 // Two of (188)'s eleven alternatives carry a type: `const
2277 // <const_type>` names the type an actual constant must have,
2278 // and `<sequence_type>` names the sequence the actual must
2279 // be. Both are the parameter's constraint.
2280 let constraint = if matches!(
2281 kind,
2282 zerodds_ast::TemplateParamKind::Const | zerodds_ast::TemplateParamKind::Sequence
2283 ) {
2284 self.type_ref(¶meter)
2285 } else {
2286 None
2287 };
2288 params.push(zerodds_ast::TemplateParam {
2289 name,
2290 kind,
2291 constraint,
2292 });
2293 }
2294 if params.is_empty() {
2295 return;
2296 }
2297 let Ok(node_id) = self.ast.module(module).map(|m| m.node_id) else {
2298 return;
2299 };
2300 if let Some(facets) = self.ast.facets_mut(AnyId::Module(module, node_id)) {
2301 facets.corba_mut().template_params = params;
2302 }
2303 self.ast.reindex_pending(AnyId::Module(module, node_id));
2304 }
2305
2306 /// Every scoped name written directly under a node.
2307 pub(crate) fn scoped_names_of(&self, node: &Arc<SyntaxNode>) -> Vec<ScopedName> {
2308 node.children()
2309 .into_iter()
2310 .filter(|child| child.kind() == CstKind::ScopedName)
2311 .filter_map(|scoped| self.scoped_name_of(&scoped))
2312 .collect()
2313 }
2314
2315 /// The string literals of a `context (...)` clause, with escapes
2316 /// resolved.
2317 fn context_strings(&self, node: &Arc<SyntaxNode>) -> Vec<String> {
2318 joined_string_literals(node.syntax_tokens(), TokenKind::StringLiteral)
2319 }
2320
2321 /// A reference to an exception by name, resolved if already bound.
2322 fn exception_ref(&self, name: &ScopedName) -> Ref<zerodds_ast::ExceptionNode> {
2323 if let Some(segment) = name.segments().last() {
2324 if let Some(AnyId::Exception(id, _)) = self.scopes.lookup(segment) {
2325 return Ref::Resolved(id);
2326 }
2327 }
2328 Ref::Pending(PendingRef::ScopedName(name.clone()))
2329 }
2330
2331 /// A reference to a named type, resolved if already bound.
2332 fn named_type_ref(&self, name: &ScopedName) -> Ref<TypeNode> {
2333 if let Some(segment) = name.segments().last() {
2334 if let Some(found) = self.scopes.lookup(segment).and_then(AnyId::as_type) {
2335 return Ref::Resolved(found);
2336 }
2337 }
2338 Ref::Pending(PendingRef::ScopedName(name.clone()))
2339 }
2340
2341 /// A reference to an interface by name, resolved if already bound.
2342 fn interface_ref(&self, name: &ScopedName) -> Ref<zerodds_ast::InterfaceNode> {
2343 if let Some(segment) = name.segments().last() {
2344 if let Some(AnyId::Interface(id, _)) = self.scopes.lookup(segment) {
2345 return Ref::Resolved(id);
2346 }
2347 }
2348 Ref::Pending(PendingRef::ScopedName(name.clone()))
2349 }
2350
2351 fn operation(&mut self, node: &Arc<SyntaxNode>, interface: Id<zerodds_ast::InterfaceNode>) {
2352 let Some(name) = first_ident(node) else {
2353 return;
2354 };
2355 // `void` has no type specification; the model's own void is the
2356 // primitive of that name.
2357 // `void` writes no type specification at all; the model records
2358 // the absence rather than a stand-in type.
2359 let return_type = self.type_ref(node);
2360 let oneway = node
2361 .syntax_tokens()
2362 .iter()
2363 .any(|(_, token)| token.kind() == TokenKind::Oneway);
2364 let Ok(id) = self
2365 .ast
2366 .add_operation(interface, &name, return_type, oneway)
2367 else {
2368 return;
2369 };
2370 let annotations = self.annotations_of(node);
2371 let node_id = self.ast.operation(id).map(|o| o.node_id).ok();
2372 if let Some(node_id) = node_id {
2373 self.record_origin(AnyId::Operation(id, node_id), node.span());
2374 self.apply_annotations(&annotations, AnyId::Operation(id, node_id));
2375 }
2376
2377 self.one_namespace_per_scope(node);
2378 for parameter in node
2379 .children()
2380 .into_iter()
2381 .filter(|child| child.kind() == CstKind::Parameter)
2382 {
2383 self.parameter(¶meter, id);
2384 }
2385 for raises in node
2386 .children()
2387 .into_iter()
2388 .filter(|child| child.kind() == CstKind::RaisesClause)
2389 {
2390 for name in self.scoped_names_of(&raises) {
2391 let reference = self.exception_ref(&name);
2392 let _ = self.ast.add_raises(id, reference);
2393 }
2394 }
2395 for context in node
2396 .children()
2397 .into_iter()
2398 .filter(|child| child.kind() == CstKind::ContextClause)
2399 {
2400 for context in self.context_strings(&context) {
2401 let _ = self.ast.add_context(id, &context);
2402 }
2403 }
2404 }
2405
2406 fn parameter(&mut self, node: &Arc<SyntaxNode>, operation: Id<zerodds_ast::OperationNode>) {
2407 let Some(name) = first_ident(node) else {
2408 return;
2409 };
2410 let direction = node
2411 .syntax_tokens()
2412 .iter()
2413 .find_map(|(_, token)| match token.kind() {
2414 TokenKind::In => Some(ParamDirection::In),
2415 TokenKind::Out => Some(ParamDirection::Out),
2416 TokenKind::Inout => Some(ParamDirection::InOut),
2417 _ => None,
2418 })
2419 .unwrap_or(ParamDirection::In);
2420 let type_ = self
2421 .type_ref(node)
2422 .unwrap_or_else(|| Ref::Broken(broken_cause()));
2423 if let Ok(id) = self.ast.add_param(operation, &name, direction, type_) {
2424 let annotations = self.annotations_of(node);
2425 let node_id = self.ast.param(id).map(|p| p.node_id).ok();
2426 if let Some(node_id) = node_id {
2427 self.record_origin(AnyId::Param(id, node_id), node.span());
2428 self.apply_annotations(&annotations, AnyId::Param(id, node_id));
2429 }
2430 }
2431 }
2432
2433 fn attribute(&mut self, node: &Arc<SyntaxNode>, interface: Id<zerodds_ast::InterfaceNode>) {
2434 let readonly = node
2435 .syntax_tokens()
2436 .iter()
2437 .any(|(_, token)| token.kind() == TokenKind::Readonly);
2438 // `attribute long a, b;` declares two attributes of one type.
2439 for (name, type_, at) in self.declared(node) {
2440 let Ok(id) = self.ast.add_attribute(interface, &name, type_, readonly) else {
2441 continue;
2442 };
2443 let annotations = self.annotations_of(node);
2444 let node_id = self.ast.attribute(id).map(|a| a.node_id).ok();
2445 if let Some(node_id) = node_id {
2446 self.record_origin(AnyId::Attribute(id, node_id), at.span());
2447 self.apply_annotations(&annotations, AnyId::Attribute(id, node_id));
2448 }
2449 for raises in node
2450 .children()
2451 .into_iter()
2452 .filter(|child| child.kind() == CstKind::RaisesClause)
2453 {
2454 let setter = raises
2455 .syntax_tokens()
2456 .iter()
2457 .any(|(_, token)| token.kind() == TokenKind::Setraises);
2458 for name in self.scoped_names_of(&raises) {
2459 let reference = self.exception_ref(&name);
2460 let _ = self.ast.add_attribute_raises(id, reference, setter);
2461 }
2462 }
2463 }
2464 }
2465
2466 fn valuetype(&mut self, node: &Arc<SyntaxNode>) {
2467 let Some(name) = first_ident(node) else {
2468 return;
2469 };
2470 let kind = valuetype_kind(node);
2471 let clauses: Vec<Arc<SyntaxNode>> = node
2472 .children()
2473 .into_iter()
2474 .filter(|child| child.kind() == CstKind::InheritanceClause)
2475 .collect();
2476 // The first clause inherits valuetypes, a `supports` clause
2477 // names interfaces; both are written the same way.
2478 let mut bases = Vec::new();
2479 let mut supported = Vec::new();
2480 for clause in clauses {
2481 let supports = clause
2482 .syntax_tokens()
2483 .iter()
2484 .any(|(_, token)| token.kind() == TokenKind::Supports);
2485 for scoped in self.scoped_names_of(&clause) {
2486 if supports {
2487 supported.push(self.interface_ref(&scoped));
2488 } else {
2489 bases.push(self.named_type_ref(&scoped));
2490 }
2491 }
2492 }
2493
2494 let declared = TypeKind::Valuetype {
2495 kind,
2496 bases,
2497 state: Vec::new(),
2498 initializers: Vec::new(),
2499 supported,
2500 };
2501
2502 // `valuetype V;` and `abstract eventtype E;` are forward
2503 // declarations — rule (105) — and a forward declaration is a
2504 // completeness state. Declaring one complete made the definition
2505 // that follows a `DuplicateName` instead of the body it is.
2506 if !has_body(node) {
2507 if self.scopes.lookup(&name).is_some() {
2508 return;
2509 }
2510 let Some(module) = self.scopes.current_module() else {
2511 return;
2512 };
2513 if let Ok(id) = self.ast.add_forward_type(module, &name, declared) {
2514 self.record_origin(AnyId::Type(id, self.node_id_of(id)), node.span());
2515 self.record_declaration_scope(AnyId::Type(id, self.node_id_of(id)), node.span());
2516 if is_event_type(node) {
2517 if let Some(facets) = self.ast.facets_mut(AnyId::Type(id, self.node_id_of(id)))
2518 {
2519 facets.corba_mut().is_event_type = true;
2520 }
2521 }
2522 self.bind(&name, id);
2523 }
2524 return;
2525 }
2526
2527 let Some(id) = self.declare_type(&name, declared, node) else {
2528 return;
2529 };
2530 // `eventtype` and `valuetype` lower to the same kind of node, so
2531 // the keyword is the only thing left that tells them apart, and
2532 // CCM 4.0 §6.6.1.1 maps the two differently.
2533 if is_event_type(node) {
2534 if let Some(facets) = self.ast.facets_mut(AnyId::Type(id, self.node_id_of(id))) {
2535 facets.corba_mut().is_event_type = true;
2536 }
2537 }
2538 self.one_namespace_per_scope(node);
2539 // The body's own scope, so a `raises` clause and a parameter
2540 // type inside an initialiser resolve the way an interface's do.
2541 self.scopes.push_container(&name);
2542 for child in node.children() {
2543 match child.kind() {
2544 CstKind::Member => self.member(&child, id),
2545 // Rule (105) puts `factory` initialisers and exported
2546 // operations in a valuetype body beside its state. This
2547 // loop used to take `Member` and nothing else, so every
2548 // one of them was parsed, kept in the CST and dropped
2549 // here — `TypeKind::Valuetype::initializers` came back
2550 // empty for every input in the tree, and the CORBA Rust
2551 // generator's `factory` output disappeared with no
2552 // diagnostic.
2553 CstKind::Operation => self.valuetype_operation(&child, id),
2554 // Rule (105)'s `<export>` also admits an attribute.
2555 // `AttributeNode::parent` is an `Id<InterfaceNode>`, so
2556 // the model has no place to put one declared here — and
2557 // an arm that did nothing dropped the declaration with
2558 // no diagnostic. Named instead: a gap that reports is a
2559 // gap, a gap that is silent is an acceptance claim.
2560 CstKind::AttributeDecl => self.diagnose_at(
2561 DiagnosticCode::Unsupported,
2562 child.span(),
2563 "an attribute declared in a valuetype body has no model form yet; \
2564 the model attaches an attribute to an interface",
2565 "OMG IDL 4.2 §7.4.5",
2566 ),
2567 // The clause was read into `bases` and `supported`
2568 // above, before the body's scope was pushed.
2569 CstKind::InheritanceClause => {}
2570 // Rule (105)'s remaining `<export>` forms — a nested
2571 // `const`, `typedef`, `struct`, `union`, `enum`,
2572 // `bitset`, `bitmask`, `native` or `exception`. They
2573 // lower into the scope this body pushed, exactly as an
2574 // interface's do, and taking `Member` and `Operation`
2575 // and nothing else dropped every one of them:
2576 // `crates/idl-parse/tests/fixtures/spec_features/corba_value_repository.idl`
2577 // declares `exception Bad {};` inside `valuetype V` and
2578 // names it in `factory create(..) raises (Bad)`, so the
2579 // `raises` reference had no declaration to resolve to.
2580 _ => self.definition(&child),
2581 }
2582 }
2583 self.scopes.pop();
2584 }
2585
2586 /// An operation inside a `valuetype` body — a `factory` initialiser
2587 /// or an exported operation. Rule (105) and rule (107).
2588 ///
2589 /// The two are told apart by the `factory` keyword, which the
2590 /// parser keeps as a token of the operation's own node.
2591 fn valuetype_operation(&mut self, node: &Arc<SyntaxNode>, valuetype: Id<TypeNode>) {
2592 let Some(name) = first_ident(node) else {
2593 return;
2594 };
2595 let initializer = node
2596 .syntax_tokens()
2597 .iter()
2598 .any(|(_, token)| token.kind() == TokenKind::Factory);
2599 // An initialiser has no return type — rule (107) — and an
2600 // exported operation reads one the same way an interface's
2601 // does.
2602 let return_type = if initializer {
2603 None
2604 } else {
2605 self.type_ref(node)
2606 };
2607 let Ok(id) = self
2608 .ast
2609 .add_valuetype_operation(valuetype, &name, return_type, initializer)
2610 else {
2611 return;
2612 };
2613 let annotations = self.annotations_of(node);
2614 let node_id = self.ast.operation(id).map(|o| o.node_id).ok();
2615 if let Some(node_id) = node_id {
2616 self.record_origin(AnyId::Operation(id, node_id), node.span());
2617 self.apply_annotations(&annotations, AnyId::Operation(id, node_id));
2618 }
2619 for parameter in node
2620 .children()
2621 .into_iter()
2622 .filter(|child| child.kind() == CstKind::Parameter)
2623 {
2624 self.parameter(¶meter, id);
2625 }
2626 for raises in node
2627 .children()
2628 .into_iter()
2629 .filter(|child| child.kind() == CstKind::RaisesClause)
2630 {
2631 for scoped in self.scoped_names_of(&raises) {
2632 let reference = self.exception_ref(&scoped);
2633 let _ = self.ast.add_raises(id, reference);
2634 }
2635 }
2636 }
2637
2638 /// `valuetype VB long;` — a boxed value. Rule (126).
2639 ///
2640 /// `TypeKind::ValueBox` was in `crates/ast` from the start and
2641 /// unreachable from IDL: the parser committed to a body at the name
2642 /// and answered "expected `{` opening the valuetype body".
2643 fn value_box(&mut self, node: &Arc<SyntaxNode>) {
2644 let Some(name) = first_ident(node) else {
2645 return;
2646 };
2647 let boxed = self
2648 .type_ref(node)
2649 .unwrap_or_else(|| Ref::Broken(broken_cause()));
2650 let _ = self.declare_type(&name, TypeKind::ValueBox { boxed }, node);
2651 }
2652
2653 /// The `struct … //@annotation` form of an annotation declaration
2654 /// (DDS-XTypes 1.3 §7.3.1.2.4, Table 22).
2655 ///
2656 /// The members are the structure's. A default value is written as the
2657 /// member's `//@default(value)`, the one place a legacy compiler lets
2658 /// a member carry one.
2659 fn annotation_from_struct(&mut self, node: &Arc<SyntaxNode>) {
2660 let Some(name) = first_ident(node) else {
2661 return;
2662 };
2663 self.check_collision(&name, node);
2664 let Some(module) = self.scopes.current_module() else {
2665 return;
2666 };
2667 let Ok(id) = self.ast.add_annotation_def(module, &name) else {
2668 return;
2669 };
2670 if let Ok(node_id) = self.ast.annotation_def(id).map(|a| a.node_id) {
2671 self.record_origin(AnyId::AnnotationDef(id, node_id), node.span());
2672 self.scopes.bind(&name, AnyId::AnnotationDef(id, node_id));
2673 }
2674 self.scopes.push(&name, None);
2675 for child in node
2676 .children()
2677 .into_iter()
2678 .filter(|child| child.kind() == CstKind::Member)
2679 {
2680 let default = self
2681 .annotations_of(&child)
2682 .into_iter()
2683 .chain(self.suffix_annotations_of(&child))
2684 .find(|applied| applied.name == "default")
2685 .and_then(|applied| applied.arguments.first().and_then(|a| a.value));
2686 for (member_name, type_ref, at) in self.declared(&child) {
2687 let result = self
2688 .ast
2689 .add_annotation_member(id, &member_name, type_ref, default);
2690 self.report_model(result, at.span());
2691 }
2692 }
2693 self.scopes.pop();
2694 }
2695
2696 /// An `@annotation` declaration.
2697 fn annotation_decl(&mut self, node: &Arc<SyntaxNode>) {
2698 let Some(name) = first_ident(node) else {
2699 return;
2700 };
2701 self.check_collision(&name, node);
2702 let Some(module) = self.scopes.current_module() else {
2703 return;
2704 };
2705 let Ok(id) = self.ast.add_annotation_def(module, &name) else {
2706 return;
2707 };
2708 let node_id = self.ast.annotation_def(id).map(|a| a.node_id).ok();
2709 if let Some(node_id) = node_id {
2710 self.record_origin(AnyId::AnnotationDef(id, node_id), node.span());
2711 // Bound in the scope so a use of `@Name` finds its
2712 // declaration rather than waiting on a name forever.
2713 self.scopes.bind(&name, AnyId::AnnotationDef(id, node_id));
2714 }
2715 // The body is its own scope, the way an interface body is. Rule
2716 // (221) admits an `enum`, a `const` and a `typedef` beside the
2717 // members — `@annotation Mode { enum Kind { READ, WRITE }; Kind
2718 // kind default READ; };` — and a member's type names one of
2719 // them. They were parsed and never lowered, which nothing
2720 // noticed while the members themselves were being dropped; with
2721 // the members read, `Kind` and `READ` had nowhere to resolve.
2722 self.scopes.push(&name, None);
2723 for child in node.children() {
2724 if matches!(
2725 child.kind(),
2726 CstKind::EnumDecl | CstKind::ConstDecl | CstKind::TypedefDecl
2727 ) {
2728 self.definition(&child);
2729 }
2730 }
2731
2732 for child in node
2733 .children()
2734 .into_iter()
2735 .filter(|child| child.kind() == CstKind::Member)
2736 {
2737 // Read directly rather than through `Self::declared`.
2738 //
2739 // `declared` walks `CstKind::Declarator` children, and rule
2740 // (222) `<annotation_member> ::= <annotation_member_type>
2741 // <simple_declarator> [ "default" <const_expr> ] ";"` admits
2742 // exactly one name, which `parse_annotation_decl` reads with
2743 // a bare `expect(Ident)` and wraps in no `Declarator` node.
2744 // So `declared` found none, returned an empty list, and
2745 // every annotation's members were dropped without a word:
2746 // `@annotation Aa { long v; };` reached the model as a
2747 // declaration with zero members. Nothing downstream could
2748 // check an application against it, which is why all four of
2749 // IDL 4.2 §7.4.15.4.2's rules had nothing to hold.
2750 //
2751 // `first_ident` reads the node's own tokens and not its
2752 // descendants', so the name of `Ea v;` is `v` and not `Ea`.
2753 let Some(member_name) = first_ident(&child) else {
2754 self.diagnose_at(
2755 DiagnosticCode::MissingToken,
2756 child.span(),
2757 "an annotation member needs a name",
2758 "OMG IDL 4.2 §7.4.15.3 rule (222)",
2759 );
2760 continue;
2761 };
2762 let type_ = self
2763 .type_ref(&child)
2764 .unwrap_or_else(|| Ref::Broken(broken_cause()));
2765 let default = self.first_expr_child(&child);
2766 let result = self
2767 .ast
2768 .add_annotation_member(id, &member_name, type_, default);
2769 self.report_model(result, child.span());
2770 }
2771 self.scopes.pop();
2772 }
2773
2774 // -- helpers ---------------------------------------------------------
2775
2776 /// The first type specification among a node's children.
2777 fn type_ref(&mut self, node: &Arc<SyntaxNode>) -> Option<Ref<TypeNode>> {
2778 let type_node = node.children().into_iter().find(|child| {
2779 matches!(
2780 child.kind(),
2781 CstKind::PrimitiveType
2782 | CstKind::NamedType
2783 | CstKind::SequenceType
2784 | CstKind::StringType
2785 | CstKind::WStringType
2786 | CstKind::MapType
2787 | CstKind::FixedType
2788 )
2789 })?;
2790 Some(self.type_ref_of(&type_node))
2791 }
2792
2793 fn type_ref_of(&mut self, node: &Arc<SyntaxNode>) -> Ref<TypeNode> {
2794 match node.kind() {
2795 CstKind::PrimitiveType => self.primitive_ref(node),
2796 CstKind::StringType | CstKind::WStringType => {
2797 let width = if node.kind() == CstKind::StringType {
2798 CharWidth::Char
2799 } else {
2800 CharWidth::WChar
2801 };
2802 // `string<N>` writes its bound in the argument list.
2803 let bound = self.bound_argument(node, 0);
2804 self.anonymous(TypeKind::String { width, bound }, node.span())
2805 }
2806 CstKind::SequenceType => {
2807 let element = self.type_argument(node, 0);
2808 let bound = self.bound_argument(node, 1);
2809 let sequence = self.anonymous(TypeKind::Sequence { element, bound }, node.span());
2810 self.use_site_annotations(node, 0, &sequence, UseSite::Element);
2811 sequence
2812 }
2813 CstKind::MapType => {
2814 let key = self.type_argument(node, 0);
2815 let value = self.type_argument(node, 1);
2816 let bound = self.bound_argument(node, 2);
2817 let map = self.anonymous(TypeKind::Map { key, value, bound }, node.span());
2818 self.use_site_annotations(node, 0, &map, UseSite::MapKey);
2819 self.use_site_annotations(node, 1, &map, UseSite::Element);
2820 map
2821 }
2822 CstKind::FixedType => self.fixed_ref(node),
2823 CstKind::NamedType => self.type_ref_from_scoped_name(node).unwrap_or_else(|| {
2824 Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(Vec::new())))
2825 }),
2826 _ => Ref::Broken(broken_cause()),
2827 }
2828 }
2829
2830 /// The type at one position of a parameterised type's argument list.
2831 fn type_argument(&mut self, node: &Arc<SyntaxNode>, position: usize) -> Ref<TypeNode> {
2832 let Some(argument) = self.argument_at(node, position) else {
2833 return Ref::Broken(broken_cause());
2834 };
2835 self.type_ref_of(&argument)
2836 }
2837
2838 /// The bound at one position of a parameterised type's argument list.
2839 ///
2840 /// Absent means unbounded; a malformed expression is not silently
2841 /// taken for one, it becomes a diagnostic in the expression lowering.
2842 fn bound_argument(&mut self, node: &Arc<SyntaxNode>, position: usize) -> Bound {
2843 let Some(argument) = self.argument_at(node, position) else {
2844 return Bound::Unbounded;
2845 };
2846 self.expr(&argument).map_or(Bound::Unbounded, Bound::Const)
2847 }
2848
2849 fn argument_at(&self, node: &Arc<SyntaxNode>, position: usize) -> Option<Arc<SyntaxNode>> {
2850 self.arguments_of(node)?
2851 .into_iter()
2852 .nth(position)
2853 .map(|(_, argument)| argument)
2854 }
2855
2856 /// A parameterised type's arguments, each with the annotations
2857 /// written immediately in front of it.
2858 ///
2859 /// `sequence<@try_construct(TRIM) String5, 10>` puts the annotation
2860 /// in the same child list as the arguments, so indexing by child
2861 /// position would count it as an argument and read the bound out of
2862 /// the element slot. Grouping is by adjacency: annotations attach to
2863 /// the argument that follows them, which is the order the source
2864 /// writes and the only grouping the grammar admits.
2865 fn arguments_of(&self, node: &Arc<SyntaxNode>) -> Option<Vec<TemplateArgument>> {
2866 let list = node
2867 .children()
2868 .into_iter()
2869 .find(|child| child.kind() == CstKind::TypeArguments)?;
2870 let mut arguments: Vec<TemplateArgument> = Vec::new();
2871 let mut pending: Vec<Arc<SyntaxNode>> = Vec::new();
2872 for child in list.children() {
2873 if child.kind() == CstKind::Annotation {
2874 pending.push(child);
2875 } else {
2876 arguments.push((core::mem::take(&mut pending), child));
2877 }
2878 }
2879 // Annotations with no argument after them are a malformed
2880 // template — `sequence<@try_construct(TRIM)>`. They are dropped
2881 // here rather than attached to the previous argument, and the
2882 // parser has already reported the missing type.
2883 Some(arguments)
2884 }
2885
2886 /// Read the annotations in front of one template argument and apply
2887 /// them to the slot they belong to on the collection itself.
2888 ///
2889 /// The collection and not the element type: the element type is
2890 /// shared — `String5` in §7.3.1.2.1.12.4 is a `typedef` that any
2891 /// number of members may use — and the behaviour belongs to this one
2892 /// use of it.
2893 fn use_site_annotations(
2894 &mut self,
2895 node: &Arc<SyntaxNode>,
2896 position: usize,
2897 collection: &Ref<TypeNode>,
2898 site: UseSite,
2899 ) {
2900 let written = self.argument_annotations(node, position);
2901 if written.is_empty() {
2902 return;
2903 }
2904 let Some(id) = collection.resolved() else {
2905 return;
2906 };
2907 let any = AnyId::Type(id, self.node_id_of(id));
2908 self.apply_use_site(&written, any, site);
2909 }
2910
2911 /// The annotations written in front of one template argument.
2912 fn argument_annotations(
2913 &mut self,
2914 node: &Arc<SyntaxNode>,
2915 position: usize,
2916 ) -> Vec<crate::annotation::Applied> {
2917 let Some(arguments) = self.arguments_of(node) else {
2918 return Vec::new();
2919 };
2920 let Some((written, _)) = arguments.into_iter().nth(position) else {
2921 return Vec::new();
2922 };
2923 written
2924 .iter()
2925 .filter_map(|annotation| self.applied_annotation(annotation))
2926 .collect()
2927 }
2928
2929 /// `fixed<d, s>`, whose parameters are digits and scale rather than
2930 /// types. IDL 4.2 §7.4.1.4.4.
2931 fn fixed_ref(&mut self, node: &Arc<SyntaxNode>) -> Ref<TypeNode> {
2932 let digits = self.fixed_parameter(node, 0);
2933 let scale = self.fixed_parameter(node, 1);
2934 match (digits, scale) {
2935 (Some(digits), Some(scale)) => {
2936 self.anonymous(TypeKind::Fixed { digits, scale }, node.span())
2937 }
2938 // A `fixed` whose parameters do not evaluate is broken rather
2939 // than defaulted: a silently chosen precision would change
2940 // the wire representation.
2941 _ => {
2942 // Two rules and two messages. (42) `<fixed_pt_type> ::=
2943 // "fixed" "<" <positive_int_const> ","
2944 // <positive_int_const> ">"` is a member's; (43)
2945 // `<fixed_pt_const_type> ::= "fixed"` is a constant's
2946 // and takes none. Naming (42) for a `const fixed` was
2947 // what made rule (43) look refused for a reason.
2948 let message = if has_type_arguments(node) {
2949 "the digits and scale of `fixed` must both be integer constants"
2950 } else {
2951 "a `fixed` used as a type needs its digits and scale — `fixed<d,s>`. Only a \
2952 constant's `fixed` is written without them, and takes them from its value"
2953 };
2954 self.diagnose_at(
2955 DiagnosticCode::UnexpectedToken,
2956 node.span(),
2957 message,
2958 "OMG IDL 4.2 §7.4.1.4.4 rule (42)",
2959 );
2960 Ref::Broken(broken_cause())
2961 }
2962 }
2963 }
2964
2965 /// The `fixed<d,s>` a `const fixed` takes from its own value.
2966 ///
2967 /// Rule (43) writes the type without parameters, so the value
2968 /// decides. §7.4.1.4.3, p. 31 says how, and Table 7-11 gives the
2969 /// arithmetic: "A fixed-point literal has the apparent number of
2970 /// total and fractional digits. For example, 0123.450d is considered
2971 /// to be fixed<7,3> and 3000.00d is fixed<6,2>. Prefix operators do
2972 /// not affect the precision".
2973 fn fixed_const_type(
2974 &mut self,
2975 expr: Id<ConstExprNode>,
2976 node: &Arc<SyntaxNode>,
2977 ) -> Ref<TypeNode> {
2978 match self.fixed_type_of(expr, 0) {
2979 Ok((digits, scale)) => self.anonymous(TypeKind::Fixed { digits, scale }, node.span()),
2980 Err(reason) => {
2981 self.diagnose_at(
2982 DiagnosticCode::UnexpectedToken,
2983 node.span(),
2984 reason,
2985 "OMG IDL 4.2 §7.4.1.4.3, Table 7-11",
2986 );
2987 Ref::Broken(broken_cause())
2988 }
2989 }
2990 }
2991
2992 /// The digits and scale one expression of a fixed constant has.
2993 ///
2994 /// Table 7-11, p. 31, for `fixed<d1,s1> op fixed<d2,s2>`:
2995 ///
2996 /// | op | result |
2997 /// |---|---|
2998 /// | `+` | `fixed<max(d1-s1,d2-s2)+max(s1,s2)+1, max(s1,s2)>` |
2999 /// | `-` | the same as `+` |
3000 /// | `*` | `fixed<d1+d2, s1+s2>` |
3001 /// | `/` | `fixed<(d1-s1+s2)+sinf, sinf>` |
3002 ///
3003 /// `sinf` is the clause's own name for a precision it leaves to the
3004 /// implementation. The broadest reading is taken and written down
3005 /// here: `sinf` fills the result to the 31 digits §7.4.1.4.3 caps
3006 /// every fixed at, so a quotient keeps as many fractional digits as
3007 /// the type can hold. A narrower choice would discard digits the
3008 /// type has room for, and the clause gives no reason to.
3009 ///
3010 /// An integer operand is refused rather than promoted. Measured on
3011 /// the Linux test host, all four front ends that take `const fixed`
3012 /// at all refuse `3.14d + 1`: omniORB with "Cannot interpret integer
3013 /// literal as fixed point", JacORB with "Expecting fixed point
3014 /// constant", OpenDDS and TAO with "incompatible types in constant
3015 /// assignment".
3016 fn fixed_type_of(
3017 &self,
3018 expr: Id<ConstExprNode>,
3019 depth: usize,
3020 ) -> Result<(u8, u8), &'static str> {
3021 if depth > MAX_FIXED_EXPR_DEPTH {
3022 return Err("this fixed-point constant expression nests too deeply to evaluate");
3023 }
3024 let Ok(node) = self.ast.expr(expr) else {
3025 return Err("this fixed-point constant expression has no value");
3026 };
3027 match &node.expr {
3028 ConstExpr::Literal(zerodds_ast::Literal::Fixed { digits, scale }) => {
3029 let total = u8::try_from(digits.len()).map_err(
3030 |_| "this fixed-point literal has more digits than `fixed` can hold",
3031 )?;
3032 Ok(cap_to_31_digits(total, *scale))
3033 }
3034 ConstExpr::Literal(zerodds_ast::Literal::Integer(_)) => Err(
3035 "an integer literal is not a fixed-point value; write it with a `d` suffix, as \
3036 `1.0d`",
3037 ),
3038 ConstExpr::Literal(_) => {
3039 Err("a `const fixed` takes a fixed-point value, written with a `d` suffix")
3040 }
3041 // A name standing for another `const fixed` carries that
3042 // constant's own digits and scale.
3043 ConstExpr::NameRef(reference) => self.fixed_type_of_name(reference),
3044 ConstExpr::Unary { op, operand } => match op {
3045 // "Prefix operators do not affect the precision."
3046 UnaryOp::Plus | UnaryOp::Minus => self.fixed_type_of(*operand, depth + 1),
3047 UnaryOp::Not => Err(
3048 "`~` is not applicable to a fixed-point expression: §7.4.1.4.3 admits unary \
3049 `+` and `-` and binary `*`, `/`, `+` and `-`",
3050 ),
3051 },
3052 ConstExpr::Binary { op, lhs, rhs } => {
3053 let (d1, s1) = self.fixed_type_of(*lhs, depth + 1)?;
3054 let (d2, s2) = self.fixed_type_of(*rhs, depth + 1)?;
3055 fixed_binary(*op, d1, s1, d2, s2)
3056 }
3057 }
3058 }
3059
3060 /// The digits and scale of the constant a name stands for.
3061 fn fixed_type_of_name(
3062 &self,
3063 reference: &Ref<zerodds_ast::ConstNode>,
3064 ) -> Result<(u8, u8), &'static str> {
3065 let named = match reference {
3066 Ref::Resolved(id) => Some(*id),
3067 Ref::Pending(PendingRef::ScopedName(name)) => name
3068 .segments()
3069 .last()
3070 .and_then(|segment| self.scopes.lookup(segment))
3071 .and_then(|any| match any {
3072 AnyId::Const(id, _) => Some(id),
3073 _ => None,
3074 }),
3075 _ => None,
3076 };
3077 let Some(named) = named else {
3078 return Err("this name does not stand for a constant declared before it");
3079 };
3080 let kind = self
3081 .ast
3082 .constant(named)
3083 .ok()
3084 .and_then(|constant| constant.type_.resolved())
3085 .and_then(|type_| self.ast.type_(type_).ok())
3086 .map(|node| node.kind.clone());
3087 match kind {
3088 Some(TypeKind::Fixed { digits, scale }) => Ok((digits, scale)),
3089 _ => Err("a fixed-point constant expression may only name another `const fixed`"),
3090 }
3091 }
3092
3093 /// One parameter of `fixed<d, s>`, evaluated.
3094 fn fixed_parameter(&mut self, node: &Arc<SyntaxNode>, position: usize) -> Option<u8> {
3095 let argument = self.argument_at(node, position)?;
3096 let expr = self.expr(&argument)?;
3097 let value = self.ast.eval_integral(expr).ok()?;
3098 u8::try_from(value).ok()
3099 }
3100
3101 /// A reference to the built-in a type keyword names.
3102 ///
3103 /// The fifteen primitives, and the built-in type names IDL provides
3104 /// that are not primitives. `any` is the first of those:
3105 /// `is_primitive_keyword` in `zerodds_idl_parse` admitted it, this
3106 /// function had no arm for it, and the member's type became
3107 /// `Ref::Broken` with **neither the parser nor the lowering saying
3108 /// anything**. The first complaint came from the layout planner
3109 /// three layers later, in a vocabulary about node ids.
3110 fn primitive_ref(&mut self, node: &Arc<SyntaxNode>) -> Ref<TypeNode> {
3111 let kinds: Vec<TokenKind> = node
3112 .syntax_tokens()
3113 .iter()
3114 .map(|(_, token)| token.kind())
3115 .filter(|kind| *kind != TokenKind::Error)
3116 .collect();
3117 if let Some((name, kind)) = builtin_of(&kinds) {
3118 return self.builtin_ref(name, kind);
3119 }
3120 let Some(primitive) = primitive_of(&kinds) else {
3121 return Ref::Broken(broken_cause());
3122 };
3123 self.builtin_ref(primitive.as_str(), TypeKind::Primitive(primitive))
3124 }
3125
3126 /// One node per built-in per model, reused rather than repeated.
3127 ///
3128 /// A file with a hundred `long` members should not hold a hundred
3129 /// primitive types, and the same holds for `any`.
3130 fn builtin_ref(&mut self, name: &str, kind: TypeKind) -> Ref<TypeNode> {
3131 if let Some(existing) = self.scopes.lookup(name).and_then(AnyId::as_type) {
3132 return Ref::Resolved(existing);
3133 }
3134 let root = self.ast.root_module();
3135 match self.ast.add_type(root, name, kind) {
3136 Ok(id) => {
3137 self.scopes.bind(name, AnyId::Type(id, self.node_id_of(id)));
3138 Ref::Resolved(id)
3139 }
3140 Err(_) => Ref::Broken(broken_cause()),
3141 }
3142 }
3143
3144 /// An anonymous type, which IDL allows inline in a member.
3145 fn anonymous(&mut self, kind: TypeKind, at: Span) -> Ref<TypeNode> {
3146 let root = self.ast.root_module();
3147 let result = self.ast.add_type(root, "", kind);
3148 self.report_model(result, at)
3149 .map_or_else(|| Ref::Broken(broken_cause()), Ref::Resolved)
3150 }
3151
3152 /// A reference by name, resolved if the name is already bound and
3153 /// pending otherwise.
3154 ///
3155 /// Pending rather than an error: forward references are legal and
3156 /// the model is built to carry them. Spec §4.
3157 fn type_ref_from_scoped_name(&mut self, node: &Arc<SyntaxNode>) -> Option<Ref<TypeNode>> {
3158 let scoped = node
3159 .descendants()
3160 .into_iter()
3161 .find(|child| child.kind() == CstKind::ScopedName)?;
3162 let segments: Vec<String> = scoped
3163 .syntax_tokens()
3164 .iter()
3165 .filter(|(_, token)| token.kind() == TokenKind::Ident)
3166 .map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
3167 .collect();
3168 if segments.is_empty() {
3169 return None;
3170 }
3171 if segments.len() == 1 {
3172 if let Some(found) = self.scopes.lookup(&segments[0]) {
3173 if let Some(as_type) = found.as_type() {
3174 return Some(Ref::Resolved(as_type));
3175 }
3176 // Found, and not a type. An interface named where a
3177 // type is expected is an object reference — §7.4.6 —
3178 // and `Ast::resolve_all_by_name` has the rule for that;
3179 // what it needs is a key it can look the declaration up
3180 // by. The written segment is *relative* to the scope
3181 // that wrote it, so recording it as an absolute name
3182 // asks the index for `::I` when the declaration is
3183 // `m::I`, and the reference breaks with `NotFound`.
3184 //
3185 // Measured: `module m { interface I {}; struct S { I r; };
3186 // };` broke, `interface I {}; struct S { I r; };` did
3187 // not, and `m::I` written out did not — the failure was
3188 // exactly the unqualified form inside a module, which is
3189 // how interface-typed members are normally written.
3190 if let Some(scoped) = self.ast.scoped_name_of(found) {
3191 return Some(Ref::Pending(PendingRef::ScopedName(scoped)));
3192 }
3193 }
3194 }
3195 Some(Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(
3196 segments,
3197 ))))
3198 }
3199
3200 /// The scoped name written under a node, absolute or relative as
3201 /// written.
3202 pub(crate) fn scoped_name_of(&self, node: &Arc<SyntaxNode>) -> Option<ScopedName> {
3203 let scoped = if node.kind() == CstKind::ScopedName {
3204 node.clone()
3205 } else {
3206 node.descendants()
3207 .into_iter()
3208 .find(|child| child.kind() == CstKind::ScopedName)?
3209 };
3210 let segments: Vec<String> = scoped
3211 .syntax_tokens()
3212 .iter()
3213 .filter(|(_, token)| token.kind() == TokenKind::Ident)
3214 .map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
3215 .collect();
3216 if segments.is_empty() {
3217 return None;
3218 }
3219 Some(ScopedName::absolute(segments))
3220 }
3221
3222 /// Record a diagnostic at a byte range.
3223 pub(crate) fn diagnose_at(
3224 &mut self,
3225 code: DiagnosticCode,
3226 span: Span,
3227 message: &str,
3228 spec: &'static str,
3229 ) {
3230 self.diagnostics
3231 .push(Diagnostic::error(code, span, message.to_string(), spec));
3232 }
3233
3234 /// Record a warning at a byte range.
3235 ///
3236 /// For what the file may keep and the reader still has to hear
3237 /// about. The counterpart of [`Self::diagnose_at`], and separate
3238 /// from [`Self::diagnose_vendor`] because that one's severity is
3239 /// the dialect's answer, not the rule's.
3240 pub(crate) fn warn_at(
3241 &mut self,
3242 code: DiagnosticCode,
3243 span: Span,
3244 message: &str,
3245 spec: &'static str,
3246 ) {
3247 self.diagnostics
3248 .push(Diagnostic::warning(code, span, message.to_string(), spec));
3249 }
3250
3251 /// Record that a construct outside OMG IDL 4.2 was written here.
3252 ///
3253 /// Always recorded; the dialect decides only the severity — an error
3254 /// when the flag is off, a warning when it is on. The parser's
3255 /// `diagnose_vendor` is the same rule one stage earlier, for the
3256 /// vendor constructs that are grammar rather than pragmas.
3257 ///
3258 /// Spec §3.5.
3259 pub(crate) fn diagnose_vendor(
3260 &mut self,
3261 flag: zerodds_idl_parse::VendorFlag,
3262 span: Span,
3263 message: &str,
3264 ) {
3265 let spec = "zerodds-idl-frontend-1.0 §3.5";
3266 let diagnostic = if self.options.dialect.admits(flag) {
3267 Diagnostic::warning(
3268 DiagnosticCode::VendorExtension,
3269 span,
3270 message.to_string(),
3271 spec,
3272 )
3273 } else {
3274 Diagnostic::error(
3275 DiagnosticCode::VendorExtension,
3276 span,
3277 message.to_string(),
3278 spec,
3279 )
3280 };
3281 self.diagnostics.push(diagnostic);
3282 }
3283
3284 /// The declared type of a constant, checked against its value.
3285 ///
3286 /// IDL 4.2 §7.4.1.4.3: a constant declaration carries "a value
3287 /// expression (`<const_expr>`), which shall be consistent with the
3288 /// type declared for the constant". §7.2.6.3 states two of the cases
3289 /// as a requirement in so many words:
3290 ///
3291 /// > Attempts to assign a wide string literal to a non-wide string
3292 /// > constant or to assign a non-wide string literal to a wide string
3293 /// > constant result in a compile-time diagnostic.
3294 ///
3295 /// Without this the declared type was discarded and the literal's own
3296 /// type took its place: `const long K = "x";` reached the Rust
3297 /// backend as `pub const K: &'static str = "x";`, and the author
3298 /// heard about it from the Rust compiler at the use site rather than
3299 /// here.
3300 ///
3301 /// **Silent where it is not certain.** The check fires only when the
3302 /// declared type resolves here to a primitive, a `string`/`wstring`
3303 /// or a `fixed`, *and* the expression settles to one literal form. A
3304 /// named type, an alias, an enumeration, a reference to another
3305 /// constant and an unresolved reference all pass without a word: a
3306 /// rejection that reaches too far breaks valid IDL, which costs more
3307 /// than the defect it removes.
3308 fn check_const_value_form(
3309 &mut self,
3310 declared: &Ref<TypeNode>,
3311 expr: Id<ConstExprNode>,
3312 node: &Arc<SyntaxNode>,
3313 ) {
3314 let Some(complaint) = self.value_disagrees_with(declared, expr) else {
3315 return;
3316 };
3317 // §7.2.6.3 states the two string cases outright; the rest are
3318 // §7.4.1.4.3's "consistent with the type declared".
3319 let spec = if complaint.contains("string literal") {
3320 "OMG IDL 4.2 §7.2.6.3"
3321 } else {
3322 "OMG IDL 4.2 §7.4.1.4.3"
3323 };
3324 self.diagnose_at(
3325 DiagnosticCode::TypeMismatch,
3326 node.span(),
3327 &alloc::format!("the constant {complaint}"),
3328 spec,
3329 );
3330 }
3331
3332 /// Whether `expr` is a value the type `declared` can hold, and if
3333 /// not, how to say so.
3334 ///
3335 /// `None` means "no complaint", which includes every case the answer
3336 /// is not settled in: an unresolved reference, a named type, an
3337 /// alias, an enumeration, a reference to another constant, or an
3338 /// expression that does not reduce to one literal form. A rejection
3339 /// that reaches too far breaks valid IDL, which costs more than the
3340 /// defect it removes.
3341 ///
3342 /// Two clauses ask this question and get the same answer here: IDL
3343 /// 4.2 §7.4.1.4.3 for a constant's value, and §7.4.15.4.2 for an
3344 /// annotation argument — "a constant expression, whose type must be
3345 /// compatible with the member's declaration". One reading, so the
3346 /// two cannot drift.
3347 pub(crate) fn value_disagrees_with(
3348 &self,
3349 declared: &Ref<TypeNode>,
3350 expr: Id<ConstExprNode>,
3351 ) -> Option<alloc::string::String> {
3352 let Ref::Resolved(id) = declared else {
3353 return None;
3354 };
3355 let kind = self.ast.type_(*id).ok()?.kind.clone();
3356 let form = self.value_form(expr, 0)?;
3357 let (admitted, wanted) = match &kind {
3358 TypeKind::Primitive(PrimitiveKind::Boolean) => {
3359 (form == ValueForm::Boolean, "a boolean")
3360 }
3361 TypeKind::Primitive(
3362 PrimitiveKind::Octet
3363 | PrimitiveKind::Int8
3364 | PrimitiveKind::UInt8
3365 | PrimitiveKind::Int16
3366 | PrimitiveKind::UInt16
3367 | PrimitiveKind::Int32
3368 | PrimitiveKind::UInt32
3369 | PrimitiveKind::Int64
3370 | PrimitiveKind::UInt64,
3371 ) => (form == ValueForm::Integer, "an integer"),
3372 TypeKind::Primitive(
3373 PrimitiveKind::Float32 | PrimitiveKind::Float64 | PrimitiveKind::Float128,
3374 ) => (
3375 matches!(form, ValueForm::Integer | ValueForm::Float),
3376 "a number",
3377 ),
3378 TypeKind::Fixed { .. } => (
3379 matches!(
3380 form,
3381 ValueForm::Integer | ValueForm::Float | ValueForm::Fixed
3382 ),
3383 "a number",
3384 ),
3385 TypeKind::Primitive(PrimitiveKind::Char) => (form == ValueForm::Char, "a character"),
3386 // A narrow character literal is a wide one written narrowly;
3387 // the reverse is not, because the code point may not fit.
3388 TypeKind::Primitive(PrimitiveKind::WChar) => (
3389 matches!(form, ValueForm::Char | ValueForm::WChar),
3390 "a character",
3391 ),
3392 TypeKind::String {
3393 width: CharWidth::Char,
3394 ..
3395 } => (form == ValueForm::String, "a string literal"),
3396 TypeKind::String {
3397 width: CharWidth::WChar,
3398 ..
3399 } => (form == ValueForm::WString, "a wide string literal"),
3400 // Everything else — an enumeration, a sequence, an aggregate
3401 // — is not settled here.
3402 _ => return None,
3403 };
3404 if admitted {
3405 return None;
3406 }
3407 Some(alloc::format!(
3408 "is declared as a type that takes {wanted}, and the value is {}; the declared \
3409 type is what it has, so the two have to agree",
3410 form.describe()
3411 ))
3412 }
3413
3414 /// Which literal form a constant expression settles to, if one.
3415 ///
3416 /// `None` wherever the answer is not settled here: a reference to
3417 /// another constant, an enumerator, a mixed arithmetic expression, or
3418 /// a tree deeper than evaluation follows. `None` means "say nothing",
3419 /// never "assume".
3420 fn value_form(&self, expr: Id<ConstExprNode>, depth: usize) -> Option<ValueForm> {
3421 if depth > zerodds_ast::node::constant::MAX_EXPR_DEPTH {
3422 return None;
3423 }
3424 let node = self.ast.expr(expr).ok()?;
3425 match &node.expr {
3426 ConstExpr::Literal(literal) => match literal {
3427 Literal::Boolean(_) => Some(ValueForm::Boolean),
3428 Literal::Integer(_) => Some(ValueForm::Integer),
3429 Literal::Float { .. } => Some(ValueForm::Float),
3430 Literal::Fixed { .. } => Some(ValueForm::Fixed),
3431 Literal::Char(_) => Some(ValueForm::Char),
3432 Literal::WChar(_) => Some(ValueForm::WChar),
3433 Literal::String(_) => Some(ValueForm::String),
3434 Literal::WString(_) => Some(ValueForm::WString),
3435 // An enumerator names a type this does not resolve here.
3436 Literal::Enumerator(_) => None,
3437 },
3438 // The operand carries the form: `-1` is an integer, `~1` is
3439 // an integer, and neither operator changes that.
3440 ConstExpr::Unary { operand, .. } => self.value_form(*operand, depth + 1),
3441 // Only when both sides agree. `1 + K` where `K` is another
3442 // constant answers `None`, which is the honest answer at this
3443 // stage.
3444 ConstExpr::Binary { lhs, rhs, .. } => {
3445 let left = self.value_form(*lhs, depth + 1)?;
3446 let right = self.value_form(*rhs, depth + 1)?;
3447 if left == right {
3448 Some(left)
3449 } else if left.is_numeric() && right.is_numeric() {
3450 // `1 + 1.5` is floating; the wider form wins.
3451 Some(if left == ValueForm::Fixed || right == ValueForm::Fixed {
3452 ValueForm::Fixed
3453 } else {
3454 ValueForm::Float
3455 })
3456 } else {
3457 None
3458 }
3459 }
3460 ConstExpr::NameRef(_) => None,
3461 }
3462 }
3463
3464 /// Give an expression node its source facet.
3465 pub(crate) fn record_expr_origin(&mut self, id: Id<zerodds_ast::ConstExprNode>, span: Span) {
3466 let node_id = self.ast.expr(id).map(|node| node.node_id).ok();
3467 if let Some(node_id) = node_id {
3468 self.record_origin(AnyId::ConstExpr(id, node_id), span);
3469 }
3470 }
3471
3472 fn bind(&mut self, name: &str, id: Id<TypeNode>) {
3473 let node_id = self.node_id_of(id);
3474 self.scopes.bind(name, AnyId::Type(id, node_id));
3475 }
3476
3477 fn node_id_of(&self, id: Id<TypeNode>) -> zerodds_ast_core::NodeId {
3478 self.ast.type_(id).map_or(
3479 zerodds_ast_core::NodeId::from_parts(zerodds_ast_core::IdSpace::SYNTHETIC, 0),
3480 |node| node.node_id,
3481 )
3482 }
3483
3484 /// Facets naming where a node came from.
3485 fn facets_at(&self, span: Span) -> Facets {
3486 Facets {
3487 source: self.source_at(span),
3488 ..Facets::default()
3489 }
3490 }
3491
3492 /// The source facet for a byte range of the file being lowered.
3493 ///
3494 /// The range is narrowed to the syntax inside it: a node's own span
3495 /// starts at its leading trivia, and a facet that kept it would put
3496 /// every consumer — the diagnostics of `complete.rs` and
3497 /// `validate.rs` among them — one blank line above the declaration.
3498 fn source_at(&self, span: Span) -> SourceFacet {
3499 let span = self.syntax_spans.trim(span);
3500 SourceFacet {
3501 origin: SourceOrigin::File {
3502 path: self.path.clone(),
3503 },
3504 span: Some(zerodds_ast::ByteSpan {
3505 start: span.start,
3506 end: span.end,
3507 }),
3508 macro_chain: Vec::new(),
3509 doc_comments: Vec::new(),
3510 }
3511 }
3512
3513 fn record_origin(&mut self, any: AnyId, span: Span) {
3514 let source = self.source_at(span);
3515 if let Some(facets) = self.ast.facets_mut(any) {
3516 facets.source = source;
3517 }
3518 }
3519
3520 /// Declare a named type in the current scope, or complete the
3521 /// forward declaration already standing there.
3522 ///
3523 /// The one place a named type enters the model, so the forward rule
3524 /// holds for every declaration form rather than for whichever ones
3525 /// remembered it. A definition following `struct S;` completes that
3526 /// node: references taken in between stay valid, which is why
3527 /// forwardness is a state and not a kind. Spec §3.2 of the AST.
3528 ///
3529 /// A genuine duplicate is reported and still declared, so the rest
3530 /// of the file is checked rather than abandoned at the first clash.
3531 /// Records the interface or value type a declaration sits in, when it
3532 /// sits in one: its scoped name and its repository id go through it
3533 /// (`ScopeStack::push_container`).
3534 fn record_declaration_scope(&mut self, any: AnyId, at: zerodds_idl_cst::Span) {
3535 let Some(scope) = self.scopes.enclosing_container().map(String::from) else {
3536 return;
3537 };
3538 let result = self.ast.set_declaration_scope(any, &scope);
3539 self.report_model(result, at);
3540 }
3541
3542 fn declare_type(
3543 &mut self,
3544 name: &str,
3545 kind: TypeKind,
3546 at: &Arc<SyntaxNode>,
3547 ) -> Option<Id<TypeNode>> {
3548 if let Some(existing) = self.scopes.lookup_here(name).and_then(AnyId::as_type) {
3549 let forward = self
3550 .ast
3551 .type_(existing)
3552 .is_ok_and(|node| node.completeness == Completeness::Forward);
3553 if forward {
3554 return match self.ast.complete_type(existing, kind) {
3555 Ok(()) => {
3556 let any = AnyId::Type(existing, self.node_id_of(existing));
3557 self.record_origin(any, at.span());
3558 // The definition's own annotations. The fresh
3559 // branch below applies them; this branch did
3560 // not, so `@mutable struct Later { ... };`
3561 // behind a `struct Later;` lost its
3562 // extensibility and serialised under the
3563 // default — invisibly wherever the default was
3564 // `appendable` and it said `@appendable`,
3565 // measured under Cyclone's `final` on
3566 // `resolve-forward-declared`.
3567 self.apply_waiting_annotations(any);
3568 Some(existing)
3569 }
3570 Err(_) => None,
3571 };
3572 }
3573 }
3574 self.check_collision(name, at);
3575 let module = self.scopes.current_module()?;
3576 let result = self.ast.add_type(module, name, kind);
3577 let id = self.report_model(result, at.span())?;
3578 self.record_origin(AnyId::Type(id, self.node_id_of(id)), at.span());
3579 self.record_declaration_scope(AnyId::Type(id, self.node_id_of(id)), at.span());
3580 self.bind(name, id);
3581 self.apply_waiting_annotations(AnyId::Type(id, self.node_id_of(id)));
3582 Some(id)
3583 }
3584
3585 /// Declare an interface in the current scope, or complete the
3586 /// forward declaration already standing there.
3587 ///
3588 /// The counterpart of [`Self::declare_type`], and it exists for the
3589 /// same reason: the forward rule has to hold for every declaration
3590 /// form rather than for whichever ones remembered it. `struct S;`
3591 /// and `union U;` had it and `interface I;` did not.
3592 fn declare_interface(
3593 &mut self,
3594 name: &str,
3595 kind: InterfaceKind,
3596 bases: Vec<Ref<zerodds_ast::InterfaceNode>>,
3597 at: &Arc<SyntaxNode>,
3598 ) -> Option<Id<zerodds_ast::InterfaceNode>> {
3599 if let Some(AnyId::Interface(existing, _)) = self.scopes.lookup_here(name) {
3600 let forward = self
3601 .ast
3602 .interface(existing)
3603 .is_ok_and(|node| node.completeness == Completeness::Forward);
3604 if forward {
3605 return match self.ast.complete_interface(existing, kind, bases) {
3606 Ok(()) => {
3607 // The definition's annotations, exactly as
3608 // `declare_type`'s forward branch applies them.
3609 if let Ok(node) = self.ast.interface(existing) {
3610 let node_id = node.node_id;
3611 self.apply_waiting_annotations(AnyId::Interface(existing, node_id));
3612 }
3613 Some(existing)
3614 }
3615 Err(_) => None,
3616 };
3617 }
3618 }
3619 self.check_collision(name, at);
3620 let module = self.scopes.current_module()?;
3621 let result = self.ast.add_interface(module, name, kind, bases);
3622 self.report_model(result, at.span())
3623 }
3624
3625 /// Apply the annotations of the wrapper node this declaration sits
3626 /// in, and any written directly on the declaration itself.
3627 pub(crate) fn apply_waiting_annotations(&mut self, target: AnyId) {
3628 let Some(annotations) = self.pending_annotations.pop() else {
3629 return;
3630 };
3631 self.apply_annotations(&annotations, target);
3632 // Put them back: one wrapper may hold several declarators, and
3633 // each of them carries the same annotations.
3634 self.pending_annotations.push(annotations);
3635 }
3636
3637 /// One namespace per scope, compared without regard to case.
3638 ///
3639 /// OMG IDL 4.2 §7.2.3.1: "There is only one namespace for IDL
3640 /// identifiers in each scope", and when two identifiers are compared
3641 /// to see whether they collide, "upper- and lower-case letters are
3642 /// treated as the same letter". The clause's own example is this
3643 /// shape —
3644 ///
3645 /// ```text
3646 /// module M {
3647 /// typedef long Foo;
3648 /// const long thing = 1;
3649 /// interface thing { void doit (in Foo foo); };
3650 /// };
3651 /// ```
3652 ///
3653 /// — and the errors the specification lists for it include "`Foo`
3654 /// and `foo` collide", although `Foo` is declared in `M` and `foo`
3655 /// in the scope of `doit`. What binds the two is that the *simple*
3656 /// name `Foo` is **used** in the scope `foo` is **declared** in. An
3657 /// aggregate is that scope for its members, so `struct S { Inner
3658 /// inner; };` is the same collision.
3659 ///
3660 /// A type reached through a scoped name — `::common::First first;`
3661 /// — puts no simple identifier in the member's scope and does not
3662 /// collide. `tao_idl` and `opendds_idl`, the two front ends that
3663 /// answer this in the clause's own words, draw the line in the same
3664 /// place.
3665 ///
3666 /// Reported here rather than through `check_collision` because a
3667 /// member is not bound in `ScopeStack`: an aggregate is a scope for
3668 /// its member names and for nothing else, and binding members there
3669 /// would put them in the way of every later type lookup.
3670 ///
3671 /// Four scopes have this shape and all four are checked: an
3672 /// aggregate over its members, a union additionally over its
3673 /// discriminator, an interface over its attributes and operation
3674 /// names, and an operation over its parameters — which is the
3675 /// example's own scope.
3676 fn one_namespace_per_scope(&mut self, node: &Arc<SyntaxNode>) {
3677 // Every simple name the scope uses as a type, and every name it
3678 // declares, in one list — the clause has one namespace and so
3679 // has this. A name is kept with the span to report it at, which
3680 // is the declaration's: the use is legal on its own and only the
3681 // declaration can move.
3682 //
3683 // Track which body child (declaration) each name comes from, so
3684 // that under the ROS 2 dialect we can admit a collision between a
3685 // member/parameter name and the simple type name used *in that
3686 // same declaration* (e.g., `Pose pose;`), while still rejecting
3687 // cross-declaration collisions and declared/declared collisions.
3688 let mut used: Vec<UsedName> = Vec::new();
3689 let mut declared: Vec<DeclaredName> = Vec::new();
3690
3691 // Which children hold the declarations of this scope, and which
3692 // an inner scope of their own. An operation's parameters are the
3693 // operation's scope and not the interface's, so an interface
3694 // stops at the operation's name and its return type.
3695 let (bodies, stop_at_inner_scope) = match node.kind() {
3696 CstKind::InterfaceDecl => (
3697 [CstKind::AttributeDecl, CstKind::Operation].as_slice(),
3698 true,
3699 ),
3700 CstKind::Operation => ([CstKind::Parameter].as_slice(), false),
3701 _ => ([CstKind::Member, CstKind::UnionCase].as_slice(), false),
3702 };
3703
3704 let mut child_index = 0;
3705 for child in node.children() {
3706 let is_body = bodies.contains(&child.kind());
3707 // A type written directly on the declaration is used in this
3708 // scope: a union's discriminator and an operation's return
3709 // type are both written there.
3710 if !is_body && child.kind() != CstKind::NamedType {
3711 continue;
3712 }
3713 let inner_scope = stop_at_inner_scope && child.kind() == CstKind::Operation;
3714 if inner_scope {
3715 // Only the operation's own name and its return type
3716 // belong to the interface; the parameters do not.
3717 if let Some(name) = first_ident(&child) {
3718 declared.push(DeclaredName {
3719 name,
3720 span: child.span(),
3721 child_index,
3722 });
3723 }
3724 for grandchild in child.children() {
3725 if grandchild.kind() == CstKind::NamedType {
3726 collect_simple_type_names_with_index(&grandchild, &mut used, child_index);
3727 }
3728 }
3729 child_index += 1;
3730 continue;
3731 }
3732 collect_simple_type_names_with_index(&child, &mut used, child_index);
3733 if !is_body {
3734 child_index += 1;
3735 continue;
3736 }
3737 for declarator in child
3738 .descendants()
3739 .into_iter()
3740 .filter(|inner| inner.kind() == CstKind::Declarator)
3741 {
3742 if let Some(name) = first_ident(&declarator) {
3743 declared.push(DeclaredName {
3744 name,
3745 span: declarator.span(),
3746 child_index,
3747 });
3748 }
3749 }
3750 // A parameter writes its name as a plain identifier rather
3751 // than through a declarator.
3752 if child.kind() == CstKind::Parameter
3753 && !child
3754 .children()
3755 .iter()
3756 .any(|inner| inner.kind() == CstKind::Declarator)
3757 {
3758 if let Some(name) = first_ident(&child) {
3759 declared.push(DeclaredName {
3760 name,
3761 span: child.span(),
3762 child_index,
3763 });
3764 }
3765 }
3766 child_index += 1;
3767 }
3768
3769 for index in 0..declared.len() {
3770 let current = &declared[index];
3771 let name = ¤t.name;
3772 let span = current.span;
3773 let child_idx = current.child_index;
3774
3775 // Check against used type names from other declarations
3776 let clash_used = used
3777 .iter()
3778 .filter(|u| u.child_index != child_idx)
3779 .map(|u| u.name.as_str())
3780 .find(|other| *other != name && other.eq_ignore_ascii_case(name));
3781
3782 // Check against previously declared names (always an error)
3783 let clash_declared = declared[..index]
3784 .iter()
3785 .map(|d| d.name.as_str())
3786 .find(|other| *other != name && other.eq_ignore_ascii_case(name));
3787
3788 // Check against used type names from the same declaration
3789 // Only relevant under ROS 2 dialect: admit this specific case
3790 let clash_same_decl = used
3791 .iter()
3792 .filter(|u| u.child_index == child_idx)
3793 .map(|u| u.name.as_str())
3794 .find(|other| *other != name && other.eq_ignore_ascii_case(name));
3795
3796 if let Some(clash) = clash_declared {
3797 // Two declared names in the same scope differing only in case — always an error
3798 let message = alloc::format!(
3799 "`{name}` and `{clash}` are in one scope and differ only in case; IDL has a \
3800 single namespace per scope and compares identifiers without regard to case"
3801 );
3802 self.diagnose_at(
3803 DiagnosticCode::DuplicateName,
3804 span,
3805 &message,
3806 "OMG IDL 4.2 §7.2.3.1",
3807 );
3808 } else if let Some(clash) = clash_used {
3809 // Declared name collides with a used type name from a different declaration — error
3810 let message = alloc::format!(
3811 "`{name}` and `{clash}` are in one scope and differ only in case; IDL has a \
3812 single namespace per scope and compares identifiers without regard to case. \
3813 Rename the declaration, or write the type's scoped name"
3814 );
3815 self.diagnose_at(
3816 DiagnosticCode::DuplicateName,
3817 span,
3818 &message,
3819 "OMG IDL 4.2 §7.2.3.1",
3820 );
3821 } else if let Some(clash) = clash_same_decl {
3822 // Declared name collides with a used type name from the same declaration
3823 // Admit under ROS 2 dialect, error otherwise
3824 if self
3825 .options
3826 .dialect
3827 .admits(zerodds_idl_parse::VendorFlag::Ros2)
3828 {
3829 // Record that this construct was admitted by the ROS 2 flag
3830 // (no diagnostic emitted, but the construct is accepted)
3831 } else {
3832 let message = alloc::format!(
3833 "`{name}` and `{clash}` are in one scope and differ only in case; IDL has a \
3834 single namespace per scope and compares identifiers without regard to case. \
3835 Rename the declaration, or write the type's scoped name"
3836 );
3837 self.diagnose_at(
3838 DiagnosticCode::DuplicateName,
3839 span,
3840 &message,
3841 "OMG IDL 4.2 §7.2.3.1",
3842 );
3843 }
3844 }
3845 }
3846 }
3847
3848 /// Collision reporting, kept where the duplicate rule is stated.
3849 fn duplicate(&mut self, name: &str, at: &Arc<SyntaxNode>) {
3850 let message = alloc::format!(
3851 "`{name}` is already declared in this scope; IDL compares identifiers case-insensitively"
3852 );
3853 self.diagnose_at(
3854 DiagnosticCode::DuplicateName,
3855 at.span(),
3856 &message,
3857 "OMG IDL 4.2 §7.2.3",
3858 );
3859 }
3860
3861 /// Turn a rejected model mutation into a diagnostic.
3862 ///
3863 /// The model refuses what would break a structural invariant, and
3864 /// the refusal is the only record of it: a lowering that discarded
3865 /// the `Err` would drop the declaration and say nothing, which is
3866 /// how `long a[0]` reached a model with no array in it and no
3867 /// complaint about one.
3868 pub(crate) fn report_model<T>(
3869 &mut self,
3870 result: Result<T, zerodds_ast::AstError>,
3871 at: Span,
3872 ) -> Option<T> {
3873 match result {
3874 Ok(value) => Some(value),
3875 Err(error) => {
3876 let (code, spec) = classify(&error);
3877 let message = self.explain(&error);
3878 self.diagnose_at(code, at, &message, spec);
3879 None
3880 }
3881 }
3882 }
3883
3884 /// A rejected mutation, in the terms the source used.
3885 ///
3886 /// The model names nodes by identity, which is what it has; a
3887 /// diagnostic has to name them the way they were written.
3888 fn explain(&self, error: &zerodds_ast::AstError) -> String {
3889 use zerodds_ast::{AstError, InvariantViolation as V};
3890 let AstError::Invariant(violations) = error else {
3891 return alloc::format!("{error}");
3892 };
3893 match violations.first() {
3894 Some(V::DuplicateMemberId {
3895 id, first, second, ..
3896 }) => {
3897 let (first_name, first_origin) = self.member_id_source(*first);
3898 let (second_name, second_origin) = self.member_id_source(*second);
3899 member_id_collision(&second_name, second_origin, &first_name, first_origin, *id)
3900 }
3901 _ => alloc::format!("{error}"),
3902 }
3903 }
3904
3905 /// A member's name and where its id came from.
3906 fn member_id_source(&self, node: zerodds_ast_core::NodeId) -> (String, &'static str) {
3907 let Some(any) = self.ast.resolve_node_id(node) else {
3908 return (String::from("a member"), "an unknown rule");
3909 };
3910 let name = match any {
3911 AnyId::Member(id, _) => self
3912 .ast
3913 .member(id)
3914 .map_or_else(|_| String::from("a member"), |m| m.name.clone()),
3915 _ => String::from("a member"),
3916 };
3917 let origin = self
3918 .ast
3919 .facets(any)
3920 .and_then(|facets| facets.xtypes.as_ref())
3921 .and_then(|x| x.member_id.as_ref())
3922 .map_or("an unknown rule", |assignment| assignment.origin.as_str());
3923 (name, origin)
3924 }
3925
3926 /// Report a name that collides case-insensitively in the same scope.
3927 ///
3928 /// IDL is case-insensitive for collisions and case-preserving
3929 /// otherwise, so `Pose` and `pose` are the same name. Spec §6.
3930 fn check_collision(&mut self, name: &str, at: &Arc<SyntaxNode>) {
3931 if self.scopes.is_bound_here(name) {
3932 self.duplicate(name, at);
3933 return;
3934 }
3935 self.check_redefines_enclosing_scope(name, at);
3936 }
3937
3938 /// A declaration may not reuse the name of the scope it is written
3939 /// in. IDL 4.2 §7.5.2.
3940 ///
3941 /// > The name of a module, structure, union, map, interface, value
3942 /// > type, event type, exception or home may not be redefined within
3943 /// > the immediate scope of the module, structure, union, map,
3944 /// > interface, value type, event type, exception or home.
3945 ///
3946 /// The specification gives it as an example with the comment
3947 /// `// Error`:
3948 ///
3949 /// ```text
3950 /// module M {
3951 /// typedef short M; // Error
3952 /// };
3953 /// ```
3954 ///
3955 /// **Immediate scope only**, which is what makes the rule safe to
3956 /// have: `module Ma { module Mb { typedef short Ma; }; };` is legal
3957 /// and stays legal, because `Ma` is not the name of the scope `Mb`.
3958 /// That distinction is the whole difference between this rule and
3959 /// one that would break valid IDL, and it is why only the innermost
3960 /// scope's name is compared.
3961 ///
3962 /// Case-insensitively, because §7.2.3 makes every identifier
3963 /// comparison in IDL case-insensitive and this is one.
3964 fn check_redefines_enclosing_scope(&mut self, name: &str, at: &Arc<SyntaxNode>) {
3965 let Some(scope) = self.scopes.innermost_name() else {
3966 return;
3967 };
3968 if !scope.eq_ignore_ascii_case(name) {
3969 return;
3970 }
3971 // The vendors split here, so the axis decides. Under
3972 // `NameOfTheEnclosingScope::Admitted` the declaration stands and
3973 // the model is the one Fast DDS and RTI Connext build — which is
3974 // the only way a probe reproduces the octets they wrote.
3975 if self.options.name_of_the_enclosing_scope == NameOfTheEnclosingScope::Admitted {
3976 return;
3977 }
3978 let message = alloc::format!(
3979 "`{name}` is the name of the scope it is declared in, and OMG IDL 4.2 §7.5.2 does \
3980 not admit redefining that name within its own immediate scope"
3981 );
3982 self.diagnose_at(
3983 DiagnosticCode::DuplicateName,
3984 at.span(),
3985 &message,
3986 "OMG IDL 4.2 §7.5.2",
3987 );
3988 }
3989}
3990
3991fn broken_cause() -> zerodds_ast_core::BreakCause {
3992 zerodds_ast_core::BreakCause::NotFound {
3993 what: PendingRef::ScopedName(ScopedName::absolute(Vec::new())),
3994 }
3995}
3996
3997/// A simple type name with the index of the body child it came from.
3998/// Used to distinguish same-declaration from cross-declaration collisions.
3999struct UsedName {
4000 name: String,
4001 child_index: usize,
4002}
4003
4004/// A declared name with its span and the index of the body child it came from.
4005struct DeclaredName {
4006 name: String,
4007 span: Span,
4008 child_index: usize,
4009}
4010
4011/// Like `collect_simple_type_names`, but records the child index.
4012fn collect_simple_type_names_with_index(
4013 node: &Arc<SyntaxNode>,
4014 into: &mut Vec<UsedName>,
4015 child_index: usize,
4016) {
4017 for named in node
4018 .descendants()
4019 .into_iter()
4020 .filter(|child| child.kind() == CstKind::NamedType)
4021 {
4022 let Some(scoped) = named
4023 .descendants()
4024 .into_iter()
4025 .find(|child| child.kind() == CstKind::ScopedName)
4026 else {
4027 continue;
4028 };
4029 let tokens = scoped.syntax_tokens();
4030 if tokens
4031 .iter()
4032 .any(|(_, token)| token.kind() == TokenKind::ColonColon)
4033 {
4034 continue;
4035 }
4036 let segments: Vec<&str> = tokens
4037 .iter()
4038 .filter(|(_, token)| token.kind() == TokenKind::Ident)
4039 .map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()))
4040 .collect();
4041 if let [only] = segments.as_slice() {
4042 into.push(UsedName {
4043 name: (*only).to_string(),
4044 child_index,
4045 });
4046 }
4047 }
4048}
4049
4050/// The first identifier token directly under a node.
4051/// The string literals in `tokens`, a run of adjacent pieces of one
4052/// width joined into one value.
4053///
4054/// §7.2.6.3: "Adjacent string literals are concatenated" (§7.2.6.4 for
4055/// wide ones). The parser keeps each piece as its own token; this joins
4056/// a run of them, each piece decoded on its own so no escape reaches
4057/// across a seam. Any other significant token between two literals —
4058/// a comma in a `context` clause — ends the run. A piece that does not
4059/// decode drops the whole run rather than leaving a shorter string.
4060pub(crate) fn joined_string_literals(
4061 tokens: impl IntoIterator<Item = (zerodds_idl_cst::Span, zerodds_idl_cst::GreenToken)>,
4062 kind: TokenKind,
4063) -> Vec<String> {
4064 let mut joined = Vec::new();
4065 let mut run: Option<Option<String>> = None;
4066 for (_, token) in tokens {
4067 if token.kind() == kind {
4068 let piece = match zerodds_idl_cst::literal_value(kind, token.text()) {
4069 zerodds_idl_cst::TokenValue::Text { value } => Some(value),
4070 _ => None,
4071 };
4072 run = Some(match (run.take(), piece) {
4073 (None, piece) => piece,
4074 (Some(Some(mut value)), Some(piece)) => {
4075 value.push_str(&piece);
4076 Some(value)
4077 }
4078 (Some(_), _) => None,
4079 });
4080 } else if let Some(finished) = run.take() {
4081 joined.extend(finished);
4082 }
4083 }
4084 if let Some(finished) = run {
4085 joined.extend(finished);
4086 }
4087 joined
4088}
4089
4090fn first_ident(node: &Arc<SyntaxNode>) -> Option<String> {
4091 node.syntax_tokens()
4092 .iter()
4093 .find(|(_, token)| token.kind() == TokenKind::Ident)
4094 .map(|(_, token)| zerodds_idl_cst::identifier_name(token.text()).to_string())
4095}
4096
4097/// Whether the declarator's name token carries the escape underscore —
4098/// the spelling `identifier_name` strips and `XTypesFacet::escaped`
4099/// keeps.
4100fn declarator_is_escaped(node: &Arc<SyntaxNode>) -> bool {
4101 node.syntax_tokens()
4102 .iter()
4103 .find(|(_, token)| token.kind() == TokenKind::Ident)
4104 .is_some_and(|(_, token)| token.text().starts_with('_'))
4105}
4106
4107/// How deep a fixed-point constant expression is followed.
4108///
4109/// The same reason [`zerodds_ast::MAX_EXPR_DEPTH`] exists: a tree read
4110/// from somewhere else can be hostile, and a walk without a bound hangs
4111/// rather than reporting.
4112const MAX_FIXED_EXPR_DEPTH: usize = 256;
4113
4114/// The largest number of digits a `fixed` may have. §7.4.1.4.3.
4115const MAX_FIXED_DIGITS: u8 = 31;
4116
4117/// Whether a declaration's type is `fixed` written without parameters.
4118///
4119/// Rule (43) `<fixed_pt_const_type> ::= "fixed"` — the constant form.
4120fn bare_fixed(node: &Arc<SyntaxNode>) -> bool {
4121 node.children()
4122 .into_iter()
4123 .find(|child| child.kind() == CstKind::FixedType)
4124 .is_some_and(|fixed| !has_type_arguments(&fixed))
4125}
4126
4127/// Whether a declaration wrote a body at all.
4128///
4129/// A `valuetype` with no `{` is rule (105)'s forward declaration.
4130fn has_body(node: &Arc<SyntaxNode>) -> bool {
4131 node.syntax_tokens()
4132 .iter()
4133 .any(|(_, token)| token.kind() == TokenKind::LBrace)
4134}
4135
4136/// Whether a parameterised type wrote its `<...>` at all.
4137fn has_type_arguments(node: &Arc<SyntaxNode>) -> bool {
4138 node.children()
4139 .iter()
4140 .any(|child| child.kind() == CstKind::TypeArguments)
4141}
4142
4143/// Table 7-11's result type for one binary operator.
4144///
4145/// In `u16` throughout, and every intermediate is bounded: both operands
4146/// have already been through [`cap_to_31_digits`], so the widest result
4147/// any row can reach is `31 + 31 + 1`. The conversion back reports
4148/// rather than clamps all the same — a number this arithmetic cannot
4149/// have produced would otherwise become a precision nobody wrote.
4150fn fixed_binary(op: BinaryOp, d1: u8, s1: u8, d2: u8, s2: u8) -> Result<(u8, u8), &'static str> {
4151 // §7.4.1.4.3's own reading of a fixed type: the digits include the
4152 // fractional ones, so a scale above them is not a `fixed` at all and
4153 // the row's `d - s` would have nothing to subtract from.
4154 if s1 > d1 || s2 > d2 {
4155 return Err("a `fixed` scale cannot exceed its digits");
4156 }
4157 let (d1, s1, d2, s2) = (u16::from(d1), u16::from(s1), u16::from(d2), u16::from(s2));
4158 let (digits, scale) = match op {
4159 // fixed<max(d1-s1,d2-s2)+max(s1,s2)+1, max(s1,s2)>
4160 BinaryOp::Add | BinaryOp::Sub => {
4161 let scale = s1.max(s2);
4162 ((d1 - s1).max(d2 - s2) + scale + 1, scale)
4163 }
4164 // fixed<d1+d2, s1+s2>
4165 BinaryOp::Mul => (d1 + d2, s1 + s2),
4166 // fixed<(d1-s1+s2)+sinf, sinf>, with `sinf` filling the result
4167 // to the 31 digits the type holds.
4168 BinaryOp::Div => {
4169 let whole = d1 - s1 + s2;
4170 let sinf = u16::from(MAX_FIXED_DIGITS).saturating_sub(whole);
4171 (whole + sinf, sinf)
4172 }
4173 BinaryOp::Or
4174 | BinaryOp::Xor
4175 | BinaryOp::And
4176 | BinaryOp::Shl
4177 | BinaryOp::Shr
4178 | BinaryOp::Mod => {
4179 return Err(
4180 "§7.4.1.4.3 admits unary `+` and `-` and binary `*`, `/`, `+` and `-` in a \
4181 fixed-point expression, and nothing else",
4182 );
4183 }
4184 };
4185 let too_wide = "this fixed-point expression reaches a precision `fixed` cannot state";
4186 Ok(cap_to_31_digits(
4187 u8::try_from(digits).map_err(|_| too_wide)?,
4188 u8::try_from(scale).map_err(|_| too_wide)?,
4189 ))
4190}
4191
4192/// §7.4.1.4.3's cap, applied after every computation.
4193///
4194/// "If an individual computation between a pair of fixed-point literals
4195/// actually generates more than 31 significant digits, then a 31-digit
4196/// result is retained as follows: `fixed<d,s> => fixed<31, 31-d+s>`."
4197///
4198/// `31 - d + s` goes negative when the integral part alone exceeds 31
4199/// digits. The clause does not say what then, and the only reading the
4200/// type admits is a scale of zero — a negative scale is not
4201/// representable and rounding is expressly forbidden, so the fractional
4202/// digits are the ones that go.
4203fn cap_to_31_digits(digits: u8, scale: u8) -> (u8, u8) {
4204 if digits <= MAX_FIXED_DIGITS {
4205 return (digits, scale);
4206 }
4207 let capped = i32::from(MAX_FIXED_DIGITS) - i32::from(digits) + i32::from(scale);
4208 (MAX_FIXED_DIGITS, u8::try_from(capped.max(0)).unwrap_or(0))
4209}
4210
4211/// Whether a declaration was written `eventtype`. Rule (166).
4212fn is_event_type(node: &Arc<SyntaxNode>) -> bool {
4213 node.syntax_tokens()
4214 .iter()
4215 .any(|(_, token)| token.kind() == TokenKind::Eventtype)
4216}
4217
4218/// Whether a member declaration opened with `public` or `private`.
4219///
4220/// Only a valuetype state member carries one; a struct member has no
4221/// visibility keyword and the answer is `None`. IDL 4.2 §7.4.4,
4222/// rule (106).
4223fn state_visibility(node: &Arc<SyntaxNode>) -> Option<zerodds_ast::StateVisibility> {
4224 node.syntax_tokens()
4225 .iter()
4226 .find_map(|(_, token)| match token.kind() {
4227 TokenKind::Public => Some(zerodds_ast::StateVisibility::Public),
4228 TokenKind::Private => Some(zerodds_ast::StateVisibility::Private),
4229 _ => None,
4230 })
4231}
4232
4233/// The built-in type names IDL provides that are not primitives.
4234///
4235/// `<base_type_spec>` reaches these through their own rules rather than
4236/// through `<primitive_type>`, and each is one keyword: `any` is
4237/// §7.4.2's rules (69) and (70), `Object` §7.4.6's (117) and (118),
4238/// `ValueBase` §7.4.7's (131) and (132).
4239/// They are named types in the model exactly as `long` is, so a member
4240/// declared with one carries a resolved reference and not a broken one.
4241fn builtin_of(kinds: &[TokenKind]) -> Option<(&'static str, TypeKind)> {
4242 match kinds {
4243 [TokenKind::Any] => Some(("any", TypeKind::Any)),
4244 // Rule (117) `<object_type> ::= "Object"` names no interface,
4245 // which is what the absent reference says.
4246 [TokenKind::Object] => Some(("Object", TypeKind::ObjectRef { interface: None })),
4247 // (131) `<value_base_type> ::= "ValueBase"`, the abstract base
4248 // every valuetype has, written as a type.
4249 [TokenKind::Valuebase] => Some(("ValueBase", TypeKind::ValueBase)),
4250 _ => None,
4251 }
4252}
4253
4254/// Which primitive a run of type keywords spells.
4255fn primitive_of(kinds: &[TokenKind]) -> Option<PrimitiveKind> {
4256 Some(match kinds {
4257 [TokenKind::Boolean] => PrimitiveKind::Boolean,
4258 [TokenKind::Octet] => PrimitiveKind::Octet,
4259 [TokenKind::Char] => PrimitiveKind::Char,
4260 [TokenKind::Wchar] => PrimitiveKind::WChar,
4261 [TokenKind::Short] => PrimitiveKind::Int16,
4262 [TokenKind::Long] => PrimitiveKind::Int32,
4263 [TokenKind::Long, TokenKind::Long] => PrimitiveKind::Int64,
4264 [TokenKind::Long, TokenKind::Double] => PrimitiveKind::Float128,
4265 [TokenKind::Unsigned, TokenKind::Short] => PrimitiveKind::UInt16,
4266 [TokenKind::Unsigned, TokenKind::Long] => PrimitiveKind::UInt32,
4267 [TokenKind::Unsigned, TokenKind::Long, TokenKind::Long] => PrimitiveKind::UInt64,
4268 [TokenKind::Float] => PrimitiveKind::Float32,
4269 [TokenKind::Double] => PrimitiveKind::Float64,
4270 [TokenKind::Int8] => PrimitiveKind::Int8,
4271 [TokenKind::Uint8] => PrimitiveKind::UInt8,
4272 [TokenKind::Int16] => PrimitiveKind::Int16,
4273 [TokenKind::Uint16] => PrimitiveKind::UInt16,
4274 [TokenKind::Int32] => PrimitiveKind::Int32,
4275 [TokenKind::Uint32] => PrimitiveKind::UInt32,
4276 [TokenKind::Int64] => PrimitiveKind::Int64,
4277 [TokenKind::Uint64] => PrimitiveKind::UInt64,
4278 _ => return None,
4279 })
4280}
4281
4282/// Whether a scoped name was written anchored at the global scope.
4283///
4284/// Rule (4) `<scoped_name> ::+ "::" <identifier>`: the leading `::` says
4285/// the root and nothing else, so a name that carries it is not looked up
4286/// outwards from where it was written.
4287fn is_root_anchored(node: &Arc<SyntaxNode>) -> bool {
4288 let scoped = if node.kind() == CstKind::ScopedName {
4289 Some(node.clone())
4290 } else {
4291 node.descendants()
4292 .into_iter()
4293 .find(|child| child.kind() == CstKind::ScopedName)
4294 };
4295 scoped.is_some_and(|scoped| {
4296 scoped
4297 .syntax_tokens()
4298 .first()
4299 .is_some_and(|(_, token)| token.kind() == TokenKind::ColonColon)
4300 })
4301}
4302
4303/// Which interface a forward declaration forwards, if it is one.
4304///
4305/// `None` for `struct S;` and `union U;`, whose forward declaration is a
4306/// type. Rule (75) `<interface_forward_dcl> ::= <interface_header>` —
4307/// and the header carries the `local` or `abstract` modifier, so the
4308/// forward and its definition agree on the form before the body arrives.
4309///
4310/// (135) `<component_forward_dcl> ::= "component" <identifier>` is the
4311/// same rule one building block on, and the four component-model
4312/// declarations reach the model as interfaces of their own kind. They
4313/// are listed here rather than left to the type path for the reason
4314/// (75) is: a `component` forward completed by a `component` definition
4315/// has to be one node.
4316fn forward_interface_kind(node: &Arc<SyntaxNode>) -> Option<InterfaceKind> {
4317 let mut modifier = None;
4318 for (_, token) in node.syntax_tokens() {
4319 match token.kind() {
4320 TokenKind::Local => modifier = Some(InterfaceKind::Local),
4321 TokenKind::Abstract => modifier = Some(InterfaceKind::Abstract),
4322 TokenKind::Interface => {
4323 return Some(modifier.unwrap_or(InterfaceKind::Interface));
4324 }
4325 TokenKind::Component => return Some(InterfaceKind::Component),
4326 TokenKind::Home => return Some(InterfaceKind::Home),
4327 TokenKind::Connector => return Some(InterfaceKind::Connector),
4328 TokenKind::Porttype => return Some(InterfaceKind::PortType),
4329 // The name ends the header; a modifier with no keyword after
4330 // it is not an interface forward.
4331 TokenKind::Ident => return None,
4332 _ => {}
4333 }
4334 }
4335 None
4336}
4337
4338/// Which interface form a declaration wrote. IDL 4.2 §7.4.3.
4339fn interface_kind(node: &Arc<SyntaxNode>) -> InterfaceKind {
4340 for (_, token) in node.syntax_tokens() {
4341 match token.kind() {
4342 TokenKind::Local => return InterfaceKind::Local,
4343 TokenKind::Abstract => return InterfaceKind::Abstract,
4344 TokenKind::Component => return InterfaceKind::Component,
4345 TokenKind::Home => return InterfaceKind::Home,
4346 TokenKind::Connector => return InterfaceKind::Connector,
4347 TokenKind::Porttype => return InterfaceKind::PortType,
4348 // The modifier precedes `interface`, so the keyword itself
4349 // ends the search rather than continuing into the body.
4350 TokenKind::Interface => break,
4351 _ => {}
4352 }
4353 }
4354 InterfaceKind::Interface
4355}
4356
4357/// Which valuetype form a declaration wrote. IDL 4.2 §7.4.4.
4358fn valuetype_kind(node: &Arc<SyntaxNode>) -> ValuetypeKind {
4359 let mut kind = ValuetypeKind::Regular;
4360 for (_, token) in node.syntax_tokens() {
4361 match token.kind() {
4362 TokenKind::Custom => kind = ValuetypeKind::Custom,
4363 TokenKind::Abstract => kind = ValuetypeKind::Abstract,
4364 TokenKind::LBrace => break,
4365 _ => {}
4366 }
4367 }
4368 if kind != ValuetypeKind::Regular {
4369 return kind;
4370 }
4371 // `truncatable` is written on the **inheritance** clause — rule
4372 // (109) `<value_inheritance_spec> ::= [ ":" [ "truncatable" ]
4373 // <value_name> …` — and not beside the `valuetype` keyword. The
4374 // loop above reads the declaration's own tokens, and the keyword is
4375 // a token of the clause's node, so it was never seen: every
4376 // `valuetype D : truncatable B` came back `Regular`, and the
4377 // §15.3.4.3 base-identifier list a peer needs in order to truncate
4378 // was not emitted. The three are mutually exclusive in valid IDL —
4379 // CORBA 3.3 part 1 §5.2.5 forbids a truncatable `custom` valuetype
4380 // and an `abstract` one has no state to truncate — so one field
4381 // holds the answer.
4382 let truncatable = node
4383 .children()
4384 .into_iter()
4385 .filter(|child| child.kind() == CstKind::InheritanceClause)
4386 .any(|clause| {
4387 clause
4388 .syntax_tokens()
4389 .iter()
4390 .any(|(_, token)| token.kind() == TokenKind::Truncatable)
4391 });
4392 if truncatable {
4393 ValuetypeKind::Truncatable
4394 } else {
4395 ValuetypeKind::Regular
4396 }
4397}
4398
4399/// The diagnostic a rejected mutation gets.
4400///
4401/// One arm per violation the model can raise, with no catch-all: a new
4402/// invariant is a compile error here rather than a diagnostic that says
4403/// only "unexpected token".
4404fn classify(error: &zerodds_ast::AstError) -> (DiagnosticCode, &'static str) {
4405 use zerodds_ast::{AstError, InvariantViolation as V};
4406 let AstError::Invariant(violations) = error else {
4407 return (DiagnosticCode::UnexpectedToken, "zerodds-ast-1.0 §8.1");
4408 };
4409 match violations.first() {
4410 Some(V::NonPositiveArrayDimension { .. }) => {
4411 (DiagnosticCode::InvalidBound, "OMG IDL 4.2 §7.4.1.4.4")
4412 }
4413 Some(V::IncompatibleCaseLabel { .. }) => {
4414 (DiagnosticCode::InvalidCaseLabel, "OMG IDL 4.2 §7.4.1.4.4.3")
4415 }
4416 Some(
4417 V::DuplicateMemberName { .. }
4418 | V::DuplicateEnumeratorName { .. }
4419 | V::NameCollidesOnlyInCase { .. },
4420 ) => (DiagnosticCode::DuplicateName, "OMG IDL 4.2 §7.2.3"),
4421 Some(V::DuplicateMemberId { .. }) => (
4422 DiagnosticCode::DuplicateMemberId,
4423 "DDS-XTypes 1.3 §7.3.1.2.1.1",
4424 ),
4425 Some(V::MultipleDefaultCases { .. }) => {
4426 (DiagnosticCode::InvalidCaseLabel, "OMG IDL 4.2 §7.4.1.4.4.3")
4427 }
4428 Some(V::DanglingReference { .. }) | None => {
4429 (DiagnosticCode::UnexpectedToken, "zerodds-ast-1.0 §8.1")
4430 }
4431 }
4432}
4433
4434/// Drop diagnostics that repeat one already reported.
4435///
4436/// Compared by code and message rather than by byte range: the same
4437/// fact reported from two places points at two different parts of it.
4438fn dedup(diagnostics: Vec<Diagnostic>) -> Vec<Diagnostic> {
4439 let mut seen: Vec<(DiagnosticCode, String)> = Vec::new();
4440 let mut out = Vec::new();
4441 for diagnostic in diagnostics {
4442 let key = (diagnostic.code, diagnostic.message.clone());
4443 if seen.contains(&key) {
4444 continue;
4445 }
4446 seen.push(key);
4447 out.push(diagnostic);
4448 }
4449 out
4450}
4451
4452/// How a member-id collision reads.
4453///
4454/// One wording, whether the collision surfaced as a rejected commit or
4455/// as a validation pass over the finished model: two spellings of one
4456/// fact would read as two problems.
4457pub(crate) fn member_id_collision(
4458 name: &str,
4459 origin: &str,
4460 other: &str,
4461 other_origin: &str,
4462 id: u32,
4463) -> String {
4464 alloc::format!(
4465 "`{name}` and `{other}` both have member id {id}: {name} from {origin}, {other} from {other_origin}"
4466 )
4467}
4468
4469/// Which kind of entity a template parameter stands for.
4470/// What an instantiation supplies for one formal parameter.
4471enum Actual {
4472 /// A type specification or constant expression written at the
4473 /// instantiation — rule (192).
4474 Written(Arc<SyntaxNode>),
4475 /// A binding handed on by an enclosing instantiation, which is what
4476 /// rule (193)'s `alias` does.
4477 Bound(AnyId),
4478}
4479
4480/// The formal parameters of a stored template body, in order.
4481///
4482/// Each is its name and whether it is the `const` alternative, which is
4483/// the only one whose actual is a value rather than a type.
4484fn template_formals(body: &Arc<SyntaxNode>) -> Vec<(String, bool)> {
4485 body.children()
4486 .into_iter()
4487 .find(|child| child.kind() == CstKind::TemplateParameters)
4488 .map_or_else(Vec::new, |list| {
4489 list.children()
4490 .into_iter()
4491 .filter(|child| child.kind() == CstKind::TemplateParameter)
4492 .filter_map(|parameter| {
4493 let name = first_ident(¶meter)?;
4494 let is_const =
4495 template_param_kind(¶meter) == zerodds_ast::TemplateParamKind::Const;
4496 Some((name, is_const))
4497 })
4498 .collect()
4499 })
4500}
4501
4502fn template_param_kind(node: &Arc<SyntaxNode>) -> zerodds_ast::TemplateParamKind {
4503 use zerodds_ast::TemplateParamKind as K;
4504 // (188)'s `<sequence_type>` alternative writes the keyword inside a
4505 // type specification of its own, so it is not one of this node's own
4506 // tokens the way the ten classifiers are.
4507 if node
4508 .children()
4509 .iter()
4510 .any(|child| child.kind() == CstKind::SequenceType)
4511 {
4512 return K::Sequence;
4513 }
4514 for (_, token) in node.syntax_tokens() {
4515 match token.kind() {
4516 TokenKind::Typename => return K::TypeName,
4517 TokenKind::Const => return K::Const,
4518 TokenKind::Sequence => return K::Sequence,
4519 TokenKind::Interface => return K::Interface,
4520 TokenKind::Valuetype => return K::ValueType,
4521 TokenKind::Eventtype => return K::EventType,
4522 TokenKind::Struct => return K::Struct,
4523 TokenKind::Union => return K::Union,
4524 TokenKind::Exception => return K::Exception,
4525 TokenKind::Enum => return K::Enum,
4526 _ => {}
4527 }
4528 }
4529 K::TypeName
4530}
4531
4532/// The type an event port carries, as a reference.
4533fn type_ref_of_port(ast: &Ast, attribute: Id<zerodds_ast::AttributeNode>) -> Ref<TypeNode> {
4534 ast.attribute(attribute)
4535 .map_or_else(|_| Ref::Broken(broken_cause()), |node| node.type_.clone())
4536}
4537
4538/// The content of a quoted pragma argument, or the word itself.
4539fn unquote(text: &str) -> Option<String> {
4540 let trimmed = text.trim();
4541 if trimmed.is_empty() {
4542 return None;
4543 }
4544 Some(
4545 trimmed
4546 .strip_prefix('"')
4547 .and_then(|rest| rest.strip_suffix('"'))
4548 .unwrap_or(trimmed)
4549 .to_string(),
4550 )
4551}
4552
4553/// The version number a `#pragma dds_xtopics` declaration named, in the
4554/// one spelling DDS-XTypes 1.3 §7.3.1.1.2 compares.
4555///
4556/// "It shall be interpreted without respect to case, and any spaces (for
4557/// example, in `1.0 Beta 1`) shall be replaced with underscores." Both
4558/// halves are needed to compare a pair: the words arrive here already
4559/// split on the spaces the clause replaces, so joining them with
4560/// underscores is that replacement, and the case is folded so that
4561/// `1.0_beta_1` and `1.0_Beta_1` are the one version they name.
4562///
4563/// An absent version number is the empty string, which the clause makes
4564/// a legal declaration — the number is optional in both forms — and
4565/// which therefore compares against nothing.
4566fn xtopics_version(words: &[&str]) -> String {
4567 words.join("_").to_ascii_lowercase()
4568}
4569
4570/// The scoped name a pragma argument names.
4571fn pragma_name(written: &str) -> ScopedName {
4572 ScopedName::absolute(
4573 written
4574 .split("::")
4575 .filter(|segment| !segment.is_empty())
4576 .map(ToString::to_string)
4577 .collect(),
4578 )
4579}