Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose user-defined callables into Python #2054

Merged
merged 17 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
95 changes: 94 additions & 1 deletion compiler/qsc/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ mod package_tests;
#[cfg(test)]
mod tests;

use std::rc::Rc;

pub use qsc_eval::{
debug::Frame,
noise::PauliNoise,
Expand All @@ -21,6 +23,7 @@ pub use qsc_eval::{
val::Value,
StepAction, StepResult,
};
use qsc_hir::{global, ty};
use qsc_linter::{HirLint, Lint, LintKind, LintLevel};
use qsc_lowerer::{map_fir_package_to_hir, map_hir_package_to_fir};
use qsc_partial_eval::ProgramEntry;
Expand Down Expand Up @@ -315,6 +318,70 @@ impl Interpreter {
})
}

/// Given a package ID, returns all the global items in the package.
/// Note this does not currently include re-exports.
pub fn get_global_items(
swernli marked this conversation as resolved.
Show resolved Hide resolved
&self,
package_id: PackageId,
) -> Vec<(Vec<Rc<str>>, Rc<str>, fir::StoreItemId)> {
swernli marked this conversation as resolved.
Show resolved Hide resolved
let mut exported_items = Vec::new();
let package = &self
.compiler
.package_store()
.get(map_fir_package_to_hir(package_id))
.expect("package should exist in the package store")
.package;
for global in global::iter_package(Some(map_fir_package_to_hir(package_id)), package) {
if let global::Kind::Term(term) = global.kind {
let store_item_id = fir::StoreItemId {
package: package_id,
item: fir::LocalItemId::from(usize::from(term.id.item)),
};
exported_items.push((global.namespace, global.name, store_item_id));
}
}
exported_items
}

/// Get the global items defined in the user source passed into initialization of the interpreter.
pub fn get_source_package_global_items(
&self,
) -> Vec<(Vec<Rc<str>>, Rc<str>, fir::StoreItemId)> {
self.get_global_items(self.source_package)
}

/// Get the global items defined in the open package being interpreted, which will include any items
/// defined by calls to `eval_fragments` and the like.
pub fn get_open_package_global_items(&self) -> Vec<(Vec<Rc<str>>, Rc<str>, fir::StoreItemId)> {
swernli marked this conversation as resolved.
Show resolved Hide resolved
self.get_global_items(self.package)
}

/// Get the input and output types of a given callable item.
/// # Panics
/// Panics if the item is not callable or a type that can be invoked as a callable.
pub fn get_callable_tys(&self, item_id: fir::StoreItemId) -> Option<(ty::Ty, ty::Ty)> {
let package_id = map_fir_package_to_hir(item_id.package);
let unit = self
.compiler
.package_store()
.get(package_id)
.expect("package should exist in the package store");
let item = unit
.package
.items
.get(qsc_hir::hir::LocalItemId::from(usize::from(item_id.item)))?;
match &item.kind {
qsc_hir::hir::ItemKind::Callable(decl) => {
Some((decl.input.ty.clone(), decl.output.clone()))
}
qsc_hir::hir::ItemKind::Ty(_, udt) => {
// We don't handle UDTs, so we return an error type that prevents later code from processing this item.
Some((udt.get_pure_ty(), ty::Ty::Err))
}
_ => panic!("item is not callable"),
}
}

pub fn set_quantum_seed(&mut self, seed: Option<u64>) {
self.quantum_seed = seed;
self.sim.set_seed(seed);
Expand Down Expand Up @@ -482,6 +549,32 @@ impl Interpreter {
self.run_with_sim(&mut sim, receiver, expr)
}

/// Invokes the given callable with the given arguments using the current environment, simlator, and compilation.
swernli marked this conversation as resolved.
Show resolved Hide resolved
pub fn invoke(
&mut self,
receiver: &mut impl Receiver,
callable: Value,
args: Value,
) -> InterpretResult {
qsc_eval::invoke(
self.package,
self.classical_seed,
&self.fir_store,
&mut self.env,
&mut self.sim,
receiver,
callable,
args,
)
.map_err(|(error, call_stack)| {
eval_error(
self.compiler.package_store(),
&self.fir_store,
call_stack,
error,
)
})
}
/// Gets the current quantum state of the simulator.
pub fn get_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
self.sim.capture_quantum_state()
Expand Down Expand Up @@ -1033,7 +1126,7 @@ impl<'a> BreakpointCollector<'a> {
.expect("Couldn't find source file")
}

fn add_stmt(&mut self, stmt: &qsc_fir::fir::Stmt) {
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;
Expand Down
215 changes: 215 additions & 0 deletions compiler/qsc/src/interpret/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ mod given_interpreter {
(result, receiver.dump())
}

fn invoke(
interpreter: &mut Interpreter,
callable: &str,
args: Value,
) -> (InterpretResult, String) {
let mut cursor = Cursor::new(Vec::<u8>::new());
let mut receiver = CursorReceiver::new(&mut cursor);
let callable = match interpreter.eval_fragments(&mut receiver, callable) {
Ok(val) => val,
Err(e) => return (Err(e), receiver.dump()),
};
let result = interpreter.invoke(&mut receiver, callable, args);
(result, receiver.dump())
}

mod without_sources {
use expect_test::expect;
use indoc::indoc;
Expand Down Expand Up @@ -514,6 +529,166 @@ mod given_interpreter {
is_only_value(&result, &output, &Value::unit());
}

#[test]
fn interpreter_without_sources_has_no_items() {
let interpreter = get_interpreter();
let items = interpreter.get_source_package_global_items();
assert!(items.is_empty());
}

#[test]
fn fragment_without_items_has_no_items() {
let mut interpreter = get_interpreter();
let (result, output) = line(&mut interpreter, "()");
is_only_value(&result, &output, &Value::unit());
let items = interpreter.get_open_package_global_items();
assert!(items.is_empty());
}

#[test]
fn fragment_defining_items_has_items() {
let mut interpreter = get_interpreter();
let (result, output) = line(
&mut interpreter,
indoc! {r#"
function Foo() : Int { 2 }
function Bar() : Int { 3 }
"#},
);
is_only_value(&result, &output, &Value::unit());
let items = interpreter.get_open_package_global_items();
assert_eq!(items.len(), 2);
// No namespace for top-level items
assert!(items[0].0.is_empty());
expect![[r#"
"Foo"
"#]]
.assert_debug_eq(&items[0].1);
// No namespace for top-level items
assert!(items[1].0.is_empty());
expect![[r#"
"Bar"
"#]]
.assert_debug_eq(&items[1].1);
}

#[test]
fn fragment_defining_items_with_namespace_has_items() {
let mut interpreter = get_interpreter();
let (result, output) = line(
&mut interpreter,
indoc! {r#"
namespace Foo {
function Bar() : Int { 3 }
}
"#},
);
is_only_value(&result, &output, &Value::unit());
let items = interpreter.get_open_package_global_items();
assert_eq!(items.len(), 1);
expect![[r#"
[
"Foo",
]
"#]]
.assert_debug_eq(&items[0].0);
expect![[r#"
"Bar"
"#]]
.assert_debug_eq(&items[0].1);
}

#[test]
fn fragments_defining_items_add_to_existing_items() {
let mut interpreter = get_interpreter();
let (result, output) = line(
&mut interpreter,
indoc! {r#"
function Foo() : Int { 2 }
function Bar() : Int { 3 }
"#},
);
is_only_value(&result, &output, &Value::unit());
let items = interpreter.get_open_package_global_items();
assert_eq!(items.len(), 2);
let (result, output) = line(
&mut interpreter,
indoc! {r#"
function Baz() : Int { 4 }
function Qux() : Int { 5 }
"#},
);
is_only_value(&result, &output, &Value::unit());
let items = interpreter.get_open_package_global_items();
assert_eq!(items.len(), 4);
// No namespace for top-level items
assert!(items[0].0.is_empty());
expect![[r#"
"Foo"
"#]]
.assert_debug_eq(&items[0].1);
// No namespace for top-level items
assert!(items[1].0.is_empty());
expect![[r#"
"Bar"
"#]]
.assert_debug_eq(&items[1].1);
// No namespace for top-level items
assert!(items[2].0.is_empty());
expect![[r#"
"Baz"
"#]]
.assert_debug_eq(&items[2].1);
// No namespace for top-level items
assert!(items[3].0.is_empty());
expect![[r#"
"Qux"
"#]]
.assert_debug_eq(&items[3].1);
}

#[test]
fn invoke_callable_without_args_succeeds() {
let mut interpreter = get_interpreter();
let (result, output) = invoke(
&mut interpreter,
"Std.Diagnostics.DumpMachine",
Value::unit(),
);
is_unit_with_output(&result, &output, "STATE:\nNo qubits allocated");
}

#[test]
fn invoke_callable_with_args_succeeds() {
let mut interpreter = get_interpreter();
let (result, output) = invoke(
&mut interpreter,
"Message",
Value::String("Hello, World!".into()),
);
is_unit_with_output(&result, &output, "Hello, World!");
}

#[test]
fn invoke_lambda_with_capture_succeeds() {
let mut interpreter = get_interpreter();
let (result, output) = line(&mut interpreter, "let x = 1; let f = y -> x + y;");
is_only_value(&result, &output, &Value::unit());
let (result, output) = invoke(&mut interpreter, "f", Value::Int(2));
is_only_value(&result, &output, &Value::Int(3));
}

#[test]
fn invoke_lambda_with_capture_in_callable_expr_succeeds() {
let mut interpreter = get_interpreter();
let (result, output) = invoke(
&mut interpreter,
"{let x = 1; let f = y -> x + y; f}",
Value::Int(2),
);
is_only_value(&result, &output, &Value::Int(3));
}

#[test]
fn callables_failing_profile_validation_are_not_registered() {
let mut interpreter =
Expand Down Expand Up @@ -1698,6 +1873,46 @@ mod given_interpreter {
);
}

#[test]
fn interpreter_returns_items_from_source() {
let sources = SourceMap::new(
[(
"test".into(),
"namespace A {
operation B(): Unit { }
}
"
.into(),
)],
Some("A.B()".into()),
);

let (std_id, store) =
crate::compile::package_store_with_stdlib(TargetCapabilityFlags::all());
let interpreter = Interpreter::new(
sources,
PackageType::Lib,
TargetCapabilityFlags::all(),
LanguageFeatures::default(),
store,
&[(std_id, None)],
)
.expect("interpreter should be created");

let items = interpreter.get_source_package_global_items();
assert_eq!(1, items.len());
expect![[r#"
[
"A",
]
"#]]
.assert_debug_eq(&items[0].0);
expect![[r#"
"B"
"#]]
.assert_debug_eq(&items[0].1);
}

#[test]
fn interpreter_can_be_created_from_ast() {
let sources = SourceMap::new(
Expand Down
3 changes: 2 additions & 1 deletion compiler/qsc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ pub mod project {
}

pub use qsc_data_structures::{
language_features::LanguageFeatures, namespaces::*, span::Span, target::TargetCapabilityFlags,
functors::FunctorApp, language_features::LanguageFeatures, namespaces::*, span::Span,
target::TargetCapabilityFlags,
};

pub use qsc_passes::{lower_hir_to_fir, PackageType, PassContext};
Expand Down
Loading
Loading