Skip to main content

plsql_ir/
decl.rs

1//! Declaration variants populated by AST→IR lowering.
2//!
3//! This module introduces the [`Declaration`] enum with one variant per
4//! kind of named entity the engine reasons about. Each variant carries a
5//! shared [`DeclCommon`] payload (name, span, owning schema, optional
6//! parent declaration) plus a small number of variant-specific fields.
7//! The actual lowering from parser AST to these declarations, together
8//! with the registration pass (`DeclTable` + scope chain), lives in the
9//! sibling lowering modules.
10//!
11//! Placeholder [`TypeRef`] payloads are narrowed into structured
12//! representations once type resolution cross-checks against the catalog.
13
14use plsql_core::{SchemaName, Span, SymbolId};
15use serde::{Deserialize, Serialize};
16use tracing::instrument;
17
18use crate::DeclId;
19
20/// Shared metadata carried by every declaration variant.
21#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
22pub struct DeclCommon {
23    /// Interned source name of the declared entity (case folded per
24    /// Oracle quoting rules at intern time).
25    pub name: SymbolId,
26    /// Span of the declaration site in the originating source file.
27    pub span: Span,
28    /// Owning schema. `None` for local-scope declarations (block-scoped
29    /// variables, parameters, cursors, exception handlers) — those are
30    /// resolved against the enclosing routine's scope, not a schema.
31    pub schema: Option<SchemaName>,
32    /// Enclosing declaration: package for package-member routines,
33    /// table for columns/triggers/indexes, type for type-body methods.
34    /// `None` for top-level objects.
35    pub parent: Option<DeclId>,
36}
37
38impl DeclCommon {
39    #[must_use]
40    #[instrument(level = "trace")]
41    pub fn new(name: SymbolId, span: Span) -> Self {
42        Self {
43            name,
44            span,
45            schema: None,
46            parent: None,
47        }
48    }
49
50    #[must_use]
51    #[instrument(level = "trace", skip(self))]
52    pub fn with_schema(mut self, schema: SchemaName) -> Self {
53        self.schema = Some(schema);
54        self
55    }
56
57    #[must_use]
58    #[instrument(level = "trace", skip(self))]
59    pub fn with_parent(mut self, parent: DeclId) -> Self {
60        self.parent = Some(parent);
61        self
62    }
63}
64
65/// Direction-of-flow marker on procedure/function parameters.
66#[derive(
67    Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
68)]
69pub enum ParamMode {
70    #[default]
71    In,
72    Out,
73    InOut,
74}
75
76/// Type reference attached to typed declarations.
77///
78/// Lowering produces [`TypeRef::Unresolved`] holding the raw source text;
79/// later passes resolve `%TYPE` / `%ROWTYPE` anchors against the catalog
80/// and narrow `Unresolved` into a structured representation. Keeping this
81/// an enum from day one means downstream crates do not have to be
82/// re-shaped when richer resolution lands.
83#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
84pub enum TypeRef {
85    /// Raw type expression from source, awaiting resolution.
86    Unresolved(String),
87    /// `%TYPE` or `%ROWTYPE` anchor; resolution target captured for
88    /// later cross-check against catalog metadata.
89    Anchored(AnchoredType),
90}
91
92#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
93pub struct AnchoredType {
94    pub raw: String,
95}
96
97/// Discriminator counterpart to [`Declaration`] for fast dispatch and
98/// fact tagging without pattern-matching the full enum.
99#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
100pub enum DeclKind {
101    Variable,
102    Param,
103    Cursor,
104    Procedure,
105    Function,
106    Package,
107    Type,
108    Table,
109    View,
110    Column,
111    Sequence,
112    Synonym,
113    Index,
114    Trigger,
115}
116
117#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
118pub struct VariableDecl {
119    pub common: DeclCommon,
120    pub ty: Option<TypeRef>,
121    pub default_text: Option<String>,
122    pub constant: bool,
123    pub not_null: bool,
124}
125
126#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
127pub struct ParamDecl {
128    pub common: DeclCommon,
129    pub mode: ParamMode,
130    pub ty: Option<TypeRef>,
131    pub default_text: Option<String>,
132}
133
134#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
135pub struct CursorDecl {
136    pub common: DeclCommon,
137}
138
139#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
140pub struct ProcedureDecl {
141    pub common: DeclCommon,
142    pub params: Vec<DeclId>,
143}
144
145#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
146pub struct FunctionDecl {
147    pub common: DeclCommon,
148    pub params: Vec<DeclId>,
149    pub return_type: Option<TypeRef>,
150}
151
152#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
153pub struct PackageDecl {
154    pub common: DeclCommon,
155    pub members: Vec<DeclId>,
156    pub body: Option<DeclId>,
157}
158
159#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
160pub struct TypeDecl {
161    pub common: DeclCommon,
162}
163
164#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
165pub struct TableDecl {
166    pub common: DeclCommon,
167    pub columns: Vec<DeclId>,
168}
169
170#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
171pub struct ViewDecl {
172    pub common: DeclCommon,
173    pub columns: Vec<DeclId>,
174}
175
176#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
177pub struct ColumnDecl {
178    pub common: DeclCommon,
179    pub ty: Option<TypeRef>,
180    pub not_null: bool,
181}
182
183#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
184pub struct SequenceDecl {
185    pub common: DeclCommon,
186}
187
188#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
189pub struct SynonymDecl {
190    pub common: DeclCommon,
191    /// Object the synonym resolves to once runs.
192    pub target: Option<DeclId>,
193    pub public_synonym: bool,
194}
195
196#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
197pub struct IndexDecl {
198    pub common: DeclCommon,
199}
200
201#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
202pub struct TriggerDecl {
203    pub common: DeclCommon,
204}
205
206/// Discriminated union of every kind of declaration the IR recognizes.
207///
208/// New variants are additive; reshape decisions are deferred to
209/// top-level lowering and the symbol resolution layer.
210#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
211pub enum Declaration {
212    Variable(VariableDecl),
213    Param(ParamDecl),
214    Cursor(CursorDecl),
215    Procedure(ProcedureDecl),
216    Function(FunctionDecl),
217    Package(PackageDecl),
218    Type(TypeDecl),
219    Table(TableDecl),
220    View(ViewDecl),
221    Column(ColumnDecl),
222    Sequence(SequenceDecl),
223    Synonym(SynonymDecl),
224    Index(IndexDecl),
225    Trigger(TriggerDecl),
226}
227
228impl Declaration {
229    #[must_use]
230    #[instrument(level = "trace", skip(self))]
231    pub fn common(&self) -> &DeclCommon {
232        match self {
233            Self::Variable(d) => &d.common,
234            Self::Param(d) => &d.common,
235            Self::Cursor(d) => &d.common,
236            Self::Procedure(d) => &d.common,
237            Self::Function(d) => &d.common,
238            Self::Package(d) => &d.common,
239            Self::Type(d) => &d.common,
240            Self::Table(d) => &d.common,
241            Self::View(d) => &d.common,
242            Self::Column(d) => &d.common,
243            Self::Sequence(d) => &d.common,
244            Self::Synonym(d) => &d.common,
245            Self::Index(d) => &d.common,
246            Self::Trigger(d) => &d.common,
247        }
248    }
249
250    #[must_use]
251    #[instrument(level = "trace", skip(self))]
252    pub fn kind(&self) -> DeclKind {
253        match self {
254            Self::Variable(_) => DeclKind::Variable,
255            Self::Param(_) => DeclKind::Param,
256            Self::Cursor(_) => DeclKind::Cursor,
257            Self::Procedure(_) => DeclKind::Procedure,
258            Self::Function(_) => DeclKind::Function,
259            Self::Package(_) => DeclKind::Package,
260            Self::Type(_) => DeclKind::Type,
261            Self::Table(_) => DeclKind::Table,
262            Self::View(_) => DeclKind::View,
263            Self::Column(_) => DeclKind::Column,
264            Self::Sequence(_) => DeclKind::Sequence,
265            Self::Synonym(_) => DeclKind::Synonym,
266            Self::Index(_) => DeclKind::Index,
267            Self::Trigger(_) => DeclKind::Trigger,
268        }
269    }
270
271    #[must_use]
272    #[instrument(level = "trace", skip(self))]
273    pub fn name(&self) -> SymbolId {
274        self.common().name
275    }
276
277    #[must_use]
278    #[instrument(level = "trace", skip(self))]
279    pub fn span(&self) -> Span {
280        self.common().span
281    }
282
283    #[must_use]
284    #[instrument(level = "trace", skip(self))]
285    pub fn is_callable(&self) -> bool {
286        matches!(self, Self::Procedure(_) | Self::Function(_))
287    }
288
289    #[must_use]
290    #[instrument(level = "trace", skip(self))]
291    pub fn is_schema_object(&self) -> bool {
292        matches!(
293            self,
294            Self::Package(_)
295                | Self::Type(_)
296                | Self::Table(_)
297                | Self::View(_)
298                | Self::Sequence(_)
299                | Self::Synonym(_)
300                | Self::Index(_)
301                | Self::Trigger(_)
302        )
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309    use plsql_core::{FileId, Position};
310
311    fn dummy_span() -> Span {
312        Span::new(
313            FileId::new(1),
314            Position::new(1, 1, 0),
315            Position::new(1, 5, 4),
316        )
317    }
318
319    fn common_with_name(raw: u64) -> DeclCommon {
320        DeclCommon::new(SymbolId::new(raw), dummy_span())
321    }
322
323    #[test]
324    fn decl_common_builders_set_optional_fields() {
325        let schema = SchemaName::from(SymbolId::new(10));
326        let common = common_with_name(1)
327            .with_schema(schema)
328            .with_parent(DeclId::new(99));
329        assert_eq!(common.schema, Some(schema));
330        assert_eq!(common.parent, Some(DeclId::new(99)));
331    }
332
333    #[test]
334    fn declaration_kind_matches_variant() {
335        let cases: Vec<(Declaration, DeclKind)> = vec![
336            (
337                Declaration::Variable(VariableDecl {
338                    common: common_with_name(1),
339                    ty: Some(TypeRef::Unresolved("NUMBER".into())),
340                    default_text: None,
341                    constant: false,
342                    not_null: false,
343                }),
344                DeclKind::Variable,
345            ),
346            (
347                Declaration::Param(ParamDecl {
348                    common: common_with_name(2),
349                    mode: ParamMode::Out,
350                    ty: None,
351                    default_text: None,
352                }),
353                DeclKind::Param,
354            ),
355            (
356                Declaration::Cursor(CursorDecl {
357                    common: common_with_name(3),
358                }),
359                DeclKind::Cursor,
360            ),
361            (
362                Declaration::Procedure(ProcedureDecl {
363                    common: common_with_name(4),
364                    params: vec![DeclId::new(2)],
365                }),
366                DeclKind::Procedure,
367            ),
368            (
369                Declaration::Function(FunctionDecl {
370                    common: common_with_name(5),
371                    params: vec![],
372                    return_type: Some(TypeRef::Unresolved("VARCHAR2".into())),
373                }),
374                DeclKind::Function,
375            ),
376            (
377                Declaration::Package(PackageDecl {
378                    common: common_with_name(6),
379                    members: vec![],
380                    body: None,
381                }),
382                DeclKind::Package,
383            ),
384            (
385                Declaration::Type(TypeDecl {
386                    common: common_with_name(7),
387                }),
388                DeclKind::Type,
389            ),
390            (
391                Declaration::Table(TableDecl {
392                    common: common_with_name(8),
393                    columns: vec![],
394                }),
395                DeclKind::Table,
396            ),
397            (
398                Declaration::View(ViewDecl {
399                    common: common_with_name(9),
400                    columns: vec![],
401                }),
402                DeclKind::View,
403            ),
404            (
405                Declaration::Column(ColumnDecl {
406                    common: common_with_name(10),
407                    ty: None,
408                    not_null: true,
409                }),
410                DeclKind::Column,
411            ),
412            (
413                Declaration::Sequence(SequenceDecl {
414                    common: common_with_name(11),
415                }),
416                DeclKind::Sequence,
417            ),
418            (
419                Declaration::Synonym(SynonymDecl {
420                    common: common_with_name(12),
421                    target: None,
422                    public_synonym: true,
423                }),
424                DeclKind::Synonym,
425            ),
426            (
427                Declaration::Index(IndexDecl {
428                    common: common_with_name(13),
429                }),
430                DeclKind::Index,
431            ),
432            (
433                Declaration::Trigger(TriggerDecl {
434                    common: common_with_name(14),
435                }),
436                DeclKind::Trigger,
437            ),
438        ];
439
440        for (decl, expected_kind) in cases {
441            assert_eq!(decl.kind(), expected_kind);
442            assert_eq!(decl.name(), decl.common().name);
443            assert_eq!(decl.span(), decl.common().span);
444        }
445    }
446
447    #[test]
448    fn is_callable_and_schema_object_partitions_match_intent() {
449        let proc = Declaration::Procedure(ProcedureDecl {
450            common: common_with_name(1),
451            params: vec![],
452        });
453        let func = Declaration::Function(FunctionDecl {
454            common: common_with_name(2),
455            params: vec![],
456            return_type: None,
457        });
458        let var = Declaration::Variable(VariableDecl {
459            common: common_with_name(3),
460            ty: None,
461            default_text: None,
462            constant: false,
463            not_null: false,
464        });
465        let pkg = Declaration::Package(PackageDecl {
466            common: common_with_name(4),
467            members: vec![],
468            body: None,
469        });
470
471        assert!(proc.is_callable());
472        assert!(func.is_callable());
473        assert!(!var.is_callable());
474        assert!(!pkg.is_callable());
475
476        assert!(pkg.is_schema_object());
477        assert!(!proc.is_schema_object());
478        assert!(!var.is_schema_object());
479    }
480
481    #[test]
482    fn synonym_resolution_target_is_optional() {
483        let mut syn = SynonymDecl {
484            common: common_with_name(1),
485            target: None,
486            public_synonym: false,
487        };
488        assert!(syn.target.is_none());
489        syn.target = Some(DeclId::new(42));
490        assert_eq!(syn.target, Some(DeclId::new(42)));
491    }
492}