Skip to main content

quantrs2_circuit/qasm/
validator.rs

1//! Validator for `OpenQASM` 3.0 programs
2
3use super::ast::{
4    BinaryOp, ClassicalRef, Condition, Declaration, Expression, Literal, Measurement, QasmGate,
5    QasmProgram, QasmStatement, QubitRef, UnaryOp,
6};
7use std::collections::{HashMap, HashSet};
8use thiserror::Error;
9
10/// Validation error types
11#[derive(Debug, Error)]
12pub enum ValidationError {
13    #[error("Undefined register: {0}")]
14    UndefinedRegister(String),
15
16    #[error("Undefined gate: {0}")]
17    UndefinedGate(String),
18
19    #[error("Undefined variable: {0}")]
20    UndefinedVariable(String),
21
22    #[error("Undefined function: {0}")]
23    UndefinedFunction(String),
24
25    #[error("Cannot index non-indexable value: {0} is a scalar")]
26    NotIndexable(String),
27
28    #[error("Type mismatch: expected {expected}, found {found}")]
29    TypeMismatch { expected: String, found: String },
30
31    #[error(
32        "Index out of bounds: register {register} has size {size}, but index {index} was used"
33    )]
34    IndexOutOfBounds {
35        register: String,
36        size: usize,
37        index: usize,
38    },
39
40    #[error("Parameter count mismatch: gate {gate} expects {expected} parameters, but {found} were provided")]
41    ParameterCountMismatch {
42        gate: String,
43        expected: usize,
44        found: usize,
45    },
46
47    #[error(
48        "Qubit count mismatch: gate {gate} expects {expected} qubits, but {found} were provided"
49    )]
50    QubitCountMismatch {
51        gate: String,
52        expected: usize,
53        found: usize,
54    },
55
56    #[error("Invalid slice: start index {start} is greater than end index {end}")]
57    InvalidSlice { start: usize, end: usize },
58
59    #[error("Duplicate declaration: {0}")]
60    DuplicateDeclaration(String),
61
62    #[error("Invalid control: {0}")]
63    InvalidControl(String),
64
65    #[error("Semantic error: {0}")]
66    SemanticError(String),
67}
68
69/// Symbol information in the validator
70#[derive(Debug, Clone)]
71enum Symbol {
72    QuantumRegister {
73        size: usize,
74    },
75    ClassicalRegister {
76        size: usize,
77    },
78    Gate {
79        params: Vec<String>,
80        qubits: Vec<String>,
81    },
82    Variable {
83        typ: ValueType,
84    },
85    Constant {
86        typ: ValueType,
87    },
88}
89
90/// Value types in QASM
91#[derive(Debug, Clone, PartialEq)]
92enum ValueType {
93    Bool,
94    Int,
95    Float,
96    Angle,
97    Duration,
98    Qubit,
99    Bit,
100    String,
101}
102
103/// QASM validator
104pub struct QasmValidator {
105    /// Symbol table
106    symbols: HashMap<String, Symbol>,
107    /// Standard gates (name -> (`param_count`, `qubit_count`))
108    standard_gates: HashMap<String, (usize, usize)>,
109    /// Current scope for nested blocks
110    scope_stack: Vec<HashMap<String, Symbol>>,
111}
112
113impl Default for QasmValidator {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl QasmValidator {
120    /// Create a new validator
121    #[must_use]
122    pub fn new() -> Self {
123        let mut standard_gates = HashMap::new();
124
125        // Single-qubit gates
126        standard_gates.insert("id".to_string(), (0, 1));
127        standard_gates.insert("x".to_string(), (0, 1));
128        standard_gates.insert("y".to_string(), (0, 1));
129        standard_gates.insert("z".to_string(), (0, 1));
130        standard_gates.insert("h".to_string(), (0, 1));
131        standard_gates.insert("s".to_string(), (0, 1));
132        standard_gates.insert("sdg".to_string(), (0, 1));
133        standard_gates.insert("t".to_string(), (0, 1));
134        standard_gates.insert("tdg".to_string(), (0, 1));
135        standard_gates.insert("sx".to_string(), (0, 1));
136        standard_gates.insert("sxdg".to_string(), (0, 1));
137
138        // Parametric single-qubit gates
139        standard_gates.insert("rx".to_string(), (1, 1));
140        standard_gates.insert("ry".to_string(), (1, 1));
141        standard_gates.insert("rz".to_string(), (1, 1));
142        standard_gates.insert("p".to_string(), (1, 1));
143        standard_gates.insert("u1".to_string(), (1, 1));
144        standard_gates.insert("u2".to_string(), (2, 1));
145        standard_gates.insert("u3".to_string(), (3, 1));
146        standard_gates.insert("u".to_string(), (3, 1));
147
148        // Two-qubit gates
149        standard_gates.insert("cx".to_string(), (0, 2));
150        standard_gates.insert("cy".to_string(), (0, 2));
151        standard_gates.insert("cz".to_string(), (0, 2));
152        standard_gates.insert("ch".to_string(), (0, 2));
153        standard_gates.insert("swap".to_string(), (0, 2));
154        standard_gates.insert("iswap".to_string(), (0, 2));
155        standard_gates.insert("ecr".to_string(), (0, 2));
156        standard_gates.insert("dcx".to_string(), (0, 2));
157
158        // Parametric two-qubit gates
159        standard_gates.insert("crx".to_string(), (1, 2));
160        standard_gates.insert("cry".to_string(), (1, 2));
161        standard_gates.insert("crz".to_string(), (1, 2));
162        standard_gates.insert("cp".to_string(), (1, 2));
163        standard_gates.insert("cu1".to_string(), (1, 2));
164        standard_gates.insert("rxx".to_string(), (1, 2));
165        standard_gates.insert("ryy".to_string(), (1, 2));
166        standard_gates.insert("rzz".to_string(), (1, 2));
167        standard_gates.insert("rzx".to_string(), (1, 2));
168
169        // Three-qubit gates
170        standard_gates.insert("ccx".to_string(), (0, 3));
171        standard_gates.insert("cswap".to_string(), (0, 3));
172
173        Self {
174            symbols: HashMap::new(),
175            standard_gates,
176            scope_stack: vec![],
177        }
178    }
179
180    /// Validate a QASM program
181    pub fn validate(&mut self, program: &QasmProgram) -> Result<(), ValidationError> {
182        // Clear previous state
183        self.symbols.clear();
184        self.scope_stack.clear();
185
186        // Add built-in constants
187        self.symbols.insert(
188            "pi".to_string(),
189            Symbol::Constant {
190                typ: ValueType::Float,
191            },
192        );
193        self.symbols.insert(
194            "e".to_string(),
195            Symbol::Constant {
196                typ: ValueType::Float,
197            },
198        );
199        self.symbols.insert(
200            "tau".to_string(),
201            Symbol::Constant {
202                typ: ValueType::Float,
203            },
204        );
205
206        // Validate declarations
207        for decl in &program.declarations {
208            self.validate_declaration(decl)?;
209        }
210
211        // Validate statements
212        for stmt in &program.statements {
213            self.validate_statement(stmt)?;
214        }
215
216        Ok(())
217    }
218
219    /// Validate a declaration
220    fn validate_declaration(&mut self, decl: &Declaration) -> Result<(), ValidationError> {
221        match decl {
222            Declaration::QuantumRegister(reg) => {
223                if self.symbols.contains_key(&reg.name) {
224                    return Err(ValidationError::DuplicateDeclaration(reg.name.clone()));
225                }
226
227                if reg.size == 0 {
228                    return Err(ValidationError::SemanticError(
229                        "Register size must be greater than 0".to_string(),
230                    ));
231                }
232
233                self.symbols
234                    .insert(reg.name.clone(), Symbol::QuantumRegister { size: reg.size });
235            }
236            Declaration::ClassicalRegister(reg) => {
237                if self.symbols.contains_key(&reg.name) {
238                    return Err(ValidationError::DuplicateDeclaration(reg.name.clone()));
239                }
240
241                if reg.size == 0 {
242                    return Err(ValidationError::SemanticError(
243                        "Register size must be greater than 0".to_string(),
244                    ));
245                }
246
247                self.symbols.insert(
248                    reg.name.clone(),
249                    Symbol::ClassicalRegister { size: reg.size },
250                );
251            }
252            Declaration::GateDefinition(def) => {
253                if self.symbols.contains_key(&def.name) {
254                    return Err(ValidationError::DuplicateDeclaration(def.name.clone()));
255                }
256
257                // Create new scope for gate body
258                self.push_scope();
259
260                // Add parameters to scope
261                for param in &def.params {
262                    self.add_to_current_scope(
263                        param.clone(),
264                        Symbol::Variable {
265                            typ: ValueType::Angle,
266                        },
267                    );
268                }
269
270                // Add qubit arguments to scope
271                for qubit in &def.qubits {
272                    self.add_to_current_scope(
273                        qubit.clone(),
274                        Symbol::Variable {
275                            typ: ValueType::Qubit,
276                        },
277                    );
278                }
279
280                // Validate gate body
281                for stmt in &def.body {
282                    self.validate_statement(stmt)?;
283                }
284
285                // Pop scope
286                self.pop_scope();
287
288                // Add gate to symbols
289                self.symbols.insert(
290                    def.name.clone(),
291                    Symbol::Gate {
292                        params: def.params.clone(),
293                        qubits: def.qubits.clone(),
294                    },
295                );
296            }
297            Declaration::Constant(name, expr) => {
298                if self.symbols.contains_key(name) {
299                    return Err(ValidationError::DuplicateDeclaration(name.clone()));
300                }
301
302                let typ = self.validate_expression(expr)?;
303
304                self.symbols.insert(name.clone(), Symbol::Constant { typ });
305            }
306        }
307
308        Ok(())
309    }
310
311    /// Validate a statement
312    fn validate_statement(&mut self, stmt: &QasmStatement) -> Result<(), ValidationError> {
313        match stmt {
314            QasmStatement::Gate(gate) => self.validate_gate(gate),
315            QasmStatement::Measure(meas) => self.validate_measure(meas),
316            QasmStatement::Reset(qubits) => {
317                for qubit in qubits {
318                    self.validate_qubit_ref(qubit)?;
319                }
320                Ok(())
321            }
322            QasmStatement::Barrier(qubits) => {
323                for qubit in qubits {
324                    self.validate_qubit_ref(qubit)?;
325                }
326                Ok(())
327            }
328            QasmStatement::Assignment(var, expr) => {
329                let typ = self.validate_expression(expr)?;
330
331                // Check if variable exists
332                if let Some(symbol) = self.lookup_symbol(var) {
333                    match symbol {
334                        Symbol::Variable { typ: var_typ } => {
335                            if !self.types_compatible(var_typ, &typ) {
336                                return Err(ValidationError::TypeMismatch {
337                                    expected: format!("{var_typ:?}"),
338                                    found: format!("{typ:?}"),
339                                });
340                            }
341                        }
342                        _ => {
343                            return Err(ValidationError::SemanticError(format!(
344                                "{var} is not a variable"
345                            )))
346                        }
347                    }
348                } else {
349                    // Create new variable
350                    self.add_to_current_scope(var.clone(), Symbol::Variable { typ });
351                }
352
353                Ok(())
354            }
355            QasmStatement::If(cond, stmt) => {
356                self.validate_condition(cond)?;
357                self.validate_statement(stmt)
358            }
359            QasmStatement::For(for_loop) => {
360                self.push_scope();
361
362                // Add loop variable
363                self.add_to_current_scope(
364                    for_loop.variable.clone(),
365                    Symbol::Variable {
366                        typ: ValueType::Int,
367                    },
368                );
369
370                // Validate range
371                let start_typ = self.validate_expression(&for_loop.start)?;
372                let end_typ = self.validate_expression(&for_loop.end)?;
373
374                if start_typ != ValueType::Int || end_typ != ValueType::Int {
375                    return Err(ValidationError::TypeMismatch {
376                        expected: "int".to_string(),
377                        found: "non-int".to_string(),
378                    });
379                }
380
381                if let Some(step) = &for_loop.step {
382                    let step_typ = self.validate_expression(step)?;
383                    if step_typ != ValueType::Int {
384                        return Err(ValidationError::TypeMismatch {
385                            expected: "int".to_string(),
386                            found: format!("{step_typ:?}"),
387                        });
388                    }
389                }
390
391                // Validate body
392                for stmt in &for_loop.body {
393                    self.validate_statement(stmt)?;
394                }
395
396                self.pop_scope();
397                Ok(())
398            }
399            QasmStatement::While(cond, body) => {
400                self.validate_condition(cond)?;
401
402                self.push_scope();
403                for stmt in body {
404                    self.validate_statement(stmt)?;
405                }
406                self.pop_scope();
407
408                Ok(())
409            }
410            QasmStatement::Call(name, args) => {
411                // For now, just check that arguments are valid expressions
412                for arg in args {
413                    self.validate_expression(arg)?;
414                }
415                Ok(())
416            }
417            QasmStatement::Delay(duration, qubits) => {
418                let dur_typ = self.validate_expression(duration)?;
419                if dur_typ != ValueType::Duration && dur_typ != ValueType::Float {
420                    return Err(ValidationError::TypeMismatch {
421                        expected: "duration".to_string(),
422                        found: format!("{dur_typ:?}"),
423                    });
424                }
425
426                for qubit in qubits {
427                    self.validate_qubit_ref(qubit)?;
428                }
429
430                Ok(())
431            }
432        }
433    }
434
435    /// Validate a gate application
436    fn validate_gate(&self, gate: &QasmGate) -> Result<(), ValidationError> {
437        // Check if gate exists
438        let (expected_params, expected_qubits) =
439            if let Some(&(p, q)) = self.standard_gates.get(&gate.name) {
440                (p, q)
441            } else if let Some(symbol) = self.symbols.get(&gate.name) {
442                match symbol {
443                    Symbol::Gate { params, qubits } => (params.len(), qubits.len()),
444                    _ => return Err(ValidationError::UndefinedGate(gate.name.clone())),
445                }
446            } else {
447                return Err(ValidationError::UndefinedGate(gate.name.clone()));
448            };
449
450        // Check parameter count
451        if gate.params.len() != expected_params {
452            return Err(ValidationError::ParameterCountMismatch {
453                gate: gate.name.clone(),
454                expected: expected_params,
455                found: gate.params.len(),
456            });
457        }
458
459        // Validate parameters
460        for param in &gate.params {
461            let typ = self.validate_expression(param)?;
462            if typ != ValueType::Float && typ != ValueType::Angle && typ != ValueType::Int {
463                return Err(ValidationError::TypeMismatch {
464                    expected: "numeric".to_string(),
465                    found: format!("{typ:?}"),
466                });
467            }
468        }
469
470        // Check qubit count (accounting for control modifier)
471        let actual_qubits = gate.qubits.len();
472        let required_qubits = expected_qubits + gate.control.unwrap_or(0);
473
474        if actual_qubits != required_qubits {
475            return Err(ValidationError::QubitCountMismatch {
476                gate: gate.name.clone(),
477                expected: required_qubits,
478                found: actual_qubits,
479            });
480        }
481
482        // Validate qubits
483        for qubit in &gate.qubits {
484            self.validate_qubit_ref(qubit)?;
485        }
486
487        Ok(())
488    }
489
490    /// Validate a measurement
491    fn validate_measure(&self, meas: &Measurement) -> Result<(), ValidationError> {
492        if meas.qubits.len() != meas.targets.len() {
493            return Err(ValidationError::SemanticError(
494                "Measurement must have equal number of qubits and classical bits".to_string(),
495            ));
496        }
497
498        for qubit in &meas.qubits {
499            self.validate_qubit_ref(qubit)?;
500        }
501
502        for target in &meas.targets {
503            self.validate_classical_ref(target)?;
504        }
505
506        Ok(())
507    }
508
509    /// Validate a qubit reference
510    fn validate_qubit_ref(&self, qubit_ref: &QubitRef) -> Result<(), ValidationError> {
511        match qubit_ref {
512            QubitRef::Single { register, index } => {
513                match self.lookup_symbol(register) {
514                    Some(Symbol::QuantumRegister { size }) => {
515                        if *index >= *size {
516                            return Err(ValidationError::IndexOutOfBounds {
517                                register: register.clone(),
518                                size: *size,
519                                index: *index,
520                            });
521                        }
522                    }
523                    Some(Symbol::Variable {
524                        typ: ValueType::Qubit,
525                    }) => {
526                        // Single qubit variable
527                    }
528                    _ => return Err(ValidationError::UndefinedRegister(register.clone())),
529                }
530            }
531            QubitRef::Slice {
532                register,
533                start,
534                end,
535            } => match self.lookup_symbol(register) {
536                Some(Symbol::QuantumRegister { size }) => {
537                    if *start >= *size || *end > *size {
538                        return Err(ValidationError::IndexOutOfBounds {
539                            register: register.clone(),
540                            size: *size,
541                            index: (*start).max(*end),
542                        });
543                    }
544                    if *start >= *end {
545                        return Err(ValidationError::InvalidSlice {
546                            start: *start,
547                            end: *end,
548                        });
549                    }
550                }
551                _ => return Err(ValidationError::UndefinedRegister(register.clone())),
552            },
553            QubitRef::Register(name) => match self.lookup_symbol(name) {
554                Some(Symbol::QuantumRegister { .. }) => {}
555                Some(Symbol::Variable {
556                    typ: ValueType::Qubit,
557                }) => {}
558                _ => return Err(ValidationError::UndefinedRegister(name.clone())),
559            },
560        }
561
562        Ok(())
563    }
564
565    /// Validate a classical reference
566    fn validate_classical_ref(&self, classical_ref: &ClassicalRef) -> Result<(), ValidationError> {
567        match classical_ref {
568            ClassicalRef::Single { register, index } => {
569                match self.lookup_symbol(register) {
570                    Some(Symbol::ClassicalRegister { size }) => {
571                        if *index >= *size {
572                            return Err(ValidationError::IndexOutOfBounds {
573                                register: register.clone(),
574                                size: *size,
575                                index: *index,
576                            });
577                        }
578                    }
579                    Some(Symbol::Variable {
580                        typ: ValueType::Bit,
581                    }) => {
582                        // Single bit variable
583                    }
584                    _ => return Err(ValidationError::UndefinedRegister(register.clone())),
585                }
586            }
587            ClassicalRef::Slice {
588                register,
589                start,
590                end,
591            } => match self.lookup_symbol(register) {
592                Some(Symbol::ClassicalRegister { size }) => {
593                    if *start >= *size || *end > *size {
594                        return Err(ValidationError::IndexOutOfBounds {
595                            register: register.clone(),
596                            size: *size,
597                            index: (*start).max(*end),
598                        });
599                    }
600                    if *start >= *end {
601                        return Err(ValidationError::InvalidSlice {
602                            start: *start,
603                            end: *end,
604                        });
605                    }
606                }
607                _ => return Err(ValidationError::UndefinedRegister(register.clone())),
608            },
609            ClassicalRef::Register(name) => match self.lookup_symbol(name) {
610                Some(Symbol::ClassicalRegister { .. }) => {}
611                Some(Symbol::Variable {
612                    typ: ValueType::Bit,
613                }) => {}
614                _ => return Err(ValidationError::UndefinedRegister(name.clone())),
615            },
616        }
617
618        Ok(())
619    }
620
621    /// Validate an expression and return its type
622    fn validate_expression(&self, expr: &Expression) -> Result<ValueType, ValidationError> {
623        match expr {
624            Expression::Literal(lit) => Ok(match lit {
625                Literal::Integer(_) => ValueType::Int,
626                Literal::Float(_) | Literal::Pi | Literal::Euler | Literal::Tau => ValueType::Float,
627                Literal::Bool(_) => ValueType::Bool,
628                Literal::String(_) => ValueType::String,
629            }),
630            Expression::Variable(name) => match self.lookup_symbol(name) {
631                Some(Symbol::Variable { typ }) => Ok(typ.clone()),
632                Some(Symbol::Constant { typ }) => Ok(typ.clone()),
633                _ => Err(ValidationError::UndefinedVariable(name.clone())),
634            },
635            Expression::Binary(op, left, right) => {
636                let left_typ = self.validate_expression(left)?;
637                let right_typ = self.validate_expression(right)?;
638
639                // Type checking for binary operations
640                match op {
641                    BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div => {
642                        if (left_typ == ValueType::Int || left_typ == ValueType::Float)
643                            && (right_typ == ValueType::Int || right_typ == ValueType::Float)
644                        {
645                            Ok(ValueType::Float)
646                        } else {
647                            Err(ValidationError::TypeMismatch {
648                                expected: "numeric".to_string(),
649                                found: format!("{left_typ:?} and {right_typ:?}"),
650                            })
651                        }
652                    }
653                    BinaryOp::Mod
654                    | BinaryOp::BitAnd
655                    | BinaryOp::BitOr
656                    | BinaryOp::BitXor
657                    | BinaryOp::Shl
658                    | BinaryOp::Shr => {
659                        if left_typ == ValueType::Int && right_typ == ValueType::Int {
660                            Ok(ValueType::Int)
661                        } else {
662                            Err(ValidationError::TypeMismatch {
663                                expected: "int".to_string(),
664                                found: format!("{left_typ:?} and {right_typ:?}"),
665                            })
666                        }
667                    }
668                    BinaryOp::And | BinaryOp::Or | BinaryOp::Xor => {
669                        if left_typ == ValueType::Bool && right_typ == ValueType::Bool {
670                            Ok(ValueType::Bool)
671                        } else {
672                            Err(ValidationError::TypeMismatch {
673                                expected: "bool".to_string(),
674                                found: format!("{left_typ:?} and {right_typ:?}"),
675                            })
676                        }
677                    }
678                    BinaryOp::Eq
679                    | BinaryOp::Ne
680                    | BinaryOp::Lt
681                    | BinaryOp::Le
682                    | BinaryOp::Gt
683                    | BinaryOp::Ge => Ok(ValueType::Bool),
684                    BinaryOp::Pow => {
685                        if (left_typ == ValueType::Int || left_typ == ValueType::Float)
686                            && (right_typ == ValueType::Int || right_typ == ValueType::Float)
687                        {
688                            Ok(ValueType::Float)
689                        } else {
690                            Err(ValidationError::TypeMismatch {
691                                expected: "numeric".to_string(),
692                                found: format!("{left_typ:?} and {right_typ:?}"),
693                            })
694                        }
695                    }
696                }
697            }
698            Expression::Unary(op, expr) => {
699                let typ = self.validate_expression(expr)?;
700
701                match op {
702                    UnaryOp::Neg => {
703                        if typ == ValueType::Int || typ == ValueType::Float {
704                            Ok(typ)
705                        } else {
706                            Err(ValidationError::TypeMismatch {
707                                expected: "numeric".to_string(),
708                                found: format!("{typ:?}"),
709                            })
710                        }
711                    }
712                    UnaryOp::Not => {
713                        if typ == ValueType::Bool {
714                            Ok(ValueType::Bool)
715                        } else {
716                            Err(ValidationError::TypeMismatch {
717                                expected: "bool".to_string(),
718                                found: format!("{typ:?}"),
719                            })
720                        }
721                    }
722                    UnaryOp::BitNot => {
723                        if typ == ValueType::Int {
724                            Ok(ValueType::Int)
725                        } else {
726                            Err(ValidationError::TypeMismatch {
727                                expected: "int".to_string(),
728                                found: format!("{typ:?}"),
729                            })
730                        }
731                    }
732                    UnaryOp::Sin
733                    | UnaryOp::Cos
734                    | UnaryOp::Tan
735                    | UnaryOp::Asin
736                    | UnaryOp::Acos
737                    | UnaryOp::Atan
738                    | UnaryOp::Exp
739                    | UnaryOp::Ln
740                    | UnaryOp::Sqrt => {
741                        if typ == ValueType::Int || typ == ValueType::Float {
742                            Ok(ValueType::Float)
743                        } else {
744                            Err(ValidationError::TypeMismatch {
745                                expected: "numeric".to_string(),
746                                found: format!("{typ:?}"),
747                            })
748                        }
749                    }
750                }
751            }
752            Expression::Function(name, args) => {
753                // Validate (and collect the types of) the arguments first.
754                let arg_types = args
755                    .iter()
756                    .map(|arg| self.validate_expression(arg))
757                    .collect::<Result<Vec<_>, _>>()?;
758
759                // Infer the return type from the known OpenQASM 3.0 built-in
760                // classical functions. An unknown name is a real error, not a
761                // silent assumption that it returns a float.
762                Self::builtin_function_return_type(name, &arg_types)
763            }
764            Expression::Index(name, index) => {
765                // The index expression itself must be an integer.
766                let idx_typ = self.validate_expression(index)?;
767                if idx_typ != ValueType::Int {
768                    return Err(ValidationError::TypeMismatch {
769                        expected: "int".to_string(),
770                        found: format!("{idx_typ:?}"),
771                    });
772                }
773
774                // Indexing yields the *element* type of the indexed symbol.
775                // Registers carry an element type (qubit / bit); scalar
776                // variables and constants cannot be indexed at all.
777                match self.lookup_symbol(name) {
778                    Some(Symbol::QuantumRegister { .. }) => Ok(ValueType::Qubit),
779                    Some(Symbol::ClassicalRegister { .. }) => Ok(ValueType::Bit),
780                    Some(Symbol::Variable { .. } | Symbol::Constant { .. }) => {
781                        Err(ValidationError::NotIndexable(name.clone()))
782                    }
783                    Some(Symbol::Gate { .. }) | None => {
784                        Err(ValidationError::UndefinedVariable(name.clone()))
785                    }
786                }
787            }
788        }
789    }
790
791    /// Infer the return type of a call to an OpenQASM 3.0 built-in classical
792    /// function, validating its argument count and types.
793    ///
794    /// The recognised functions are exactly those defined by the OpenQASM 3.0
795    /// specification: `arccos`, `arcsin`, `arctan`, `ceiling`, `cos`, `exp`,
796    /// `floor`, `log`, `mod`, `popcount`, `pow`, `rotl`, `rotr`, `sin`, `sqrt`
797    /// and `tan`. An unrecognised name yields [`ValidationError::UndefinedFunction`]
798    /// rather than a fabricated default type.
799    fn builtin_function_return_type(
800        name: &str,
801        arg_types: &[ValueType],
802    ) -> Result<ValueType, ValidationError> {
803        // Helper closures for argument-count / numeric checks producing honest
804        // semantic errors.
805        let expect_arity = |expected: usize| -> Result<(), ValidationError> {
806            if arg_types.len() == expected {
807                Ok(())
808            } else {
809                Err(ValidationError::SemanticError(format!(
810                    "function '{name}' expects {expected} argument(s), but {} were provided",
811                    arg_types.len()
812                )))
813            }
814        };
815        let is_numeric = |typ: &ValueType| matches!(typ, ValueType::Int | ValueType::Float);
816        let require_numeric = |typ: &ValueType| -> Result<(), ValidationError> {
817            if is_numeric(typ) {
818                Ok(())
819            } else {
820                Err(ValidationError::TypeMismatch {
821                    expected: "numeric".to_string(),
822                    found: format!("{typ:?}"),
823                })
824            }
825        };
826        let require_int = |typ: &ValueType| -> Result<(), ValidationError> {
827            if matches!(typ, ValueType::Int) {
828                Ok(())
829            } else {
830                Err(ValidationError::TypeMismatch {
831                    expected: "int".to_string(),
832                    found: format!("{typ:?}"),
833                })
834            }
835        };
836
837        match name {
838            // Real-valued transcendental / root functions: one numeric argument,
839            // float result.
840            "sin" | "cos" | "tan" | "arcsin" | "arccos" | "arctan" | "exp" | "log" | "sqrt" => {
841                expect_arity(1)?;
842                require_numeric(&arg_types[0])?;
843                Ok(ValueType::Float)
844            }
845            // Rounding functions: one numeric argument, float result.
846            "ceiling" | "floor" => {
847                expect_arity(1)?;
848                require_numeric(&arg_types[0])?;
849                Ok(ValueType::Float)
850            }
851            // mod(a, b): integer result iff both arguments are integers,
852            // otherwise float (matching the generalized real modulus).
853            "mod" => {
854                expect_arity(2)?;
855                require_numeric(&arg_types[0])?;
856                require_numeric(&arg_types[1])?;
857                if arg_types[0] == ValueType::Int && arg_types[1] == ValueType::Int {
858                    Ok(ValueType::Int)
859                } else {
860                    Ok(ValueType::Float)
861                }
862            }
863            // pow(base, exponent): float result.
864            "pow" => {
865                expect_arity(2)?;
866                require_numeric(&arg_types[0])?;
867                require_numeric(&arg_types[1])?;
868                Ok(ValueType::Float)
869            }
870            // popcount(x): counts set bits, integer result.
871            "popcount" => {
872                expect_arity(1)?;
873                require_int(&arg_types[0])?;
874                Ok(ValueType::Int)
875            }
876            // rotl(value, distance) / rotr(value, distance): bit-rotation,
877            // integer result with an integer rotation distance.
878            "rotl" | "rotr" => {
879                expect_arity(2)?;
880                require_int(&arg_types[0])?;
881                require_int(&arg_types[1])?;
882                Ok(ValueType::Int)
883            }
884            _ => Err(ValidationError::UndefinedFunction(name.to_string())),
885        }
886    }
887
888    /// Validate a condition
889    fn validate_condition(&self, cond: &Condition) -> Result<(), ValidationError> {
890        let left_typ = self.validate_expression(&cond.left)?;
891        let right_typ = self.validate_expression(&cond.right)?;
892
893        // For comparisons, types should be compatible
894        if !self.types_compatible(&left_typ, &right_typ) {
895            return Err(ValidationError::TypeMismatch {
896                expected: format!("{left_typ:?}"),
897                found: format!("{right_typ:?}"),
898            });
899        }
900
901        Ok(())
902    }
903
904    /// Check if two types are compatible
905    fn types_compatible(&self, typ1: &ValueType, typ2: &ValueType) -> bool {
906        match (typ1, typ2) {
907            (ValueType::Int, ValueType::Float) | (ValueType::Float, ValueType::Int) => true,
908            (ValueType::Angle, ValueType::Float) | (ValueType::Float, ValueType::Angle) => true,
909            (ValueType::Duration, ValueType::Float) | (ValueType::Float, ValueType::Duration) => {
910                true
911            }
912            (t1, t2) => t1 == t2,
913        }
914    }
915
916    /// Push a new scope
917    fn push_scope(&mut self) {
918        self.scope_stack.push(HashMap::new());
919    }
920
921    /// Pop the current scope
922    fn pop_scope(&mut self) {
923        self.scope_stack.pop();
924    }
925
926    /// Add symbol to current scope
927    fn add_to_current_scope(&mut self, name: String, symbol: Symbol) {
928        if let Some(scope) = self.scope_stack.last_mut() {
929            scope.insert(name, symbol);
930        } else {
931            self.symbols.insert(name, symbol);
932        }
933    }
934
935    /// Look up a symbol in all scopes
936    fn lookup_symbol(&self, name: &str) -> Option<&Symbol> {
937        // Check scopes from innermost to outermost
938        for scope in self.scope_stack.iter().rev() {
939            if let Some(symbol) = scope.get(name) {
940                return Some(symbol);
941            }
942        }
943
944        // Check global symbols
945        self.symbols.get(name)
946    }
947}
948
949/// Validate a QASM 3.0 program
950pub fn validate_qasm3(program: &QasmProgram) -> Result<(), ValidationError> {
951    let mut validator = QasmValidator::new();
952    validator.validate(program)
953}
954
955#[cfg(test)]
956mod tests {
957    use super::*;
958    use crate::qasm::parser::parse_qasm3;
959
960    #[test]
961    fn test_validate_simple_circuit() {
962        let input = r"
963OPENQASM 3.0;
964
965qubit[2] q;
966bit[2] c;
967
968h q[0];
969cx q[0], q[1];
970measure q -> c;
971";
972
973        let program = parse_qasm3(input).expect("parse_qasm3 should succeed for valid circuit");
974        let result = validate_qasm3(&program);
975        assert!(result.is_ok());
976    }
977
978    #[test]
979    fn test_validate_undefined_register() {
980        let input = r"
981OPENQASM 3.0;
982
983qubit[2] q;
984
985h q[0];
986cx q[0], r[1];  // r is undefined
987";
988
989        let program =
990            parse_qasm3(input).expect("parse_qasm3 should succeed for undefined register test");
991        let result = validate_qasm3(&program);
992        assert!(matches!(result, Err(ValidationError::UndefinedRegister(_))));
993    }
994
995    #[test]
996    fn test_validate_index_out_of_bounds() {
997        let input = r"
998OPENQASM 3.0;
999
1000qubit[2] q;
1001
1002h q[5];  // Index 5 is out of bounds
1003";
1004
1005        let program =
1006            parse_qasm3(input).expect("parse_qasm3 should succeed for out of bounds test");
1007        let result = validate_qasm3(&program);
1008        assert!(matches!(
1009            result,
1010            Err(ValidationError::IndexOutOfBounds { .. })
1011        ));
1012    }
1013
1014    #[test]
1015    fn test_validate_gate_parameters() {
1016        let input = r"
1017OPENQASM 3.0;
1018
1019qubit q;
1020
1021rx(pi/2) q;  // Correct
1022rx q;        // Missing parameter
1023";
1024
1025        let program =
1026            parse_qasm3(input).expect("parse_qasm3 should succeed for gate parameter test");
1027        let result = validate_qasm3(&program);
1028        assert!(matches!(
1029            result,
1030            Err(ValidationError::ParameterCountMismatch { .. })
1031        ));
1032    }
1033
1034    #[test]
1035    fn test_validate_known_function_returns_float() {
1036        // A recognised built-in function used in a gate angle must validate, and
1037        // its inferred float return type must satisfy the numeric requirement.
1038        let input = r"
1039OPENQASM 3.0;
1040
1041qubit q;
1042
1043rx(sin(0.5)) q;
1044";
1045        let program =
1046            parse_qasm3(input).expect("parse_qasm3 should succeed for known function test");
1047        let result = validate_qasm3(&program);
1048        assert!(result.is_ok(), "known function should validate: {result:?}");
1049    }
1050
1051    #[test]
1052    fn test_validate_unknown_function_is_rejected() {
1053        // An unknown function name must be an honest error, not a silent Float.
1054        let input = r"
1055OPENQASM 3.0;
1056
1057const x = wibble(1.0);
1058";
1059        let program =
1060            parse_qasm3(input).expect("parse_qasm3 should succeed for unknown function test");
1061        let result = validate_qasm3(&program);
1062        assert!(
1063            matches!(result, Err(ValidationError::UndefinedFunction(ref n)) if n == "wibble"),
1064            "expected UndefinedFunction error, got: {result:?}"
1065        );
1066    }
1067
1068    #[test]
1069    fn test_validate_function_arity_checked() {
1070        // sin takes exactly one argument; two must be rejected.
1071        let input = r"
1072OPENQASM 3.0;
1073
1074const x = sin(1.0, 2.0);
1075";
1076        let program =
1077            parse_qasm3(input).expect("parse_qasm3 should succeed for function arity test");
1078        let result = validate_qasm3(&program);
1079        assert!(
1080            matches!(result, Err(ValidationError::SemanticError(_))),
1081            "expected arity SemanticError, got: {result:?}"
1082        );
1083    }
1084
1085    #[test]
1086    fn test_validate_index_into_classical_register_is_bit() {
1087        // Indexing a bit register yields a Bit element; using it where a Bit is
1088        // valid (another classical register element) must type-check.
1089        let input = r"
1090OPENQASM 3.0;
1091
1092bit[4] c;
1093const x = c[2];
1094";
1095        let program =
1096            parse_qasm3(input).expect("parse_qasm3 should succeed for register index test");
1097        let result = validate_qasm3(&program);
1098        assert!(
1099            result.is_ok(),
1100            "indexing a classical register should validate: {result:?}"
1101        );
1102    }
1103
1104    #[test]
1105    fn test_validate_index_into_scalar_is_rejected() {
1106        // A scalar constant cannot be indexed; this must be an honest error.
1107        let input = r"
1108OPENQASM 3.0;
1109
1110const a = 3;
1111const b = a[0];
1112";
1113        let program = parse_qasm3(input).expect("parse_qasm3 should succeed for scalar index test");
1114        let result = validate_qasm3(&program);
1115        assert!(
1116            matches!(result, Err(ValidationError::NotIndexable(ref n)) if n == "a"),
1117            "expected NotIndexable error, got: {result:?}"
1118        );
1119    }
1120}