Skip to content

Conversation

@tonycoz
Copy link
Contributor

@tonycoz tonycoz commented Nov 27, 2025

As discussed in #23918 this adds APIs corresponding to the other perl numeric comparison operators.

Separate APIs for each comparison a provided as each calls the appropriate overload for that comparison.

This also fixes a bug in sv_numeq() which didn't correctly handle the case where the operator itself isn't overloaded, but conversion to a number is, and that conversion returns a large integer not representable as a NV.


  • This set of changes requires a perldelta entry, and it is included.

@tonycoz tonycoz marked this pull request as draft November 30, 2025 20:09
@tonycoz
Copy link
Contributor Author

tonycoz commented Nov 30, 2025

Draft for now, I suspect the overloading support has problems when the current op context is void.

some refactoring next, since sv_numeq_flags and sv_numne_flags are
similar.

Used a separate test file since putting every sv_num*() variant in the
one file would be ugly

Addresses GH Perl#23918 but isn't a direct fix
If nothing else putting them together may avoid someone doing
`!sv_numeq(...)`
do_ncmp() expects simple SVs and for overloaded SVs will just compare
the SvNV() of each SV, mishandling the case where the 0+ overload
returns a large UV or IV that isn't exactly representable as an NV.

# Conflicts:
#	ext/XS-APItest/t/sv_numeq.t
#	ext/XS-APItest/t/sv_numne.t
#	sv.c
These are all needed because overloading may make them inconsistent
with <=> overloading.
Discovered while working on another module, in many amagic_call() will
use the current context when calling the overload sub, but these APIs
might be called in XS code that simply needs a comparison, regardless
of the current OP context.
and use it from the numeric comparison APIs.
instead of a special case in amagic_call()
modified the sv.c documentation since the perldelta sv_numeq link had
multiple targets.
@tonycoz
Copy link
Contributor Author

tonycoz commented Dec 3, 2025

There was some discussion in IRC on how to handle overloading.

In particular, amagic_call() will check if the HINT_NO_AMAGIC hint is set (by no overloading;) and prevent any overloading calls when it is set, including "0+" overloading that converts the overloaded reference into a number.

I can see these and similar APIs being called in a few contexts:

  1. no overloading at all (prevent any overloading, including the "0+" overloading done by sv_2num())
  2. overload based on the current state, doing overloading if HINT_NO_AMAGIC is clear
  3. always overload, regardless of HINT_NO_AMAGIC

I think the default (sv_numeq(), the non-flags APIs) should overload based on the current state, which is the current behaviour

Setting SV_SKIP_OVERLOAD would do no overloading at all, this only differs from the current behaviour in this PR in that "0+" overloading is still done. In this case references to objects with overloads would be treated like non-overloaded references are, as (typically large) integers in numeric context and as Class=TYPE(0xXXXX) in string context.

Setting SV_FORCE_OVERLOAD would overload even if HINT_NO_AMAGIC is set.

I plan to add a flags variant of sv_2num() (which isn't API /cry) and a flag for amagic_call() to force overload and ignore HINT_NO_AMAGIC.

Copy link
Contributor

@leonerd leonerd left a comment

Choose a reason for hiding this comment

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

Overall this seems a good direction. A couple of small comments.


#define AMGf_want_list 0x0040
#define AMGf_numarg 0x0080
#define AMGf_force_scalar 0x0100
Copy link
Contributor

Choose a reason for hiding this comment

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

This style is fine, but I think in general I usually add these in the form (1<<0), (1<<1), etc to make it clearer they are single bitflags, and easier to see gaps or where to add the next.

* regardless of the overall context of the multiconcat op
*/
U8 gimme = (force_scalar || !PL_op || PL_op->op_type == OP_MULTICONCAT)
U8 gimme = (force_scalar || (flags & AMGf_force_scalar) || !PL_op )
Copy link
Contributor

Choose a reason for hiding this comment

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

++

I definitely like that this decouples the function at least from deep knowledge of being OP_MULTICONCAT.

}

PERL_STATIC_INLINE bool
S_sv_numcmp_common(pTHX_ SV **sv1, SV **sv2, const U32 flags,
Copy link
Contributor

Choose a reason for hiding this comment

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

Took me a moment to realise this function isn't doing the regular non-overloaded comparison. It might do with adding a comment, saying something like:

this function only handles overloaded comparison, returning true if the comparison is already done. if it returns false, then the caller should compare numerically using do_ncmp().

{
$Config{d_double_has_nan}
or skip "No NAN", 2;
my $nan = 0+"NaN";
Copy link
Contributor

Choose a reason for hiding this comment

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

We have builtin::nan now ;)
(repeated variously throughout the test files)

# GMAGIC
"10" =~ m/(\d+)/;
is sv_numcmp_flags($1, 10, 0), -1, 'sv_numcmp_flags with no flags does not GETMAGIC';
is sv_numcmp_flags($1, 10, SV_GMAGIC), 0, 'sv_numecmp_flags with SV_GMAGIC does';
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo sv_numecmp

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants