Skip to content

Commit

Permalink
Use Value as type exposed from interpreter
Browse files Browse the repository at this point in the history
  • Loading branch information
swernli committed Dec 11, 2024
1 parent fad5d4a commit ba543dd
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 38 deletions.
34 changes: 17 additions & 17 deletions compiler/qsc/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,10 +320,7 @@ 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(
&self,
package_id: PackageId,
) -> Vec<(Vec<Rc<str>>, Rc<str>, fir::StoreItemId)> {
fn package_globals(&self, package_id: PackageId) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
let mut exported_items = Vec::new();
let package = &self
.compiler
Expand All @@ -337,31 +334,34 @@ impl Interpreter {
package: package_id,
item: fir::LocalItemId::from(usize::from(term.id.item)),
};
exported_items.push((global.namespace, global.name, store_item_id));
exported_items.push((
global.namespace,
global.name,
Value::Global(store_item_id, FunctorApp::default()),
));
}
}
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 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 items defined in the open package being interpreted, which will include any items
/// 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 get_current_package_global_items(
&self,
) -> Vec<(Vec<Rc<str>>, Rc<str>, fir::StoreItemId)> {
self.get_global_items(self.package)
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 callable item.
/// 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 get_callable_tys(&self, item_id: fir::StoreItemId) -> Option<(ty::Ty, ty::Ty)> {
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
Expand Down
14 changes: 7 additions & 7 deletions compiler/qsc/src/interpret/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ mod given_interpreter {
#[test]
fn interpreter_without_sources_has_no_items() {
let interpreter = get_interpreter();
let items = interpreter.get_source_package_global_items();
let items = interpreter.user_globals();
assert!(items.is_empty());
}

Expand All @@ -541,7 +541,7 @@ mod given_interpreter {
let mut interpreter = get_interpreter();
let (result, output) = line(&mut interpreter, "()");
is_only_value(&result, &output, &Value::unit());
let items = interpreter.get_current_package_global_items();
let items = interpreter.source_globals();
assert!(items.is_empty());
}

Expand All @@ -556,7 +556,7 @@ mod given_interpreter {
"#},
);
is_only_value(&result, &output, &Value::unit());
let items = interpreter.get_current_package_global_items();
let items = interpreter.source_globals();
assert_eq!(items.len(), 2);
// No namespace for top-level items
assert!(items[0].0.is_empty());
Expand Down Expand Up @@ -584,7 +584,7 @@ mod given_interpreter {
"#},
);
is_only_value(&result, &output, &Value::unit());
let items = interpreter.get_current_package_global_items();
let items = interpreter.source_globals();
assert_eq!(items.len(), 1);
expect![[r#"
[
Expand All @@ -609,7 +609,7 @@ mod given_interpreter {
"#},
);
is_only_value(&result, &output, &Value::unit());
let items = interpreter.get_current_package_global_items();
let items = interpreter.source_globals();
assert_eq!(items.len(), 2);
let (result, output) = line(
&mut interpreter,
Expand All @@ -619,7 +619,7 @@ mod given_interpreter {
"#},
);
is_only_value(&result, &output, &Value::unit());
let items = interpreter.get_current_package_global_items();
let items = interpreter.source_globals();
assert_eq!(items.len(), 4);
// No namespace for top-level items
assert!(items[0].0.is_empty());
Expand Down Expand Up @@ -1899,7 +1899,7 @@ mod given_interpreter {
)
.expect("interpreter should be created");

let items = interpreter.get_source_package_global_items();
let items = interpreter.user_globals();
assert_eq!(1, items.len());
expect![[r#"
[
Expand Down
42 changes: 28 additions & 14 deletions pip/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,9 @@ impl Interpreter {
Ok(interpreter) => {
if let Some(make_callable) = &make_callable {
// Add any global callables from the user source as Python functions to the environment.
let exported_items = interpreter.get_source_package_global_items();
for (namespace, name, item_id) in exported_items {
create_py_callable(py, make_callable, &namespace, &name, item_id)?;
let exported_items = interpreter.user_globals();
for (namespace, name, val) in exported_items {
create_py_callable(py, make_callable, &namespace, &name, val)?;
}
}
Ok(Self {
Expand Down Expand Up @@ -324,9 +324,9 @@ impl Interpreter {
// every callable that was defined in the input and by previous calls that added to the open package.
// This is safe because either the callable will be replaced with itself or a new callable with the
// same name will shadow the previous one, which is the expected behavior.
let new_items = self.interpreter.get_current_package_global_items();
for (namespace, name, item_id) in new_items {
create_py_callable(py, make_callable, &namespace, &name, item_id)?;
let new_items = self.interpreter.source_globals();
for (namespace, name, val) in new_items {
create_py_callable(py, make_callable, &namespace, &name, val)?;
}
}
Ok(ValueWrapper(value).into_py(py))
Expand Down Expand Up @@ -398,7 +398,7 @@ impl Interpreter {
let mut receiver = OptionalCallbackReceiver { callback, py };
let (input_ty, output_ty) = self
.interpreter
.get_callable_tys(callable.id)
.global_tys(&callable.into())
.ok_or(QSharpError::new_err("callable not found"))?;

// If the types are not supported, we can't convert the arguments or return value.
Expand All @@ -414,8 +414,6 @@ impl Interpreter {
)));
}

let callable = Value::Global(callable.id, FunctorApp::default());

// Conver the Python arguments to Q# values, treating None as an empty tuple aka `Unit`.
let args = if matches!(&input_ty, Ty::Tuple(tup) if tup.is_empty()) {
// Special case for unit, where args should be None
Expand All @@ -433,7 +431,10 @@ impl Interpreter {
convert_obj_with_ty(py, &args, &input_ty)?
};

match self.interpreter.invoke(&mut receiver, callable, args) {
match self
.interpreter
.invoke(&mut receiver, callable.into(), args)
{
Ok(value) => Ok(ValueWrapper(value).into_py(py)),
Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
}
Expand Down Expand Up @@ -992,8 +993,21 @@ where

#[pyclass]
#[derive(Clone, Copy)]
struct GlobalCallable {
id: StoreItemId,
struct GlobalCallable(StoreItemId, FunctorApp);

impl From<Value> for GlobalCallable {
fn from(val: Value) -> Self {
match val {
Value::Global(id, app) => GlobalCallable(id, app),
_ => panic!("expected global callable"),
}
}
}

impl From<GlobalCallable> for Value {
fn from(val: GlobalCallable) -> Self {
Value::Global(val.0, val.1)
}
}

/// Create a Python callable from a Q# callable and adds it to the given environment.
Expand All @@ -1002,15 +1016,15 @@ fn create_py_callable(
make_callable: &PyObject,
namespace: &[Rc<str>],
name: &str,
item_id: StoreItemId,
val: Value,
) -> PyResult<()> {
if namespace.is_empty() && name == "<lambda>" {
// We don't want to bind auto-generated lambda callables.
return Ok(());
}

let args = (
Py::new(py, GlobalCallable { id: item_id }).expect("should be able to create callable"), // callable id
Py::new(py, GlobalCallable::from(val)).expect("should be able to create callable"), // callable id
PyList::new_bound(py, namespace.iter().map(ToString::to_string)), // namespace as string array
PyString::new_bound(py, name), // name of callable
);
Expand Down

0 comments on commit ba543dd

Please sign in to comment.