Fix bug on OOP-style calling of exported map object.

This commit is contained in:
Stephen Chung 2023-04-27 13:19:28 +08:00
parent ff2a23189a
commit 76e6c1b9e4
3 changed files with 205 additions and 139 deletions

View File

@ -15,6 +15,7 @@ Bug fixes
* Shadowed variable exports are now handled correctly. * Shadowed variable exports are now handled correctly.
* Shadowed constant definitions are now optimized correctly when propagated (e.g. `const X = 1; const X = 1 + 1 + 1; X` now evaluates to 3 instead of 0). * Shadowed constant definitions are now optimized correctly when propagated (e.g. `const X = 1; const X = 1 + 1 + 1; X` now evaluates to 3 instead of 0).
* Identifiers and comma's in the middle of custom syntax now register correctly. * Identifiers and comma's in the middle of custom syntax now register correctly.
* Exporting an object map from a module with closures defined on properties now works correctly when those properties are called to mimic method calls in OOP-style.
New features New features
------------ ------------

View File

@ -768,51 +768,55 @@ impl Engine {
// Linked to scripted function? // Linked to scripted function?
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if let Some(fn_def) = fn_ptr.fn_def() { let fn_def = fn_ptr.fn_def();
if fn_def.params.len() == args.len() { #[cfg(feature = "no_function")]
return self let fn_def = ();
.call_script_fn(
global, match fn_def {
caches, #[cfg(not(feature = "no_function"))]
&mut Scope::new(), Some(fn_def) if fn_def.params.len() == args.len() => self
None, .call_script_fn(
fn_ptr.encapsulated_environ(), global,
fn_def, caches,
args, &mut Scope::new(),
true, None,
fn_call_pos, fn_ptr.encapsulated_environ().map(|r| r.as_ref()),
) fn_def,
.map(|v| (v, false)); args,
true,
fn_call_pos,
)
.map(|v| (v, false)),
_ => {
#[cfg(not(feature = "no_function"))]
let is_anon = fn_ptr.is_anonymous();
#[cfg(feature = "no_function")]
let is_anon = false;
// Redirect function name
let fn_name = fn_ptr.fn_name();
// Recalculate hashes
let new_hash = if !is_anon && !is_valid_function_name(fn_name) {
FnCallHashes::from_native_only(calc_fn_hash(None, fn_name, args.len()))
} else {
FnCallHashes::from_hash(calc_fn_hash(None, fn_name, args.len()))
};
// Map it to name(args) in function-call style
self.exec_fn_call(
global,
caches,
None,
fn_name,
None,
new_hash,
args,
false,
false,
fn_call_pos,
)
} }
} }
#[cfg(not(feature = "no_function"))]
let is_anon = fn_ptr.is_anonymous();
#[cfg(feature = "no_function")]
let is_anon = false;
// Redirect function name
let fn_name = fn_ptr.fn_name();
// Recalculate hashes
let new_hash = if !is_anon && !is_valid_function_name(fn_name) {
FnCallHashes::from_native_only(calc_fn_hash(None, fn_name, args.len()))
} else {
FnCallHashes::from_hash(calc_fn_hash(None, fn_name, args.len()))
};
// Map it to name(args) in function-call style
self.exec_fn_call(
global,
caches,
None,
fn_name,
None,
new_hash,
args,
false,
false,
fn_call_pos,
)
} }
// Handle obj.call(fn_ptr, ...) // Handle obj.call(fn_ptr, ...)
@ -833,16 +837,27 @@ impl Engine {
// FnPtr call on object // FnPtr call on object
let fn_ptr = call_args[0].take().cast::<FnPtr>(); let fn_ptr = call_args[0].take().cast::<FnPtr>();
#[cfg(not(feature = "no_function"))]
let (fn_name, is_anon, fn_curry, _environ, fn_def) = { let (fn_name, is_anon, fn_curry, _environ, fn_def) = {
#[cfg(not(feature = "no_function"))]
let is_anon = fn_ptr.is_anonymous(); let is_anon = fn_ptr.is_anonymous();
#[cfg(feature = "no_function")]
let is_anon = false;
#[cfg(not(feature = "no_function"))]
let (fn_name, fn_curry, environ, fn_def) = fn_ptr.take_data(); let (fn_name, fn_curry, environ, fn_def) = fn_ptr.take_data();
(fn_name, is_anon, fn_curry, environ, fn_def) #[cfg(feature = "no_function")]
};
#[cfg(feature = "no_function")]
let (fn_name, is_anon, fn_curry, _environ) = {
let (fn_name, fn_curry, environ) = fn_ptr.take_data(); let (fn_name, fn_curry, environ) = fn_ptr.take_data();
(fn_name, false, fn_curry, environ)
(
fn_name,
is_anon,
fn_curry,
environ,
#[cfg(not(feature = "no_function"))]
fn_def,
#[cfg(feature = "no_function")]
(),
)
}; };
// Replace the first argument with the object pointer, adding the curried arguments // Replace the first argument with the object pointer, adding the curried arguments
@ -855,59 +870,67 @@ impl Engine {
args.extend(call_args.iter_mut()); args.extend(call_args.iter_mut());
// Linked to scripted function? // Linked to scripted function?
#[cfg(not(feature = "no_function"))] match fn_def {
if let Some(fn_def) = fn_def { #[cfg(not(feature = "no_function"))]
if fn_def.params.len() == args.len() { Some(fn_def) if fn_def.params.len() == args.len() => {
// Check for data race. // Check for data race.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
ensure_no_data_race(&fn_def.name, args, false)?; ensure_no_data_race(&fn_def.name, args, false)?;
return self self.call_script_fn(
.call_script_fn( global,
global, caches,
caches, &mut Scope::new(),
&mut Scope::new(), Some(target),
Some(target), _environ.as_deref(),
_environ.as_deref(), &fn_def,
&fn_def, args,
args, true,
true, fn_call_pos,
fn_call_pos, )
) .map(|v| (v, false))
.map(|v| (v, false)); }
_ => {
// Add the first argument with the object pointer
args.insert(0, target.as_mut());
// Recalculate hash
let new_hash = match is_anon {
false if !is_valid_function_name(&fn_name) => {
FnCallHashes::from_native_only(calc_fn_hash(
None,
&fn_name,
args.len(),
))
}
#[cfg(not(feature = "no_function"))]
_ => FnCallHashes::from_script_and_native(
calc_fn_hash(None, &fn_name, args.len() - 1),
calc_fn_hash(None, &fn_name, args.len()),
),
#[cfg(feature = "no_function")]
_ => FnCallHashes::from_native_only(calc_fn_hash(
None,
&fn_name,
args.len(),
)),
};
// Map it to name(args) in function-call style
self.exec_fn_call(
global,
caches,
None,
&fn_name,
None,
new_hash,
args,
is_ref_mut,
true,
fn_call_pos,
)
} }
} }
// Add the first argument with the object pointer
args.insert(0, target.as_mut());
// Recalculate hash
let new_hash = match is_anon {
false if !is_valid_function_name(&fn_name) => {
FnCallHashes::from_native_only(calc_fn_hash(None, &fn_name, args.len()))
}
#[cfg(not(feature = "no_function"))]
_ => FnCallHashes::from_script_and_native(
calc_fn_hash(None, &fn_name, args.len() - 1),
calc_fn_hash(None, &fn_name, args.len()),
),
#[cfg(feature = "no_function")]
_ => FnCallHashes::from_native_only(calc_fn_hash(None, &fn_name, args.len())),
};
// Map it to name(args) in function-call style
self.exec_fn_call(
global,
caches,
None,
&fn_name,
None,
new_hash,
args,
is_ref_mut,
true,
fn_call_pos,
)
} }
KEYWORD_FN_PTR_CURRY => { KEYWORD_FN_PTR_CURRY => {
if !target.is_fnptr() { if !target.is_fnptr() {
@ -936,19 +959,16 @@ impl Engine {
_ => { _ => {
let mut fn_name = fn_name; let mut fn_name = fn_name;
let _redirected; let _redirected;
let mut _linked = None;
let mut _arg_values: FnArgsVec<_>; let mut _arg_values: FnArgsVec<_>;
let mut call_args = call_args; let mut call_args = call_args;
// Check if it is a map method call in OOP style // Check if it is a map method call in OOP style
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if let Some(map) = target.read_lock::<crate::Map>() { if let Some(map) = target.read_lock::<crate::Map>() {
if let Some(val) = map.get(fn_name) { if let Some(val) = map.get(fn_name) {
if let Some(fn_ptr) = val.read_lock::<FnPtr>() { if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
#[cfg(not(feature = "no_function"))]
let is_anon = fn_ptr.is_anonymous();
#[cfg(feature = "no_function")]
let is_anon = false;
// Remap the function name // Remap the function name
_redirected = fn_ptr.fn_name_raw().clone(); _redirected = fn_ptr.fn_name_raw().clone();
fn_name = &_redirected; fn_name = &_redirected;
@ -962,45 +982,90 @@ impl Engine {
.collect(); .collect();
call_args = &mut _arg_values; call_args = &mut _arg_values;
} }
// Recalculate the hash based on the new function name and new arguments
let args_len = call_args.len() + 1; // Linked to scripted function?
hash = match is_anon { #[cfg(not(feature = "no_function"))]
false if !is_valid_function_name(fn_name) => { let fn_def = fn_ptr.fn_def();
FnCallHashes::from_native_only(calc_fn_hash( #[cfg(feature = "no_function")]
None, fn_name, args_len, let fn_def = ();
match fn_def {
#[cfg(not(feature = "no_function"))]
Some(fn_def) if fn_def.params.len() == call_args.len() => {
_linked = Some((
fn_def.clone(),
fn_ptr.encapsulated_environ().cloned(),
)) ))
} }
#[cfg(not(feature = "no_function"))] _ => {
_ => FnCallHashes::from_script_and_native( #[cfg(not(feature = "no_function"))]
calc_fn_hash(None, fn_name, args_len - 1), let is_anon = fn_ptr.is_anonymous();
calc_fn_hash(None, fn_name, args_len), #[cfg(feature = "no_function")]
), let is_anon = false;
#[cfg(feature = "no_function")]
_ => FnCallHashes::from_native_only(calc_fn_hash( // Recalculate the hash based on the new function name and new arguments
None, fn_name, args_len, let args_len = call_args.len() + 1;
)), hash = match is_anon {
}; false if !is_valid_function_name(fn_name) => {
FnCallHashes::from_native_only(calc_fn_hash(
None, fn_name, args_len,
))
}
#[cfg(not(feature = "no_function"))]
_ => FnCallHashes::from_script_and_native(
calc_fn_hash(None, fn_name, args_len - 1),
calc_fn_hash(None, fn_name, args_len),
),
#[cfg(feature = "no_function")]
_ => FnCallHashes::from_native_only(calc_fn_hash(
None, fn_name, args_len,
)),
};
}
}
} }
} }
}; }
// Attached object pointer in front of the arguments match _linked {
let mut args = FnArgsVec::with_capacity(call_args.len() + 1); #[cfg(not(feature = "no_function"))]
args.push(target.as_mut()); Some((fn_def, environ)) => {
args.extend(call_args.iter_mut()); // Linked to scripted function
self.call_script_fn(
global,
caches,
&mut Scope::new(),
Some(target),
environ.as_deref(),
&*fn_def,
&mut call_args.iter_mut().collect::<FnArgsVec<_>>(),
true,
fn_call_pos,
)
.map(|v| (v, false))
}
#[cfg(feature = "no_function")]
Some(()) => unreachable!(),
None => {
// Attached object pointer in front of the arguments
let mut args = FnArgsVec::with_capacity(call_args.len() + 1);
args.push(target.as_mut());
args.extend(call_args.iter_mut());
self.exec_fn_call( self.exec_fn_call(
global, global,
caches, caches,
None, None,
fn_name, fn_name,
None, None,
hash, hash,
&mut args, &mut args,
is_ref_mut, is_ref_mut,
true, true,
fn_call_pos, fn_call_pos,
) )
}
}
} }
}?; }?;

