Skip to content

Commit e1556b9

Browse files
committed
Fix user-defined rules not overriding built-in functions
User-defined DownValues (via Unprotect + SetDelayed) were ignored for built-in functions because built-in dispatch ran before checking FUNC_DEFS. Reorder evaluation so user-defined rules are matched first, falling through to built-in implementations only when no user rule matches.
1 parent 32fc5be commit e1556b9

File tree

2 files changed

+113
-83
lines changed

2 files changed

+113
-83
lines changed

src/evaluator/dispatch/mod.rs

Lines changed: 84 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -359,89 +359,8 @@ pub fn evaluate_function_call_ast_inner(
359359
_ => {}
360360
}
361361

362-
// Dispatch through submodules in order
363-
if let Some(result) = structural::dispatch_structural(name, args) {
364-
return result;
365-
}
366-
if let Some(result) = attributes::dispatch_attributes(name, args) {
367-
return result;
368-
}
369-
if let Some(result) =
370-
evaluation_control::dispatch_evaluation_control(name, args)
371-
{
372-
return result;
373-
}
374-
if let Some(result) = list_operations::dispatch_list_operations(name, args) {
375-
return result;
376-
}
377-
if let Some(result) = string_functions::dispatch_string_functions(name, args)
378-
{
379-
return result;
380-
}
381-
if let Some(result) = image_functions::dispatch_image_functions(name, args) {
382-
return result;
383-
}
384-
if let Some(result) = io_functions::dispatch_io_functions(name, args) {
385-
return result;
386-
}
387-
if let Some(result) =
388-
datetime_functions::dispatch_datetime_functions(name, args)
389-
{
390-
return result;
391-
}
392-
if let Some(result) = plotting::dispatch_plotting(name, args) {
393-
return result;
394-
}
395-
if let Some(result) =
396-
predicate_functions::dispatch_predicate_functions(name, args)
397-
{
398-
return result;
399-
}
400-
if let Some(result) =
401-
association_functions::dispatch_association_functions(name, args)
402-
{
403-
return result;
404-
}
405-
if let Some(result) =
406-
quantity_functions::dispatch_quantity_functions(name, args)
407-
{
408-
return result;
409-
}
410-
if let Some(result) =
411-
interval_functions::dispatch_interval_functions(name, args)
412-
{
413-
return result;
414-
}
415-
if let Some(result) = math_functions::dispatch_math_functions(name, args) {
416-
return result;
417-
}
418-
if let Some(result) =
419-
boolean_functions::dispatch_boolean_functions(name, args)
420-
{
421-
return result;
422-
}
423-
if let Some(result) =
424-
polynomial_functions::dispatch_polynomial_functions(name, args)
425-
{
426-
return result;
427-
}
428-
if let Some(result) =
429-
calculus_functions::dispatch_calculus_functions(name, args)
430-
{
431-
return result;
432-
}
433-
if let Some(result) =
434-
linear_algebra_functions::dispatch_linear_algebra_functions(name, args)
435-
{
436-
return result;
437-
}
438-
if let Some(result) =
439-
complex_and_special::dispatch_complex_and_special(name, args)
440-
{
441-
return result;
442-
}
443-
444-
// Check for user-defined functions
362+
// Check for user-defined functions (before built-in dispatch, so user
363+
// overrides take precedence — matching Wolfram Language semantics)
445364
// Clone overloads to avoid holding the borrow across evaluate calls
446365
let overloads = crate::FUNC_DEFS.with(|m| {
447366
let defs = m.borrow();
@@ -782,6 +701,88 @@ pub fn evaluate_function_call_ast_inner(
782701
}
783702
}
784703

704+
// Dispatch through built-in submodules (after user-defined functions)
705+
if let Some(result) = structural::dispatch_structural(name, args) {
706+
return result;
707+
}
708+
if let Some(result) = attributes::dispatch_attributes(name, args) {
709+
return result;
710+
}
711+
if let Some(result) =
712+
evaluation_control::dispatch_evaluation_control(name, args)
713+
{
714+
return result;
715+
}
716+
if let Some(result) = list_operations::dispatch_list_operations(name, args) {
717+
return result;
718+
}
719+
if let Some(result) = string_functions::dispatch_string_functions(name, args)
720+
{
721+
return result;
722+
}
723+
if let Some(result) = image_functions::dispatch_image_functions(name, args) {
724+
return result;
725+
}
726+
if let Some(result) = io_functions::dispatch_io_functions(name, args) {
727+
return result;
728+
}
729+
if let Some(result) =
730+
datetime_functions::dispatch_datetime_functions(name, args)
731+
{
732+
return result;
733+
}
734+
if let Some(result) = plotting::dispatch_plotting(name, args) {
735+
return result;
736+
}
737+
if let Some(result) =
738+
predicate_functions::dispatch_predicate_functions(name, args)
739+
{
740+
return result;
741+
}
742+
if let Some(result) =
743+
association_functions::dispatch_association_functions(name, args)
744+
{
745+
return result;
746+
}
747+
if let Some(result) =
748+
quantity_functions::dispatch_quantity_functions(name, args)
749+
{
750+
return result;
751+
}
752+
if let Some(result) =
753+
interval_functions::dispatch_interval_functions(name, args)
754+
{
755+
return result;
756+
}
757+
if let Some(result) = math_functions::dispatch_math_functions(name, args) {
758+
return result;
759+
}
760+
if let Some(result) =
761+
boolean_functions::dispatch_boolean_functions(name, args)
762+
{
763+
return result;
764+
}
765+
if let Some(result) =
766+
polynomial_functions::dispatch_polynomial_functions(name, args)
767+
{
768+
return result;
769+
}
770+
if let Some(result) =
771+
calculus_functions::dispatch_calculus_functions(name, args)
772+
{
773+
return result;
774+
}
775+
if let Some(result) =
776+
linear_algebra_functions::dispatch_linear_algebra_functions(name, args)
777+
{
778+
return result;
779+
}
780+
if let Some(result) =
781+
complex_and_special::dispatch_complex_and_special(name, args)
782+
{
783+
return result;
784+
}
785+
785786
// Check if the variable stores a value that can be called as a function
786787
// (e.g., anonymous function stored in a variable: f = (# + 1) &; f[5])
787788
let stored_value = crate::ENV.with(|e| {

tests/interpreter_tests/functions.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,35 @@ mod protect_unprotect {
459459
"{}"
460460
);
461461
}
462+
463+
#[test]
464+
fn override_builtin_function_with_user_rule() {
465+
// Regression test: user-defined rules should take precedence over built-in
466+
// implementations when the function is Unprotected and a matching rule exists.
467+
clear_state();
468+
assert_eq!(
469+
interpret(
470+
"Unprotect[PolynomialQ]; PolynomialQ[u_List, x_Symbol] := Foo[u, x]; \
471+
Protect[PolynomialQ]; PolynomialQ[{x + 2}, x]"
472+
)
473+
.unwrap(),
474+
"Foo[{2 + x}, x]"
475+
);
476+
}
477+
478+
#[test]
479+
fn override_builtin_falls_through_to_builtin() {
480+
// When user rule doesn't match, built-in should still work
481+
clear_state();
482+
assert_eq!(
483+
interpret(
484+
"Unprotect[PolynomialQ]; PolynomialQ[u_List, x_Symbol] := Foo[u, x]; \
485+
Protect[PolynomialQ]; PolynomialQ[x^2 + 1, x]"
486+
)
487+
.unwrap(),
488+
"True"
489+
);
490+
}
462491
}
463492

464493
mod attributes_assignment {

0 commit comments

Comments
 (0)