Skip to content

Convert the macros to templates#1225

Open
VictorSohier wants to merge 12 commits intoRedot-Engine:masterfrom
VictorSohier:variantUtilityFunctionRegistration
Open

Convert the macros to templates#1225
VictorSohier wants to merge 12 commits intoRedot-Engine:masterfrom
VictorSohier:variantUtilityFunctionRegistration

Conversation

@VictorSohier
Copy link

@VictorSohier VictorSohier commented Mar 14, 2026

This has been discussed for a hot minute moving these to templates from C macros.

A consequence of this is that any functions which emit an error as a value now must have their error parameter be the first parameter.

Other than that, it should now be less cumbersome to manage, be better parameterizable and more flexible, namely, being able to register functions that aren't tied to the VariantUtilityFunctions struct and actually being able to use function overloading and templated functions as a result. Also, the registered name is now dis-associated from the function name provided.

Summary by CodeRabbit

  • Refactor

    • Overhauled the internal binding system to improve stability, maintainability, and type-safe function dispatch.
  • Bug Fixes

    • Standardized error-reporting order across many utility functions for more consistent diagnostics and error handling.
  • Documentation

    • lerp now requires a float weight (was Variant), clarifying usage and improving type safety.

@coderabbitai
Copy link

coderabbitai bot commented Mar 14, 2026

Walkthrough

Reorders many VariantUtilityFunctions to take Callable::CallError &r_error as the first parameter and replaces macro-based function bindings with a template-driven binding system; also tightens lerp's weight parameter to float and adds explicit includes in type_info.h.

Changes

Cohort / File(s) Summary
Header Declarations
core/variant/variant_utility.h
Reordered signatures for ~20 static methods (e.g., floor, ceil, round, abs, sign, snapped, lerp, wrap, max, min, clamp, weakref, str, printing APIs, push_error, push_warning) so Callable::CallError &r_error is the first parameter.
Binding System Refactor
core/variant/variant_utility.cpp
Removed macro-based binding registrations and introduced template-based binding scaffolding (FuncInner, Helper specializations, bind_fn, bind_fn_vuf, etc.), updated internal dispatch, argument/return handling, and replaced many FUNCBIND* usages with typed/variadic template wrappers.
Type Includes
core/variant/type_info.h
Added explicit includes: core/object/object.h and core/variant/variant.h.
Documentation
doc/classes/@GlobalScope.xml
Changed lerp(from: Variant, to: Variant, weight: Variant)lerp(from: Variant, to: Variant, weight: float) (weight now float).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Convert the macros to templates' clearly summarizes the main change: replacing macro-based registration with template-driven binding system across variant utility functions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can enable review details to help with troubleshooting, context usage and more.

Enable the reviews.review_details setting to include review details such as the model used, the time taken for each step and more in the review comments.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
core/variant/variant_utility.cpp (2)

1035-1043: ⚠️ Potential issue | 🔴 Critical

Missing early return after error detection.

When p_arg_count < 1, the error is set but execution continues, calling ERR_PRINT with potentially empty arguments and then overwriting r_error.error with CALL_OK. This masks the error from callers.

Compare with str() at lines 954-958 which correctly returns after setting the error.

