diff --git a/compiler/qsc_project/src/openqasm.rs b/compiler/qsc_project/src/openqasm.rs index f340d02264..9006932b20 100644 --- a/compiler/qsc_project/src/openqasm.rs +++ b/compiler/qsc_project/src/openqasm.rs @@ -13,7 +13,6 @@ where { let mut loaded_files = FxHashMap::default(); let mut pending_includes = vec![]; - let mut errors = vec![]; // this is the root of the project // it is the only file that has a full path. @@ -61,11 +60,10 @@ where pending_includes.push(include); } } - Err(e) => { - errors.push(super::project::Error::FileSystem { - about_path: doc_uri.to_string(), - error: e.to_string(), - }); + Err(_e) => { + // If the source failed to resolve we don't push an error here. + // We will push an error later during lowering, so that we can + // construct the error with the right span. } } } @@ -76,7 +74,7 @@ where path: doc_uri.clone(), name: get_file_name_from_uri(doc_uri), lints: Vec::default(), - errors, + errors: vec![], project_type: super::ProjectType::OpenQASM(sources), } } diff --git a/compiler/qsc_qasm/src/parser.rs b/compiler/qsc_qasm/src/parser.rs index fa51f01c76..eb381f4fa3 100644 --- a/compiler/qsc_qasm/src/parser.rs +++ b/compiler/qsc_qasm/src/parser.rs @@ -24,6 +24,9 @@ mod prim; mod scan; mod stmt; +type QasmSourceResult = std::result::Result; +type QasmSourceResultVec = Vec>; + struct Offsetter(pub(super) u32); impl MutVisitor for Offsetter { @@ -52,13 +55,20 @@ impl QasmParseResult { self.source.has_errors() } + #[must_use] pub fn all_errors(&self) -> Vec> { let mut self_errors = self.errors(); let include_errors = self .source .includes() .iter() - .flat_map(QasmSource::all_errors) + .flat_map(|res| match res { + Ok(qasm_source) => qasm_source.all_errors(), + // If the source failed to resolve we don't push an error here. + // We will push an error later during lowering, so that we can + // construct the error with the right span. + Err(_) => vec![], + }) .map(|e| self.map_error(e)) .collect::>(); @@ -100,7 +110,7 @@ fn update_offsets(source_map: &SourceMap, source: &mut QasmSource) { offsetter.visit_program(&mut source.program); // Recursively update the includes, their programs, and errors - for include in source.includes_mut() { + for include in source.includes_mut().iter_mut().flatten() { update_offsets(source_map, include); } } @@ -132,7 +142,7 @@ fn collect_source_files(source: &QasmSource, files: &mut Vec<(Arc, Arc files.push((source.path(), source.source())); // Collect all source files from the includes, this // begins the recursive process of collecting all source files. - for include in source.includes() { + for include in source.includes().iter().flatten() { collect_source_files(include, files); } } @@ -151,7 +161,7 @@ pub struct QasmSource { errors: Vec, /// Any included files that were resolved. /// Note that this is a recursive structure. - included: Vec, + included: QasmSourceResultVec, } impl QasmSource { @@ -161,7 +171,7 @@ impl QasmSource { path: Arc, program: Program, errors: Vec, - included: Vec, + included: QasmSourceResultVec, ) -> QasmSource { QasmSource { path, @@ -177,24 +187,33 @@ impl QasmSource { if !self.errors().is_empty() { return true; } - self.includes().iter().any(QasmSource::has_errors) + self.includes().iter().any(|res| match res { + Ok(qasm_source) => qasm_source.has_errors(), + Err(_) => true, + }) } #[must_use] pub fn all_errors(&self) -> Vec { let mut self_errors = self.errors(); - let include_errors = self.includes().iter().flat_map(QasmSource::all_errors); + let include_errors = self.includes().iter().flat_map(|res| match res { + Ok(qasm_source) => qasm_source.all_errors(), + // If the source failed to resolve we don't push an error here. + // We will push an error later during lowering, so that we can + // construct the error with the right span. + Err(_) => vec![], + }); self_errors.extend(include_errors); self_errors } #[must_use] - pub fn includes(&self) -> &Vec { + pub fn includes(&self) -> &QasmSourceResultVec { self.included.as_ref() } #[must_use] - pub fn includes_mut(&mut self) -> &mut Vec { + pub fn includes_mut(&mut self) -> &mut QasmSourceResultVec { self.included.as_mut() } @@ -226,7 +245,7 @@ impl QasmSource { /// This function is the start of a recursive process that will resolve all /// includes in the QASM file. Any includes are parsed as if their contents /// were defined where the include statement is. -fn parse_qasm_file(path: &Arc, resolver: &mut R) -> QasmSource +fn parse_qasm_file(path: &Arc, resolver: &mut R) -> QasmSourceResult where R: SourceResolver, { @@ -239,22 +258,11 @@ where // and cyclic includes. resolver.ctx().pop_current_file(); - parse_result + Ok(parse_result) } Err(e) => { let error = crate::parser::error::ErrorKind::IO(e); - let error = crate::parser::Error(error, None); - QasmSource { - path: path.clone(), - source: Default::default(), - program: Program { - span: Span::default(), - statements: vec![].into_boxed_slice(), - version: None, - }, - errors: vec![error], - included: vec![], - } + Err(crate::parser::Error(error, None)) } } } @@ -270,7 +278,7 @@ where fn parse_source_and_includes, R>( source: P, resolver: &mut R, -) -> (Program, Vec, Vec) +) -> (Program, Vec, QasmSourceResultVec) where R: SourceResolver, { @@ -279,7 +287,7 @@ where (program, errors, included) } -fn parse_includes(program: &Program, resolver: &mut R) -> Vec +fn parse_includes(program: &Program, resolver: &mut R) -> QasmSourceResultVec where R: SourceResolver, { diff --git a/compiler/qsc_qasm/src/parser/tests.rs b/compiler/qsc_qasm/src/parser/tests.rs index a0e31b7cc9..e6be228192 100644 --- a/compiler/qsc_qasm/src/parser/tests.rs +++ b/compiler/qsc_qasm/src/parser/tests.rs @@ -150,6 +150,13 @@ fn programs_with_includes_with_includes_can_be_parsed() -> miette::Result<(), Ve let res = parse_all("source0.qasm", all_sources)?; assert!(res.source.includes().len() == 1); - assert!(res.source.includes()[0].includes().len() == 1); + assert!( + res.source.includes()[0] + .as_ref() + .expect("file should exists") + .includes() + .len() + == 1 + ); Ok(()) } diff --git a/compiler/qsc_qasm/src/semantic.rs b/compiler/qsc_qasm/src/semantic.rs index d92e3dda47..3f6c24f164 100644 --- a/compiler/qsc_qasm/src/semantic.rs +++ b/compiler/qsc_qasm/src/semantic.rs @@ -47,6 +47,7 @@ impl QasmSemanticParseResult { !self.errors.is_empty() } + #[must_use] pub fn sytax_errors(&self) -> Vec> { let mut self_errors = self .source @@ -58,7 +59,13 @@ impl QasmSemanticParseResult { .source .includes() .iter() - .flat_map(QasmSource::all_errors) + .flat_map(|res| match res { + Ok(qasm_source) => qasm_source.all_errors(), + // If the source failed to resolve we don't push an error here. + // We will push an error later during lowering, so that we can + // construct the error with the right span. + Err(_) => vec![], + }) .map(|e| self.map_parse_error(e)) .collect::>(); diff --git a/compiler/qsc_qasm/src/semantic/error.rs b/compiler/qsc_qasm/src/semantic/error.rs index 721128c22f..5adc52fa71 100644 --- a/compiler/qsc_qasm/src/semantic/error.rs +++ b/compiler/qsc_qasm/src/semantic/error.rs @@ -148,6 +148,9 @@ pub enum SemanticErrorKind { #[error("{0} can only appear in {1} scopes")] #[diagnostic(code("Qasm.Lowerer.InvalidScope"))] InvalidScope(String, String, #[label] Span), + #[error("{0}")] + #[diagnostic(code("Qasm.Lowerer.IO"))] + IO(String, #[label] Span), #[error("measure statements must have a name")] #[diagnostic(code("Qasm.Lowerer.MeasureExpressionsMustHaveName"))] MeasureExpressionsMustHaveName(#[label] Span), diff --git a/compiler/qsc_qasm/src/semantic/lowerer.rs b/compiler/qsc_qasm/src/semantic/lowerer.rs index 323390fb1a..fc376d8f55 100644 --- a/compiler/qsc_qasm/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm/src/semantic/lowerer.rs @@ -173,8 +173,15 @@ impl Lowerer { continue; } - let include = includes.next().expect("missing include"); - self.lower_source(include); + match includes.next().expect("missing include") { + Ok(include) => self.lower_source(include), + Err(e) => { + self.push_semantic_error(SemanticErrorKind::IO( + e.to_string(), + include.span, + )); + } + } } else { let mut stmts = self.lower_stmt(stmt); self.stmts.append(&mut stmts); diff --git a/compiler/qsc_qasm/src/tests/statement/include.rs b/compiler/qsc_qasm/src/tests/statement/include.rs index 00e163425b..a091e61b32 100644 --- a/compiler/qsc_qasm/src/tests/statement/include.rs +++ b/compiler/qsc_qasm/src/tests/statement/include.rs @@ -304,3 +304,26 @@ fn cyclic_include_errors() { source3.inc includes source1.inc"#]] .assert_eq(&errors_string); } + +#[test] +fn missing_include_error() { + let main = r#" + include "source1.inc"; + "#; + let all_sources = [("main.qasm".into(), main.into())]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + expect!["Not Found Could not resolve include file: source1.inc"].assert_eq(&errors_string); +}