Skip to content

Commit f6e8acf

Browse files
swernliminestarks
andauthored
Expose user-defined callables into Python (#2054)
This change adds new interop capabilities for the qsharp Python module. Any user-defined global callables with types supported by interop will automatically be surfaced into the `qsharp.code` module and available for invocation or import directly in Python. --------- Co-authored-by: Mine Starks <[email protected]>
1 parent 502ae34 commit f6e8acf

File tree

11 files changed

+890
-10
lines changed

11 files changed

+890
-10
lines changed

compiler/qsc/src/interpret.rs

+97-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ mod package_tests;
1111
#[cfg(test)]
1212
mod tests;
1313

14+
use std::rc::Rc;
15+
1416
pub use qsc_eval::{
1517
debug::Frame,
1618
noise::PauliNoise,
@@ -21,6 +23,7 @@ pub use qsc_eval::{
2123
val::Value,
2224
StepAction, StepResult,
2325
};
26+
use qsc_hir::{global, ty};
2427
use qsc_linter::{HirLint, Lint, LintKind, LintLevel};
2528
use qsc_lowerer::{map_fir_package_to_hir, map_hir_package_to_fir};
2629
use qsc_partial_eval::ProgramEntry;
@@ -315,6 +318,72 @@ impl Interpreter {
315318
})
316319
}
317320

321+
/// Given a package ID, returns all the global items in the package.
322+
/// Note this does not currently include re-exports.
323+
fn package_globals(&self, package_id: PackageId) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
324+
let mut exported_items = Vec::new();
325+
let package = &self
326+
.compiler
327+
.package_store()
328+
.get(map_fir_package_to_hir(package_id))
329+
.expect("package should exist in the package store")
330+
.package;
331+
for global in global::iter_package(Some(map_fir_package_to_hir(package_id)), package) {
332+
if let global::Kind::Term(term) = global.kind {
333+
let store_item_id = fir::StoreItemId {
334+
package: package_id,
335+
item: fir::LocalItemId::from(usize::from(term.id.item)),
336+
};
337+
exported_items.push((
338+
global.namespace,
339+
global.name,
340+
Value::Global(store_item_id, FunctorApp::default()),
341+
));
342+
}
343+
}
344+
exported_items
345+
}
346+
347+
/// Get the global callables defined in the user source passed into initialization of the interpreter as `Value` instances.
348+
pub fn user_globals(&self) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
349+
self.package_globals(self.source_package)
350+
}
351+
352+
/// Get the global callables defined in the open package being interpreted as `Value` instances, which will include any items
353+
/// defined by calls to `eval_fragments` and the like.
354+
pub fn source_globals(&self) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
355+
self.package_globals(self.package)
356+
}
357+
358+
/// Get the input and output types of a given value representing a global item.
359+
/// # Panics
360+
/// Panics if the item is not callable or a type that can be invoked as a callable.
361+
pub fn global_tys(&self, item_id: &Value) -> Option<(ty::Ty, ty::Ty)> {
362+
let Value::Global(item_id, _) = item_id else {
363+
panic!("value is not a global callable");
364+
};
365+
let package_id = map_fir_package_to_hir(item_id.package);
366+
let unit = self
367+
.compiler
368+
.package_store()
369+
.get(package_id)
370+
.expect("package should exist in the package store");
371+
let item = unit
372+
.package
373+
.items
374+
.get(qsc_hir::hir::LocalItemId::from(usize::from(item_id.item)))?;
375+
match &item.kind {
376+
qsc_hir::hir::ItemKind::Callable(decl) => {
377+
Some((decl.input.ty.clone(), decl.output.clone()))
378+
}
379+
qsc_hir::hir::ItemKind::Ty(_, udt) => {
380+
// We don't handle UDTs, so we return an error type that prevents later code from processing this item.
381+
Some((udt.get_pure_ty(), ty::Ty::Err))
382+
}
383+
_ => panic!("item is not callable"),
384+
}
385+
}
386+
318387
pub fn set_quantum_seed(&mut self, seed: Option<u64>) {
319388
self.quantum_seed = seed;
320389
self.sim.set_seed(seed);
@@ -467,6 +536,33 @@ impl Interpreter {
467536
)
468537
}
469538