🐛 Proposed fix
 void VariantUtilityFunctions::push_error(Callable::CallError &r_error, const Variant **p_args, int p_arg_count) {
 	if (p_arg_count < 1) {
 		r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
 		r_error.expected = 1;
+		return;
 	}
 
 	ERR_PRINT(join_string(p_args, p_arg_count));
 	r_error.error = Callable::CallError::CALL_OK;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1035 - 1043, The function
VariantUtilityFunctions::push_error currently sets r_error.error to
CALL_ERROR_TOO_FEW_ARGUMENTS when p_arg_count < 1 but then continues to call
ERR_PRINT(join_string(...)) and overwrites r_error.error with CALL_OK; fix this
by returning immediately after setting r_error.error and r_error.expected when
p_arg_count < 1 so ERR_PRINT and the subsequent assignment to CALL_OK are not
executed and the error is preserved for callers (locate the guard in
VariantUtilityFunctions::push_error and add an early return similar to the str()
implementation).

1045-1053: ⚠️ Potential issue | 🔴 Critical

Same missing early return issue in push_warning.

Identical issue as push_error - error is set but then overwritten with CALL_OK.

🐛 Proposed fix
 void VariantUtilityFunctions::push_warning(Callable::CallError &r_error, const Variant **p_args, int p_arg_count) {
 	if (p_arg_count < 1) {
 		r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
 		r_error.expected = 1;
+		return;
 	}
 
 	WARN_PRINT(join_string(p_args, p_arg_count));
 	r_error.error = Callable::CallError::CALL_OK;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1045 - 1053, The push_warning
implementation sets r_error.error to CALL_ERROR_TOO_FEW_ARGUMENTS when
p_arg_count < 1 but then continues and overwrites it with CALL_OK; update
VariantUtilityFunctions::push_warning to return early when p_arg_count < 1 (or
otherwise avoid overwriting the error) so the CALL_ERROR_TOO_FEW_ARGUMENTS value
is preserved, and only set r_error.error = CALL_OK after successfully handling
the warning and printing via WARN_PRINT(join_string(p_args, p_arg_count)).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1535-1556: The ptrcall specialization currently forces String
return handling and Variant::STRING via PtrToArg<String>::encode and
get_return_type(), which mis-encodes functions like max/min; change ptrcall to
encode the actual template return type TRet (use PtrToArg<TRet>::encode and
obtain the TRet value from the returned Variant rather than calling operator
String), and update get_return_type() to return the correct Variant::Type for
TRet (implement a small type-trait or separate specializations for TRet==String
and TRet==Variant to map TRet to the proper Variant::Type); update references to
PtrToArg<String>::encode to PtrToArg<TRet>::encode and adjust conversion from
Variant r accordingly in ptrcall.
- Around line 1296-1299: The get_arg_type_helperr overload incorrectly adds +1
to p_arg when forwarding to call_get_argument_type, causing an off-by-one for
functions whose first parameter is Callable::CallError&; change the forwarding
in get_arg_type_helperr (the function template named get_arg_type_helperr that
accepts R (*p_func)(Callable::CallError &, P...), int p_arg) to pass p_arg
directly to call_get_argument_type<P...> (i.e., remove the +1) so the index maps
correctly to the first parameter in the P... pack.

---

Outside diff comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1035-1043: The function VariantUtilityFunctions::push_error
currently sets r_error.error to CALL_ERROR_TOO_FEW_ARGUMENTS when p_arg_count <
1 but then continues to call ERR_PRINT(join_string(...)) and overwrites
r_error.error with CALL_OK; fix this by returning immediately after setting
r_error.error and r_error.expected when p_arg_count < 1 so ERR_PRINT and the
subsequent assignment to CALL_OK are not executed and the error is preserved for
callers (locate the guard in VariantUtilityFunctions::push_error and add an
early return similar to the str() implementation).
- Around line 1045-1053: The push_warning implementation sets r_error.error to
CALL_ERROR_TOO_FEW_ARGUMENTS when p_arg_count < 1 but then continues and
overwrites it with CALL_OK; update VariantUtilityFunctions::push_warning to
return early when p_arg_count < 1 (or otherwise avoid overwriting the error) so
the CALL_ERROR_TOO_FEW_ARGUMENTS value is preserved, and only set r_error.error
= CALL_OK after successfully handling the warning and printing via
WARN_PRINT(join_string(p_args, p_arg_count)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5886422a-3c32-4343-8615-3e31a4b50d98

📥 Commits

Reviewing files that changed from the base of the PR and between 7ff8093 and 4f4d3c2.

📒 Files selected for processing (3)
  • core/variant/method_ptrcall.h
  • core/variant/variant_utility.cpp
  • core/variant/variant_utility.h

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
core/variant/variant_utility.cpp (2)

1298-1301: ⚠️ Potential issue | 🟠 Major

Remove the +1 offset in CallError argument-type lookup.

P... already excludes Callable::CallError &, so shifting p_arg misreports argument types for all R(Callable::CallError &, P...) registrations.

Proposed fix
 template <typename R, typename... P>
 static _FORCE_INLINE_ Variant::Type get_arg_type_helperr(R (*p_func)(Callable::CallError &, P...), int p_arg) {
-	return call_get_argument_type<P...>(p_arg + 1);
+	return call_get_argument_type<P...>(p_arg);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1298 - 1301, The
get_arg_type_helperr helper incorrectly adds a +1 offset when calling
call_get_argument_type, causing arg indices to be shifted for functions of
signature R (*p_func)(Callable::CallError &, P...); update get_arg_type_helperr
to call call_get_argument_type<P...>(p_arg) (i.e., remove the +1) so the
argument-type lookup for the parameter pack P... matches the original p_arg
value.

1548-1548: ⚠️ Potential issue | 🔴 Critical

Generic vararg-return binder is incorrectly hardcoded to String.

This specialization is templated on TRet, but ptrcall and get_return_type() force String, which breaks non-String functions (e.g., max/min returning Variant).

Proposed fix
 		static void ptrcall(void *ret, const void **p_args, int p_argcount) {
 			Vector<Variant> args;
 			for (int i = 0; i < p_argcount; i++) {
 				args.push_back(PtrToArg<Variant>::convert(p_args[i]));
 			}
 			Vector<const Variant *> argsp;
 			for (int i = 0; i < p_argcount; i++) {
 				argsp.push_back(&args[i]);
 			}
 			Variant r;
 			validated_call(&r, (const Variant **)argsp.ptr(), p_argcount);
-			PtrToArg<String>::encode(r.operator String(), ret);
+			PtrToArg<TRet>::encode(r.operator TRet(), ret);
 		}
@@
 		static Variant::Type get_return_type() {
-			return Variant::STRING;
+			return GetTypeInfo<TRet>::VARIANT_TYPE;
 		}

Also applies to: 1556-1558

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` at line 1548, The vararg-return binder
specialization currently hardcodes String by calling PtrToArg<String>::encode
and forcing get_return_type()/ptrcall to String; update the specialization to
use the template parameter TRet instead of String (e.g., replace
PtrToArg<String>::encode(...) with PtrToArg<TRet>::encode(...) and ensure
ptrcall/get_return_type return/type-check against TRet), and apply the same
change to the other identical occurrences in the file (the block around the
PtrToArg usage at the other occurrence).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1298-1301: The get_arg_type_helperr helper incorrectly adds a +1
offset when calling call_get_argument_type, causing arg indices to be shifted
for functions of signature R (*p_func)(Callable::CallError &, P...); update
get_arg_type_helperr to call call_get_argument_type<P...>(p_arg) (i.e., remove
the +1) so the argument-type lookup for the parameter pack P... matches the
original p_arg value.
- Line 1548: The vararg-return binder specialization currently hardcodes String
by calling PtrToArg<String>::encode and forcing get_return_type()/ptrcall to
String; update the specialization to use the template parameter TRet instead of
String (e.g., replace PtrToArg<String>::encode(...) with
PtrToArg<TRet>::encode(...) and ensure ptrcall/get_return_type return/type-check
against TRet), and apply the same change to the other identical occurrences in
the file (the block around the PtrToArg usage at the other occurrence).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9d0e3b52-d9f7-42ab-894b-b7caa1ccea48

📥 Commits

Reviewing files that changed from the base of the PR and between 4f4d3c2 and c03ffea.

📒 Files selected for processing (1)
  • core/variant/variant_utility.cpp

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
core/variant/variant_utility.cpp (1)

1586-1606: ⚠️ Potential issue | 🔴 Critical

Critical: ptrcall doesn't encode the result for generic TRet vararg functions.

The ptrcall implementation creates a local Variant r, calls validated_call, but then discards the result without encoding it to ret. This affects max and min functions which return Variant.

GDExtension consumers calling these functions via get_ptr_utility_function() will receive uninitialized/garbage data since the result is never written.

Additionally, get_return_type() returns Variant::NIL unconditionally instead of using the actual template type.

🐛 Proposed fix
 		static void ptrcall(void *ret, const void **p_args, int p_argcount) {
 			Vector<Variant> args;
 			for (int i = 0; i < p_argcount; i++) {
 				args.push_back(PtrToArg<Variant>::convert(p_args[i]));
 			}
 			Vector<const Variant *> argsp;
 			for (int i = 0; i < p_argcount; i++) {
 				argsp.push_back(&args[i]);
 			}
 			Variant r;
 			validated_call(&r, (const Variant **)argsp.ptr(), p_argcount);
+			PtrToArg<TRet>::encode(r, ret);
 		}
 		// ...
 		static Variant::Type get_return_type() {
-			return Variant::NIL;
+			return GetTypeInfo<TRet>::VARIANT_TYPE;
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1586 - 1606, ptrcall currently
calls validated_call(&r, ...) then drops r instead of encoding it into the void*
ret buffer; update ptrcall (in the same template struct containing PtrToArg and
validated_call) to encode the resulting Variant r into the ret pointer using the
same encoding/path used elsewhere for TRet (i.e., write the proper TRet
representation into ret via the variant-to-pointer conversion used for
PtrToArg), and change get_return_type() to return the Variant::Type
corresponding to the template return type (use the existing mapping/trait that
converts TRet to Variant::Type) instead of always Variant::NIL so callers
receive the correct return-type metadata.
🧹 Nitpick comments (1)
core/variant/variant_utility.cpp (1)

1318-1345: Remove commented-out dead code.

This block contains experimental code that was not used. Consider removing it to reduce maintenance burden. If needed for future reference, it's preserved in version control history.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1318 - 1345, Remove the
commented-out experimental dead code block containing CallHelperVoid,
CallHelperRet, the _call_helper template and the call_helperp2 function stub
from variant_utility.cpp; locate the commented region around the identifiers
CallHelperVoid, CallHelperRet, _call_helper and call_helperp2 and delete those
commented lines so the file no longer contains unused commented code (rely on
VCS history if the code needs to be recovered).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1586-1606: ptrcall currently calls validated_call(&r, ...) then
drops r instead of encoding it into the void* ret buffer; update ptrcall (in the
same template struct containing PtrToArg and validated_call) to encode the
resulting Variant r into the ret pointer using the same encoding/path used
elsewhere for TRet (i.e., write the proper TRet representation into ret via the
variant-to-pointer conversion used for PtrToArg), and change get_return_type()
to return the Variant::Type corresponding to the template return type (use the
existing mapping/trait that converts TRet to Variant::Type) instead of always
Variant::NIL so callers receive the correct return-type metadata.

---

Nitpick comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1318-1345: Remove the commented-out experimental dead code block
containing CallHelperVoid, CallHelperRet, the _call_helper template and the
call_helperp2 function stub from variant_utility.cpp; locate the commented
region around the identifiers CallHelperVoid, CallHelperRet, _call_helper and
call_helperp2 and delete those commented lines so the file no longer contains
unused commented code (rely on VCS history if the code needs to be recovered).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 07273228-795b-4a0a-b258-849c2034bc36

📥 Commits

Reviewing files that changed from the base of the PR and between c03ffea and 4305471.

📒 Files selected for processing (1)
  • core/variant/variant_utility.cpp

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
core/variant/variant_utility.cpp (1)

1558-1569: ⚠️ Potential issue | 🔴 Critical

ptrcall() never writes the vararg return back to the caller.

Lines 1721-1726 bind max() and min() through this specialization. validated_call() fills the local Variant r, but nothing ever encodes r into ret, so PTR callers get stale/undefined output.

🐛 Proposed fix
 		static void ptrcall(void *ret, const void **p_args, int p_argcount) {
 			Vector<Variant> args;
 			for (int i = 0; i < p_argcount; i++) {
 				args.push_back(PtrToArg<Variant>::convert(p_args[i]));
 			}
 			Vector<const Variant *> argsp;
 			for (int i = 0; i < p_argcount; i++) {
 				argsp.push_back(&args[i]);
 			}
 			Variant r;
 			validated_call(&r, (const Variant **)argsp.ptr(), p_argcount);
+			PtrToArg<TRet>::encode(VariantCaster<TRet>::cast(r), ret);
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1558 - 1569, ptrcall builds
argument Variants and calls validated_call(&r,...), but never writes the result
back to the caller pointer ret; update ptrcall to encode the local Variant r
into the caller buffer after validated_call returns (e.g. call the corresponding
pointer-write helper such as ArgToPtr<Variant>::write or a Ptr/Arg conversion
routine with ret and r) so the PTR ABI receives the computed value; keep
existing usage of PtrToArg<Variant>::convert for inputs and add the symmetric
call (using the project's Variant-to-pointer helper) to store r into ret.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1473-1477: The current vararg utility wrapper hardcodes
get_argument_count() to 1 and returns Variant::NIL from get_argument_type(),
which misreports minimum arity for bound utilities; update the wrapper(s) so
get_argument_count() returns the actual minimum arity for each registered
utility (e.g., for the generic-return vararg wrapper used by print* set min
arity to 0, and for max/min set min arity to 2) and ensure get_argument_type(int
p_arg) still reports the correct per-argument type; apply the same change in
both locations (the pair around the generic-return vararg wrapper and the
similar block at the other occurrence) so
Variant::get_utility_function_argument_count() and the signature hash reflect
the true arities.
- Around line 1634-1791: The calls use the nonstandard GNU extension typeof(...)
(e.g. in lines registering VariantUtilityFunctions::sin, ::cos, ::tan, etc.),
which breaks MSVC; replace each Func<typeof(VariantUtilityFunctions::X)> with
Func<decltype(&VariantUtilityFunctions::X)> so the type deduction is standard
C++11, keeping the existing FuncInner<...,
Variant::UTILITY_FUNC_TYPE_...>::register_fn(...) invocations unchanged; update
every occurrence in this block (functions like VariantUtilityFunctions::sin,
::atan2, ::randi_range, ::weakref, ::type_convert, etc.) to use decltype(&...)
instead of typeof(...).

---

Duplicate comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1558-1569: ptrcall builds argument Variants and calls
validated_call(&r,...), but never writes the result back to the caller pointer
ret; update ptrcall to encode the local Variant r into the caller buffer after
validated_call returns (e.g. call the corresponding pointer-write helper such as
ArgToPtr<Variant>::write or a Ptr/Arg conversion routine with ret and r) so the
PTR ABI receives the computed value; keep existing usage of
PtrToArg<Variant>::convert for inputs and add the symmetric call (using the
project's Variant-to-pointer helper) to store r into ret.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 41d77028-4542-498b-867c-adef80ebd9d1

📥 Commits

Reviewing files that changed from the base of the PR and between 4305471 and 0450fdd.

📒 Files selected for processing (1)
  • core/variant/variant_utility.cpp

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
core/variant/variant_utility.cpp (1)

1558-1569: ⚠️ Potential issue | 🔴 Critical

ptrcall does not encode the return value—max/min will return uninitialized data.

The result is computed into local Variant r at line 1568, but the function returns without writing anything to the ret pointer. Compare with the String specialization (lines 1519-1520) which correctly encodes the result.

🐛 Proposed fix
 		static void ptrcall(void *ret, const void **p_args, int p_argcount) {
 			Vector<Variant> args;
 			for (int i = 0; i < p_argcount; i++) {
 				args.push_back(PtrToArg<Variant>::convert(p_args[i]));
 			}
 			Vector<const Variant *> argsp;
 			for (int i = 0; i < p_argcount; i++) {
 				argsp.push_back(&args[i]);
 			}
 			Variant r;
 			validated_call(&r, (const Variant **)argsp.ptr(), p_argcount);
+			PtrToArg<Variant>::encode(r, ret);
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1558 - 1569, The ptrcall
helper currently computes the result into a local Variant r but never writes it
to the caller-provided ret pointer, causing uninitialized returns for functions
like max/min; modify ptrcall (the function that constructs args with
PtrToArg<Variant>::convert, builds argsp, calls validated_call(&r, ...)) to
encode/copy the result Variant r into the void* ret using the same
conversion/encoding used by the String specialization — i.e., after
validated_call returns, write the encoded Variant result into ret (using the
appropriate PtrFromRet or equivalent conversion used elsewhere) so the caller
receives the computed value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1558-1569: The ptrcall helper currently computes the result into a
local Variant r but never writes it to the caller-provided ret pointer, causing
uninitialized returns for functions like max/min; modify ptrcall (the function
that constructs args with PtrToArg<Variant>::convert, builds argsp, calls
validated_call(&r, ...)) to encode/copy the result Variant r into the void* ret
using the same conversion/encoding used by the String specialization — i.e.,
after validated_call returns, write the encoded Variant result into ret (using
the appropriate PtrFromRet or equivalent conversion used elsewhere) so the
caller receives the computed value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a143a94e-dce4-4f4e-a7c5-bfe2f26bd045

📥 Commits

Reviewing files that changed from the base of the PR and between 0450fdd and 8daef20.

📒 Files selected for processing (1)
  • core/variant/variant_utility.cpp

@JoltedJon
Copy link
Contributor

@VictorSohier Changing the interface that users interact with can be risky. Do we know if moving around the parameters of these functions will effect user projects? As in are these functions exposed to users?

@VictorSohier
Copy link
Author

VictorSohier commented Mar 14, 2026

@VictorSohier Changing the interface that users interact with can be risky. Do we know if moving around the parameters of these functions will effect user projects? As in are these functions exposed to users?

The error parameter isn't exposed in GDScript and these functions aren't called from anywhere else. All calls to these functions happen through the call helpers. Still compiles and tests still pass. Maybe for paranoia's sake, I should find a test project and see if it still functions. Ultimately though, this is a limitation of template specialization.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
core/variant/variant_utility.cpp (1)

1545-1556: ⚠️ Potential issue | 🔴 Critical

ptrcall drops the computed return value for generic vararg bindings.

At Line 1545-1556, validated_call writes into local Variant r, but ret is never encoded. This breaks ptrcall return propagation for this specialization (notably Variant-returning vararg utilities).

💡 Proposed fix
 static void ptrcall(void *ret, const void **p_args, int p_argcount) {
 	Vector<Variant> args;
 	for (int i = 0; i < p_argcount; i++) {
 		args.push_back(PtrToArg<Variant>::convert(p_args[i]));
 	}
 	Vector<const Variant *> argsp;
 	for (int i = 0; i < p_argcount; i++) {
 		argsp.push_back(&args[i]);
 	}
 	Variant r;
 	validated_call(&r, (const Variant **)argsp.ptr(), p_argcount);
+	PtrToArg<TRet>::encode(static_cast<TRet>(r), ret);
 }
#!/bin/bash
# Confirm ptrcall in generic vararg specialization does not encode ret.
rg -n -C6 'struct Func<TRet\(Callable::CallError &, const Variant \*\*, int\)>' core/variant/variant_utility.cpp
rg -n -C4 'validated_call\(&r, \(const Variant \*\*\)argsp\.ptr\(\), p_argcount\);' core/variant/variant_utility.cpp
rg -n 'PtrToArg<TRet>::encode' core/variant/variant_utility.cpp

# Show current registrations that likely hit this specialization (e.g., max/min).
rg -n 'bind_fn_vuf\((max|min),' core/variant/variant_utility.cpp
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1545 - 1556, ptrcall currently
calls validated_call(&r, ...) but never writes the computed Variant r into the
provided ret pointer; update ptrcall to encode the local r back into ret using
the PtrToArg<Variant>::encode routine (or the generic PtrToArg<TRet>::encode
when templated) after validated_call returns so the return value is propagated;
reference symbols: ptrcall, validated_call, Variant r, ret, and
PtrToArg<Variant>::encode.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1375-1378: validated_call_helper currently does *ret = p_func(...)
which is invalid when p_func returns void; change the implementation to detect
the call return type (e.g. using std::invoke_result_t or decltype on p_func) and
use if constexpr to handle void vs non-void: if the return type is void, call
p_func(err, ...) and then set *ret to a default/empty Variant (or otherwise
initialize it); otherwise call and assign the returned value to *ret. Reference
validated_call_helper, p_func, Callable::CallError and Variant *ret when
applying the conditional fix.

---

Duplicate comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1545-1556: ptrcall currently calls validated_call(&r, ...) but
never writes the computed Variant r into the provided ret pointer; update
ptrcall to encode the local r back into ret using the PtrToArg<Variant>::encode
routine (or the generic PtrToArg<TRet>::encode when templated) after
validated_call returns so the return value is propagated; reference symbols:
ptrcall, validated_call, Variant r, ret, and PtrToArg<Variant>::encode.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ce671111-0682-4c3b-a5c7-2b577c3dc9c7

📥 Commits

Reviewing files that changed from the base of the PR and between 0450fdd and b868576.

📒 Files selected for processing (2)
  • core/variant/type_info.h
  • core/variant/variant_utility.cpp

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/variant/variant_utility.cpp (1)

35-42: ⚠️ Potential issue | 🟡 Minor

Remove duplicate include of binder_common.h.

Line 35 includes "binder_common.h" (relative path) and line 42 includes "core/variant/binder_common.h" (full path). Both resolve to the same file. Remove one to avoid redundancy.

Suggested fix
 `#include` "variant_utility.h"

-#include "binder_common.h"
 `#include` "core/io/marshalls.h"
 `#include` "core/object/ref_counted.h"
 `#include` "core/os/os.h"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 35 - 42, The file contains a
duplicate include of binder_common.h; remove one of the two includes so the
header is only included once (e.g., delete the duplicate relative include
"binder_common.h" or the duplicate "core/variant/binder_common.h" line), leaving
a single include for binder_common.h to avoid redundancy and potential
maintenance confusion—update the includes near the top of variant_utility.cpp to
keep only one binder_common.h reference.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1545-1556: The ptrcall function builds the result into local
Variant r but never writes it to the output pointer ret; update ptrcall to
encode or placement-construct the result into ret (similar to the String
specialization): use PtrToArg<Variant>::encode(r, ret) if PtrToArg<Variant>
provides encode, otherwise call memnew_placement(ret, Variant(r)) to construct
the returned Variant in-place; modify the ptrcall function (which creates args,
argsp and Variant r) to perform this encode/placement before returning.

---

Outside diff comments:
In `@core/variant/variant_utility.cpp`:
- Around line 35-42: The file contains a duplicate include of binder_common.h;
remove one of the two includes so the header is only included once (e.g., delete
the duplicate relative include "binder_common.h" or the duplicate
"core/variant/binder_common.h" line), leaving a single include for
binder_common.h to avoid redundancy and potential maintenance confusion—update
the includes near the top of variant_utility.cpp to keep only one
binder_common.h reference.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a2119408-8df4-4b29-b0c3-ffd5fa22be05

📥 Commits

Reviewing files that changed from the base of the PR and between 0450fdd and b868576.

📒 Files selected for processing (2)
  • core/variant/type_info.h
  • core/variant/variant_utility.cpp

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
core/variant/variant_utility.cpp (2)

1545-1556: ⚠️ Potential issue | 🟠 Major

The generic vararg ptrcall never stores its computed return value.

Line 1555 materializes the result in Variant r, but nothing encodes it into ret. max() and min() use this wrapper, so PTR utility callers can observe an uninitialized or stale return slot.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1545 - 1556, The ptrcall
wrapper computes the return in local Variant r but never writes it back to the
provided ret slot; update ptrcall (the function that calls validated_call and
declares Variant r) to store the result into ret after calling validated_call
(for example, copy or assign r into the memory pointed to by ret, e.g.
*(Variant*)ret = r) so callers (e.g., max/min via the PTR utility) observe the
computed return value.

1374-1378: ⚠️ Potential issue | 🟠 Major

validated_call_helper cannot assign a void result.

Line 1377 does *ret = p_func(...) in the void(Callable::CallError &, TArgs...) specialization. That is ill-formed C++, so the first non-vararg void utility registered through this helper will fail to compile.

Suggested fix
 template <size_t... Is>
 static _FORCE_INLINE_ void validated_call_helper(void (*p_func)(Callable::CallError &, TArgs...), Variant *ret, const Variant **p_args, IndexSequence<Is...>) {
 	Callable::CallError err;
-	*ret = p_func(err, VariantCaster<TArgs>::cast(*p_args[Is])...);
+	p_func(err, VariantCaster<TArgs>::cast(*p_args[Is])...);
+	(void)ret;
 	(void)p_args;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1374 - 1378,
validated_call_helper currently attempts to assign the result of a void function
to *ret (using *ret = p_func(...)), which is ill-formed; change the body for the
void-returning specialization (the template with void
(*p_func)(Callable::CallError &, TArgs...)) to call p_func(err,
VariantCaster<TArgs>::cast(*p_args[Is])...) without assignment and then set *ret
to a default/empty Variant (e.g., *ret = Variant()) so the caller still gets a
valid Variant; reference the function validated_call_helper, the parameter
p_func, the types Callable::CallError and VariantCaster<TArgs>, and the pointer
Variant *ret when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1283-1286: The call_helper function currently reuses the same
Callable::CallError (r_error) that VariantCasterAndValidate writes into, which
lets a subsequent assignment r_error.error = CALL_OK erase caster-side failures;
modify call_helper (and the same pattern at the other occurrence around the
1360-1363 region) to validate into a local temporary Callable::CallError
temp_error and, if temp_error.error != CALL_OK, copy that error into r_error and
return before calling p_func/VCALLER; only if temp_error is OK proceed to invoke
p_func and set r_error based on the callee result.

---

Duplicate comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1545-1556: The ptrcall wrapper computes the return in local
Variant r but never writes it back to the provided ret slot; update ptrcall (the
function that calls validated_call and declares Variant r) to store the result
into ret after calling validated_call (for example, copy or assign r into the
memory pointed to by ret, e.g. *(Variant*)ret = r) so callers (e.g., max/min via
the PTR utility) observe the computed return value.
- Around line 1374-1378: validated_call_helper currently attempts to assign the
result of a void function to *ret (using *ret = p_func(...)), which is
ill-formed; change the body for the void-returning specialization (the template
with void (*p_func)(Callable::CallError &, TArgs...)) to call p_func(err,
VariantCaster<TArgs>::cast(*p_args[Is])...) without assignment and then set *ret
to a default/empty Variant (e.g., *ret = Variant()) so the caller still gets a
valid Variant; reference the function validated_call_helper, the parameter
p_func, the types Callable::CallError and VariantCaster<TArgs>, and the pointer
Variant *ret when making this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1f4bde93-27dc-4bdb-a0c6-97463b784052

📥 Commits

Reviewing files that changed from the base of the PR and between 0450fdd and e918047.

📒 Files selected for processing (3)
  • core/variant/type_info.h
  • core/variant/variant_utility.cpp
  • doc/classes/@GlobalScope.xml

Comment on lines +1283 to +1286
static _FORCE_INLINE_ void call_helper(TRet (*p_func)(Callable::CallError &, TArgs...), Variant *ret, const Variant **p_args, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
VCALLER(TArgs, ret, p_args, Is, r_error);
(void)p_args; // avoid gcc warning
Copy link

@coderabbitai coderabbitai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t let the callee erase binder-side cast failures.

Under DEBUG_ENABLED, VariantCasterAndValidate(...) writes into r_error before p_func(...) runs. Reusing that same object for the callee means any later CALL_OK assignment inside the utility can clear a conversion error. lerp() is affected today: an invalid weight can be default-cast and still return success. Validate into a temporary Callable::CallError, or bail out before invoking p_func once casting fails.

Also applies to: 1360-1363

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1283 - 1286, The call_helper
function currently reuses the same Callable::CallError (r_error) that
VariantCasterAndValidate writes into, which lets a subsequent assignment
r_error.error = CALL_OK erase caster-side failures; modify call_helper (and the
same pattern at the other occurrence around the 1360-1363 region) to validate
into a local temporary Callable::CallError temp_error and, if temp_error.error
!= CALL_OK, copy that error into r_error and return before calling
p_func/VCALLER; only if temp_error is OK proceed to invoke p_func and set
r_error based on the callee result.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VictorSohier, you're right — looking at the original call_helperpr, it already set r_error.error = CALL_OK upfront and passed the same r_error into the VCALLR expansion, which is the same pattern as the new code. This is a pre-existing behavior that predates this PR and is out of scope here. Thanks for the clarification!


✏️ Learnings added
Learnt from: VictorSohier
Repo: Redot-Engine/redot-engine PR: 1225
File: core/variant/variant_utility.cpp:1283-1286
Timestamp: 2026-03-17T01:20:15.454Z
Learning: In core/variant/variant_utility.cpp (Redot Engine), the pattern of setting r_error.error = CALL_OK before invoking VariantCasterAndValidate with the same r_error object in call_helper/call_helperpr is pre-existing behavior from before PR `#1225`. Do not flag this pattern as a regression introduced by the macro-to-template refactor; it is identical to the original implementation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: VictorSohier
Repo: Redot-Engine/redot-engine PR: 1225
File: core/variant/variant_utility.cpp:1473-1477
Timestamp: 2026-03-14T02:00:02.021Z
Learning: In core/variant/variant_utility.cpp (Redot Engine), the get_argument_count() returning 1 for vararg utility function specializations (e.g., Func<void(Callable::CallError &, const Variant **, int)>) is intentional and harmless. The argcount validation in Variant::call_utility_function() is fully gated behind !bfi->is_vararg, so the stored argcount is never used for dispatch validation on vararg functions. Minimum arity for vararg functions is enforced inside each function body. Do not flag these vararg cases for arity issues; ensure review notes focus on non-vararg dispatch paths and per-function arity checks instead.

This likely would've been fixed if and when this case was realized, however,
it was incorrect by construction and probably should be handled now.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
core/variant/variant_utility.cpp (1)

1546-1557: ⚠️ Potential issue | 🟠 Major

Add result encoding to generic vararg ptrcall.

The generic vararg ptrcall implementation (lines 1546–1557) computes the result into local Variant r but never encodes it to the output ret pointer. The String specialization at line 1520 correctly uses PtrToArg<String>::encode() to write the result; the generic template should follow the same pattern.

		static void ptrcall(void *ret, const void **p_args, int p_argcount) {
			Vector<Variant> args;
			for (int i = 0; i < p_argcount; i++) {
				args.push_back(PtrToArg<Variant>::convert(p_args[i]));
			}
			Vector<const Variant *> argsp;
			for (int i = 0; i < p_argcount; i++) {
				argsp.push_back(&args[i]);
			}
			Variant r;
			validated_call(&r, (const Variant **)argsp.ptr(), p_argcount);
+			PtrToArg<Variant>::encode(r, ret);
		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 1546 - 1557, The ptrcall
function currently computes the call result into local Variant r but never
writes it back to the output pointer ret; modify ptrcall to encode the result
into ret after validated_call by calling the appropriate encoder (use
PtrToArg<Variant>::encode(&r, ret) or the same encoding pattern used by the
String specialization), leaving the existing argument conversion
(PtrToArg<Variant>::convert) and validated_call invocation unchanged.
🧹 Nitpick comments (1)
core/variant/variant_utility.cpp (1)

35-42: Duplicate include of binder_common.h.

Line 35 includes "binder_common.h" (relative path) and line 42 includes "core/variant/binder_common.h" (full path). These resolve to the same file. Remove one of them.

 `#include` "variant_utility.h"
 
-#include "binder_common.h"
 `#include` "core/io/marshalls.h"
 `#include` "core/object/ref_counted.h"
 `#include` "core/os/os.h"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/variant/variant_utility.cpp` around lines 35 - 42, Remove the duplicate
include of binder_common.h: there are two `#include` directives for the same
header (one as "binder_common.h" and one as "core/variant/binder_common.h");
delete the redundant one (keep the correctly scoped include
"core/variant/binder_common.h" to maintain consistent include paths) so only a
single include for binder_common.h remains in variant_utility.cpp.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@core/variant/variant_utility.cpp`:
- Around line 1546-1557: The ptrcall function currently computes the call result
into local Variant r but never writes it back to the output pointer ret; modify
ptrcall to encode the result into ret after validated_call by calling the
appropriate encoder (use PtrToArg<Variant>::encode(&r, ret) or the same encoding
pattern used by the String specialization), leaving the existing argument
conversion (PtrToArg<Variant>::convert) and validated_call invocation unchanged.

---

Nitpick comments:
In `@core/variant/variant_utility.cpp`:
- Around line 35-42: Remove the duplicate include of binder_common.h: there are
two `#include` directives for the same header (one as "binder_common.h" and one as
"core/variant/binder_common.h"); delete the redundant one (keep the correctly
scoped include "core/variant/binder_common.h" to maintain consistent include
paths) so only a single include for binder_common.h remains in
variant_utility.cpp.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a2f23296-6ce4-4a5b-baf9-6ac2172d69b4

📥 Commits

Reviewing files that changed from the base of the PR and between e918047 and 1c2c34b.

📒 Files selected for processing (1)
  • core/variant/variant_utility.cpp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants