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 16 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
98 changes: 97 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,72 @@ impl Interpreter {
})
}

/// Given a package ID, returns all the global items in the package.
/// Note this does not currently include re-exports.
fn package_globals(&self, package_id: PackageId) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
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,
Value::Global(store_item_id, FunctorApp::default()),
));
}
}
exported_items
}

/// Get the global callables defined in the user source passed into initialization of the interpreter as `Value` instances.
pub fn user_globals(&self) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
self.package_globals(self.source_package)
}

/// Get the global callables defined in the open package being interpreted as `Value` instances, which will include any items
/// defined by calls to `eval_fragments` and the like.
pub fn source_globals(&self) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
self.package_globals(self.package)
}

/// Get the input and output types of a given value representing a global item.
/// # Panics
/// Panics if the item is not callable or a type that can be invoked as a callable.
pub fn global_tys(&self, item_id: &Value) -> Option<(ty::Ty, ty::Ty)> {
let Value::Global(item_id, _) = item_id else {
panic!("value is not a global callable");
};
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 @@ -467,6 +536,33 @@ impl Interpreter {
)
}

/// 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,
)
})
}

/// Runs the given entry expression on a new instance of the environment and simulator,
/// but using the current compilation.
pub fn run(
Expand Down Expand Up @@ -1033,7 +1129,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.user_globals();
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.source_globals();
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.source_globals();
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.source_globals();
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.source_globals();
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.source_globals();
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.user_globals();
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