539+
/// Invokes the given callable with the given arguments using the current environment, simlator, and compilation.
540+
pub fn invoke(
541+
&mut self,
542+
receiver: &mut impl Receiver,
543+
callable: Value,
544+
args: Value,
545+
) -> InterpretResult {
546+
qsc_eval::invoke(
547+
self.package,
548+
self.classical_seed,
549+
&self.fir_store,
550+
&mut self.env,
551+
&mut self.sim,
552+
receiver,
553+
callable,
554+
args,
555+
)
556+
.map_err(|(error, call_stack)| {
557+
eval_error(
558+
self.compiler.package_store(),
559+
&self.fir_store,
560+
call_stack,
561+
error,
562+
)
563+
})
564+
}
565+
470566
/// Runs the given entry expression on a new instance of the environment and simulator,
471567
/// but using the current compilation.
472568
pub fn run(
@@ -1033,7 +1129,7 @@ impl<'a> BreakpointCollector<'a> {
10331129
.expect("Couldn't find source file")
10341130
}
10351131

1036-
fn add_stmt(&mut self, stmt: &qsc_fir::fir::Stmt) {
1132+
fn add_stmt(&mut self, stmt: &fir::Stmt) {
10371133
let source: &Source = self.get_source(stmt.span.lo);
10381134
if source.offset == self.offset {
10391135
let span = stmt.span - source.offset;

compiler/qsc/src/interpret/tests.rs

+215
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,21 @@ mod given_interpreter {
4646
(result, receiver.dump())
4747
}
4848

49+
fn invoke(
50+
interpreter: &mut Interpreter,
51+
callable: &str,
52+
args: Value,
53+
) -> (InterpretResult, String) {
54+
let mut cursor = Cursor::new(Vec::<u8>::new());
55+
let mut receiver = CursorReceiver::new(&mut cursor);
56+
let callable = match interpreter.eval_fragments(&mut receiver, callable) {
57+
Ok(val) => val,
58+
Err(e) => return (Err(e), receiver.dump()),
59+
};
60+
let result = interpreter.invoke(&mut receiver, callable, args);
61+
(result, receiver.dump())
62+
}
63+
4964
mod without_sources {
5065
use expect_test::expect;
5166
use indoc::indoc;
@@ -514,6 +529,166 @@ mod given_interpreter {
514529
is_only_value(&result, &output, &Value::unit());
515530
}
516531

532+
#[test]
533+
fn interpreter_without_sources_has_no_items() {
534+
let interpreter = get_interpreter();
535+
let items = interpreter.user_globals();
536+
assert!(items.is_empty());
537+
}
538+
539+
#[test]
540+
fn fragment_without_items_has_no_items() {
541+
let mut interpreter = get_interpreter();
542+
let (result, output) = line(&mut interpreter, "()");
543+
is_only_value(&result, &output, &Value::unit());
544+
let items = interpreter.source_globals();
545+
assert!(items.is_empty());
546+
}
547+
548+
#[test]
549+
fn fragment_defining_items_has_items() {
550+
let mut interpreter = get_interpreter();
551+
let (result, output) = line(
552+
&mut interpreter,
553+
indoc! {r#"
554+
function Foo() : Int { 2 }
555+
function Bar() : Int { 3 }
556+
"#},
557+
);
558+
is_only_value(&result, &output, &Value::unit());
559+
let items = interpreter.source_globals();
560+
assert_eq!(items.len(), 2);
561+
// No namespace for top-level items
562+
assert!(items[0].0.is_empty());
563+
expect![[r#"
564+
"Foo"
565+
"#]]
566+
.assert_debug_eq(&items[0].1);
567+
// No namespace for top-level items
568+
assert!(items[1].0.is_empty());
569+
expect![[r#"
570+
"Bar"
571+
"#]]
572+
.assert_debug_eq(&items[1].1);
573+
}
574+
575+
#[test]
576+
fn fragment_defining_items_with_namespace_has_items() {
577+
let mut interpreter = get_interpreter();
578+
let (result, output) = line(
579+
&mut interpreter,
580+
indoc! {r#"
581+
namespace Foo {
582+
function Bar() : Int { 3 }
583+
}
584+
"#},
585+
);
586+
is_only_value(&result, &output, &Value::unit());
587+
let items = interpreter.source_globals();
588+
assert_eq!(items.len(), 1);
589+
expect![[r#"
590+
[
591+
"Foo",
592+
]
593+
"#]]
594+
.assert_debug_eq(&items[0].0);
595+
expect![[r#"
596+
"Bar"
597+
"#]]
598+
.assert_debug_eq(&items[0].1);
599+
}
600+
601+
#[test]
602+
fn fragments_defining_items_add_to_existing_items() {
603+
let mut interpreter = get_interpreter();
604+
let (result, output) = line(
605+
&mut interpreter,
606+
indoc! {r#"
607+
function Foo() : Int { 2 }
608+
function Bar() : Int { 3 }
609+
"#},
610+
);
611+
is_only_value(&result, &output, &Value::unit());
612+
let items = interpreter.source_globals();
613+
assert_eq!(items.len(), 2);
614+
let (result, output) = line(
615+
&mut interpreter,
616+
indoc! {r#"
617+
function Baz() : Int { 4 }
618+
function Qux() : Int { 5 }
619+
"#},
620+
);
621+
is_only_value(&result, &output, &Value::unit());
622+
let items = interpreter.source_globals();
623+
assert_eq!(items.len(), 4);
624+
// No namespace for top-level items
625+
assert!(items[0].0.is_empty());
626+
expect![[r#"
627+
"Foo"
628+
"#]]
629+
.assert_debug_eq(&items[0].1);
630+
// No namespace for top-level items
631+
assert!(items[1].0.is_empty());
632+
expect![[r#"
633+
"Bar"
634+
"#]]
635+
.assert_debug_eq(&items[1].1);
636+
// No namespace for top-level items
637+
assert!(items[2].0.is_empty());
638+
expect![[r#"
639+
"Baz"
640+
"#]]
641+
.assert_debug_eq(&items[2].1);
642+
// No namespace for top-level items
643+
assert!(items[3].0.is_empty());
644+
expect![[r#"
645+
"Qux"
646+
"#]]
647+
.assert_debug_eq(&items[3].1);
648+
}
649+
650+
#[test]
651+
fn invoke_callable_without_args_succeeds() {
652+
let mut interpreter = get_interpreter();
653+
let (result, output) = invoke(
654+
&mut interpreter,
655+
"Std.Diagnostics.DumpMachine",
656+
Value::unit(),
657+
);
658+
is_unit_with_output(&result, &output, "STATE:\nNo qubits allocated");
659+
}
660+
661+
#[test]
662+
fn invoke_callable_with_args_succeeds() {
663+
let mut interpreter = get_interpreter();
664+
let (result, output) = invoke(
665+
&mut interpreter,
666+
"Message",
667+
Value::String("Hello, World!".into()),
668+
);
669+
is_unit_with_output(&result, &output, "Hello, World!");
670+
}
671+
672+
#[test]
673+
fn invoke_lambda_with_capture_succeeds() {
674+
let mut interpreter = get_interpreter();
675+
let (result, output) = line(&mut interpreter, "let x = 1; let f = y -> x + y;");
676+
is_only_value(&result, &output, &Value::unit());
677+
let (result, output) = invoke(&mut interpreter, "f", Value::Int(2));
678+
is_only_value(&result, &output, &Value::Int(3));
679+
}
680+
681+
#[test]
682+
fn invoke_lambda_with_capture_in_callable_expr_succeeds() {
683+
let mut interpreter = get_interpreter();
684+
let (result, output) = invoke(
685+
&mut interpreter,
686+
"{let x = 1; let f = y -> x + y; f}",
687+
Value::Int(2),
688+
);
689+
is_only_value(&result, &output, &Value::Int(3));
690+
}
691+
517692
#[test]
518693
fn callables_failing_profile_validation_are_not_registered() {
519694
let mut interpreter =
@@ -1698,6 +1873,46 @@ mod given_interpreter {
16981873
);
16991874
}
17001875

1876+
#[test]
1877+
fn interpreter_returns_items_from_source() {
1878+
let sources = SourceMap::new(
1879+
[(
1880+
"test".into(),
1881+
"namespace A {
1882+
operation B(): Unit { }
1883+
}
1884+
"
1885+
.into(),
1886+
)],
1887+
Some("A.B()".into()),
1888+
);
1889+
1890+
let (std_id, store) =
1891+
crate::compile::package_store_with_stdlib(TargetCapabilityFlags::all());
1892+
let interpreter = Interpreter::new(
1893+
sources,
1894+
PackageType::Lib,
1895+
TargetCapabilityFlags::all(),
1896+
LanguageFeatures::default(),
1897+
store,
1898+
&[(std_id, None)],
1899+
)
1900+
.expect("interpreter should be created");
1901+
1902+
let items = interpreter.user_globals();
1903+
assert_eq!(1, items.len());
1904+
expect![[r#"
1905+
[
1906+
"A",
1907+
]
1908+
"#]]
1909+
.assert_debug_eq(&items[0].0);
1910+
expect![[r#"
1911+
"B"
1912+
"#]]
1913+
.assert_debug_eq(&items[0].1);
1914+
}
1915+
17011916
#[test]
17021917
fn interpreter_can_be_created_from_ast() {
17031918
let sources = SourceMap::new(

compiler/qsc/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ pub mod project {
3838
}
3939

4040
pub use qsc_data_structures::{
41-
language_features::LanguageFeatures, namespaces::*, span::Span, target::TargetCapabilityFlags,
41+
functors::FunctorApp, language_features::LanguageFeatures, namespaces::*, span::Span,
42+
target::TargetCapabilityFlags,
4243
};
4344

4445
pub use qsc_passes::{lower_hir_to_fir, PackageType, PassContext};

0 commit comments

Comments
 (0)