Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion source/compiler/qsc/benches/rca.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ fn lower_hir_package_store(hir_package_store: &HirPackageStore) -> PackageStore
for (id, unit) in hir_package_store {
fir_store.insert(
map_hir_package_to_fir(id),
Lowerer::new().lower_package(&unit.package, &fir_store),
Lowerer::new().lower_package(&unit.package, &fir_store, map_hir_package_to_fir(id)),
);
}
fir_store
Expand Down
56 changes: 35 additions & 21 deletions source/compiler/qsc/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,23 @@ pub mod qir {
&unit.package
};

let fir_id = qsc_lowerer::map_hir_package_to_fir(id);
let mut lowerer = qsc_lowerer::Lowerer::new();
let fir_package = if id == package_id {
let mut fir_package = Package::default();
let mut fir_package = Package {
id: fir_id,
..Package::default()
};
lowerer.lower_and_update_package(&mut fir_package, hir_package);
fir_package.entry_exec_graph = lowerer.take_exec_graph();
fir_package
} else {
lowerer.lower_package(hir_package, &fir_store)
lowerer.lower_package(hir_package, &fir_store, fir_id)
};
if id == package_id {
fir_assigner = lowerer.into_assigner();
}
fir_store.insert(qsc_lowerer::map_hir_package_to_fir(id), fir_package);
fir_store.insert(fir_id, fir_package);
}

