Allow exporting function pointers from modules.
This commit is contained in:
parent
ec5ad320a1
commit
d350a948e7
@ -36,7 +36,7 @@ Net features
|
|||||||
|
|
||||||
* A function pointer created via a closure definition now links to the particular anonymous function itself.
|
* A function pointer created via a closure definition now links to the particular anonymous function itself.
|
||||||
* This avoids a potentially expensive function lookup when the function pointer is called, speeding up closures.
|
* This avoids a potentially expensive function lookup when the function pointer is called, speeding up closures.
|
||||||
* It does _not_, however, allow the function pointer to be `export`ed as a constant from a script module because the closure may cross-call other functions defined in the module and the function pointer won't keep the fully encapsulated environment.
|
* An additional benefit is that function pointers can now be `export`ed from modules!
|
||||||
|
|
||||||
### `!in`
|
### `!in`
|
||||||
|
|
||||||
|
@ -781,7 +781,7 @@ impl Engine {
|
|||||||
caches,
|
caches,
|
||||||
&mut Scope::new(),
|
&mut Scope::new(),
|
||||||
&mut this_ptr,
|
&mut this_ptr,
|
||||||
None,
|
fn_ptr.encapsulated_environ(),
|
||||||
fn_def,
|
fn_def,
|
||||||
args,
|
args,
|
||||||
true,
|
true,
|
||||||
@ -834,15 +834,15 @@ impl Engine {
|
|||||||
let fn_ptr = mem::take(&mut call_args[0]).cast::<FnPtr>();
|
let fn_ptr = mem::take(&mut call_args[0]).cast::<FnPtr>();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
let (fn_name, is_anon, fn_curry, fn_def) = {
|
let (fn_name, is_anon, fn_curry, environ, fn_def) = {
|
||||||
let is_anon = fn_ptr.is_anonymous();
|
let is_anon = fn_ptr.is_anonymous();
|
||||||
let (fn_name, fn_curry, fn_def) = fn_ptr.take_data();
|
let (fn_name, fn_curry, environ, fn_def) = fn_ptr.take_data();
|
||||||
(fn_name, is_anon, fn_curry, fn_def)
|
(fn_name, is_anon, fn_curry, environ, fn_def)
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(feature = "no_function")]
|
||||||
let (fn_name, is_anon, fn_curry) = {
|
let (fn_name, is_anon, environ, fn_curry) = {
|
||||||
let (fn_name, fn_curry) = fn_ptr.take_data();
|
let (fn_name, fn_curry, environ) = fn_ptr.take_data();
|
||||||
(fn_name, false, fn_curry)
|
(fn_name, false, fn_curry, environ)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Replace the first argument with the object pointer, adding the curried arguments
|
// Replace the first argument with the object pointer, adding the curried arguments
|
||||||
@ -868,7 +868,7 @@ impl Engine {
|
|||||||
caches,
|
caches,
|
||||||
&mut Scope::new(),
|
&mut Scope::new(),
|
||||||
target,
|
target,
|
||||||
None,
|
environ.as_deref(),
|
||||||
&fn_def,
|
&fn_def,
|
||||||
args,
|
args,
|
||||||
true,
|
true,
|
||||||
@ -1043,15 +1043,15 @@ impl Engine {
|
|||||||
let fn_ptr = arg_value.cast::<FnPtr>();
|
let fn_ptr = arg_value.cast::<FnPtr>();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
let (fn_name, is_anon, fn_curry, fn_def) = {
|
let (fn_name, is_anon, fn_curry, environ, fn_def) = {
|
||||||
let is_anon = fn_ptr.is_anonymous();
|
let is_anon = fn_ptr.is_anonymous();
|
||||||
let (fn_name, fn_curry, fn_def) = fn_ptr.take_data();
|
let (fn_name, fn_curry, environ, fn_def) = fn_ptr.take_data();
|
||||||
(fn_name, is_anon, fn_curry, fn_def)
|
(fn_name, is_anon, fn_curry, environ, fn_def)
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(feature = "no_function")]
|
||||||
let (fn_name, is_anon, fn_curry) = {
|
let (fn_name, is_anon, fn_curry, environ) = {
|
||||||
let (fn_name, fn_curry) = fn_ptr.take_data();
|
let (fn_name, fn_curry, environ) = fn_ptr.take_data();
|
||||||
(fn_name, false, fn_curry)
|
(fn_name, false, fn_curry, environ)
|
||||||
};
|
};
|
||||||
|
|
||||||
curry.extend(fn_curry.into_iter());
|
curry.extend(fn_curry.into_iter());
|
||||||
@ -1077,7 +1077,7 @@ impl Engine {
|
|||||||
caches,
|
caches,
|
||||||
&mut Scope::new(),
|
&mut Scope::new(),
|
||||||
&mut this_ptr,
|
&mut this_ptr,
|
||||||
None,
|
environ.as_deref(),
|
||||||
&fn_def,
|
&fn_def,
|
||||||
args,
|
args,
|
||||||
true,
|
true,
|
||||||
|
@ -2138,30 +2138,19 @@ impl Module {
|
|||||||
// The return value is thrown away and not used
|
// The return value is thrown away and not used
|
||||||
let _ = result?;
|
let _ = result?;
|
||||||
|
|
||||||
// Variables with an alias left in the scope become module variables
|
// Encapsulated environment
|
||||||
for (_name, value, mut aliases) in scope {
|
|
||||||
// It is an error to export function pointers that refer to encapsulated local functions.
|
|
||||||
//
|
|
||||||
// Even if the function pointer already links to a scripted function definition, it may
|
|
||||||
// cross-call other functions inside the module and won't have the full encapsulated
|
|
||||||
// environment available.
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if let Some(fn_ptr) = value.downcast_ref::<crate::FnPtr>() {
|
let environ = Shared::new(crate::func::EncapsulatedEnviron {
|
||||||
if ast.iter_fn_def().any(|f| f.name == fn_ptr.fn_name()) {
|
lib: ast.shared_lib().clone(),
|
||||||
return Err(crate::ERR::ErrorMismatchDataType(
|
imports: imports.into_boxed_slice(),
|
||||||
String::new(),
|
constants,
|
||||||
if fn_ptr.is_anonymous() {
|
});
|
||||||
format!("cannot export closure in variable {_name}")
|
|
||||||
} else {
|
// Variables with an alias left in the scope become module variables
|
||||||
format!(
|
for (_name, mut value, mut aliases) in scope {
|
||||||
"cannot export function pointer to local function '{}' in variable {_name}",
|
#[cfg(not(feature = "no_function"))]
|
||||||
fn_ptr.fn_name()
|
if let Some(mut fn_ptr) = value.write_lock::<crate::FnPtr>() {
|
||||||
)
|
fn_ptr.set_encapsulated_environ(Some(environ.clone()));
|
||||||
},
|
|
||||||
crate::Position::NONE,
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match aliases.len() {
|
match aliases.len() {
|
||||||
@ -2183,13 +2172,6 @@ impl Module {
|
|||||||
|
|
||||||
// Non-private functions defined become module functions
|
// Non-private functions defined become module functions
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
|
||||||
let environ = Shared::new(crate::func::EncapsulatedEnviron {
|
|
||||||
lib: ast.shared_lib().clone(),
|
|
||||||
imports: imports.into_boxed_slice(),
|
|
||||||
constants,
|
|
||||||
});
|
|
||||||
|
|
||||||
ast.iter_fn_def()
|
ast.iter_fn_def()
|
||||||
.filter(|&f| match f.access {
|
.filter(|&f| match f.access {
|
||||||
FnAccess::Public => true,
|
FnAccess::Public => true,
|
||||||
@ -2205,7 +2187,6 @@ impl Module {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
module.id = ast.source_raw().cloned();
|
module.id = ast.source_raw().cloned();
|
||||||
|
|
||||||
|
@ -50,9 +50,9 @@ fn check_struct_sizes() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
size_of::<FnPtr>(),
|
size_of::<FnPtr>(),
|
||||||
if cfg!(feature = "no_function") {
|
if cfg!(feature = "no_function") {
|
||||||
64
|
|
||||||
} else {
|
|
||||||
72
|
72
|
||||||
|
} else {
|
||||||
|
80
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(size_of::<LexError>(), 56);
|
assert_eq!(size_of::<LexError>(), 56);
|
||||||
|
@ -23,6 +23,7 @@ use std::{
|
|||||||
pub struct FnPtr {
|
pub struct FnPtr {
|
||||||
name: ImmutableString,
|
name: ImmutableString,
|
||||||
curry: StaticVec<Dynamic>,
|
curry: StaticVec<Dynamic>,
|
||||||
|
environ: Option<crate::Shared<crate::func::EncapsulatedEnviron>>,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
fn_def: Option<crate::Shared<crate::ast::ScriptFnDef>>,
|
fn_def: Option<crate::Shared<crate::ast::ScriptFnDef>>,
|
||||||
}
|
}
|
||||||
@ -75,6 +76,7 @@ impl FnPtr {
|
|||||||
Self {
|
Self {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
curry,
|
curry,
|
||||||
|
environ: None,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
fn_def: None,
|
fn_def: None,
|
||||||
}
|
}
|
||||||
@ -100,16 +102,23 @@ impl FnPtr {
|
|||||||
) -> (
|
) -> (
|
||||||
ImmutableString,
|
ImmutableString,
|
||||||
StaticVec<Dynamic>,
|
StaticVec<Dynamic>,
|
||||||
|
Option<crate::Shared<crate::func::EncapsulatedEnviron>>,
|
||||||
Option<crate::Shared<crate::ast::ScriptFnDef>>,
|
Option<crate::Shared<crate::ast::ScriptFnDef>>,
|
||||||
) {
|
) {
|
||||||
(self.name, self.curry, self.fn_def)
|
(self.name, self.curry, self.environ, self.fn_def)
|
||||||
}
|
}
|
||||||
/// Get the underlying data of the function pointer.
|
/// Get the underlying data of the function pointer.
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(feature = "no_function")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn take_data(self) -> (ImmutableString, StaticVec<Dynamic>) {
|
pub(crate) fn take_data(
|
||||||
(self.name, self.curry)
|
self,
|
||||||
|
) -> (
|
||||||
|
ImmutableString,
|
||||||
|
StaticVec<Dynamic>,
|
||||||
|
Option<crate::Shared<crate::func::EncapsulatedEnviron>>,
|
||||||
|
) {
|
||||||
|
(self.name, self.curry, self.environ)
|
||||||
}
|
}
|
||||||
/// Get the curried arguments.
|
/// Get the curried arguments.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -290,7 +299,7 @@ impl FnPtr {
|
|||||||
caches,
|
caches,
|
||||||
&mut crate::Scope::new(),
|
&mut crate::Scope::new(),
|
||||||
this_ptr.unwrap_or(&mut null_ptr),
|
this_ptr.unwrap_or(&mut null_ptr),
|
||||||
None,
|
self.encapsulated_environ(),
|
||||||
&fn_def,
|
&fn_def,
|
||||||
args,
|
args,
|
||||||
true,
|
true,
|
||||||
@ -307,6 +316,20 @@ impl FnPtr {
|
|||||||
|
|
||||||
context.call_fn_raw(self.fn_name(), is_method, is_method, args)
|
context.call_fn_raw(self.fn_name(), is_method, is_method, args)
|
||||||
}
|
}
|
||||||
|
/// Get a reference to the [encapsulated environment][crate::func::EncapsulatedEnviron].
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn encapsulated_environ(&self) -> Option<&crate::func::EncapsulatedEnviron> {
|
||||||
|
self.environ.as_deref()
|
||||||
|
}
|
||||||
|
/// Set a reference to the [encapsulated environment][crate::func::EncapsulatedEnviron].
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn set_encapsulated_environ(
|
||||||
|
&mut self,
|
||||||
|
value: Option<impl Into<crate::Shared<crate::func::EncapsulatedEnviron>>>,
|
||||||
|
) {
|
||||||
|
self.environ = value.map(Into::into);
|
||||||
|
}
|
||||||
/// Get a reference to the linked [`ScriptFnDef`][crate::ast::ScriptFnDef].
|
/// Get a reference to the linked [`ScriptFnDef`][crate::ast::ScriptFnDef].
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -340,6 +363,7 @@ impl TryFrom<ImmutableString> for FnPtr {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: value,
|
name: value,
|
||||||
curry: StaticVec::new_const(),
|
curry: StaticVec::new_const(),
|
||||||
|
environ: None,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
fn_def: None,
|
fn_def: None,
|
||||||
})
|
})
|
||||||
@ -358,6 +382,7 @@ impl<T: Into<crate::Shared<crate::ast::ScriptFnDef>>> From<T> for FnPtr {
|
|||||||
Self {
|
Self {
|
||||||
name: fn_def.name.clone(),
|
name: fn_def.name.clone(),
|
||||||
curry: StaticVec::new_const(),
|
curry: StaticVec::new_const(),
|
||||||
|
environ: None,
|
||||||
fn_def: Some(fn_def),
|
fn_def: Some(fn_def),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user