Skip to content

Commit 5ed8d18

Browse files
committed
support wrapping subs with prototypes
While Class::Method::Modifiers is primarily meant to wrap methods, it can also be used to wrap functions. Functions may have prototypes, so it would be better if they could maintained in the wrapper. This works similarly to the lvalue attribute. If a before or after is applied, the wrapper takes its prototype from the sub being wrapped. If an around is applied, the modifier sub's prototype is used. This is rather strange for arounds, as the parameters it is passed will still include the wrapped sub as the first parameter, so the parameters won't match the prototype exactly. Even with that oddness, it still seems to be the best option. This also means an around could change the prototype, which will throw warnings.
1 parent d90049c commit 5ed8d18

File tree

2 files changed

+79
-10
lines changed

2 files changed

+79
-10
lines changed

lib/Class/Method/Modifiers.pm

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ sub install_modifier {
8282
# the Moose equivalent. :)
8383
if ($type eq 'around') {
8484
my $method = $cache->{wrapped};
85-
my $attrs = _sub_attrs($code);
85+
my $sig = _sub_sig($code);
8686
# a bare "sub :lvalue {...}" will be parsed as a label and an
8787
# indirect method call. force it to be treated as an expression
8888
# using +
89-
$cache->{wrapped} = eval "package $into; +sub $attrs { \$code->(\$method, \@_); };";
89+
$cache->{wrapped} = eval "package $into; +sub $sig { \$code->(\$method, \@_); };";
9090
}
9191

9292
# install our new method which dispatches the modifiers, but only
@@ -101,10 +101,10 @@ sub install_modifier {
101101
# to take a reference to it. better a deref than a hash lookup
102102
my $wrapped = \$cache->{"wrapped"};
103103

104-
my $attrs = _sub_attrs($cache->{wrapped});
104+
my $sig = _sub_sig($cache->{wrapped});
105105

106106
my $generated = "package $into;\n";
107-
$generated .= "sub $name $attrs {";
107+
$generated .= "sub $name $sig {";
108108

109109
# before is easy, it doesn't affect the return value(s)
110110
if (@$before) {
@@ -198,17 +198,27 @@ sub _fresh {
198198
}
199199
else {
200200
no warnings 'closure'; # for 5.8.x
201-
my $attrs = _sub_attrs($code);
202-
eval "package $into; sub $name $attrs { \$code->(\@_) }";
201+
my $sig = _sub_sig($code);
202+
eval "package $into; sub $name $sig { \$code->(\@_) }";
203203
}
204204
}
205205
}
206206

207-
sub _sub_attrs {
207+
sub _sub_sig {
208208
my ($coderef) = @_;
209-
local *_sub = $coderef;
210-
local $@;
211-
(eval 'return 1; &_sub = 1') ? ':lvalue' : '';
209+
my @sig;
210+
if (defined(my $proto = prototype($coderef))) {
211+
push @sig, "($proto)";
212+
}
213+
if (do {
214+
local *_sub = $coderef;
215+
local $@;
216+
local $SIG{__DIE__};
217+
eval 'return 1; &_sub = 1';
218+
}) {
219+
push @sig, ':lvalue';
220+
}
221+
join ' ', @sig;
212222
}
213223

214224
sub _is_in_package {

t/141-prototype.t

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use strict;
2+
use warnings;
3+
use Test::More 0.88;
4+
use Test::Warnings ($ENV{AUTHOR_TESTING} ? () : ':no_end_test'), 'warning';
5+
use Test::Fatal;
6+
7+
use Class::Method::Modifiers;
8+
9+
{
10+
sub foo ($) { scalar @_ }
11+
12+
my $after;
13+
after foo => sub { $after = @_ };
14+
15+
is eval q{ foo( @{[10, 20]} ) }, 1,
16+
'after wrapped sub maintains prototype';
17+
is $after, 1,
18+
'after modifier applied';
19+
}
20+
21+
{
22+
my $bar;
23+
my $guff;
24+
sub bar ($) :lvalue { $guff = @_; $bar }
25+
26+
my $after;
27+
after bar => sub { $after = @_ };
28+
29+
eval q{ bar( @{[10, 20]} ) = 5 };
30+
is $guff, 1,
31+
'after wrapped lvalue sub maintains prototype';
32+
is $bar, 5,
33+
'after wrapped lvalue sub maintains lvalue';
34+
is $after, 1,
35+
'after modifier applied';
36+
}
37+
38+
{
39+
sub bog ($) { scalar @_ }
40+
41+
my $around;
42+
my $warn = warning {
43+
around bog => sub ($$) {
44+
my $orig = shift;
45+
$around = @_;
46+
$orig->(@_);
47+
};
48+
};
49+
50+
is eval q{ bog( @{[5, 6]}, @{[10, 11]} ) }, 2,
51+
'around wrapped lvalue sub takes modifier prototype';
52+
is $around, 2,
53+
'around modifier applied';
54+
like $warn, qr/Prototype mismatch/,
55+
'changing prototype throws warning';
56+
}
57+
58+
done_testing;
59+
# vim: set ts=8 sts=4 sw=4 tw=115 et :

0 commit comments

Comments
 (0)