(
Expand Down Expand Up @@ -263,13 +267,22 @@ pub mod qir {
package_id: qsc_hir::hir::PackageId,
errors: Vec<qsc_passes::Error>,
) -> Vec<Error> {
let source_package = package_store
.get(package_id)
.expect("package should be in store");

errors
.into_iter()
.map(|e| Error::Pass(WithSource::from_map(&source_package.sources, e)))
.map(|e| {
// A capability error can point into a dependency, so resolve it
// against the package that actually owns its span.
let owner = match &e {
qsc_passes::Error::CapabilitiesCk(inner) => {
qsc_lowerer::map_fir_package_to_hir(inner.span().package)
}
_ => package_id,
};
let source_package = package_store
.get(owner)
.expect("package should be in store");
Error::Pass(WithSource::from_map(&source_package.sources, e))
})
.collect()
}

Expand Down Expand Up @@ -401,7 +414,7 @@ pub mod qir {
fn callable_expr_span_and_ty(
fir_store: &qsc_fir::fir::PackageStore,
callable_store_id: qsc_fir::fir::StoreItemId,
) -> (qsc_data_structures::span::Span, qsc_fir::ty::Ty) {
) -> (qsc_fir::fir::PackageSpan, qsc_fir::ty::Ty) {
use qsc_fir::fir::{Global, PackageLookup};

let package = fir_store.get(callable_store_id.package);
Expand All @@ -421,7 +434,7 @@ pub mod qir {
(callable_decl.span, ty)
}

fn seed_entry_with_callables(
pub(super) fn seed_entry_with_callables(
fir_store: &mut qsc_fir::fir::PackageStore,
fir_package_id: qsc_fir::fir::PackageId,
callables: &FxHashSet<qsc_fir::fir::StoreItemId>,
Expand Down Expand Up @@ -938,7 +951,7 @@ pub mod qir {
expr_id,
qsc_fir::fir::Expr {
id: expr_id,
span: qsc_data_structures::span::Span::default(),
span: package.synthetic_span(),
ty: qsc_fir::ty::Ty::Tuple(Vec::new()),
kind: qsc_fir::fir::ExprKind::Tuple(Vec::new()),
exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
Expand Down Expand Up @@ -978,7 +991,7 @@ pub mod qir {
expr_id,
qsc_fir::fir::Expr {
id: expr_id,
span: qsc_data_structures::span::Span::default(),
span: package.synthetic_span(),
ty: input_ty.clone(),
kind: qsc_fir::fir::ExprKind::Tuple(elem_ids),
exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
Expand Down Expand Up @@ -1006,7 +1019,7 @@ pub mod qir {
expr_id,
qsc_fir::fir::Expr {
id: expr_id,
span: qsc_data_structures::span::Span::default(),
span: package.synthetic_span(),
ty: input_ty.clone(),
kind: qsc_fir::fir::ExprKind::Tuple(elem_ids),
exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
Expand Down Expand Up @@ -1120,7 +1133,7 @@ pub mod qir {
expr_id,
qsc_fir::fir::Expr {
id: expr_id,
span: qsc_data_structures::span::Span::default(),
span: package.synthetic_span(),
ty: ty.clone(),
kind: qsc_fir::fir::ExprKind::Lit(qsc_fir::fir::Lit::Int(0)),
exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
Expand Down Expand Up @@ -1417,11 +1430,12 @@ pub mod qir {
-> Option<qsc_fir::fir::ExprId> {
opt.map(|n| {
let id = a.next_expr();
let span = pkg.synthetic_span();
pkg.exprs.insert(
id,
qsc_fir::fir::Expr {
id,
span: qsc_data_structures::span::Span::default(),
span,
ty: qsc_fir::ty::Ty::Prim(qsc_fir::ty::Prim::Int),
kind: qsc_fir::fir::ExprKind::Lit(qsc_fir::fir::Lit::Int(n)),
exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
Expand Down Expand Up @@ -1460,7 +1474,7 @@ pub mod qir {
expr_id,
qsc_fir::fir::Expr {
id: expr_id,
span: qsc_data_structures::span::Span::default(),
span: package.synthetic_span(),
ty,
kind,
exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
Expand Down Expand Up @@ -1497,7 +1511,7 @@ pub mod qir {
expr_id,
qsc_fir::fir::Expr {
id: expr_id,
span: qsc_data_structures::span::Span::default(),
span: package.synthetic_span(),
ty: ty.clone(),
kind: qsc_fir::fir::ExprKind::Var(
qsc_fir::fir::Res::Item(qsc_fir::fir::ItemId {
Expand Down Expand Up @@ -1668,7 +1682,7 @@ pub mod qir {
expr_id,
qsc_fir::fir::Expr {
id: expr_id,
span: qsc_data_structures::span::Span::default(),
span: package.synthetic_span(),
ty: ty.clone(),
kind: qsc_fir::fir::ExprKind::UnOp(qsc_fir::fir::UnOp::Functor(functor), inner_id),
exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
Expand Down Expand Up @@ -1716,7 +1730,7 @@ pub mod qir {
expr_id,
qsc_fir::fir::Expr {
id: expr_id,
span: qsc_data_structures::span::Span::default(),
span: package.synthetic_span(),
ty: full_ty.clone(),
kind,
exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
Expand Down Expand Up @@ -1769,7 +1783,7 @@ pub mod qir {
expr_id,
qsc_fir::fir::Expr {
id: expr_id,
span: qsc_data_structures::span::Span::default(),
span: package.synthetic_span(),
ty: closure_ty.clone(),
kind: qsc_fir::fir::ExprKind::Closure(capture_locals, closure.id.item),
exec_graph_range: qsc_fir::fir::ExecGraphIdx::ZERO
Expand Down Expand Up @@ -1809,7 +1823,7 @@ pub mod qir {
value_expr_id: qsc_fir::fir::ExprId,
value_ty: &qsc_fir::ty::Ty,
) -> (qsc_fir::fir::StmtId, qsc_fir::fir::LocalVarId) {
let span = qsc_data_structures::span::Span::default();
let span = package.synthetic_span();
let local_var_id = assigner.next_local();

let pat_id = assigner.next_pat();
Expand Down
116 changes: 114 additions & 2 deletions source/compiler/qsc/src/codegen/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,20 @@ use qsc_data_structures::{
};
use qsc_eval::output::CursorReceiver;
use qsc_eval::val::Value;
use qsc_fir::fir::{
ExprKind as FirExprKind, ItemKind as FirItemKind, PackageLookup, Res as FirRes, StoreItemId,
};
use qsc_frontend::compile::parse_all;
use qsc_hir::hir::{ItemKind, PackageId};
use qsc_passes::PackageType;
use rustc_hash::FxHashMap;
use qsc_passes::{PackageType, lower_hir_to_fir};
use rustc_hash::{FxHashMap, FxHashSet};

use crate::codegen::qir::{
CallableArgsBackend, get_qir, get_qir_from_ast, get_rir, prepare_codegen_fir_from_callable_args,
};

use super::qir::seed_entry_with_callables;

fn format_interpret_errors(errors: Vec<crate::interpret::Error>) -> String {
errors
.into_iter()
Expand Down Expand Up @@ -1717,6 +1722,67 @@ fn two_callable_hof_closure_preserves_array_arg_threading() {
.assert_eq(&qir);
}

#[test]
fn seed_entry_with_callables_preserves_callable_source_packages() {
let capabilities = TargetCapabilityFlags::all();
let (std_id, mut store) = crate::compile::package_store_with_stdlib(capabilities);
let dependencies: Vec<(PackageId, Option<Arc<str>>)> = vec![(std_id, None)];
let (unit, errors) = crate::compile::compile(
&store,
&dependencies,
source_map_from_source("namespace Test { operation Destination() : Unit {} }"),
PackageType::Lib,
capabilities,
LanguageFeatures::default(),
);
assert!(errors.is_empty(), "compilation failed: {errors:?}");
let destination_hir_package = store.insert(unit);
let (mut fir_store, destination_package, _) = lower_hir_to_fir(&store, destination_hir_package);
let foreign_package = qsc_lowerer::map_hir_package_to_fir(std_id);

let expected_spans: FxHashMap<StoreItemId, qsc_fir::fir::PackageSpan> = fir_store
.get(foreign_package)
.items
.values()
.filter_map(|item| match &item.kind {
FirItemKind::Callable(decl) if decl.span.package == foreign_package => Some((
StoreItemId {
package: foreign_package,
item: item.id,
},
decl.span,
)),
FirItemKind::Callable(_) | FirItemKind::Ty(..) => None,
})
.take(2)
.collect();
assert_eq!(expected_spans.len(), 2, "expected two foreign callables");
let callables: FxHashSet<_> = expected_spans.keys().copied().collect();

seed_entry_with_callables(&mut fir_store, destination_package, &callables);

let destination = fir_store.get(destination_package);
let entry = destination
.entry
.expect("plural seed should create an entry expression");
let FirExprKind::Tuple(entry_exprs) = &destination.get_expr(entry).kind else {
panic!("two callable seeds should create a tuple entry");
};
assert_eq!(entry_exprs.len(), expected_spans.len());
for expr_id in entry_exprs {
let expr = destination.get_expr(*expr_id);
let FirExprKind::Var(FirRes::Item(item), _) = &expr.kind else {
panic!("seeded entry child should be a global callable variable");
};
let callable = StoreItemId {
package: item.package,
item: item.item,
};
assert_eq!(expr.span, expected_spans[&callable]);
assert_ne!(expr.span.package, destination_package);
}
}

#[test]
fn callable_args_with_arrow_input_survives_dce() {
let source = indoc::indoc! {r#"
Expand Down Expand Up @@ -6207,3 +6273,49 @@ fn foreign_table_lookup_callable_generates_qir() {
);
}
}

#[test]
fn foreign_hof_capability_error_resolves_against_owning_package() {
// `ForEach` lives in the std package; specializing it for this call site
// clones std nodes into the user package, so the capability diagnostic must
// still resolve against std's source map rather than the entry map.
let source = r#"
namespace Test {
import Std.Arrays.ForEach;
@EntryPoint()
operation Main() : Unit {
use q = Qubit();
ForEach(q => M(q) == One, [q]);
}
}
"#;
let errors = compile_source_to_qir_result(source, TargetCapabilityFlags::from(Profile::Base))
.expect_err("Base profile must reject a dynamic bool");

// The diagnostics must be owned by two different packages: the user's lambda
// and the std `ForEach` body specialized into the user package.
let mut packages: Vec<_> = errors
.iter()
.filter_map(|e| match e {
crate::interpret::Error::Pass(with_source) => match with_source.error() {
qsc_passes::Error::CapabilitiesCk(inner) => Some(inner.span().package),
_ => None,
},
_ => None,
})
.collect();
packages.sort_unstable();
packages.dedup();
assert!(
packages.len() >= 2,
"expected capability diagnostics from both the user and std packages, got {packages:?}"
);

// Rendering is the regression guard: resolving a std-owned span against the
// entry source map used to panic inside `WithSource::from_map`.
let rendered = format_interpret_errors(errors);
assert!(
rendered.contains("cannot use a dynamic bool"),
"expected a dynamic-bool capability diagnostic, got:\n{rendered}"
);
}
32 changes: 28 additions & 4 deletions source/compiler/qsc/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ impl Interpreter {
let mut fir_store = fir::PackageStore::new();
for (id, unit) in compiler.package_store() {
let mut lowerer = qsc_lowerer::Lowerer::new();
let pkg = lowerer.lower_package(&unit.package, &fir_store);
let pkg = lowerer.lower_package(&unit.package, &fir_store, map_hir_package_to_fir(id));
fir_store.insert(map_hir_package_to_fir(id), pkg);
}

Expand Down Expand Up @@ -420,7 +420,18 @@ impl Interpreter {
caps_errors
.into_iter()
.map(|error| {
Error::Pass(WithSource::from_map(&source_package.sources, error))
let sources = if let qsc_passes::Error::CapabilitiesCk(err) = &error
{
let err_package = err.span().package;
&compiler
.package_store()
.get((Into::<usize>::into(err_package)).into())
.expect("package for error should exist in store")
.sources
} else {
&source_package.sources
};
Error::Pass(WithSource::from_map(sources, error))
})
.collect::<Vec<_>>()
})?;
Expand Down Expand Up @@ -1738,7 +1749,20 @@ impl Interpreter {

caps_errors
.into_iter()
.map(|error| Error::Pass(WithSource::from_map(&source_package.sources, error)))
.map(|error| {
let sources = if let qsc_passes::Error::CapabilitiesCk(err) = &error {
let err_package = err.span().package;
&self
.compiler
.package_store()
.get((Into::<usize>::into(err_package)).into())
.expect("package for error should exist in store")
.sources
} else {
&source_package.sources
};
Error::Pass(WithSource::from_map(sources, error))
})
.collect::<Vec<_>>()
})?;

Expand Down Expand Up @@ -2087,7 +2111,7 @@ impl<'a> BreakpointCollector<'a> {
fn add_stmt(&mut self, stmt: &fir::Stmt) {
let source: &Source = self.get_source(stmt.span.lo);
if source.offset == self.offset {
let span = stmt.span - source.offset;
let span = stmt.span.span - source.offset;
if span != Span::default() {
let range = Range::from_span(self.position_encoding, &source.contents, &span);
let bps = BreakpointSpan {
Expand Down
2 changes: 1 addition & 1 deletion source/compiler/qsc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub use qsc_data_structures::{
span::Span, target::TargetCapabilityFlags,
};

pub use qsc_passes::{PackageType, PassContext, lower_hir_to_fir};
pub use qsc_passes::{Error as PassError, PackageType, PassContext, lower_hir_to_fir};

pub mod line_column {
pub use qsc_data_structures::line_column::{Encoding, Position, Range};
Expand Down
Loading