View File

@ -313,7 +313,7 @@ impl FnPtr {
caches, caches,
&mut crate::Scope::new(), &mut crate::Scope::new(),
this_ptr, this_ptr,
self.encapsulated_environ(), self.encapsulated_environ().map(|r| r.as_ref()),
fn_def, fn_def,
args, args,
true, true,
@ -334,8 +334,8 @@ impl FnPtr {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn encapsulated_environ(&self) -> Option<&EncapsulatedEnviron> { pub(crate) fn encapsulated_environ(&self) -> Option<&Shared<EncapsulatedEnviron>> {
self.environ.as_deref() self.environ.as_ref()
} }
/// Set a reference to the [encapsulated environment][EncapsulatedEnviron]. /// Set a reference to the [encapsulated environment][EncapsulatedEnviron].
#[inline(always)] #[inline(always)]
@ -350,8 +350,8 @@ impl FnPtr {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn fn_def(&self) -> Option<&crate::ast::ScriptFnDef> { pub(crate) fn fn_def(&self) -> Option<&Shared<crate::ast::ScriptFnDef>> {
self.fn_def.as_deref() self.fn_def.as_ref()
} }
/// Set a reference to the linked [`ScriptFnDef`][crate::ast::ScriptFnDef]. /// Set a reference to the linked [`ScriptFnDef`][crate::ast::ScriptFnDef].
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]