1use 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#[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#[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#[derive(Debug, Clone, PartialEq)]
92enum ValueType {
93 Bool,
94 Int,
95 Float,
96 Angle,
97 Duration,
98 Qubit,
99 Bit,
100 String,
101}
102
103pub struct QasmValidator {
105 symbols: HashMap<String, Symbol>,
107 standard_gates: HashMap<String, (usize, usize)>,
109 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 #[must_use]
122 pub fn new() -> Self {
123 let mut standard_gates = HashMap::new();
124
125 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 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 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 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 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 pub fn validate(&mut self, program: &QasmProgram) -> Result<(), ValidationError> {
182 self.symbols.clear();
184 self.scope_stack.clear();
185
186 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 for decl in &program.declarations {
208 self.validate_declaration(decl)?;
209 }
210
211 for stmt in &program.statements {
213 self.validate_statement(stmt)?;
214 }
215
216 Ok(())
217 }
218
219 fn validate_declaration(&mut self, decl: &Declaration) -> Result<(), ValidationError> {
221 match decl {
222 Declaration::QuantumRegister(reg) => {
223 if self.symbols.contains_key(®.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(®.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 self.push_scope();
259
260 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 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 for stmt in &def.body {
282 self.validate_statement(stmt)?;
283 }
284
285 self.pop_scope();
287
288 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 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 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 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 self.add_to_current_scope(
364 for_loop.variable.clone(),
365 Symbol::Variable {
366 typ: ValueType::Int,
367 },
368 );
369
370 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 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 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 fn validate_gate(&self, gate: &QasmGate) -> Result<(), ValidationError> {
437 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 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 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 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 for qubit in &gate.qubits {
484 self.validate_qubit_ref(qubit)?;
485 }
486
487 Ok(())
488 }
489
490 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 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 }
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 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 }
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 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 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 let arg_types = args
755 .iter()
756 .map(|arg| self.validate_expression(arg))
757 .collect::<Result<Vec<_>, _>>()?;
758
759 Self::builtin_function_return_type(name, &arg_types)
763 }
764 Expression::Index(name, index) => {
765 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 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 fn builtin_function_return_type(
800 name: &str,
801 arg_types: &[ValueType],
802 ) -> Result<ValueType, ValidationError> {
803 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 "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 "ceiling" | "floor" => {
847 expect_arity(1)?;
848 require_numeric(&arg_types[0])?;
849 Ok(ValueType::Float)
850 }
851 "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" => {
865 expect_arity(2)?;
866 require_numeric(&arg_types[0])?;
867 require_numeric(&arg_types[1])?;
868 Ok(ValueType::Float)
869 }
870 "popcount" => {
872 expect_arity(1)?;
873 require_int(&arg_types[0])?;
874 Ok(ValueType::Int)
875 }
876 "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 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 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 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 fn push_scope(&mut self) {
918 self.scope_stack.push(HashMap::new());
919 }
920
921 fn pop_scope(&mut self) {
923 self.scope_stack.pop();
924 }
925
926 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 fn lookup_symbol(&self, name: &str) -> Option<&Symbol> {
937 for scope in self.scope_stack.iter().rev() {
939 if let Some(symbol) = scope.get(name) {
940 return Some(symbol);
941 }
942 }
943
944 self.symbols.get(name)
946 }
947}
948
949pub 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 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 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 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 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 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}